$_) { if (!isset($abilities[$name])) { fwrite(STDERR, 'Planner ability missing in MITIGATION_ABILITIES: ' . $name . PHP_EOL); continue; } $ability = $abilities[$name]; $id = (int)($ability['extraAbilityGameID'] ?? 0); if ($id <= 0) { fwrite(STDERR, 'Skipping mitigation without extraAbilityGameID: ' . $name . PHP_EOL); continue; } $ids[$id] = true; } if (!$ids) { fail('No extraAbilityGameID values found for abilities from js/ffxiv-data.js'); } ksort($ids, SORT_NUMERIC); return array_keys($ids); } function download_url(string $url): string { $lastError = ''; $allowInsecureDownload = getenv('FF14_MITIGATOR_INSECURE_DOWNLOAD') === '1'; if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_CONNECTTIMEOUT => 15, CURLOPT_TIMEOUT => 120, CURLOPT_USERAGENT => 'ff14-mitigator-action-cache/1.0', CURLOPT_SSL_VERIFYPEER => !$allowInsecureDownload, CURLOPT_SSL_VERIFYHOST => $allowInsecureDownload ? 0 : 2, ]); $body = curl_exec($ch); $error = curl_error($ch); $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($body !== false && $status >= 200 && $status < 300) { return $body; } $lastError = 'cURL HTTP ' . $status . ($error ? ': ' . $error : ''); } $wrappers = stream_get_wrappers(); if (in_array('https', $wrappers, true)) { $context = stream_context_create([ 'http' => [ 'timeout' => 120, 'user_agent' => 'ff14-mitigator-action-cache/1.0', ], 'ssl' => [ 'verify_peer' => !$allowInsecureDownload, 'verify_peer_name' => !$allowInsecureDownload, ], ]); $body = file_get_contents($url, false, $context); if ($body !== false) { return $body; } $lastError = trim($lastError . '; file_get_contents failed', '; '); } if ($lastError !== '') { fail('Could not download Action.json. ' . $lastError); } fail('This PHP installation has neither cURL nor the HTTPS stream wrapper enabled.'); } function action_field(array $action, string $field): ?int { if (array_key_exists($field, $action) && is_numeric($action[$field])) { return (int)$action[$field]; } $fields = $action['fields'] ?? null; if (is_array($fields) && array_key_exists($field, $fields) && is_numeric($fields[$field])) { return (int)$fields[$field]; } return null; } $plannerAbilityNames = read_planner_ability_names($plannerDataSource); $actionIds = read_mitigation_action_ids($mitigationSource, $plannerAbilityNames); $wanted = array_fill_keys(array_map('strval', $actionIds), true); $json = download_url(ACTION_SOURCE_URL); $actions = json_decode($json, true); if (!is_array($actions)) { fail('Downloaded Action.json is not valid JSON: ' . json_last_error_msg()); } $filtered = []; foreach ($wanted as $id => $_) { $action = $actions[$id] ?? null; if (!is_array($action)) { fwrite(STDERR, 'Missing action in downloaded Action.json: ' . $id . PHP_EOL); continue; } $filtered[$id] = [ 'cast' => action_field($action, 'Cast100ms'), 'recast' => action_field($action, 'Recast100ms'), ]; } if (!$filtered) { fail('No matching mitigation actions found in downloaded Action.json'); } ksort($filtered, SORT_NUMERIC); $outputDir = dirname($outputFile); if (!is_dir($outputDir) && !mkdir($outputDir, 0775, true) && !is_dir($outputDir)) { fail('Could not create output directory: ' . $outputDir); } $encoded = json_encode($filtered, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if ($encoded === false) { fail('Could not encode filtered Action.json: ' . json_last_error_msg()); } if (file_put_contents($outputFile, $encoded . PHP_EOL, LOCK_EX) === false) { fail('Could not write output file: ' . $outputFile); } echo 'Saved ' . count($filtered) . ' actions to ' . $outputFile . PHP_EOL;