// ==UserScript== // @name OpenFront Helper (userscript) // @namespace openfront-helper // @version 1.2.0 // @description Auto-join lobbies + in-game helper overlays and a private-lobby auto-bot for OpenFront.io. Standalone userscript build with a multi-language UI. // @author nguyenvancaokyfpt (https://github.com/nguyenvancaokyfpt) // @license AGPL-3.0-or-later // @homepageURL https://github.com/nguyenvancaokyfpt/openfront-helper-userscript // @supportURL https://github.com/nguyenvancaokyfpt/openfront-helper-userscript/issues // @updateURL https://raw.githubusercontent.com/nguyenvancaokyfpt/openfront-helper-userscript/main/openfront-helper.user.js // @downloadURL https://raw.githubusercontent.com/nguyenvancaokyfpt/openfront-helper-userscript/main/openfront-helper.user.js // @match https://openfront.io/* // @run-at document-start // @inject-into page // @grant none // @noframes // ==/UserScript== window.__OFH_ASSETS = {"version":"1.2.0","locales":{"en":{"languageCode":"en","languageName":"English","settings":"Settings","openSettings":"Open settings","customNotificationSound":"Custom notification sound","test":"Test","upload":"Upload","remove":"Remove","cancel":"Cancel","defaultSound":"Default","customSound":"Custom","language":"Language","openLanguageMenu":"Change language","searchLanguages":"Search languages","noLanguagesFound":"No languages found","openMacros":"Open macros","autoJoinOn":"Auto-Join ON","autoJoinOff":"Auto-Join OFF","showFloatingHelpersPanel":"Show floating helpers panel on OpenFront","hideFloatingHelpersPanel":"Hide floating helpers panel on OpenFront","showFloatingAutoJoinPanel":"Show floating Auto-Join panel on OpenFront","hideFloatingAutoJoinPanel":"Hide floating Auto-Join panel on OpenFront","Close auto-join panel":"Close auto-join panel","Resize auto-join panel":"Resize auto-join panel","Expand":"Expand","Searching":"Searching","Filters":"Filters","Team size":"Team size","Any":"Any","Select at least one filter in the popup to enable auto-join.":"Select at least one filter in the popup to enable auto-join.","Auto-Join & Helpers for OpenFront":"Auto-Join & Helpers for OpenFront","Settings":"Settings","Open settings":"Open settings","Change language":"Change language","Open macros":"Open macros","Custom notification sound":"Custom notification sound","Show floating helpers panel on OpenFront":"Show floating helpers panel on OpenFront","Test":"Test","Upload":"Upload","Remove":"Remove","Cancel":"Cancel","Default":"Default","Search languages":"Search languages","Languages":"Languages","Join our Discord":"Join our Discord","Macros":"Macros","⚓ Send 1% Boat":"⚓ Send 1% Boat","Send a transport with 1% troops, then restore the attack ratio.":"Send a transport with 1% troops, then restore the attack ratio.","Hotkey":"Hotkey","Shift + B":"Shift + B","Show button in selection wheel":"Show button in selection wheel","One-time setup":"One-time setup","If openfront.io was already open when you installed this extension, reload that tab once. Otherwise the extension will not work on that tab.":"If openfront.io was already open when you installed this extension, reload that tab once. Otherwise the extension will not work on that tab.","Got it":"Got it","Helpers":"Helpers","Float":"Float","Game helpers":"Game helpers","Mark bot nations red":"Mark bot nations red","Adds a red marker to nation AI names on the map.":"Adds a red marker to nation AI names on the map.","Adds a red marker to nation AI names.":"Adds a red marker to nation AI names.","Alliances":"Alliances","Show alliances":"Show alliances","Highlights allies with remaining alliance time.":"Highlights allies with remaining alliance time.","Alliance requests panel":"Alliance requests panel","Moves alliance requests and renewal prompts into a separate right-side window.":"Moves alliance requests and renewal prompts into a separate right-side window.","Cheats":"Cheats","Only available in solo or custom games.":"Only available in solo or custom games.","⚠ Only available in solo or custom games.":"⚠ Only available in solo or custom games.","Nuke prediction":"Nuke prediction","Shows predicted enemy nuke landing points and explosion radius.":"Shows predicted enemy nuke landing points and explosion radius.","Shows predicted enemy nuke landing points and blast radius.":"Shows predicted enemy nuke landing points and blast radius.","Boat prediction":"Boat prediction","Shows enemy boat landing points. Red = targeting you, yellow = targeting others.":"Shows enemy boat landing points. Red = targeting you, yellow = targeting others.","Nuke suggestion":"Nuke suggestion","Beta":"Beta","May lag":"May lag","Hover an enemy to show high-damage atom and hydrogen targets.":"Hover an enemy to show high-damage atom and hydrogen targets.","Auto nuke":"Auto nuke","Adds auto economy and population nuke actions to the player wheel.":"Adds auto economy and population nuke actions to the player wheel.","Include allies":"Include allies","↳ Include allies":"↳ Include allies","Also show auto nuke options when right-clicking allied players.":"Also show auto nuke options when right-clicking allied players.","Block non-team trades":"Block non-team trades","Team games only: blocks trades with players who are not on your team.":"Team games only: blocks trades with players who are not on your team.","Blocks trades with players who are not on your team.":"Blocks trades with players who are not on your team.","Available only during an active team game.":"Available only during an active team game.","Economic helpers":"Economic helpers","Gold per minute":"Gold per minute","Show gold per minute":"Show gold per minute","Show gold per minute preview":"Show gold per minute preview","Adds GPM to the player hover panel.":"Adds GPM to the player hover panel.","Team gold per minute":"Team gold per minute","Show team gold per minute":"Show team gold per minute","Show team gold per minute preview":"Show team gold per minute preview","Lists each team's total GPM in team games.":"Lists each team's total GPM in team games.","Lists each team's total GPM.":"Lists each team's total GPM.","Top 10 gold per minute":"Top 10 gold per minute","Lists the highest tracked player GPM.":"Lists the highest tracked player GPM.","Trade balances":"Trade balances","Show trade balances":"Show trade balances","Show trade balances preview":"Show trade balances preview","Shows observed ship and train trade imports and exports.":"Shows observed ship and train trade imports and exports.","Shows observed trade imports and exports.":"Shows observed trade imports and exports.","Heatmaps":"Heatmaps","Economic heatmap":"Economic heatmap","Highlights structures with observed trade revenue.":"Highlights structures with observed trade revenue.","Intensity":"Intensity","Economic heatmap intensity":"Economic heatmap intensity","Low":"Low","High":"High","Export partner heatmap":"Export partner heatmap","Hover a player to highlight the partner industry that fuels their exports.":"Hover a player to highlight the partner industry that fuels their exports.","Hover a player to highlight export partners.":"Hover a player to highlight export partners.","Auto-Join":"Auto-Join","Auto-Join ON":"Auto-Join ON","Auto-Join OFF":"Auto-Join OFF","Lobby Forecast":"Lobby Forecast","Lobby forecast":"Lobby forecast","ETA (estimate)":"ETA (estimate)","Hit chance next 10 lobbies":"Hit chance next 10 lobbies","Median to match":"Median to match","lobbies":"lobbies","Send 1% Boat":"Send 1% Boat","Right-click any tile to send a boat with 1% troops, then restores your ratio.":"Right-click any tile to send a boat with 1% troops, then restores your ratio.","Not enough data yet":"Not enough data yet","Game found notification popup":"Game found notification popup","Show a notification when a game is found":"Show a notification when a game is found","Min lobby size":"Min lobby size","Only join lobbies whose max player count is greater than this number.":"Only join lobbies whose max player count is greater than this number.","Match size":"Match size","Players per team":"Players per team","Teams in match":"Teams in match","Players":"Players","Player":"Player","Owned":"Owned","Gold/min":"Gold/min","Show building counts":"Show building counts","Player stats panel":"Player stats panel","All-players table (owned, gold, gold/min, max troops; expandable); hides the game's leaderboard.":"All-players table (owned, gold, gold/min, max troops; expandable); hides the game's leaderboard.","Highlight hovered player":"Highlight hovered player","Highlights the hovered player's row in the stats panel.":"Highlights the hovered player's row in the stats panel.","Min":"Min","Max":"Max","per team":"per team","teams":"teams","players":"players","These limits cancel out — no lobby can match.":"These limits cancel out — no lobby can match.","Searching for":"Searching for","Lobby Type":"Lobby Type","FFA":"FFA","Duos":"Duos","Trios":"Trios","Quads":"Quads","Teams larger than Quads":"Teams larger than Quads","Matches team lobbies with more than 4 players per team.":"Matches team lobbies with more than 4 players per team.","Modifier":"Modifier","Random spawn":"Random spawn","Alliances disabled":"Alliances disabled","Ports disabled":"Ports disabled","Nukes disabled":"Nukes disabled","SAMs disabled":"SAMs disabled","Water nukes":"Water nukes","4min peace time":"4min peace time","2x gold":"2x gold","Start Gold":"Start Gold","You can turn on all three at the same time. Only one of them has to match.":"You can turn on all three at the same time. Only one of them has to match.","You can turn on all four at the same time. Only one of them has to match.":"You can turn on all four at the same time. Only one of them has to match.","0M starting gold":"0M starting gold","Default case with no explicit starting gold.":"Default case with no explicit starting gold.","1M starting gold":"1M starting gold","5M starting gold":"5M starting gold","25M starting gold":"25M starting gold","Exclude":"Exclude","Include":"Include","Maps":"Maps","Clear":"Clear","Search maps":"Search maps","Map filters":"Map filters","Include requires a match. Exclude blocks an option. For starting gold, any included value is enough, while excluded values are always rejected.":"Include requires a match. Exclude blocks an option. For starting gold, any included value is enough, while excluded values are always rejected.","Helper preview":"Helper preview","Close":"Close","Close helper preview":"Close helper preview","Previous image":"Previous image","Next image":"Next image","Close helpers panel":"Close helpers panel","Resize helpers panel":"Resize helpers panel","On":"On","Off":"Off","Show Mark bot nations red preview":"Show Mark bot nations red preview","Show alliances preview":"Show alliances preview","Show Nuke prediction preview":"Show Nuke prediction preview","Show Auto nuke preview":"Show Auto nuke preview","Show Economic heatmap preview":"Show Economic heatmap preview","Game Found!":"Game Found!","Joining now...":"Joining now...","Cologne":"Cologne","Cologne, Germany and European Union":"Cologne, Germany and European Union","Cologne Cathedral":"Cologne Cathedral","Germany":"Germany","European Union":"European Union","github":"GitHub","openGitHubRepository":"Open GitHub repository","GitHub":"GitHub","Open GitHub repository":"Open GitHub repository","N":"N","global":"Global","lobby":"Lobby","send":"Send","writeAMessage":"Write a message...","noMessagesYet":"No messages yet.","anonymous":"Anonymous","Global":"Global","Lobby":"Lobby","Send":"Send","Write a message...":"Write a message...","No messages yet.":"No messages yet.","Anonymous":"Anonymous","Starting…":"Starting…","Disabled":"Disabled","Waiting to enter game…":"Waiting to enter game…","⛔ Blocked in a public lobby":"⛔ Blocked in a public lobby","Waiting for player…":"Waiting for player…","Spawn phase…":"Spawn phase…","💀 Eliminated":"💀 Eliminated","Expanding…":"Expanding…","Massing troops / building…":"Massing troops / building…","Error (see console)":"Error (see console)","Waiting for build-menu (eventBus)…":"Waiting for build-menu (eventBus)…","Spawn intent not found":"Spawn intent not found","No suitable spawn tile found":"No suitable spawn tile found","Spawn tile selected":"Spawn tile selected","Attack intent not found":"Attack intent not found","🛡️ Defending…":"🛡️ Defending…","build-menu not found":"build-menu not found","Waiting for border data (borderTiles)…":"Waiting for border data (borderTiles)…","⛔ Public lobby — auto-disabled":"⛔ Public lobby — auto-disabled","Checking lobby…":"Checking lobby…","⛔ Public lobby — bot locked":"⛔ Public lobby — bot locked","Private lobby":"Private lobby","Singleplayer":"Singleplayer","🚢 Deploy warship":"🚢 Deploy warship","🚢 Dispatch {moved}/{total} ships (keep {home} on guard)":"🚢 Dispatch {moved}/{total} ships (keep {home} on guard)","🤝➡️ Send alliance {name}":"🤝➡️ Send alliance {name}","🔁 Renew alliance {name}":"🔁 Renew alliance {name}","🤝 Alliance {name}":"🤝 Alliance {name}","🏗️ Build {type} (score {score})":"🏗️ Build {type} (score {score})","⬆️ Upgrade {type} (level {level})":"⬆️ Upgrade {type} (level {level})","🚫 Embargo {name}":"🚫 Embargo {name}","🎁 Donate troops {name}":"🎁 Donate troops {name}","🤖 Crush {count} bots":"🤖 Crush {count} bots","🛡️ Retaliate {name}":"🛡️ Retaliate {name}","🟩 Grab empty land":"🟩 Grab empty land","☢️🟩 Reclaim irradiated land":"☢️🟩 Reclaim irradiated land","🌊⚔️ Surge from beachhead {name}":"🌊⚔️ Surge from beachhead {name}","🎯 Attack weakened {name}":"🎯 Attack weakened {name}","🤝⚔️ Assist ally vs {name}":"🤝⚔️ Assist ally vs {name}","🗡️ Attack traitor {name}":"🗡️ Attack traitor {name}","😴 Attack AFK {name}":"😴 Attack AFK {name}","⚔️ Pile on {name}":"⚔️ Pile on {name}","😠 Attack nemesis {name}":"😠 Attack nemesis {name}","🏭⚔️ Seize economy {name}":"🏭⚔️ Seize economy {name}","⚔️ Attack weakest {name}":"⚔️ Attack weakest {name}","⛵ Probe landing {name}":"⛵ Probe landing {name}","☢️ MIRV the dominant enemy":"☢️ MIRV the dominant enemy","☢️ Saturate air defense ({bombs} bombs, {sams} SAMs on path)":"☢️ Saturate air defense ({bombs} bombs, {sams} SAMs on path)","☢️ Nuclear RETALIATION":"☢️ Nuclear RETALIATION","☢️ Launch":"☢️ Launch","Toggle on/off":"Toggle on/off","Collapse":"Collapse","🎮 Controls":"🎮 Controls","📜 Log":"📜 Log","Auto-spawn":"Auto-spawn","Auto-expand / attack":"Auto-expand / attack","Auto-landing (1% boat)":"Auto-landing (1% boat)","Auto-deploy warships":"Auto-deploy warships","Auto-build":"Auto-build","Auto-launch nukes":"Auto-launch nukes","Auto-accept alliances":"Auto-accept alliances","Auto-donate troops (allies)":"Auto-donate troops (allies)","⚙ Advanced settings ▾":"⚙ Advanced settings ▾","⚙ Advanced settings ▴":"⚙ Advanced settings ▴","Troop reserve (vs players)":"Troop reserve (vs players)","Trigger threshold":"Trigger threshold","Reserve when grabbing land":"Reserve when grabbing land","Troops per landing boat":"Troops per landing boat","Tick interval":"Tick interval","Troops":"Troops","Gold":"Gold","Tiles":"Tiles","Estates":"Estates","Estates panel":"Estates panel","Lists your separate land parcels (tile counts); click a row to jump there.":"Lists your separate land parcels (tile counts); click a row to jump there.","Attacks":"Attacks","Builds":"Builds","Nukes":"Nukes","All":"All","No actions yet":"No actions yet","Spawn":"Spawn","Combat":"Combat","Naval":"Naval","Building":"Building","Strikes":"Strikes","Diplomacy":"Diplomacy","🏁 Spawn":"🏁 Spawn","AI difficulty (faithful in-game Nation)":"AI difficulty (faithful in-game Nation)","Advanced settings ▴":"Advanced settings ▴","Advanced settings ▾":"Advanced settings ▾","Auto toggles ▴":"Auto toggles ▴","Auto toggles ▾":"Auto toggles ▾","Edit filters":"Edit filters","Public lobby":"Public lobby","Status & stats ▴":"Status & stats ▴","Status & stats ▾":"Status & stats ▾","Win-fixes (snowball to 80% — deviates from pure faithful)":"Win-fixes (snowball to 80% — deviates from pure faithful)","Auto Bot":"Auto Bot","Auto-join":"Auto-join","Automatically join lobbies that match your filters.":"Automatically join lobbies that match your filters.","Found-game notification":"Found-game notification","Show a toast and play a chime when a match is found.":"Show a toast and play a chime when a match is found.","Keep searching after joining":"Keep searching after joining","Don't turn auto-join off when a match is found, so it resumes after the game ends.":"Don't turn auto-join off when a match is found, so it resumes after the game ends.","Auto-leave on team win":"Auto-leave on team win","Leave the match automatically once you / your team win.":"Leave the match automatically once you / your team win.","Minimum lobby size":"Minimum lobby size","Only join lobbies with at least this many players (blank = any).":"Only join lobbies with at least this many players (blank = any).","Number of teams in the lobby (blank = any).":"Number of teams in the lobby (blank = any).","In-game panel":"In-game panel","Floating auto-join panel":"Floating auto-join panel","Show the in-game auto-join quick panel.":"Show the in-game auto-join quick panel.","▶ Test chime":"▶ Test chime","Tap a filter to cycle: off → include (green, must match) → exclude (red, skip these).":"Tap a filter to cycle: off → include (green, must match) → exclude (red, skip these).","Start gold":"Start gold","Modifiers":"Modifiers","Include maps":"Include maps","Exclude maps":"Exclude maps","Select all":"Select all","Economic":"Economic","Game":"Game","Advanced":"Advanced","Team build stats":"Team build stats","Counts each team's structures; warns on a first Missile Silo.":"Counts each team's structures; warns on a first Missile Silo.","Shows observed ship and train trade imports/exports.":"Shows observed ship and train trade imports/exports.","Moves alliance requests into a side window.":"Moves alliance requests into a side window.","Shows predicted enemy nuke landing points & radius.":"Shows predicted enemy nuke landing points & radius.","Shows boat landings (blue = you, teal = team, green = allies, yellow/red = enemies).":"Shows boat landings (blue = you, teal = team, green = allies, yellow/red = enemies).","Always show your boat routes":"Always show your boat routes","Draws your own boat routes without hovering.":"Draws your own boat routes without hovering.","Always show team boat routes":"Always show team boat routes","Draws teammates' boat routes without hovering.":"Draws teammates' boat routes without hovering.","Always show ally boat routes":"Always show ally boat routes","Draws allies' (alliance) boat routes without hovering.":"Draws allies' (alliance) boat routes without hovering.","Always show enemy boat routes":"Always show enemy boat routes","Draws enemy boat routes without hovering.":"Draws enemy boat routes without hovering.","Boat panel":"Boat panel","Lists boats you sent and boats incoming to your territory, with arrival times.":"Lists boats you sent and boats incoming to your territory, with arrival times.","Incoming boat warning":"Incoming boat warning","Center-screen alert when a new boat targets your territory (like the Missile Silo warning).":"Center-screen alert when a new boat targets your territory (like the Missile Silo warning).","⚠ Incoming boat":"⚠ Incoming boat","is landing in your territory!":"is landing in your territory!","Boats":"Boats","Sent":"Sent","Incoming":"Incoming","No boats":"No boats","arriving":"arriving","Nuke suggestions":"Nuke suggestions","Suggests high-value nuke targets.":"Suggests high-value nuke targets.","Automatically fires nukes at suggested targets.":"Automatically fires nukes at suggested targets.","Auto nuke — include allies":"Auto nuke — include allies","Allow auto nuke to target allies.":"Allow auto nuke to target allies.","Send 1% boat":"Send 1% boat","Adds a 1%-troop boat send to the context menu.":"Adds a 1%-troop boat send to the context menu.","Economy heatmap":"Economy heatmap","Overlays an economic-activity heatmap.":"Overlays an economic-activity heatmap.","Highlights your trade export partners.":"Highlights your trade export partners.","The auto-bot loads inside a game. Open this tab while in a match to configure it.":"The auto-bot loads inside a game. Open this tab while in a match to configure it.","Auto-bot enabled":"Auto-bot enabled","Run the auto-bot (private-lobby training tool).":"Run the auto-bot (private-lobby training tool).","Show auto-bot panel":"Show auto-bot panel","Show the in-game auto-bot panel (X on the panel hides it).":"Show the in-game auto-bot panel (X on the panel hides it).","Difficulty":"Difficulty","Behaviors":"Behaviors","Win-condition fixes":"Win-condition fixes","Adjust the bot's internal win thresholds.":"Adjust the bot's internal win thresholds.","Tick interval (ms)":"Tick interval (ms)","Engine poll interval; lower = snappier but heavier.":"Engine poll interval; lower = snappier but heavier.","Automatically pick a spawn tile.":"Automatically pick a spawn tile.","Expand into land and attack targets.":"Expand into land and attack targets.","Build and upgrade structures.":"Build and upgrade structures.","Auto-landing (boats)":"Auto-landing (boats)","Send probe and landing boats overseas.":"Send probe and landing boats overseas.","Fire nukes at high-value targets.":"Fire nukes at high-value targets.","Build and dispatch warships.":"Build and dispatch warships.","Auto-alliances":"Auto-alliances","Send and accept alliance requests.":"Send and accept alliance requests.","Auto-donate troops":"Auto-donate troops","Donate troops to needy allies.":"Donate troops to needy allies.","Allow betrayal":"Allow betrayal","Off = stay loyal (defensive retaliation only).":"Off = stay loyal (defensive retaliation only).","Easy":"Easy","Medium":"Medium","Hard":"Hard","Impossible":"Impossible","Language":"Language","Language of the helper UI (English / Tiếng Việt). Applies in-game too.":"Language of the helper UI (English / Tiếng Việt). Applies in-game too.","About":"About","Language of the helper UI. Missing text falls back to English. Applies in-game too.":"Language of the helper UI. Missing text falls back to English. Applies in-game too.","⚠ Missile Silo warning":"⚠ Missile Silo warning","just built its first Missile Silo!":"just built its first Missile Silo!","Focus":"Focus","Move camera to the silo":"Move camera to the silo","Team build":"Team build","Total structures":"Total structures","Owned tiles":"Owned tiles","Owned (% of map)":"Owned (% of map)","Max troops":"Max troops","Auto-build structures":"Auto-build structures","City":"City","Port":"Port","Factory":"Factory","Defense Post":"Defense Post","SAM Launcher":"SAM Launcher","Missile Silo":"Missile Silo","Shows the hovered player's GPM inside the Top 10 panel.":"Shows the hovered player's GPM inside the Top 10 panel.","Per-team structures, gold/min, owned tiles & max troops; warns on a first Missile Silo.":"Per-team structures, gold/min, owned tiles & max troops; warns on a first Missile Silo.","⚠ First silo: {teams}":"⚠ First silo: {teams}","Trade balance":"Trade balance","Total imports":"Total imports","Total exports":"Total exports","Factory/port spent":"Factory/port spent","Return on investment":"Return on investment","Break even":"Break even","tracking":"tracking","No observed trade yet":"No observed trade yet","Alliance requests":"Alliance requests","Expires soon":"Expires soon","Nation":"Nation","My nuke":"My nuke","Ally nuke":"Ally nuke","Enemy nuke":"Enemy nuke","Ally landing":"Ally landing","Team landing":"Team landing","Your landing":"Your landing","Landing!":"Landing!","Landing":"Landing","⛵ Random boat":"⛵ Random boat","⚔️ Attack":"⚔️ Attack","⛵ Boat (land grab)":"⛵ Boat (land grab)","⛵ Boat attack":"⛵ Boat attack","🎁 Donate troops":"🎁 Donate troops","Thinking…":"Thinking…","Loading bot…":"Loading bot…","☢️ MIRV":"☢️ MIRV","🏁 Spawned":"🏁 Spawned","☢️ Upgrade silo":"☢️ Upgrade silo","🛡️ Defense post":"🛡️ Defense post","🚢 Retaliate warship":"🚢 Retaliate warship","🚢 Counter warship":"🚢 Counter warship","Atom":"Atom","Atom batch-fire":"Atom batch-fire","Aim at a target and press the hotkey to open the batch-fire dialog. It paces shots just under the server's rate limit so none are dropped, works out how many atoms pierce the SAMs along the flight path (+1 to land), and can finish with a Hydrogen once the atoms bait the interceptors. Press Esc to abort a salvo.":"Aim at a target and press the hotkey to open the batch-fire dialog. It paces shots just under the server's rate limit so none are dropped, works out how many atoms pierce the SAMs along the flight path (+1 to land), and can finish with a Hydrogen once the atoms bait the interceptors. Press Esc to abort a salvo.","Firing throttle":"Firing throttle","shots/sec":"shots/sec","Atoms per burst":"Atoms per burst","Pacing hint. The macro never fires faster than the server-safe rate, so this only matters when you slow it down.":"Pacing hint. The macro never fires faster than the server-safe rate, so this only matters when you slow it down.","Delay (ms)":"Delay (ms)","Spacing between bursts. Raising it throttles the fire rate below the safe maximum — it can never speed past it.":"Spacing between bursts. Raising it throttles the fire rate below the safe maximum — it can never speed past it.","Effective fire rate":"Effective fire rate","Live delivery cadence at the current throttle (green = server-safe max).":"Live delivery cadence at the current throttle (green = server-safe max).","Reset to safe defaults":"Reset to safe defaults","These knobs only throttle the macro below its server-safe maximum (~7 shots/sec) — they never make it fire faster. Quantity and the “last bomb = Hydrogen” choice are picked per target in the in-game dialog.":"These knobs only throttle the macro below its server-safe maximum (~7 shots/sec) — they never make it fire faster. Quantity and the “last bomb = Hydrogen” choice are picked per target in the in-game dialog.","No languages found":"No languages found","Source, issues & updates":"Source, issues & updates","Test chime":"Test chime","standalone userscript":"standalone userscript","Lobby found — joining!":"Lobby found — joining!"},"vi":{"languageCode":"vi","languageName":"Tiếng Việt","settings":"Thiết lập","openSettings":"Thiết lập","customNotificationSound":"Âm thanh thông báo tự chọn","test":"Thử","upload":"Tải lên","remove":"Bỏ","defaultSound":"Mặc định","customSound":"Tự chọn","language":"Ngôn ngữ","openLanguageMenu":"Đổi ngôn ngữ","searchLanguages":"Tìm kiếm ngôn ngữ","noLanguagesFound":"Không tìm thấy ngôn ngữ","autoJoinOn":"Auto-Join: BẬT","autoJoinOff":"Auto-Join: TẮT","showFloatingHelpersPanel":"Hiện bảng trợ giúp nổi trên OpenFront","hideFloatingHelpersPanel":"Ẩn bảng trợ giúp nổi trên OpenFront","Auto-Join & Helpers for OpenFront":"& Trợ giúp tự động cho OpenFront","Settings":"Thiết lập","Open settings":"Thiết lập","Change language":"Đổi ngôn ngữ","Custom notification sound":"Âm thanh thông báo tự chọn","Show floating helpers panel on OpenFront":"Hiện bảng trợ giúp nổi trên OpenFront","Test":"Thử","Upload":"Tải lên","Remove":"Bỏ","Default":"Mặc định","Search languages":"Tìm kiếm ngôn ngữ","Languages":"Ngôn ngữ","Join our Discord":"Gia nhập hội nghị","One-time setup":"Thiết lập một lần","If openfront.io was already open when you installed this extension, reload that tab once. Otherwise the extension will not work on that tab.":"Nếu openfront.io đã được mở khi bạn cài đặt phần mở rộng này, nạp lại trang đó một lần. Nếu không, phần mở rộng sẽ không hoạt động trên trang đó.","Got it":"Hiểu rồi.","Helpers":"Trợ giúp","Float":"Nổi","Game helpers":"Trợ giúp trò chơi","Mark bot nations red":"Đánh dấu đỏ các Nation AI","Adds a red marker to nation AI names on the map.":"Thêm điểm đánh dấu đỏ vào tên AI trên bản đồ.","Adds a red marker to nation AI names.":"Thêm dấu đỏ vào tên AI của quốc gia.","Alliances":"Liên kết","Show alliances":"Hiện các liên kết","Highlights allies with remaining alliance time.":"Đồng minh nổi bật với thời gian liên minh còn lại.","Cheats":"Gian lận","Only available in solo or custom games.":"Hãy chỉ dùng trong trò chơi đơn hoặc tùy chỉnh.","⚠ Only available in solo or custom games.":"Xin vui lòng chỉ sử dụng trong trò chơi đơn hoặc tùy chỉnh.","Nuke prediction":"Dự đoán Nuke","Shows predicted enemy nuke landing points and explosion radius.":"Cho thấy kẻ địch đang hạ cánh và bán kính nổ.","Shows predicted enemy nuke landing points and blast radius.":"Cho thấy kẻ thù đang hạ cánh và bán kính nổ.","Boat prediction":"Tiên đoán về thuyền","Shows enemy boat landing points. Red = targeting you, yellow = targeting others.":"Cho thấy tàu địch đang hạ cánh. Red = nhắm vào bạn, màu vàng = mục tiêu khác.","Warship routes":"Đường đi tàu chiến","Draws warship destinations & routes (blue = you, teal = team, green = allies, red = enemies).":"Vẽ đích & đường đi của tàu chiến (xanh = bạn, teal = đội, lục = đồng minh, đỏ = địch).","Nuke suggestion":"Đề nghị đầu tư","Beta":"Beta","May lag":"chiều","Hover an enemy to show high-damage atom and hydrogen targets.":"Qua một kẻ thù để hiển thị các nguyên tử cao độ và mục tiêu hydro cao.","Auto nuke":"Tự động hạt nhân","Adds auto economy and population nuke actions to the player wheel.":"Thêm nền kinh tế tự động và hoạt động hạt nhân dân số vào bánh xe cầu thủ.","Include allies":"Bao gồm các đồng minh","↳ Include allies":"thể hiện sự đồng minh","Also show auto nuke options when right-clicking allied players.":"Cũng hiển thị tùy chọn hạt nhân tự động khi cầu thủ nhấn chuột phải.","Block non-team trades":"Chặn giao thương ngoài đội","Team games only: blocks trades with players who are not on your team.":"Chỉ đội game: ngăn chặn giao dịch với những người không cùng đội.","Blocks trades with players who are not on your team.":"Giao dịch với những người không cùng đội.","Available only during an active team game.":"Chỉ sẵn sàng trong một trò chơi đội hoạt động.","Economic helpers":"Những người trợ giúp về kinh tế","Gold per minute":"Vàng trên phút","Show gold per minute":"Hiện vàng trên phút","Show gold per minute preview":"Hiện ô xem thử vàng trên phút","Adds GPM to the player hover panel.":"Thêm GPM vào bảng điều khiển đang bay.","Team gold per minute":"Đội vàng mỗi phút","Show team gold per minute":"Hiện vàng của đội trên phút","Show team gold per minute preview":"Hiện vàng nhóm trên xem thử phút","Lists each team's total GPM in team games.":"Danh sách tổng số GPM của mỗi đội trong trò chơi đồng đội.","Lists each team's total GPM.":"Ghi lại tổng số GPM của mỗi đội.","Top 10 gold per minute":"Vàng trên 10 ngàn mỗi phút","Lists the highest tracked player GPM.":"Danh sách người chơi được đánh dấu cao nhất GPM.","Trade balances":"Cán cân giao thương","Show trade balances":"Hiển thị cân bằng thương mại","Show trade balances preview":"Hiển thị ô xem thử cân bằng thương mại","Shows observed ship and train trade imports and exports.":"Người ta quan sát thấy tàu bè và tàu hỏa nhập khẩu và xuất khẩu.","Shows observed trade imports and exports.":"Hiển thị nhập khẩu và xuất khẩu thương mại.","Heatmaps":"Sơ đồ nhiệt","Economic heatmap":"Sơ đồ nhiệt độ kinh tế","Highlights structures with observed trade revenue.":"Những kiến trúc nổi bật với thu nhập thương mại quan sát được.","Intensity":"Cường độ","Economic heatmap intensity":"Cường độ bản đồ nhiệt kinh tế","Low":"Thấp","High":"Cao","Export partner heatmap":"Xuất bản đồ nhiệt cộng sự","Hover a player to highlight the partner industry that fuels their exports.":"Một cầu thủ để nhấn mạnh đến ngành công nghiệp cộng sự đã cung cấp nhiên liệu cho xuất khẩu của họ.","Hover a player to highlight export partners.":"Thêm một cầu thủ để làm nổi bật các đối tác xuất khẩu.","Auto-Join":"Auto-Join","Auto-Join ON":"Auto-Join: BẬT","Auto-Join OFF":"Auto-Join: TẮT","Game found notification popup":"Thông báo bật lên khi tìm được trận","Show a notification when a game is found":"Hiển thị thông báo khi tìm trò chơi","Searching for":"Tìm kiếm","Lobby Type":"Loại phòng","FFA":"FFA","Duos":"Duos","Trios":"Trios","Quads":"Quads","Teams larger than Quads":"Đội lớn hơn đội Quads","Matches team lobbies with more than 4 players per team.":"Khớp đội với hơn 4 cầu thủ mỗi đội.","Modifier":"Sửa đổi","Random spawn":"Tự nhiên sinh sản","Alliances disabled":"Các liên kết bị tắt","2x gold":"2x vàng","Start Gold":"Bắt đầu vàng","You can turn on all three at the same time. Only one of them has to match.":"Anh có thể bật cả ba cùng một lúc. Chỉ có một trong số họ phải khớp.","0M starting gold":"0M bắt đầu vàng","Default case with no explicit starting gold.":"Trường hợp mặc định mà không có rõ ràng bắt đầu vàng.","5M starting gold":"5M bắt đầu vàng","25M starting gold":"25M bắt đầu vàng","Exclude":"Mở rộng","Maps":"Bản đồ","Clear":"& Xoá","Search maps":"Bản đồ tìm kiếm","Map filters":"Lọc sơ đồ","Include requires a match. Exclude blocks an option. For starting gold, any included value is enough, while excluded values are always rejected.":"Bao gồm yêu cầu một trận đấu. Thoát khỏi một lựa chọn. Để bắt đầu việc sản xuất vàng, bất cứ giá trị nào bao gồm đều đủ, trong khi loại bỏ các giá trị thì luôn luôn bị từ chối.","Helper preview":"Xem thử trợ giúp","Close":"Đóng","Close helper preview":"Xem thử trợ giúp đóng","Previous image":"Ảnh trước","Next image":"Ảnh kế","Close helpers panel":"Đóng bảng trợ giúp","Resize helpers panel":"Đổi cỡ bảng trợ giúp","On":"Vào","Off":"Tắt","Show Mark bot nations red preview":"Hiện ô xem thử cho quốc gia robot màu đỏ","Show alliances preview":"Hiện ô xem thử liên kết","Show Nuke prediction preview":"Hiện ô xem thử dự đoán","Show Auto nuke preview":"Hiện ô xem thử hạt nhân tự động","Show Economic heatmap preview":"Hiện ô xem thử sơ đồ nhiệt độ kinh tế","Game Found!":"Tìm thấy rồi!","Joining now...":"Tham gia bây giờ...","Cologne":"Cologne","Cologne, Germany and European Union":"Cologne, Đức và Liên minh Châu Âu","Cologne Cathedral":"Nhà thờ Cologne","Germany":"Đức","European Union":"Liên minh châu Âu","Min lobby size":"Cỡ phòng tối thiểu","Only join lobbies whose max player count is greater than this number.":"Only join lobbies whose max player count is greater than this number.","Ports disabled":"Ports disabled","Nukes disabled":"Nukes disabled","SAMs disabled":"SAMs disabled","Water nukes":"Water nukes","4min peace time":"4min peace time","You can turn on all four at the same time. Only one of them has to match.":"You can turn on all four at the same time. Only one of them has to match.","1M starting gold":"1M starting gold","openMacros":"Open macros","Open macros":"Open macros","Macros":"Macros","⚓ Send 1% Boat":"⚓ Send 1% Boat","Send a transport with 1% troops, then restore the attack ratio.":"Send a transport with 1% troops, then restore the attack ratio.","Hotkey":"Hotkey","Shift + B":"Shift + B","Show button in selection wheel":"Show button in selection wheel","Alliance requests panel":"Alliance requests panel","Moves alliance requests and renewal prompts into a separate right-side window.":"Moves alliance requests and renewal prompts into a separate right-side window.","Lobby Forecast":"Lobby Forecast","Lobby forecast":"Dự báo phòng","ETA (estimate)":"ETA (ước tính)","Hit chance next 10 lobbies":"Tỉ lệ trúng trong 10 phòng tới","Median to match":"Trung vị tới khi khớp","lobbies":"lobbies","Send 1% Boat":"Send 1% Boat","Right-click any tile to send a boat with 1% troops, then restores your ratio.":"Right-click any tile to send a boat with 1% troops, then restores your ratio.","Not enough data yet":"Not enough data yet","cancel":"Hủy","Cancel":"Cancel","github":"GitHub","openGitHubRepository":"Open GitHub repository","GitHub":"GitHub","Open GitHub repository":"Open GitHub repository","N":"N","global":"Global","lobby":"Lobby","send":"Gửi","writeAMessage":"Write a message...","noMessagesYet":"No messages yet.","anonymous":"Ẩn danh","Global":"Global","Lobby":"Lobby","Send":"Send","Write a message...":"Write a message...","No messages yet.":"No messages yet.","Anonymous":"Anonymous","Starting…":"Đang khởi động…","Disabled":"Đã tắt","Waiting to enter game…":"Đang chờ vào game…","⛔ Blocked in a public lobby":"⛔ Bị chặn trong Public lobby","Waiting for player…":"Đang chờ người chơi…","Spawn phase…":"Giai đoạn spawn…","💀 Eliminated":"💀 Đã bị loại","Expanding…":"Đang bành trướng…","Massing troops / building…":"Đang tích quân / xây dựng…","Error (see console)":"Lỗi (xem console)","Waiting for build-menu (eventBus)…":"Đang chờ build-menu (eventBus)…","Spawn intent not found":"Không tìm thấy intent spawn","No suitable spawn tile found":"Không tìm được ô spawn phù hợp","Spawn tile selected":"Đã chọn ô spawn","Attack intent not found":"Không tìm thấy intent tấn công","🛡️ Defending…":"🛡️ Đang phòng thủ…","build-menu not found":"Không tìm thấy build-menu","Waiting for border data (borderTiles)…":"Chờ dữ liệu biên giới (borderTiles)…","⛔ Public lobby — auto-disabled":"⛔ Public lobby — đã tự tắt","Checking lobby…":"Đang kiểm tra lobby…","⛔ Public lobby — bot locked":"⛔ Public lobby — bot bị khoá","Private lobby":"Phòng riêng","Singleplayer":"Chơi đơn","🚢 Deploy warship":"🚢 Thả thuyền chiến","🚢 Dispatch {moved}/{total} ships (keep {home} on guard)":"🚢 Điều {moved}/{total} tàu (giữ {home} coi nhà)","🤝➡️ Send alliance {name}":"🤝➡️ Gửi liên minh {name}","🔁 Renew alliance {name}":"🔁 Gia hạn liên minh {name}","🤝 Alliance {name}":"🤝 Liên minh {name}","🏗️ Build {type} (score {score})":"🏗️ Xây {type} (điểm {score})","⬆️ Upgrade {type} (level {level})":"⬆️ Nâng cấp {type} (cấp {level})","🚫 Embargo {name}":"🚫 Cấm vận {name}","🎁 Donate troops {name}":"🎁 Tặng quân {name}","🤖 Crush {count} bots":"🤖 Diệt {count} bot","🛡️ Retaliate {name}":"🛡️ Phản công {name}","🟩 Grab empty land":"🟩 Chiếm đất trống","☢️🟩 Reclaim irradiated land":"☢️🟩 Chiếm đất nhiễm xạ","🌊⚔️ Surge from beachhead {name}":"🌊⚔️ Dồn quân từ bãi đổ bộ {name}","🎯 Attack weakened {name}":"🎯 Đánh suy yếu {name}","🤝⚔️ Assist ally vs {name}":"🤝⚔️ Hỗ trợ đồng minh đánh {name}","🗡️ Attack traitor {name}":"🗡️ Đánh phản bội {name}","😴 Attack AFK {name}":"😴 Đánh AFK {name}","⚔️ Pile on {name}":"⚔️ Hôi của {name}","😠 Attack nemesis {name}":"😠 Đánh kẻ thù dai dẳng {name}","🏭⚔️ Seize economy {name}":"🏭⚔️ Chiếm kinh tế {name}","⚔️ Attack weakest {name}":"⚔️ Đánh yếu nhất {name}","⛵ Probe landing {name}":"⛵ Đổ bộ thăm dò {name}","☢️ MIRV the dominant enemy":"☢️ MIRV diệt kẻ thống trị","☢️ Saturate air defense ({bombs} bombs, {sams} SAMs on path)":"☢️ Bão hòa phòng không ({bombs} quả, {sams} SAM chặn đường)","☢️ Nuclear RETALIATION":"☢️ TRẢ ĐŨA hạt nhân","☢️ Launch":"☢️ Phóng","Toggle on/off":"Bật/Tắt","Collapse":"Thu gọn","🎮 Controls":"🎮 Điều khiển","📜 Log":"📜 Nhật ký","Auto-spawn":"Tự spawn","Auto-expand / attack":"Tự chiếm đất / đánh","Auto-landing (1% boat)":"Tự đổ bộ (boat 1%)","Auto-deploy warships":"Tự thả thuyền chiến","Auto-build":"Tự xây dựng","Auto-launch nukes":"Tự phóng bom hạt nhân","Auto-accept alliances":"Tự nhận liên minh","Auto-donate troops (allies)":"Tự tặng quân cho đồng minh","⚙ Advanced settings ▾":"⚙ Tuỳ chỉnh nâng cao ▾","⚙ Advanced settings ▴":"⚙ Tuỳ chỉnh nâng cao ▴","Troop reserve (vs players)":"Quân dự trữ (đánh người)","Trigger threshold":"Ngưỡng kích hoạt","Reserve when grabbing land":"Dự trữ khi chiếm đất","Troops per landing boat":"Quân mỗi thuyền đổ bộ","Tick interval":"Nhịp xử lý","Troops":"Quân","Gold":"Vàng","Players":"Người chơi","Player":"Người chơi","Owned":"Sở hữu","Gold/min":"Vàng/phút","Show building counts":"Hiện số công trình","Player stats panel":"Bảng thống kê người chơi","All-players table (owned, gold, gold/min, max troops; expandable); hides the game's leaderboard.":"Bảng tất cả người chơi (sở hữu, vàng, vàng/phút, quân tối đa; mở rộng được); ẩn bảng xếp hạng của game.","Highlight hovered player":"Tô sáng người chơi đang hover","Highlights the hovered player's row in the stats panel.":"Tô sáng dòng của người chơi đang hover trong bảng thống kê.","Tiles":"Ô đất","Estates":"Bất động sản","Estates panel":"Bảng bất động sản","Lists your separate land parcels (tile counts); click a row to jump there.":"Liệt kê các vùng đất tách rời của bạn (số ô); click một dòng để nhảy tới đó.","Attacks":"Đánh","Builds":"Xây","Nukes":"Nuke","All":"Tất cả","No actions yet":"Chưa có hành động nào","Spawn":"Spawn","Combat":"Đánh","Naval":"Hải quân","Building":"Xây dựng","Strikes":"Bắn phá","Diplomacy":"Ngoại giao","🏁 Spawn":"🏁 Spawn","AI difficulty (faithful in-game Nation)":"Độ khó AI (mô phỏng Nation trong game)","Advanced settings ▴":"Cài đặt nâng cao ▴","Advanced settings ▾":"Cài đặt nâng cao ▾","Auto toggles ▴":"Tự động ▴","Auto toggles ▾":"Tự động ▾","Any":"Bất kỳ","Close auto-join panel":"Đóng bảng Auto-Join","Edit filters":"Sửa bộ lọc","Expand":"Mở rộng","Filters":"Bộ lọc","Players per team":"Người mỗi đội","Public lobby":"Phòng công khai","Resize auto-join panel":"Đổi cỡ bảng Auto-Join","Searching":"Đang tìm","Select at least one filter in the popup to enable auto-join.":"Chọn ít nhất một bộ lọc trong popup để bật Auto-Join.","Status & stats ▴":"Trạng thái & thống kê ▴","Status & stats ▾":"Trạng thái & thống kê ▾","Teams in match":"Số đội trong trận","Win-fixes (snowball to 80% — deviates from pure faithful)":"Win-fixes (cuốn chiếu tới 80% — lệch khỏi bản faithful gốc)","Auto Bot":"Auto Bot","Auto-join":"Auto-Join","Automatically join lobbies that match your filters.":"Tự động vào phòng khớp bộ lọc của bạn.","Found-game notification":"Thông báo khi tìm được trận","Keep searching after joining":"Tiếp tục tìm sau khi vào trận","Don't turn auto-join off when a match is found, so it resumes after the game ends.":"Không tắt auto-join khi tìm thấy trận, để nó tiếp tục sau khi trận kết thúc.","Auto-leave on team win":"Tự thoát khi team thắng","Leave the match automatically once you / your team win.":"Tự động rời trận khi bạn / team của bạn thắng.","Show a toast and play a chime when a match is found.":"Hiện thông báo và phát chuông khi tìm được trận.","Team size":"Cỡ đội","Minimum lobby size":"Cỡ phòng tối thiểu","Only join lobbies with at least this many players (blank = any).":"Chỉ vào phòng có ít nhất số người này (để trống = bất kỳ).","Number of teams in the lobby (blank = any).":"Số đội trong phòng (để trống = bất kỳ).","In-game panel":"Bảng trong game","Floating auto-join panel":"Bảng Auto-Join nổi","Show the in-game auto-join quick panel.":"Hiện bảng Auto-Join nhanh trong game.","▶ Test chime":"▶ Thử chuông","Tap a filter to cycle: off → include (green, must match) → exclude (red, skip these).":"Chạm để đổi: tắt → bao gồm (xanh, phải khớp) → loại trừ (đỏ, bỏ qua).","Start gold":"Vàng khởi đầu","Modifiers":"Tùy biến","Include maps":"Map bao gồm","Exclude maps":"Map loại trừ","Select all":"Chọn tất cả","Economic":"Kinh tế","Game":"Trong game","Advanced":"Nâng cao","Team build stats":"Thống kê xây dựng của đội","Counts each team's structures; warns on a first Missile Silo.":"Đếm công trình mỗi đội; cảnh báo khi có Missile Silo đầu tiên.","Shows observed ship and train trade imports/exports.":"Hiện xuất/nhập giao thương qua tàu và xe lửa quan sát được.","Moves alliance requests into a side window.":"Chuyển yêu cầu liên minh sang cửa sổ bên.","Shows predicted enemy nuke landing points & radius.":"Hiện điểm rơi & bán kính bom hạt nhân địch dự đoán.","Shows boat landings (blue = you, teal = team, green = allies, yellow/red = enemies).":"Hiện điểm đổ bộ thuyền (xanh dương = bạn, lục lam = team, xanh lá = đồng minh, vàng/đỏ = địch).","Always show your boat routes":"Luôn hiện đường đi thuyền của bạn","Draws your own boat routes without hovering.":"Vẽ đường đi thuyền của bạn mà không cần rê chuột.","Always show team boat routes":"Luôn hiện đường đi thuyền team","Draws teammates' boat routes without hovering.":"Vẽ đường đi thuyền đồng đội mà không cần rê chuột.","Always show ally boat routes":"Luôn hiện đường đi thuyền đồng minh","Draws allies' (alliance) boat routes without hovering.":"Vẽ đường đi thuyền đồng minh (liên minh) mà không cần rê chuột.","Always show enemy boat routes":"Luôn hiện đường đi thuyền địch","Draws enemy boat routes without hovering.":"Vẽ đường đi thuyền địch mà không cần rê chuột.","Boat panel":"Bảng thuyền","Lists boats you sent and boats incoming to your territory, with arrival times.":"Liệt kê thuyền bạn gửi đi và thuyền đang tới lãnh thổ của bạn, kèm thời gian cập bến.","Incoming boat warning":"Cảnh báo thuyền đang tới","Center-screen alert when a new boat targets your territory (like the Missile Silo warning).":"Cảnh báo giữa màn hình khi có thuyền mới nhắm vào lãnh thổ của bạn (giống cảnh báo Missile Silo).","⚠ Incoming boat":"⚠ Thuyền đang tới","is landing in your territory!":"đang đổ bộ vào lãnh thổ của bạn!","Boats":"Thuyền","Sent":"Đã gửi","Incoming":"Đang tới","No boats":"Không có thuyền","arriving":"đang tới","Nuke suggestions":"Gợi ý bom hạt nhân","Suggests high-value nuke targets.":"Gợi ý mục tiêu hạt nhân giá trị cao.","Automatically fires nukes at suggested targets.":"Tự động bắn bom hạt nhân vào mục tiêu được gợi ý.","Auto nuke — include allies":"Auto nuke — gồm cả đồng minh","Allow auto nuke to target allies.":"Cho phép auto nuke nhắm cả đồng minh.","Send 1% boat":"Gửi thuyền 1%","Adds a 1%-troop boat send to the context menu.":"Thêm lệnh gửi thuyền 1% quân vào menu chuột phải.","Economy heatmap":"Bản đồ nhiệt kinh tế","Overlays an economic-activity heatmap.":"Phủ bản đồ nhiệt hoạt động kinh tế.","Highlights your trade export partners.":"Tô sáng các đối tác xuất khẩu của bạn.","The auto-bot loads inside a game. Open this tab while in a match to configure it.":"Auto-bot chỉ chạy khi vào trận. Mở tab này trong trận để cấu hình.","Auto-bot enabled":"Bật Auto-bot","Run the auto-bot (private-lobby training tool).":"Chạy auto-bot (công cụ luyện tập phòng riêng).","Show auto-bot panel":"Hiện bảng auto-bot","Show the in-game auto-bot panel (X on the panel hides it).":"Hiện bảng auto-bot trong game (nút X trên bảng để ẩn).","Difficulty":"Độ khó","Behaviors":"Hành vi","Win-condition fixes":"Tinh chỉnh điều kiện thắng","Adjust the bot's internal win thresholds.":"Điều chỉnh ngưỡng thắng nội bộ của bot.","Tick interval (ms)":"Chu kỳ tick (ms)","Engine poll interval; lower = snappier but heavier.":"Chu kỳ vòng lặp engine; thấp = nhạy hơn nhưng nặng hơn.","Automatically pick a spawn tile.":"Tự chọn ô spawn.","Expand into land and attack targets.":"Mở rộng lãnh thổ và tấn công mục tiêu.","Build and upgrade structures.":"Xây và nâng cấp công trình.","Auto-landing (boats)":"Tự đổ bộ (thuyền)","Send probe and landing boats overseas.":"Gửi thuyền thăm dò và đổ bộ ra nước ngoài.","Fire nukes at high-value targets.":"Bắn bom hạt nhân vào mục tiêu giá trị cao.","Build and dispatch warships.":"Xây và điều tàu chiến.","Auto-alliances":"Tự liên minh","Send and accept alliance requests.":"Gửi và chấp nhận yêu cầu liên minh.","Auto-donate troops":"Tự tặng quân","Donate troops to needy allies.":"Tặng quân cho đồng minh đang cần.","Allow betrayal":"Cho phép phản bội","Off = stay loyal (defensive retaliation only).":"Tắt = trung thành (chỉ phản đòn phòng thủ).","Easy":"Dễ","Medium":"Trung bình","Hard":"Khó","Impossible":"Bất khả thi","Language":"Ngôn ngữ","Language of the helper UI (English / Tiếng Việt). Applies in-game too.":"Ngôn ngữ giao diện helper (English / Tiếng Việt). Áp dụng cả trong game.","About":"Giới thiệu","Game found notification":"Thông báo tìm được trận","Language of the helper UI. Missing text falls back to English. Applies in-game too.":"Ngôn ngữ giao diện helper. Phần thiếu sẽ hiển thị English. Áp dụng cả trong game.","⚠ Missile Silo warning":"⚠ Cảnh báo Missile Silo","just built its first Missile Silo!":"vừa xây Silo đầu tiên!","Focus":"Ngắm tới","Move camera to the silo":"Đưa camera tới silo","Team build":"Xây dựng đội","Total structures":"Tổng công trình","Owned tiles":"Số ô sở hữu","Owned (% of map)":"Sở hữu (% bản đồ)","Max troops":"Quân tối đa","Auto-build structures":"Tự build công trình","City":"Thành phố","Port":"Cảng","Factory":"Nhà máy","Defense Post":"Trạm phòng thủ","SAM Launcher":"Bệ phóng SAM","Missile Silo":"Hầm tên lửa","Shows the hovered player's GPM inside the Top 10 panel.":"Hiện GPM của nước đang hover ngay trong panel Top 10.","Per-team structures, gold/min, owned tiles & max troops; warns on a first Missile Silo.":"Công trình mỗi đội, gold/phút, số ô sở hữu & quân tối đa; cảnh báo khi có Missile Silo đầu tiên.","⚠ First silo: {teams}":"⚠ Silo đầu tiên: {teams}","Trade balance":"Cán cân giao thương","Total imports":"Tổng nhập","Total exports":"Tổng xuất","Factory/port spent":"Chi cho factory/port","Return on investment":"Lợi nhuận đầu tư (ROI)","Break even":"Hoàn vốn","tracking":"đang theo dõi","No observed trade yet":"Chưa quan sát được giao thương","Alliance requests":"Yêu cầu liên minh","Expires soon":"Sắp hết hạn","Nation":"Nation","My nuke":"Nuke của tôi","Ally nuke":"Nuke đồng minh","Enemy nuke":"Nuke địch","Ally landing":"Đổ bộ đồng minh","Team landing":"Đổ bộ team","Your landing":"Đổ bộ của bạn","Landing!":"Đổ bộ!","Landing":"Đổ bộ","⛵ Random boat":"⛵ Thuyền ngẫu nhiên","⚔️ Attack":"⚔️ Tấn công","⛵ Boat (land grab)":"⛵ Thuyền (chiếm đất)","⛵ Boat attack":"⛵ Thuyền tấn công","🎁 Donate troops":"🎁 Tặng quân","Thinking…":"Đang tính…","Loading bot…":"Đang tải bot…","☢️ MIRV":"☢️ MIRV","🏁 Spawned":"🏁 Đã spawn","☢️ Upgrade silo":"☢️ Nâng cấp silo","🛡️ Defense post":"🛡️ Tháp phòng thủ","🚢 Retaliate warship":"🚢 Phản đòn tàu chiến","🚢 Counter warship":"🚢 Tàu chiến đối kháng","🚢 Dodge nuke":"🚢 Né bom hạt nhân","Atom":"Atom","Atom batch-fire":"Bắn bom Atom hàng loạt","Aim at a target and press the hotkey to open the batch-fire dialog. It paces shots just under the server's rate limit so none are dropped, works out how many atoms pierce the SAMs along the flight path (+1 to land), and can finish with a Hydrogen once the atoms bait the interceptors. Press Esc to abort a salvo.":"Nhắm vào mục tiêu rồi nhấn phím tắt để mở hộp thoại bắn hàng loạt. Macro giãn nhịp bắn ngay dưới giới hạn tốc độ của máy chủ để không phát nào bị mất, tự tính số bom Atom cần để xuyên qua các SAM trên đường bay (+1 để rơi trúng), và có thể kết thúc bằng một quả Hydro sau khi đám Atom đã nhử cạn đạn đánh chặn. Nhấn Esc để hủy loạt bắn.","Firing throttle":"Giới hạn tốc độ bắn","shots/sec":"phát/giây","Atoms per burst":"Số bom mỗi loạt","Pacing hint. The macro never fires faster than the server-safe rate, so this only matters when you slow it down.":"Gợi ý giãn nhịp. Macro không bao giờ bắn nhanh hơn mức an toàn của máy chủ, nên thông số này chỉ có tác dụng khi bạn muốn bắn chậm lại.","Delay (ms)":"Độ trễ (ms)","Spacing between bursts. Raising it throttles the fire rate below the safe maximum — it can never speed past it.":"Khoảng cách giữa các loạt. Tăng lên sẽ ghìm tốc độ bắn xuống dưới mức tối đa an toàn — không bao giờ vượt qua được mức đó.","Effective fire rate":"Tốc độ bắn thực tế","Live delivery cadence at the current throttle (green = server-safe max).":"Nhịp bắn thực tế ở mức giới hạn hiện tại (xanh = mức tối đa an toàn).","Reset to safe defaults":"Đặt lại về mặc định an toàn","These knobs only throttle the macro below its server-safe maximum (~7 shots/sec) — they never make it fire faster. Quantity and the “last bomb = Hydrogen” choice are picked per target in the in-game dialog.":"Các thông số này chỉ ghìm macro xuống dưới mức tối đa an toàn của máy chủ (~7 phát/giây) — không bao giờ làm nó bắn nhanh hơn. Số lượng và lựa chọn “quả cuối = Hydro” được chọn theo từng mục tiêu trong hộp thoại trong trận.","Max":"Tối đa","Min":"Tối thiểu","No languages found":"Không tìm thấy ngôn ngữ","Source, issues & updates":"Mã nguồn, vấn đề & cập nhật","Test chime":"Thử âm báo","standalone userscript":"userscript độc lập","Lobby found — joining!":"Đã tìm thấy phòng — đang vào!"}}}; "use strict";(()=>{function Q(){let e=window.BOOTSTRAP_CONFIG;return e&&typeof e=="object"?e:null}function Oe(){let e=window.ASSET_MANIFEST||Q()?.assetManifest;return e&&typeof e=="object"?e:null}function _e(){return window.CDN_BASE||Q()?.cdnBase||""}function I(e){let n=Oe()?.[e];return n?_e()+n:null}function Z(e){return I(`maps/${e}/thumbnail.webp`)}var Pe={City:"images/CityIconWhite.svg",Port:"images/PortIcon.svg",Factory:"images/FactoryIconWhite.svg","Defense Post":"images/ShieldIconWhite.svg","SAM Launcher":"images/SamLauncherIconWhite.svg","Missile Silo":"images/MissileSiloIconWhite.svg"};function ee(e){let t=Pe[e];return t?I(t):null}var y=globalThis.__OFH_ASSETS??{version:"0.0.0",locales:{}};function X(e,t){try{let n=new Intl.DisplayNames([t],{type:"language"}).of(e);if(n&&n.toLowerCase()!==e.toLowerCase())return n.charAt(0).toUpperCase()+n.slice(1)}catch{}return e}function te(e="en"){let t=Object.keys(y.locales).map(n=>({code:n,name:X(n,e),nativeName:X(n,n)}));return t.sort((n,r)=>n.code==="en"?-1:r.code==="en"?1:n.nativeName.localeCompare(r.nativeName)),t}var k=null;function C(){if(!k){let e=window.AudioContext||window.webkitAudioContext;k=new e}return k}function He(){let e=()=>{k&&k.state==="suspended"&&k.resume()},t={capture:!0,passive:!0};window.addEventListener("pointerdown",e,t),window.addEventListener("keydown",e,t)}function ne(e){let t=C(),n=t.currentTime,r=[{freq:880,start:0,dur:.16},{freq:1318.5,start:.13,dur:.22}];for(let s of r){let a=t.createOscillator(),i=t.createGain();a.type="sine",a.frequency.value=s.freq;let c=n+s.start;i.gain.setValueAtTime(0,c),i.gain.linearRampToValueAtTime(.25*e,c+.02),i.gain.exponentialRampToValueAtTime(1e-4,c+s.dur),a.connect(i).connect(t.destination),a.start(c),a.stop(c+s.dur+.02)}}function Re(e){let t=atob(e),n=new Uint8Array(t.length);for(let r=0;r{let n=";base64,",r=e.indexOf(n);if(!e.startsWith("data:")||r<0)return null;try{return await C().decodeAudioData(Re(e.slice(r+n.length)))}catch(s){return console.error("[ofh] custom sound decode failed:",s),null}})(),oe.set(e,t),t)}var S=class{constructor(t=""){this.preload="auto";this.currentTime=0;this.volume=1;this.loop=!1;this.src=t}async play(){let t=C();if(t.state==="suspended")try{await t.resume()}catch{}let n=this.src.startsWith("data:")?await Ne(this.src):null;if(n){let r=t.createBufferSource();r.buffer=n;let s=t.createGain();s.gain.value=this.volume,r.connect(s).connect(t.destination),r.start(0)}else ne(this.volume)}pause(){}};function re(){let e=C();e.state==="suspended"&&e.resume(),ne(1)}function se(){return He(),S}var O=new Set,ae={id:"openfront-helper",tab:{id:1}};function ie(e){return new Promise(t=>{let n=!1,r=s=>{n||(n=!0,t(s))};for(let s of O)try{let a=s(e,ae,r);a instanceof Promise&&a.then(i=>{i!==void 0&&r(i)})}catch(a){console.error("[ofh] message listener threw:",a)}queueMicrotask(()=>r(void 0))})}var le={addListener:e=>void O.add(e),removeListener:e=>void O.delete(e),hasListener:e=>O.has(e)},pe=e=>ie(e),de={async query(e){return[{id:ae.tab.id,url:location.href}]},async sendMessage(e,t){return ie(t)},async reload(e){location.reload()}};var E="ofh:",_=new Set,z=e=>E+e;function F(e){let t=localStorage.getItem(z(e));if(t!==null)try{return JSON.parse(t)}catch{return}}function Fe(e,t){localStorage.setItem(z(e),JSON.stringify(t))}function B(e){for(let t of _)try{t(e,"local")}catch(n){console.error("[ofh] storage listener threw:",n)}}function Be(e){if(e==null){let t=[];for(let n=0;nvoid _.add(e),removeListener:e=>void _.delete(e),hasListener:e=>_.has(e)};function fe(){window.addEventListener("storage",e=>{if(!e.key||!e.key.startsWith(E)||e.storageArea!==localStorage)return;let t=n=>{if(n!==null)try{return JSON.parse(n)}catch{return}};B({[e.key.slice(E.length)]:{oldValue:t(e.oldValue),newValue:t(e.newValue)}})})}var ze="ofh:beep";function je(e){return e==="assets/autojoin.mp3"?ze:I(e)??e}var De={id:"openfront-helper",lastError:void 0,getURL:je,getManifest:()=>({name:"OpenFront Helper",version:y.version,manifest_version:3}),sendMessage:pe,onMessage:le,onInstalled:{addListener(){},removeListener(){}}},j={runtime:De,storage:{local:ce,onChanged:ue},tabs:de,action:{setIcon(){}},windows:{create(){}}};function he(){let e=window;try{e.chrome=j}catch(t){console.warn("[ofh] could not set window.chrome:",t)}try{typeof e.browser>"u"&&(e.browser=j)}catch{}return fe(),j}function Ge(e){let n=window.OpenFrontHelperI18n?.DEFAULT_TRANSLATIONS||{},r=y.locales.en||{},s=y.locales[e]||{};return Promise.resolve({...n,...r,...s})}function ge(){let e=window;e.OpenFrontHelperI18n&&(e.OpenFrontHelperI18n.loadBundle=Ge)}var Ue="⊕ OpenFront Helper";function L(){try{"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission()}catch{}}function qe(e){try{if("Notification"in window&&Notification.permission==="granted"){let t=new Notification(Ue,{body:e,tag:"ofh-lobby-found"});return t.onclick=()=>{try{window.focus()}catch{}t.close()},!0}}catch{}return!1}function We(e){let t="openfront-helper-join-toast";document.getElementById(t)?.remove();let n=document.createElement("div");n.id=t,n.textContent=e,n.setAttribute("style",["position:fixed","top:16px","left:50%","transform:translateX(-50%)","z-index:2147483646","background:linear-gradient(135deg,#16a34a,#0d9488)","color:#f0fdf4","font:600 14px/1.4 'Aptos','Segoe UI',system-ui,sans-serif","padding:12px 20px","border-radius:10px","border:1px solid rgba(187,247,208,.4)","box-shadow:0 10px 30px rgba(0,0,0,.5)","cursor:pointer","transition:opacity .3s"].join(";")),n.onclick=()=>n.remove(),document.body.appendChild(n),setTimeout(()=>{n.style.opacity="0",setTimeout(()=>n.remove(),350)},5e3)}function me(e){qe(e),We(e)}var $e=[["City","🏙️"],["Port","⚓"],["Factory","🏭"],["Defense Post","🛡️"],["SAM Launcher","🛰️"],["Missile Silo","🚀"]];function Je(e,t){let n=window.__OFH_gameIconUrl?.(e)||"";if(!n)return o("span",{},t);let r=o("img",{src:n,alt:e,width:"16",height:"16",draggable:"false",style:"object-fit:contain;"});return r.addEventListener("error",()=>r.replaceWith(o("span",{},t)),{once:!0}),r}var Ve=()=>window.__OFH_autobot,ve="openfront-helper-atom-batch-v1",ye=140,T={batchSize:10,delayMs:150,lastHydrogen:!1},$=()=>window.__OFH_atomBatch;function q(){let e=$();if(e?.get)try{return{...T,...e.get()}}catch{}try{let t=window.localStorage.getItem(ve);if(t)return{...T,...JSON.parse(t)}}catch{}return{...T}}function be(e){let t=$();if(t?.set)try{t.set(e);return}catch{}let n={...q(),...e};try{window.localStorage.setItem(ve,JSON.stringify(n))}catch{}}function Ke(e,t){let n=$();if(n?.effectiveGapMs)try{return n.effectiveGapMs(e,t)}catch{}let r=e>=1?(t||0)/e:t||0;return Math.max(ye,Math.floor(r)||0)}var Ye="https://github.com/nguyenvancaokyfpt/openfront-helper-userscript",Xe={spawn:["Auto-spawn","Automatically pick a spawn tile."],expand:["Auto-expand / attack","Expand into land and attack targets."],build:["Auto-build","Build and upgrade structures."],boat:["Auto-landing (boats)","Send probe and landing boats overseas."],nuke:["Auto-launch nukes","Fire nukes at high-value targets."],warship:["Auto-deploy warships","Build and dispatch warships."],alliance:["Auto-alliances","Send and accept alliance requests."],donate:["Auto-donate troops","Donate troops to needy allies."],betray:["Allow betrayal","Off = stay loyal (defensive retaliation only)."]},J=()=>window.OpenFrontHelperSettings,V=()=>window.OpenFrontHelperI18n,ke=()=>window.chrome,Qe=[["startingGold0M","0M starting gold"],["startingGold1M","1M starting gold"],["startingGold5M","5M starting gold"],["startingGold25M","25M starting gold"]],Ze=[["randomSpawn","Random spawn"],["alliancesDisabled","Alliances disabled"],["portsDisabled","Ports disabled"],["nukesDisabled","Nukes disabled"],["samsDisabled","SAMs disabled"],["waterNukes","Water nukes"],["peaceTime4m","4min peace time"],["goldMultiplier2x","2x gold"]],xe=[["FFA",1,1],["Duos",2,2],["Trios",3,3],["Quads",4,4],["5+",5,null]],et=[{name:"showTopGoldPerMinute",title:"Player stats panel",desc:"All-players table (owned, gold, gold/min, max troops; expandable); hides the game's leaderboard."},{name:"showGoldPerMinute",title:"Highlight hovered player",desc:"Highlights the hovered player's row in the stats panel."},{name:"showTeamBuildStats",title:"Team build stats",desc:"Per-team structures, gold/min, owned tiles & max troops; warns on a first Missile Silo."},{name:"showTradeBalances",title:"Trade balances",desc:"Shows observed ship and train trade imports/exports."}],tt=[{name:"markBotNationsRed",title:"Mark bot nations red",desc:"Adds a red marker to nation AI names."},{name:"markHoveredAlliesGreen",title:"Alliances",desc:"Highlights allies with remaining alliance time."},{name:"showAllianceRequestsPanel",title:"Alliance requests panel",desc:"Moves alliance requests into a side window."},{name:"showNukePrediction",title:"Nuke prediction",desc:"Shows predicted enemy nuke landing points & radius."},{name:"showBoatPrediction",title:"Boat prediction",desc:"Shows boat landings (blue = you, teal = team, green = allies, yellow/red = enemies)."},{name:"alwaysShowOwnBoatRoutes",title:"Always show your boat routes",desc:"Draws your own boat routes without hovering.",parent:"showBoatPrediction"},{name:"alwaysShowTeamBoatRoutes",title:"Always show team boat routes",desc:"Draws teammates' boat routes without hovering.",parent:"showBoatPrediction"},{name:"alwaysShowAllyBoatRoutes",title:"Always show ally boat routes",desc:"Draws allies' (alliance) boat routes without hovering.",parent:"showBoatPrediction"},{name:"alwaysShowEnemyBoatRoutes",title:"Always show enemy boat routes",desc:"Draws enemy boat routes without hovering.",parent:"showBoatPrediction"},{name:"showWarshipRoutes",title:"Warship routes",desc:"Draws warship destinations & routes (blue = you, teal = team, green = allies, red = enemies)."},{name:"showBoatPanel",title:"Boat panel",desc:"Lists boats you sent and boats incoming to your territory, with arrival times."},{name:"warnIncomingBoats",title:"Incoming boat warning",desc:"Center-screen alert when a new boat targets your territory (like the Missile Silo warning)."},{name:"showEstatePanel",title:"Estates panel",desc:"Lists your separate land parcels (tile counts); click a row to jump there."}],ot=[{name:"showNukeSuggestions",title:"Nuke suggestions",desc:"Suggests high-value nuke targets."},{name:"autoNuke",title:"Auto nuke",desc:"Automatically fires nukes at suggested targets."},{name:"autoNukeIncludeAllies",title:"Auto nuke — include allies",desc:"Allow auto nuke to target allies."},{name:"send1PercentBoat",title:"Send 1% boat",desc:"Adds a 1%-troop boat send to the context menu."},{name:"showEconomyHeatmap",title:"Economy heatmap",desc:"Overlays an economic-activity heatmap."},{name:"showExportPartnerHeatmap",title:"Export partner heatmap",desc:"Highlights your trade export partners."}],x=null,D=!1,p={},H={},R="autojoin",P="main",G="include",l=e=>{let t=V();return t&&t.getMessage(H,e)||e};function o(e,t={},...n){let r=document.createElement(e);for(let[s,a]of Object.entries(t))s==="class"?r.className=a:s==="html"?r.innerHTML=a:s.startsWith("on")&&typeof a=="function"?r.addEventListener(s.slice(2).toLowerCase(),a):a!=null&&r.setAttribute(s,String(a));for(let s of n)r.append(s);return r}async function b(){let e=J();e&&(p=e.normalizeSettings(p,{ensureActiveSearchTimestamp:!0}),await ke().storage.local.set({[e.STORAGE_KEY]:p}))}function w(e,t,n,r){let s=o("div",{class:`ofh-switch${n?" on":""}`}),a=o("div",{class:"ofh-row"},o("div",{class:"ofh-txt"},o("div",{class:"ofh-name"},l(e)),t?o("div",{class:"ofh-desc"},l(t)):""),s),i=()=>{let c=!s.classList.contains("on");s.classList.toggle("on",c),r(c)};return s.addEventListener("click",i),a}function U(e){var s;let t=o("div",{class:"ofh-grid"}),n={},r=a=>{let i=!!p[a];for(let c of n[a]||[])c.classList.toggle("disabled",!i)};for(let a of e){let i=o("div",{class:`ofh-switch${p[a.name]?" on":""}`}),c=o("div",{class:`ofh-row${a.parent?" ofh-sub":""}`},o("div",{class:"ofh-txt"},o("div",{class:"ofh-name"},l(a.title)),a.desc?o("div",{class:"ofh-desc"},l(a.desc)):""),i);i.addEventListener("click",()=>{if(i.classList.contains("disabled"))return;let d=!i.classList.contains("on");i.classList.toggle("on",d),p[a.name]=d,b(),n[a.name]&&r(a.name)}),a.parent&&(n[s=a.parent]||(n[s]=[])).push(i),t.append(c)}for(let a of Object.keys(n))r(a);return t}function nt(e){e.append(o("div",{class:"ofh-section-title"},l("Auto-join"))),e.append(w("Auto-join","Automatically join lobbies that match your filters.",!!p.enabled,i=>{p.enabled=i,b()})),e.append(w("Found-game notification","Show a toast and play a chime when a match is found.",!!p.joinNotification,i=>{p.joinNotification=i,i&&L(),b()})),e.append(w("Keep searching after joining","Don't turn auto-join off when a match is found, so it resumes after the game ends.",!!p.keepAutoJoinAfterMatch,i=>{p.keepAutoJoinAfterMatch=i,b()})),e.append(w("Auto-leave on team win","Leave the match automatically once you / your team win.",!!p.autoLeaveOnTeamWin,i=>{p.autoLeaveOnTeamWin=i,b()})),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Team size")));let t=o("div",{class:"ofh-chips"}),n=()=>xe.find(([,i,c])=>p.minTeamSize===i&&p.maxTeamSize===c)?.[0]??null;for(let[i,c,d]of xe){let u=o("div",{class:`ofh-chip${n()===i?" on":""}`},i);u.addEventListener("click",()=>{p.minTeamSize=c,p.maxTeamSize=d,t.querySelectorAll(".ofh-chip").forEach(f=>f.classList.remove("on")),u.classList.add("on"),b()}),t.append(u)}e.append(t);let r=o("input",{class:"ofh-num",type:"number",min:"0",max:"100",value:p.minLobbySize??""});r.addEventListener("change",()=>{p.minLobbySize=r.value===""?null:Number(r.value),b()}),e.append(o("div",{class:"ofh-row",style:"margin-top:10px"},o("div",{class:"ofh-txt"},o("div",{class:"ofh-name"},l("Minimum lobby size")),o("div",{class:"ofh-desc"},l("Only join lobbies with at least this many players (blank = any)."))),o("div",{class:"ofh-field"},r)));let s=i=>{let c=o("input",{class:"ofh-num",type:"number",min:"1",max:"100",value:p[i]??""});return c.addEventListener("change",()=>{p[i]=c.value===""?null:Number(c.value),b()}),c};e.append(o("div",{class:"ofh-row",style:"margin-top:10px"},o("div",{class:"ofh-txt"},o("div",{class:"ofh-name"},l("Teams in match")),o("div",{class:"ofh-desc"},l("Number of teams in the lobby (blank = any)."))),o("div",{class:"ofh-field"},o("label",{},l("Min")),s("minTeamCount"),o("label",{},l("Max")),s("maxTeamCount")))),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("In-game panel"))),e.append(w("Floating auto-join panel","Show the in-game auto-join quick panel.",!!p.showFloatingAutoJoinPanel,i=>{p.showFloatingAutoJoinPanel=i,b()}));let a=o("button",{class:"ofh-btn"},"▶ "+l("Test chime"));a.addEventListener("click",()=>re()),e.append(o("div",{style:"margin-top:14px"},a))}function rt(e){let t=[["Start gold",Qe],["Modifiers",Ze]];e.append(o("div",{class:"ofh-note",style:"margin:0 2px 12px"},l("Tap a filter to cycle: off → include (green, must match) → exclude (red, skip these).")));for(let[n,r]of t){e.append(o("div",{class:"ofh-section-title"},l(n)));let s=o("div",{class:"ofh-chips",style:"margin-bottom:16px"});for(let[a,i]of r){let c=o("div",{class:"ofh-chip"},l(i)),d=()=>{let u=!!p.includeFilters?.[a],f=!!p.excludeFilters?.[a];c.classList.toggle("on",u||f),c.classList.toggle("exclude",f)};d(),c.addEventListener("click",()=>{p.includeFilters=p.includeFilters||{},p.excludeFilters=p.excludeFilters||{};let u=!!p.includeFilters[a],f=!!p.excludeFilters[a];!u&&!f?p.includeFilters[a]=!0:u?(p.includeFilters[a]=!1,p.excludeFilters[a]=!0):p.excludeFilters[a]=!1,d(),b()}),s.append(c)}e.append(s)}}function st(e){let n=J()?.MAPS??[],r=o("div",{class:"ofh-chips",style:"margin-bottom:12px"});["include","exclude"].forEach(u=>{let f=o("div",{class:`ofh-chip${G===u?" on":""}${u==="exclude"?" exclude":""}`},l(u==="include"?"Include maps":"Exclude maps"));f.addEventListener("click",()=>{G=u,v()}),r.append(f)}),e.append(r);let s=G==="include"?"mapFilters":"mapExcludeFilters",a=o("div",{class:"ofh-chips",style:"margin-bottom:12px"}),i=o("button",{class:"ofh-btn"},l("Select all")),c=o("button",{class:"ofh-btn"},l("Clear"));i.addEventListener("click",()=>{p[s]=Object.fromEntries(n.map(u=>[u.id,!0])),v(),b()}),c.addEventListener("click",()=>{p[s]={},v(),b()}),a.append(i,c),e.append(a);let d=o("div",{class:"ofh-maps"});for(let u of n){let f=!!p[s]?.[u.id],g=o("div",{class:`ofh-map${f?" on":""}`}),m=Z(u.id);if(m){let h=o("img",{class:"ofh-thumb",src:m,alt:u.name,draggable:"false",loading:"lazy"});h.addEventListener("error",()=>{h.replaceWith(o("div",{class:"ofh-noimg"},"🗺"))}),g.append(h)}else g.append(o("div",{class:"ofh-noimg"},"🗺"));g.append(o("div",{class:"ofh-mapname"},u.name),o("div",{class:"ofh-check"},"✓")),g.addEventListener("click",()=>{p[s]=p[s]||{};let h=!p[s][u.id];p[s][u.id]=h,g.classList.toggle("on",h),b()}),d.append(g)}e.append(d)}function at(e){e.append(o("div",{class:"ofh-section-title"},l("Economic"))),e.append(U(et)),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Game"))),e.append(U(tt)),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Advanced"))),e.append(U(ot))}function it(e){let t=Ve();if(!t){e.append(o("div",{class:"ofh-note"},l("The auto-bot loads inside a game. Open this tab while in a match to configure it.")));return}let n=t.get();e.append(w("Auto-bot enabled","Run the auto-bot (private-lobby training tool).",n.enabled,d=>t.set({enabled:d}))),e.append(w("Show auto-bot panel","Show the in-game auto-bot panel (X on the panel hides it).",!n.hidden,d=>t.set({hidden:!d}))),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Difficulty")));let r=o("div",{class:"ofh-chips"});for(let d of t.DIFFICULTIES){let u=o("div",{class:`ofh-chip${n.difficulty===d?" on":""}`},l(d));u.addEventListener("click",()=>{t.set({difficulty:d}),r.querySelectorAll(".ofh-chip").forEach(f=>f.classList.remove("on")),u.classList.add("on")}),r.append(u)}e.append(r),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Behaviors")));let s=o("div",{class:"ofh-grid"});for(let d of t.FEATURE_KEYS){let[u,f]=Xe[d]||[d,""];s.append(w(u,f,!!n.features[d],g=>t.set({features:{[d]:g}})))}e.append(s),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Auto-build structures")));let a=o("div",{class:"ofh-grid"}),i=n.buildStructures||{};for(let[d,u]of $e){let f=o("div",{class:`ofh-switch${i[d]!==!1?" on":""}`}),g=o("div",{class:"ofh-row"},o("div",{class:"ofh-txt",style:"display:flex;align-items:center;gap:8px;min-width:0;"},Je(d,u),o("div",{class:"ofh-name"},l(d))),f);f.addEventListener("click",()=>{let m=!f.classList.contains("on");f.classList.toggle("on",m),t.set({buildStructures:{[d]:m}})}),a.append(g)}e.append(a),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Advanced"))),e.append(w("Win-condition fixes","Adjust the bot's internal win thresholds.",n.winFixes,d=>t.set({winFixes:d})));let c=o("input",{class:"ofh-num",type:"number",min:"50",max:"2000",step:"50",value:n.tickMs});c.addEventListener("change",()=>{let d=Number(c.value);d>0&&t.set({tickMs:d})}),e.append(o("div",{class:"ofh-row",style:"margin-top:6px"},o("div",{class:"ofh-txt"},o("div",{class:"ofh-name"},l("Tick interval (ms)")),o("div",{class:"ofh-desc"},l("Engine poll interval; lower = snappier but heavier."))),o("div",{class:"ofh-field"},c)))}function lt(e){e.append(o("div",{class:"ofh-section-title"},l("Atom batch-fire"))),e.append(o("div",{class:"ofh-atom-hero"},o("div",{class:"ofh-atom-hero-ico"},"☢️"),o("div",{class:"ofh-atom-hero-body"},o("div",{class:"ofh-atom-hero-desc"},l("Aim at a target and press the hotkey to open the batch-fire dialog. It paces shots just under the server's rate limit so none are dropped, works out how many atoms pierce the SAMs along the flight path (+1 to land), and can finish with a Hydrogen once the atoms bait the interceptors. Press Esc to abort a salvo.")),o("div",{class:"ofh-atom-keyrow"},o("span",{class:"ofh-atom-keylbl"},l("Hotkey")),o("span",{class:"ofh-kbd"},"\\"))))),e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("Firing throttle")));let t=o("span",{class:"ofh-cad"}),n=()=>{let i=q(),c=Ke(i.batchSize,i.delayMs),d=(1e3/c).toFixed(1);t.textContent=`≈ ${d} ${l("shots/sec")}`,t.classList.toggle("max",c<=ye)},r=(i,c,d,u,f)=>{let g=o("input",{class:"ofh-num",type:"number",min:String(u),step:String(f),value:String(q()[d])}),m=()=>{let h=Math.floor(Number(g.value));(!Number.isFinite(h)||h{be({batchSize:T.batchSize,delayMs:T.delayMs}),v()}),e.append(a),e.append(o("div",{class:"ofh-note"},l("These knobs only throttle the macro below its server-safe maximum (~7 shots/sec) — they never make it fire faster. Quantity and the “last bomb = Hydrogen” choice are picked per target in the in-game dialog.")))}function pt(e){p.language=e,(async()=>{await b();let t=V();if(t)try{H=await t.loadBundle(e)}catch{}v()})()}function dt(){let e=te(p.language||"en"),t=e.find(f=>f.code===p.language)||e[0],n=o("div",{class:"ofh-dd-menu",hidden:""}),r=o("input",{class:"ofh-dd-search",type:"text",placeholder:l("Search languages")}),s=o("div",{class:"ofh-dd-list"}),a=f=>{let g=f.trim().toLowerCase(),m=e.filter(h=>!g||h.nativeName.toLowerCase().includes(g)||h.name.toLowerCase().includes(g)||h.code.toLowerCase().includes(g));if(s.replaceChildren(),!m.length){s.append(o("div",{class:"ofh-dd-empty"},l("No languages found")));return}for(let h of m){let A=o("div",{class:`ofh-dd-item${h.code===p.language?" on":""}`},o("span",{},h.nativeName),o("span",{class:"ofh-dd-code"},h.code.toUpperCase()));A.addEventListener("click",()=>pt(h.code)),s.append(A)}},i=o("button",{class:"ofh-dd-btn",type:"button"},o("span",{},t?t.nativeName:p.language||"en"),o("span",{class:"ofh-dd-caret"},"▾")),c=o("div",{class:"ofh-dd"},i,n);n.append(r,s);let d=()=>{n.setAttribute("hidden",""),document.removeEventListener("pointerdown",u,!0)},u=f=>{c.contains(f.target)||d()};return i.addEventListener("click",()=>{n.hasAttribute("hidden")?(n.removeAttribute("hidden"),r.value="",a(""),r.focus(),document.addEventListener("pointerdown",u,!0)):d()}),r.addEventListener("input",()=>a(r.value)),c}function ct(e){e.append(o("div",{class:"ofh-section-title"},l("Language"))),e.append(dt()),e.append(o("div",{class:"ofh-note"},l("Language of the helper UI. Missing text falls back to English. Applies in-game too.")));let t=window.__OFH_ASSETS?.version||"";e.append(o("div",{class:"ofh-section-title",style:"margin-top:16px"},l("About"))),e.append(o("div",{class:"ofh-note"},`OpenFront Helper v${t} — ${l("standalone userscript")}.`)),e.append(o("a",{class:"ofh-link",href:Ye,target:"_blank",rel:"noopener noreferrer"},o("span",{class:"ofh-link-ico"},"★"),o("div",{class:"ofh-link-body"},o("div",{class:"ofh-link-title"},"GitHub — openfront-helper-userscript"),o("div",{class:"ofh-link-sub"},l("Source, issues & updates"))),o("span",{class:"ofh-link-arrow"},"↗")))}var we=[["main","Auto-join",nt],["filters","Filters",rt],["maps","Maps",st]];function ut(e){let t=o("div",{class:"ofh-subtabs"});for(let[r,s]of we){let a=o("div",{class:`ofh-subtab${P===r?" on":""}`},l(s));a.addEventListener("click",()=>{P=r,v()}),t.append(a)}e.append(t);let n=o("div",{class:"ofh-subbody"});e.append(n),we.find(([r])=>r===P)?.[2](n)}var W=[["autojoin","Auto-Join",ut],["helpers","Helpers",at],["atom","Atom",lt],["autobot","Auto Bot",it],["settings","Settings",ct]];function v(){if(!x)return;let e=x.querySelector(".ofh-body");e&&(e.replaceChildren(),W.find(([t])=>t===R)?.[2](e),x.querySelectorAll(".ofh-tab").forEach(t=>{let n=t,r=n.dataset.tab,s=W.find(([a])=>a===r);s&&(n.textContent=l(s[1])),n.classList.toggle("active",r===R)}))}function ft(e){let t=o("div",{id:"openfront-helper-popup"}),n=o("div",{class:"ofh-head"},o("h1",{},"⊕ OpenFront Helper",o("span",{class:"ofh-ver"},"v"+e)),o("div",{class:"ofh-spacer"})),r=o("div",{class:"ofh-x",title:l("Close")},"✕");r.addEventListener("click",M),n.append(r),t.append(n);let s=o("div",{class:"ofh-tabs"});for(let[i,c]of W){let d=o("div",{class:"ofh-tab","data-tab":i},l(c));d.addEventListener("click",()=>{R=i,v()}),s.append(d)}t.append(s),t.append(o("div",{class:"ofh-body"}));let a=o("div",{id:"openfront-helper-popup-overlay"},t);return a.addEventListener("pointerdown",i=>{i.target===a&&M()}),a}function Ae(e){e.key==="Escape"&&M()}async function K(){if(!(x||D)){D=!0;try{await ht()}catch(e){console.error("[ofh] popup failed to open:",e),M()}finally{D=!1}}}async function ht(){let e=J(),t=V();if(!e||!t){x=o("div",{id:"openfront-helper-popup-overlay"},o("div",{id:"openfront-helper-popup"},o("div",{class:"ofh-body"},o("div",{class:"ofh-note"},"Initializing… reopen in a moment.")))),x.addEventListener("pointerdown",s=>{s.target===x&&M()}),document.body.append(x);return}let n=await ke().storage.local.get(e.STORAGE_KEY);p=e.normalizeSettings(n[e.STORAGE_KEY]),p.joinNotification&&L();try{H=await t.loadBundle(p.language||"en")}catch{H={}}let r=window.__OFH_ASSETS?.version||"";x=ft(r),document.body.append(x),document.addEventListener("keydown",Ae),v()}function M(){x?.remove(),x=null,document.removeEventListener("keydown",Ae)}function Se(e,t){e&&(R=e),t&&(P=t),x?v():K()}var Y="openfront-helper-popup-styles",Ee=` #openfront-helper-popup-overlay { position: fixed; inset: 0; z-index: 2147483646; display: flex; align-items: center; justify-content: center; background: radial-gradient(circle at 50% 30%, rgba(4,16,14,.55), rgba(0,0,0,.72)); backdrop-filter: blur(2px); animation: ofh-fade .16s ease-out; } @keyframes ofh-fade { from { opacity: 0 } to { opacity: 1 } } @keyframes ofh-rise { from { opacity: 0; transform: translateY(10px) scale(.98) } to { opacity: 1; transform: none } } #openfront-helper-popup { width: min(720px, 94vw); max-height: 88vh; display: flex; flex-direction: column; color: #eef2f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", system-ui, sans-serif; border: 1px solid rgba(134, 239, 172, 0.30); border-radius: 14px; background: linear-gradient(135deg, rgba(34, 197, 94, 0.14), transparent 42%), radial-gradient(circle at 88% 4%, rgba(56, 189, 248, 0.07), transparent 30%), rgba(7, 24, 22, 0.97); box-shadow: 0 24px 64px rgba(0,0,0,.55), inset 0 1px 0 rgba(187,247,208,.12); animation: ofh-rise .2s cubic-bezier(.2,.8,.2,1); overflow: hidden; } #openfront-helper-popup .ofh-head { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid rgba(134, 239, 172, 0.16); background: rgba(7, 24, 22, 0.6); } #openfront-helper-popup .ofh-head h1 { margin: 0; font-size: 15px; font-weight: 700; letter-spacing: .3px; color: #d1fae5; display: flex; align-items: center; gap: 8px; } #openfront-helper-popup .ofh-head .ofh-ver { font-size: 11px; font-weight: 600; color: rgba(187,247,208,.55); border: 1px solid rgba(187,247,208,.2); border-radius: 999px; padding: 1px 8px; } #openfront-helper-popup .ofh-spacer { flex: 1; } #openfront-helper-popup .ofh-x { cursor: pointer; border: 1px solid rgba(248,113,113,.34); color: #fecaca; background: rgba(69,10,10,.4); border-radius: 8px; width: 30px; height: 30px; font-size: 16px; line-height: 1; display: grid; place-items: center; transition: .15s; } #openfront-helper-popup .ofh-x:hover { background: rgba(127,29,29,.6); } #openfront-helper-popup .ofh-tabs { display: flex; gap: 4px; padding: 10px 14px 0; flex-wrap: wrap; } #openfront-helper-popup .ofh-tab { cursor: pointer; border: 1px solid transparent; border-radius: 9px 9px 0 0; padding: 8px 14px; font-size: 12px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase; color: rgba(187,247,208,.6); background: transparent; transition: .15s; } #openfront-helper-popup .ofh-tab:hover { color: #d1fae5; } #openfront-helper-popup .ofh-tab.active { color: #ecfdf5; background: rgba(16, 185, 129, 0.14); border-color: rgba(134,239,172,.28); border-bottom-color: transparent; } /* sub-tabs (Filters / Maps under Auto-Join) */ #openfront-helper-popup .ofh-subtabs { display: flex; gap: 6px; margin-bottom: 14px; } #openfront-helper-popup .ofh-subtab { cursor: pointer; font-size: 11px; font-weight: 700; letter-spacing: .3px; padding: 6px 13px; border-radius: 999px; color: rgba(187,247,208,.6); background: rgba(8,31,28,.6); border: 1px solid rgba(151,181,214,.16); transition: .14s; } #openfront-helper-popup .ofh-subtab:hover { color: #d1fae5; border-color: rgba(134,239,172,.3); } #openfront-helper-popup .ofh-subtab.on { color: #06281f; background: linear-gradient(135deg, #34d399, #10b981); border-color: rgba(187,247,208,.5); } /* searchable language dropdown */ #openfront-helper-popup .ofh-dd { position: relative; max-width: 300px; } #openfront-helper-popup .ofh-dd-btn { width: 100%; display: flex; align-items: center; justify-content: space-between; cursor: pointer; font: 600 13px "Aptos", "Segoe UI", sans-serif; color: #eef2f0; background: rgba(3,12,11,.7); border: 1px solid rgba(151,181,214,.2); border-radius: 9px; padding: 9px 12px; transition: .14s; } #openfront-helper-popup .ofh-dd-btn:hover { border-color: rgba(134,239,172,.4); } #openfront-helper-popup .ofh-dd-caret { opacity: .6; font-size: 10px; } #openfront-helper-popup .ofh-dd-menu { position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 5; background: #0b1620; border: 1px solid rgba(134,239,172,.28); border-radius: 10px; box-shadow: 0 14px 34px rgba(0,0,0,.55); overflow: hidden; } #openfront-helper-popup .ofh-dd-menu[hidden] { display: none; } #openfront-helper-popup .ofh-dd-search { width: 100%; box-sizing: border-box; font: inherit; font-size: 12px; color: #eef2f0; background: rgba(0,0,0,.3); border: none; border-bottom: 1px solid rgba(151,181,214,.16); padding: 9px 12px; outline: none; } #openfront-helper-popup .ofh-dd-list { max-height: 220px; overflow-y: auto; } #openfront-helper-popup .ofh-dd-list::-webkit-scrollbar { width: 8px; } #openfront-helper-popup .ofh-dd-list::-webkit-scrollbar-thumb { background: rgba(134,239,172,.22); border-radius: 999px; } #openfront-helper-popup .ofh-dd-item { display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 8px 12px; font-size: 13px; color: rgba(226,232,240,.85); transition: .12s; } #openfront-helper-popup .ofh-dd-item:hover { background: rgba(16,185,129,.14); color: #ecfdf5; } #openfront-helper-popup .ofh-dd-item.on { color: #06281f; background: linear-gradient(135deg, #34d399, #10b981); } #openfront-helper-popup .ofh-dd-code { font-size: 10px; opacity: .55; font-weight: 700; letter-spacing: .5px; } #openfront-helper-popup .ofh-dd-empty { padding: 12px; text-align: center; font-size: 12px; color: rgba(187,247,208,.5); } #openfront-helper-popup .ofh-body { padding: 16px 18px 20px; overflow-y: auto; } #openfront-helper-popup .ofh-body::-webkit-scrollbar { width: 9px; } #openfront-helper-popup .ofh-body::-webkit-scrollbar-thumb { background: rgba(134,239,172,.22); border-radius: 999px; } #openfront-helper-popup .ofh-section-title { font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: rgba(187,247,208,.5); margin: 4px 0 10px; } #openfront-helper-popup .ofh-grid { display: grid; gap: 8px; } #openfront-helper-popup .ofh-grid.cols2 { grid-template-columns: 1fr 1fr; } /* row: label + control */ #openfront-helper-popup .ofh-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 10px; border: 1px solid rgba(151, 181, 214, 0.14); background: rgba(8, 31, 28, 0.6); transition: .15s; } #openfront-helper-popup .ofh-row:hover { border-color: rgba(134,239,172,.26); background: rgba(10,38,34,.7); } #openfront-helper-popup .ofh-row .ofh-txt { flex: 1; min-width: 0; } #openfront-helper-popup .ofh-row .ofh-name { font-size: 13px; font-weight: 600; color: #eef2f0; } #openfront-helper-popup .ofh-row .ofh-desc { font-size: 11px; color: rgba(187,247,208,.55); margin-top: 2px; line-height: 1.4; } #openfront-helper-popup .ofh-row.ofh-sub { margin-left: 16px; border-left: 2px solid rgba(134,239,172,.22); border-top-left-radius: 4px; border-bottom-left-radius: 4px; } /* pill switch */ #openfront-helper-popup .ofh-switch { position: relative; width: 40px; height: 22px; flex: none; cursor: pointer; border-radius: 999px; background: rgba(120,120,120,.28); border: 1px solid rgba(255,255,255,.1); transition: .18s; } #openfront-helper-popup .ofh-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #e5e7eb; transition: .18s; box-shadow: 0 1px 3px rgba(0,0,0,.4); } #openfront-helper-popup .ofh-switch.on { background: linear-gradient(135deg,#22c55e,#10b981); border-color: rgba(187,247,208,.5); } #openfront-helper-popup .ofh-switch.on::after { left: 20px; background: #ecfdf5; } #openfront-helper-popup .ofh-switch.disabled { opacity: .4; cursor: not-allowed; } /* chip toggle (filters / presets) */ #openfront-helper-popup .ofh-chips { display: flex; flex-wrap: wrap; gap: 7px; } #openfront-helper-popup .ofh-chip { cursor: pointer; user-select: none; font-size: 12px; font-weight: 600; padding: 7px 12px; border-radius: 999px; transition: .14s; border: 1px solid rgba(151,181,214,.18); background: rgba(8,31,28,.6); color: rgba(226,232,240,.8); } #openfront-helper-popup .ofh-chip:hover { border-color: rgba(134,239,172,.3); } #openfront-helper-popup .ofh-chip.on { color: #06281f; background: linear-gradient(135deg,#34d399,#10b981); border-color: rgba(187,247,208,.6); box-shadow: 0 4px 14px rgba(16,185,129,.25); } #openfront-helper-popup .ofh-chip.exclude.on { color: #2a0a0a; background: linear-gradient(135deg,#fca5a5,#f87171); border-color: rgba(254,202,202,.6); box-shadow: 0 4px 14px rgba(248,113,113,.25); } #openfront-helper-popup .ofh-field { display: flex; align-items: center; gap: 8px; } #openfront-helper-popup .ofh-field label { font-size: 12px; color: rgba(187,247,208,.7); } #openfront-helper-popup input.ofh-num { width: 70px; font: inherit; font-size: 13px; color: #eef2f0; background: rgba(3,12,11,.7); border: 1px solid rgba(151,181,214,.2); border-radius: 8px; padding: 6px 8px; } #openfront-helper-popup input.ofh-num:focus { outline: none; border-color: rgba(52,211,153,.6); } /* map grid */ #openfront-helper-popup .ofh-maps { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; } #openfront-helper-popup .ofh-map { cursor: pointer; border-radius: 10px; overflow: hidden; position: relative; border: 1px solid rgba(151,181,214,.16); background: rgba(3,12,11,.6); transition: .15s; } #openfront-helper-popup .ofh-map:hover { transform: translateY(-2px); border-color: rgba(134,239,172,.3); } #openfront-helper-popup .ofh-map.on { border-color: rgba(52,211,153,.8); box-shadow: 0 0 0 2px rgba(52,211,153,.3); } #openfront-helper-popup .ofh-map .ofh-thumb { width: 100%; aspect-ratio: 2/1; object-fit: cover; display: block; background: rgba(0,0,0,.3); } #openfront-helper-popup .ofh-map .ofh-noimg { width: 100%; aspect-ratio: 2/1; display: grid; place-items: center; font-size: 22px; color: rgba(187,247,208,.4); background: rgba(8,31,28,.7); } #openfront-helper-popup .ofh-map .ofh-mapname { font-size: 11px; font-weight: 600; text-align: center; padding: 6px 4px; color: #dbeafe; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #openfront-helper-popup .ofh-map .ofh-check { position: absolute; top: 6px; right: 6px; width: 20px; height: 20px; border-radius: 50%; background: rgba(16,185,129,.95); color: #06281f; display: none; place-items: center; font-size: 13px; font-weight: 800; } #openfront-helper-popup .ofh-map.on .ofh-check { display: grid; } #openfront-helper-popup .ofh-note { font-size: 11px; color: rgba(187,247,208,.5); margin: 12px 2px 0; line-height: 1.5; } /* external link card (About → GitHub) */ #openfront-helper-popup .ofh-link { display: flex; align-items: center; gap: 11px; margin-top: 10px; text-decoration: none; padding: 11px 13px; border-radius: 10px; color: #d1fae5; border: 1px solid rgba(151,181,214,.18); background: rgba(8,31,28,.6); transition: .15s; } #openfront-helper-popup .ofh-link:hover { border-color: rgba(134,239,172,.4); background: rgba(10,38,34,.72); transform: translateY(-1px); } #openfront-helper-popup .ofh-link-ico { flex: none; width: 30px; height: 30px; display: grid; place-items: center; border-radius: 8px; font-size: 15px; color: #fde68a; background: rgba(250,204,21,.12); border: 1px solid rgba(250,204,21,.25); } #openfront-helper-popup .ofh-link-body { flex: 1; min-width: 0; } #openfront-helper-popup .ofh-link-title { font-size: 13px; font-weight: 700; } #openfront-helper-popup .ofh-link-sub { font-size: 11px; color: rgba(187,247,208,.55); margin-top: 1px; } #openfront-helper-popup .ofh-link-arrow { flex: none; opacity: .5; font-size: 14px; } #openfront-helper-popup .ofh-link:hover .ofh-link-arrow { opacity: .9; } #openfront-helper-popup .ofh-btn { cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 8px 14px; border-radius: 9px; color: #ecfdf5; border: 1px solid rgba(134,239,172,.3); background: rgba(16,185,129,.16); transition: .15s; } #openfront-helper-popup .ofh-btn:hover { background: rgba(16,185,129,.28); } /* atom tab — teal system with red used only as a danger accent */ #openfront-helper-popup .ofh-atom-hero { display: flex; gap: 13px; align-items: flex-start; padding: 13px 14px; border-radius: 12px; margin-bottom: 2px; border: 1px solid rgba(248,113,113,.22); background: linear-gradient(135deg, rgba(248,113,113,.10), transparent 55%), rgba(8, 31, 28, 0.55); } #openfront-helper-popup .ofh-atom-hero-ico { flex: none; font-size: 22px; line-height: 1; margin-top: 1px; filter: drop-shadow(0 0 8px rgba(248,113,113,.5)); } #openfront-helper-popup .ofh-atom-hero-body { flex: 1; min-width: 0; } #openfront-helper-popup .ofh-atom-hero-desc { font-size: 12px; line-height: 1.55; color: rgba(226,232,240,.82); } #openfront-helper-popup .ofh-atom-keyrow { display: flex; align-items: center; gap: 8px; margin-top: 10px; } #openfront-helper-popup .ofh-atom-keylbl { font-size: 10px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: rgba(187,247,208,.5); } #openfront-helper-popup .ofh-kbd { display: inline-grid; place-items: center; min-width: 22px; height: 22px; padding: 0 7px; font: 800 13px/1 "Aptos", "Consolas", monospace; color: #fecaca; border: 1px solid rgba(248,113,113,.4); border-bottom-width: 2px; border-radius: 6px; background: rgba(127,29,29,.28); box-shadow: 0 1px 0 rgba(0,0,0,.3); } #openfront-helper-popup .ofh-cad { font-size: 12px; font-weight: 800; letter-spacing: .3px; color: #fbbf24; white-space: nowrap; padding: 6px 10px; border-radius: 8px; border: 1px solid rgba(251,191,36,.28); background: rgba(251,191,36,.10); transition: .15s; } #openfront-helper-popup .ofh-cad.max { color: #34d399; border-color: rgba(52,211,153,.3); background: rgba(52,211,153,.10); } /* launcher icon */ #openfront-helper-launcher { position: fixed; z-index: 2147483645; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; color: #6ee7b7; cursor: grab; user-select: none; touch-action: none; border-radius: 50%; border: 1px solid rgba(134,239,172,.34); background: linear-gradient(135deg, rgba(34,197,94,.22), transparent 50%), rgba(7, 24, 22, 0.94); box-shadow: 0 6px 18px rgba(0,0,0,.5), inset 0 1px 0 rgba(187,247,208,.14); transition: transform .12s, box-shadow .12s; } #openfront-helper-launcher:hover { transform: scale(1.08); box-shadow: 0 8px 24px rgba(16,185,129,.35); } `;var Le="openfront-helper-launcher",Me="ofh:iconPos",gt=` `,N=(e,t,n)=>Math.max(t,Math.min(n,e));function mt(){try{let e=localStorage.getItem(Me);return e?JSON.parse(e):null}catch{return null}}function bt(e){try{localStorage.setItem(Me,JSON.stringify(e))}catch{}}function xt(){if(document.getElementById(Y))return;let e=document.createElement("style");e.id=Y,e.textContent=Ee,(document.head||document.documentElement).appendChild(e)}function Te(){if(document.getElementById(Le))return;xt();let e=document.createElement("div");e.id=Le,e.setAttribute("role","button"),e.setAttribute("aria-label","OpenFront Helper"),e.title="OpenFront Helper — click to open, drag to move",e.innerHTML=gt;let t=44,n=mt(),r=n?N(n.left,0,innerWidth-t):16,s=n?N(n.top,0,innerHeight-t):130;e.style.left=`${r}px`,e.style.top=`${s}px`;let a=!1,i=!1,c=0,d=0,u=0,f=0;e.addEventListener("pointerdown",m=>{a=!0,i=!1,c=m.clientX,d=m.clientY;let h=e.getBoundingClientRect();u=h.left,f=h.top,e.setPointerCapture(m.pointerId),e.style.cursor="grabbing",m.preventDefault()}),e.addEventListener("pointermove",m=>{if(!a)return;let h=m.clientX-c,A=m.clientY-d;(Math.abs(h)>4||Math.abs(A)>4)&&(i=!0),e.style.left=`${N(u+h,0,innerWidth-t)}px`,e.style.top=`${N(f+A,0,innerHeight-t)}px`});let g=m=>{if(a){a=!1,e.style.cursor="grab";try{e.releasePointerCapture(m.pointerId)}catch{}if(i){let h=e.getBoundingClientRect();bt({left:h.left,top:h.top})}else K()}};e.addEventListener("pointerup",g),e.addEventListener("pointercancel",g),document.body.appendChild(e)}function Ie(){document.body?Te():document.addEventListener("DOMContentLoaded",Te,{once:!0})}var wt=["#openfront-helper-auto-bot-panel","#openfront-helper-floating-autojoin-panel","#openfront-helper-floating-helpers-panel","#openfront-helper-team-build-stats","#openfront-helper-alliance-request-panel","#openfront-helper-auto-nuke-process","#openfront-helper-stats-container","#openfront-helper-trade-balance-badge","#openfront-helper-launcher"];function vt(e){let t=e.getBoundingClientRect();if(t.width===0&&t.height===0)return;let n=Math.max(8,window.innerWidth-t.width-8),r=Math.max(8,window.innerHeight-t.height-8),s=Math.min(Math.max(t.left,8),n),a=Math.min(Math.max(t.top,8),r);(Math.round(s)!==Math.round(t.left)||Math.round(a)!==Math.round(t.top))&&(e.style.left=`${Math.round(s)}px`,e.style.top=`${Math.round(a)}px`,e.style.right="auto",e.style.bottom="auto")}function yt(){for(let e of wt)document.querySelectorAll(e).forEach(vt)}function Ce(){let e=0,t=()=>{e||(e=requestAnimationFrame(()=>{e=0,yt()}))};window.addEventListener("resize",t),window.addEventListener("focus",t),window.visualViewport?.addEventListener("resize",t)}function kt(){let e=he(),t=se();globalThis.__OFH={chrome:e,Audio:t,WebAudioElement:S,installI18nOverride:ge},globalThis.__OFH_gameIconUrl=ee,globalThis.__OFH_openPopup=Se,globalThis.__OFH_requestNotifyPermission=L,e.runtime.onMessage.addListener(n=>{let r=n;r?.type==="SHOW_JOIN_NOTIFICATION"&&me(r.text||"OpenFront Helper: lobby found — joining!")}),Ie(),Ce()}kt();})(); ;(function(){ try { const chrome=window.__OFH&&window.__OFH.chrome||window.chrome;const Audio=window.__OFH&&window.__OFH.Audio||window.Audio;function syncFloatingHelpersPanel(){}function positionFloatingHelpersPanel(){}(function(){const e=[{id:"achiran",name:"Achiran",thumbnail:"assets/map-thumbnails/achiran/thumbnail.webp"},{id:"aegean",name:"Aegean",thumbnail:"assets/map-thumbnails/aegean/thumbnail.webp"},{id:"africa",name:"Africa",thumbnail:"assets/map-thumbnails/africa/thumbnail.webp"},{id:"alps",name:"Alps",thumbnail:"assets/map-thumbnails/alps/thumbnail.webp"},{id:"amazonriver",name:"Amazon River",thumbnail:"assets/map-thumbnails/amazonriver/thumbnail.webp"},{id:"antarctica",name:"Antarctica",thumbnail:"assets/map-thumbnails/antarctica/thumbnail.webp"},{id:"archipelagosea",name:"Archipelago Sea",thumbnail:"assets/map-thumbnails/archipelagosea/thumbnail.webp"},{id:"arctic",name:"Arctic",thumbnail:"assets/map-thumbnails/arctic/thumbnail.webp"},{id:"asia",name:"Asia",thumbnail:"assets/map-thumbnails/asia/thumbnail.webp"},{id:"australia",name:"Australia",thumbnail:"assets/map-thumbnails/australia/thumbnail.webp"},{id:"baikal",name:"Baikal",thumbnail:"assets/map-thumbnails/baikal/thumbnail.webp"},{id:"baikalnukewars",name:"Baikal Nuke Wars",thumbnail:"assets/map-thumbnails/baikalnukewars/thumbnail.webp"},{id:"bajacalifornia",name:"Baja California",thumbnail:"assets/map-thumbnails/bajacalifornia/thumbnail.webp"},{id:"balkans",name:"Balkans",thumbnail:"assets/map-thumbnails/balkans/thumbnail.webp"},{id:"beringsea",name:"Bering Sea",thumbnail:"assets/map-thumbnails/beringsea/thumbnail.webp"},{id:"beringstrait",name:"Bering Strait",thumbnail:"assets/map-thumbnails/beringstrait/thumbnail.webp"},{id:"betweentwoseas",name:"Between Two Seas",thumbnail:"assets/map-thumbnails/betweentwoseas/thumbnail.webp"},{id:"blacksea",name:"Black Sea",thumbnail:"assets/map-thumbnails/blacksea/thumbnail.webp"},{id:"bosphorusstraits",name:"Bosphorus Straits",thumbnail:"assets/map-thumbnails/bosphorusstraits/thumbnail.webp"},{id:"britannia",name:"Britannia",thumbnail:"assets/map-thumbnails/britannia/thumbnail.webp"},{id:"britanniaclassic",name:"Britannia Classic",thumbnail:"assets/map-thumbnails/britanniaclassic/thumbnail.webp"},{id:"caribbean",name:"Caribbean",thumbnail:"assets/map-thumbnails/caribbean/thumbnail.webp"},{id:"caucasus",name:"Caucasus",thumbnail:"assets/map-thumbnails/caucasus/thumbnail.webp"},{id:"choppingblock",name:"Chopping Block",thumbnail:"assets/map-thumbnails/choppingblock/thumbnail.webp"},{id:"conakry",name:"Conakry",thumbnail:"assets/map-thumbnails/conakry/thumbnail.webp"},{id:"danishstraits",name:"Danish Straits",thumbnail:"assets/map-thumbnails/danishstraits/thumbnail.webp"},{id:"deglaciatedantarctica",name:"Deglaciated Antarctica",thumbnail:"assets/map-thumbnails/deglaciatedantarctica/thumbnail.webp"},{id:"didier",name:"Didier",thumbnail:"assets/map-thumbnails/didier/thumbnail.webp"},{id:"didierfrance",name:"Didier (France)",thumbnail:"assets/map-thumbnails/didierfrance/thumbnail.webp"},{id:"dyslexdria",name:"Dyslexdria",thumbnail:"assets/map-thumbnails/dyslexdria/thumbnail.webp"},{id:"eastasia",name:"East Asia",thumbnail:"assets/map-thumbnails/eastasia/thumbnail.webp"},{id:"europe",name:"Europe",thumbnail:"assets/map-thumbnails/europe/thumbnail.webp"},{id:"europeclassic",name:"Europe Classic",thumbnail:"assets/map-thumbnails/europeclassic/thumbnail.webp"},{id:"falklandislands",name:"Falkland Islands",thumbnail:"assets/map-thumbnails/falklandislands/thumbnail.webp"},{id:"faroeislands",name:"Faroe Islands",thumbnail:"assets/map-thumbnails/faroeislands/thumbnail.webp"},{id:"fourislands",name:"Four Islands",thumbnail:"assets/map-thumbnails/fourislands/thumbnail.webp"},{id:"gatewaytotheatlantic",name:"Gateway to the Atlantic",thumbnail:"assets/map-thumbnails/gatewaytotheatlantic/thumbnail.webp"},{id:"giantworldmap",name:"Giant World Map",thumbnail:"assets/map-thumbnails/giantworldmap/thumbnail.webp"},{id:"greatlakes",name:"Great Lakes",thumbnail:"assets/map-thumbnails/greatlakes/thumbnail.webp"},{id:"gulfofstlawrence",name:"Gulf of St. Lawrence",thumbnail:"assets/map-thumbnails/gulfofstlawrence/thumbnail.webp"},{id:"halkidiki",name:"Halkidiki",thumbnail:"assets/map-thumbnails/halkidiki/thumbnail.webp"},{id:"hawaii",name:"Hawaii",thumbnail:"assets/map-thumbnails/hawaii/thumbnail.webp"},{id:"hongkong",name:"Hong Kong",thumbnail:"assets/map-thumbnails/hongkong/thumbnail.webp"},{id:"iceland",name:"Iceland",thumbnail:"assets/map-thumbnails/iceland/thumbnail.webp"},{id:"indiansubcontinent",name:"Indian Subcontinent",thumbnail:"assets/map-thumbnails/indiansubcontinent/thumbnail.webp"},{id:"italia",name:"Italia",thumbnail:"assets/map-thumbnails/italia/thumbnail.webp"},{id:"japan",name:"Japan",thumbnail:"assets/map-thumbnails/japan/thumbnail.webp"},{id:"juandefucastrait",name:"Juan de Fuca Strait",thumbnail:"assets/map-thumbnails/juandefucastrait/thumbnail.webp"},{id:"korea",name:"Korea",thumbnail:"assets/map-thumbnails/korea/thumbnail.webp"},{id:"labyrinth",name:"Labyrinth",thumbnail:"assets/map-thumbnails/labyrinth/thumbnail.webp"},{id:"lemnos",name:"Lemnos",thumbnail:"assets/map-thumbnails/lemnos/thumbnail.webp"},{id:"lisbon",name:"Lisbon",thumbnail:"assets/map-thumbnails/lisbon/thumbnail.webp"},{id:"losangeles",name:"Los Angeles",thumbnail:"assets/map-thumbnails/losangeles/thumbnail.webp"},{id:"luna",name:"Luna",thumbnail:"assets/map-thumbnails/luna/thumbnail.webp"},{id:"manicouagan",name:"Manicouagan",thumbnail:"assets/map-thumbnails/manicouagan/thumbnail.webp"},{id:"marenostrum",name:"Mare Nostrum",thumbnail:"assets/map-thumbnails/marenostrum/thumbnail.webp"},{id:"mars",name:"Mars",thumbnail:"assets/map-thumbnails/mars/thumbnail.webp"},{id:"mena",name:"MENA",thumbnail:"assets/map-thumbnails/mena/thumbnail.webp"},{id:"middleeast",name:"Middle East",thumbnail:"assets/map-thumbnails/middleeast/thumbnail.webp"},{id:"milkyway",name:"Milky Way",thumbnail:"assets/map-thumbnails/milkyway/thumbnail.webp"},{id:"mississippiriver",name:"Mississippi River",thumbnail:"assets/map-thumbnails/mississippiriver/thumbnail.webp"},{id:"montreal",name:"Montreal",thumbnail:"assets/map-thumbnails/montreal/thumbnail.webp"},{id:"newyorkcity",name:"New York City",thumbnail:"assets/map-thumbnails/newyorkcity/thumbnail.webp"},{id:"niledelta",name:"Nile Delta",thumbnail:"assets/map-thumbnails/niledelta/thumbnail.webp"},{id:"northamerica",name:"North America",thumbnail:"assets/map-thumbnails/northamerica/thumbnail.webp"},{id:"northwestpassage",name:"Northwest Passage",thumbnail:"assets/map-thumbnails/northwestpassage/thumbnail.webp"},{id:"oceania",name:"Oceania",thumbnail:"assets/map-thumbnails/oceania/thumbnail.webp"},{id:"onion",name:"Onion",thumbnail:"assets/map-thumbnails/onion/thumbnail.webp"},{id:"pangaea",name:"Pangaea",thumbnail:"assets/map-thumbnails/pangaea/thumbnail.webp"},{id:"passage",name:"Passage",thumbnail:"assets/map-thumbnails/passage/thumbnail.webp"},{id:"pluto",name:"Pluto",thumbnail:"assets/map-thumbnails/pluto/thumbnail.webp"},{id:"sanfrancisco",name:"San Francisco",thumbnail:"assets/map-thumbnails/sanfrancisco/thumbnail.webp"},{id:"sierpinski",name:"Sierpinski",thumbnail:"assets/map-thumbnails/sierpinski/thumbnail.webp"},{id:"southamerica",name:"South America",thumbnail:"assets/map-thumbnails/southamerica/thumbnail.webp"},{id:"southeastasia",name:"Southeast Asia",thumbnail:"assets/map-thumbnails/southeastasia/thumbnail.webp"},{id:"straitofgibraltar",name:"Strait of Gibraltar",thumbnail:"assets/map-thumbnails/straitofgibraltar/thumbnail.webp"},{id:"straitofhormuz",name:"Strait of Hormuz",thumbnail:"assets/map-thumbnails/straitofhormuz/thumbnail.webp"},{id:"straitofmalacca",name:"Strait of Malacca",thumbnail:"assets/map-thumbnails/straitofmalacca/thumbnail.webp"},{id:"surrounded",name:"Surrounded",thumbnail:"assets/map-thumbnails/surrounded/thumbnail.webp"},{id:"svalmel",name:"Svalmel",thumbnail:"assets/map-thumbnails/svalmel/thumbnail.webp"},{id:"taiwanstrait",name:"Taiwan Strait",thumbnail:"assets/map-thumbnails/taiwanstrait/thumbnail.webp"},{id:"thebox",name:"The Box",thumbnail:"assets/map-thumbnails/thebox/thumbnail.webp"},{id:"titan",name:"Titan",thumbnail:"assets/map-thumbnails/titan/thumbnail.webp"},{id:"tourney1",name:"Tourney (2 Teams)",thumbnail:"assets/map-thumbnails/tourney1/thumbnail.webp"},{id:"tourney2",name:"Tourney (3 Teams)",thumbnail:"assets/map-thumbnails/tourney2/thumbnail.webp"},{id:"tourney3",name:"Tourney (4 Teams)",thumbnail:"assets/map-thumbnails/tourney3/thumbnail.webp"},{id:"tourney4",name:"Tourney (8 Teams)",thumbnail:"assets/map-thumbnails/tourney4/thumbnail.webp"},{id:"tradersdream",name:"Traders Dream",thumbnail:"assets/map-thumbnails/tradersdream/thumbnail.webp"},{id:"twolakes",name:"Two Lakes",thumbnail:"assets/map-thumbnails/twolakes/thumbnail.webp"},{id:"venice",name:"Venice",thumbnail:"assets/map-thumbnails/venice/thumbnail.webp"},{id:"warshipwarship",name:"Warship Warship",thumbnail:"assets/map-thumbnails/warshipwarship/thumbnail.webp"},{id:"world",name:"World",thumbnail:"assets/map-thumbnails/world/thumbnail.webp"},{id:"worldinverted",name:"World Inverted",thumbnail:"assets/map-thumbnails/worldinverted/thumbnail.webp"},{id:"yellowsea",name:"Yellow Sea",thumbnail:"assets/map-thumbnails/yellowsea/thumbnail.webp"},{id:"yenisei",name:"Yenisei",thumbnail:"assets/map-thumbnails/yenisei/thumbnail.webp"}];globalThis.OPENFRONT_MAPS=Object.freeze(e)})();(function e(a){const n=Array.isArray(a.OPENFRONT_MAPS)?a.OPENFRONT_MAPS:[];const o=n.map(u=>u.id);const i=["startingGold0M","randomSpawn","alliancesDisabled","portsDisabled","nukesDisabled","samsDisabled","waterNukes","peaceTime4m","startingGold5M","startingGold1M","startingGold25M","goldMultiplier2x"];const l=1;const s=100;const c=["startingGold0M","startingGold1M","startingGold5M","startingGold25M"];function g(){return Object.fromEntries(o.map(u=>[u,false]))}const y=Object.fromEntries(i.map(u=>[u,false]));const w={language:"en",enabled:false,searchStartedAt:null,joinNotification:false,minLobbySize:null,minTeamSize:5,maxTeamSize:null,minTeamCount:null,maxTeamCount:5,keepAutoJoinAfterMatch:true,autoLeaveOnTeamWin:false,markBotNationsRed:false,showGoldPerMinute:true,showTopGoldPerMinute:true,showTeamBuildStats:true,markHoveredAlliesGreen:true,showAllianceRequestsPanel:true,showTradeBalances:false,selectiveTradePolicyEnabled:false,autoCancelDeniedTradesAvailable:false,cheatsAvailable:false,showNukePrediction:true,showNukeSuggestions:false,showBoatPrediction:true,showWarshipRoutes:true,alwaysShowOwnBoatRoutes:true,alwaysShowTeamBoatRoutes:true,alwaysShowAllyBoatRoutes:true,alwaysShowEnemyBoatRoutes:true,showBoatPanel:true,warnIncomingBoats:true,showEstatePanel:true,autoNuke:true,autoNukeIncludeAllies:true,send1PercentBoat:true,showEconomyHeatmap:false,economyHeatmapIntensity:1,showExportPartnerHeatmap:false,applySelectiveTradePolicyRequestAt:null,showFloatingHelpersPanel:false,lobbyForecast:{available:false,sampleSize:0,etaMinSeconds:null,etaMaxSeconds:null,hitChanceNext10:null,medianLobbiesToMatch:null,last100Averages:{windowSize:100,sampleSize:0,hitRate:null,avgLobbyIntervalMs:null,avgLobbiesPerMinute:null,etaSeconds:null,updatedAt:null}},floatingHelpersPanelPosition:{left:null,top:null},floatingHelpersPanelHeight:null,showFloatingAutoJoinPanel:false,floatingAutoJoinPanelPosition:{left:null,top:null},floatingAutoJoinPanelHeight:null,floatingAutoJoinPanelCollapsed:false,collapsedHelperCategories:{game:false,economic:false},includeFilters:{...y},excludeFilters:{...y},mapFilters:{...g(),baikal:true,luna:true},mapExcludeFilters:g()};function M(u){const r=Number(u);if(!Number.isFinite(r)){return w.economyHeatmapIntensity}return Math.max(0,Math.min(2,Math.round(r)))}function E(u){return["Low","Default","High"][M(u)]}function p(u={}){const r=Number(u.left);const m=Number(u.top);return{left:u.left==null||u.left===""||!Number.isFinite(r)?null:r,top:u.top==null||u.top===""||!Number.isFinite(m)?null:m}}function h(u){const r=Number(u);return Number.isFinite(r)&&r>0?r:null}function b(u){const r=Number(u);return Number.isFinite(r)&&r>0?r:null}function S(u){if(u==null||u===""){return null}const r=Number(u);return Number.isFinite(r)&&r>0?Math.min(100,Math.floor(r)):null}function f(u){if(u==null||u===""){return null}const r=Number(u);if(!Number.isFinite(r)||r<=0){return null}return Math.min(s,Math.max(l,Math.floor(r)))}function A(u,r){let m=f(u);let d=f(r);if(m!=null&&d!=null&&m>d){const x=m;m=d;d=x}return{minTeamSize:m,maxTeamSize:d}}function F(u,r){let m=f(u);let d=f(r);if(m!=null&&d!=null&&m>d){const x=m;m=d;d=x}return{minTeamCount:m,maxTeamCount:d}}function v(u,r){if(!u?.enabled){return null}const m=Number(u.searchStartedAt);if(Number.isFinite(m)&&m>0){return m}return r?Date.now():null}function N(u){const r=Number(u);return Number.isFinite(r)&&r>0?Math.round(r):null}function k(u){const r=Number(u);if(!Number.isFinite(r)){return null}return Math.max(0,Math.min(1,r))}function _(u){const r=Number(u);return Number.isFinite(r)&&r>=0?Math.round(r):0}function L(u){const r=Number(u);return Number.isFinite(r)&&r>0?Math.round(r):null}function P(u={}){const r=u||{};const m=r.last100Averages||{};return{available:Boolean(r.available),sampleSize:_(r.sampleSize),etaMinSeconds:N(r.etaMinSeconds),etaMaxSeconds:N(r.etaMaxSeconds),hitChanceNext10:k(r.hitChanceNext10),medianLobbiesToMatch:r.medianLobbiesToMatch==null?null:Math.max(1,_(r.medianLobbiesToMatch)),last100Averages:{windowSize:100,sampleSize:Math.min(100,_(m.sampleSize)),hitRate:k(m.hitRate),avgLobbyIntervalMs:L(m.avgLobbyIntervalMs),avgLobbiesPerMinute:m.avgLobbiesPerMinute==null?null:Number.isFinite(Number(m.avgLobbiesPerMinute))?Math.max(0,Number(m.avgLobbiesPerMinute)):null,etaSeconds:N(m.etaSeconds),updatedAt:b(m.updatedAt)}}}function I(u){const r=String(u||"").trim().toLowerCase();return/^[a-z]{2}$/.test(r)?r:w.language}function O(u={},r=null){const m=g();for(const d of o){if(d in u){m[d]=Boolean(u[d])}else if(r&&d in r){m[d]=Boolean(r[d])}}return m}function R(u={},r={}){const{ensureActiveSearchTimestamp:m=false}=r;const d=u||{};const x=C=>C in d?d[C]:w[C];const j=d.includeFilters||d.filters||{};const B=d.excludeFilters||d.excludes||{};const H=d.mapFilters||d.maps||{};const D=d.mapExcludeFilters||d.mapExcludes||{};const z=d.floatingHelpersPanelPosition||{};const G=d.floatingAutoJoinPanelPosition||{};const $=d.collapsedHelperCategories||{};const{minTeamSize:J,maxTeamSize:W}=A(x("minTeamSize"),x("maxTeamSize"));const{minTeamCount:q,maxTeamCount:U}=F(x("minTeamCount"),x("maxTeamCount"));const T={...w,...d,searchStartedAt:v(d,m),minLobbySize:S(x("minLobbySize")),minTeamSize:J,maxTeamSize:W,minTeamCount:q,maxTeamCount:U,floatingHelpersPanelPosition:p(z),floatingHelpersPanelHeight:h(d.floatingHelpersPanelHeight),showFloatingAutoJoinPanel:Boolean(d.showFloatingAutoJoinPanel),floatingAutoJoinPanelPosition:p(G),floatingAutoJoinPanelHeight:h(d.floatingAutoJoinPanelHeight),floatingAutoJoinPanelCollapsed:Boolean(d.floatingAutoJoinPanelCollapsed),lobbyForecast:P(d.lobbyForecast),collapsedHelperCategories:{...w.collapsedHelperCategories,...$},includeFilters:{...w.includeFilters,...j},excludeFilters:{...w.excludeFilters,...B},mapFilters:O(H,w.mapFilters),mapExcludeFilters:O(D)};delete T.showNukeTargetHeatmap;if(T.showExportPartnerHeatmap){T.showEconomyHeatmap=false}T.economyHeatmapIntensity=M(T.economyHeatmapIntensity);T.language=I(T.language);T.applySelectiveTradePolicyRequestAt=b(T.applySelectiveTradePolicyRequestAt);return T}a.OpenFrontHelperSettings={STORAGE_KEY:"settings",WHATS_NEW_NOTICE_KEY:"whatsNewNoticePending",MAPS:n,MAP_IDS:o,FILTER_KEYS:i,START_GOLD_FILTER_KEYS:c,DEFAULT_SETTINGS:w,createDefaultMapFilters:g,normalizeSettings:R,normalizeMinLobbySize:S,normalizeTeamSize:f,normalizeLanguage:I,normalizeMapFilters:O,normalizeEconomyHeatmapIntensity:M,normalizeFloatingHelpersPanelPosition:p,normalizeFloatingHelpersPanelHeight:h,normalizeActionRequestTimestamp:b,getEconomyHeatmapIntensityLabel:E}})(globalThis);(function e(a){const n=["af","ak","am","an","ar","as","av","ay","az","ba","be","bg","bh","bi","bm","bn","bo","br","bs","ca","ce","ch","co","cr","cs","cu","cv","cy","da","de","dv","dz","ee","el","en","eo","es","et","eu","fa","ff","fi","fj","fo","fr","fy","ga","gd","gl","gn","gu","gv","ha","he","hi","ho","hr","ht","hu","hy","hz","ia","id","ie","ig","ii","ik","io","is","it","iu","ja","jv","ka","kg","ki","kj","kk","kl","km","kn","ko","kr","ks","ku","kv","kw","ky","la","lb","lg","li","ln","lo","lt","lu","lv","mg","mh","mi","mk","ml","mn","mr","ms","mt","my","na","nd","ne","ng","nl","no","nr","nv","ny","oc","oj","om","or","os","pa","pi","pl","ps","pt","qu","rm","rn","ro","ru","rw","sa","sc","sd","se","sg","si","sk","sl","sm","sn","so","sq","sr","ss","st","su","sv","sw","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty","ug","uk","ur","uz","ve","vi","vo","wa","wo","xh","yi","yo","za","zh","zu"];const o={languageCode:"en",languageName:"English",settings:"Settings",openSettings:"Open settings",github:"GitHub",openGitHubRepository:"Open GitHub repository",openFrontReloadTitle:"Reload OpenFront?",openFrontReloadText:"OpenFront was already open, so the extension is not active on this tab yet. Reload the page to enable OpenFront Helper.",openFrontReloadButton:"Reload page",global:"Global",lobby:"Lobby",send:"Send",writeAMessage:"Write a message...",noMessagesYet:"No messages yet.",anonymous:"Anonymous",customNotificationSound:"Custom notification sound",test:"Test",upload:"Upload",remove:"Remove",cancel:"Cancel",defaultSound:"Default",customSound:"Custom",language:"Language",openLanguageMenu:"Change language",searchLanguages:"Search languages",noLanguagesFound:"No languages found",openMacros:"Open macros",autoJoinOn:"Auto-Join ON",autoJoinOff:"Auto-Join OFF",showFloatingHelpersPanel:"Show floating helpers panel on OpenFront",hideFloatingHelpersPanel:"Hide floating helpers panel on OpenFront","Auto-Join & Helpers for OpenFront":"Auto-Join & Helpers for OpenFront","Settings":"Settings","Open settings":"Open settings","GitHub":"GitHub","Open GitHub repository":"Open GitHub repository","Global":"Global","Lobby":"Lobby","Send":"Send","Write a message...":"Write a message...","No messages yet.":"No messages yet.","Anonymous":"Anonymous","Change language":"Change language","Open macros":"Open macros","Custom notification sound":"Custom notification sound","Show floating helpers panel on OpenFront":"Show floating helpers panel on OpenFront","Test":"Test","Upload":"Upload","Remove":"Remove","Cancel":"Cancel","Default":"Default","Search languages":"Search languages","Languages":"Languages","Join our Discord":"Join our Discord","Macros":"Macros","⚓ Send 1% Boat":"⚓ Send 1% Boat","Send a transport with 1% troops, then restore the attack ratio.":"Send a transport with 1% troops, then restore the attack ratio.","Hotkey":"Hotkey","Shift + B":"Shift + B","N":"N","Show button in selection wheel":"Show button in selection wheel","One-time setup":"One-time setup","If openfront.io was already open when you installed this extension, reload that tab once. Otherwise the extension will not work on that tab.":"If openfront.io was already open when you installed this extension, reload that tab once. Otherwise the extension will not work on that tab.","Got it":"Got it","Helpers":"Helpers","Float":"Float","Game helpers":"Game helpers","Mark bot nations red":"Mark bot nations red","Adds a red marker to nation AI names on the map.":"Adds a red marker to nation AI names on the map.","Adds a red marker to nation AI names.":"Adds a red marker to nation AI names.","Alliances":"Alliances","Show alliances":"Show alliances","Highlights allies with remaining alliance time.":"Highlights allies with remaining alliance time.","Alliance requests panel":"Alliance requests panel","Moves alliance requests and renewal prompts into a separate right-side window.":"Moves alliance requests and renewal prompts into a separate right-side window.","Cheats":"Cheats","Only available in solo or custom games.":"Only available in solo or custom games.","⚠ Only available in solo or custom games.":"⚠ Only available in solo or custom games.","Nuke prediction":"Nuke prediction","Shows predicted enemy nuke landing points and explosion radius.":"Shows predicted enemy nuke landing points and explosion radius.","Shows predicted enemy nuke landing points and blast radius.":"Shows predicted enemy nuke landing points and blast radius.","Boat prediction":"Boat prediction","Shows enemy boat landing points. Red = targeting you, yellow = targeting others.":"Shows enemy boat landing points. Red = targeting you, yellow = targeting others.","Nuke suggestion":"Nuke suggestion","Beta":"Beta","May lag":"May lag","Hover an enemy to show high-damage atom and hydrogen targets.":"Hover an enemy to show high-damage atom and hydrogen targets.","Auto nuke":"Auto nuke","Adds auto economy and population nuke actions to the player wheel.":"Adds auto economy and population nuke actions to the player wheel.","Include allies":"Include allies","↳ Include allies":"↳ Include allies","Also show auto nuke options when right-clicking allied players.":"Also show auto nuke options when right-clicking allied players.","Block non-team trades":"Block non-team trades","Team games only: blocks trades with players who are not on your team.":"Team games only: blocks trades with players who are not on your team.","Blocks trades with players who are not on your team.":"Blocks trades with players who are not on your team.","Available only during an active team game.":"Available only during an active team game.","Economic helpers":"Economic helpers","Gold per minute":"Gold per minute","Show gold per minute":"Show gold per minute","Show gold per minute preview":"Show gold per minute preview","Adds GPM to the player hover panel.":"Adds GPM to the player hover panel.","Team gold per minute":"Team gold per minute","Show team gold per minute":"Show team gold per minute","Show team gold per minute preview":"Show team gold per minute preview","Lists each team's total GPM in team games.":"Lists each team's total GPM in team games.","Lists each team's total GPM.":"Lists each team's total GPM.","Top 10 gold per minute":"Top 10 gold per minute","Lists the highest tracked player GPM.":"Lists the highest tracked player GPM.","Trade balances":"Trade balances","Show trade balances":"Show trade balances","Show trade balances preview":"Show trade balances preview","Shows observed ship and train trade imports and exports.":"Shows observed ship and train trade imports and exports.","Shows observed trade imports and exports.":"Shows observed trade imports and exports.","Heatmaps":"Heatmaps","Economic heatmap":"Economic heatmap","Highlights structures with observed trade revenue.":"Highlights structures with observed trade revenue.","Intensity":"Intensity","Economic heatmap intensity":"Economic heatmap intensity","Low":"Low","High":"High","Export partner heatmap":"Export partner heatmap","Hover a player to highlight the partner industry that fuels their exports.":"Hover a player to highlight the partner industry that fuels their exports.","Hover a player to highlight export partners.":"Hover a player to highlight export partners.","Auto-Join":"Auto-Join","Auto-Join ON":"Auto-Join ON","Auto-Join OFF":"Auto-Join OFF","Lobby Forecast":"Lobby Forecast","Lobby forecast":"Lobby forecast","ETA (estimate)":"ETA (estimate)","Hit chance next 10 lobbies":"Hit chance next 10 lobbies","Median to match":"Median to match","lobbies":"lobbies","Send 1% Boat":"Send 1% Boat","Right-click any tile to send a boat with 1% troops, then restores your ratio.":"Right-click any tile to send a boat with 1% troops, then restores your ratio.","Not enough data yet":"Not enough data yet","Game found notification popup":"Game found notification popup","Show a notification when a game is found":"Show a notification when a game is found","Min lobby size":"Min lobby size","Only join lobbies whose max player count is greater than this number.":"Only join lobbies whose max player count is greater than this number.","Searching for":"Searching for","Lobby Type":"Lobby Type","Team size":"Team size","Min":"Min","Max":"Max","Any":"Any","FFA":"FFA","Duos":"Duos","Trios":"Trios","Quads":"Quads","Teams larger than Quads":"Teams larger than Quads","Matches team lobbies with more than 4 players per team.":"Matches team lobbies with more than 4 players per team.","Modifier":"Modifier","Random spawn":"Random spawn","Alliances disabled":"Alliances disabled","Ports disabled":"Ports disabled","Nukes disabled":"Nukes disabled","SAMs disabled":"SAMs disabled","Water nukes":"Water nukes","4min peace time":"4min peace time","2x gold":"2x gold","Start Gold":"Start Gold","You can turn on all three at the same time. Only one of them has to match.":"You can turn on all three at the same time. Only one of them has to match.","You can turn on all four at the same time. Only one of them has to match.":"You can turn on all four at the same time. Only one of them has to match.","0M starting gold":"0M starting gold","Default case with no explicit starting gold.":"Default case with no explicit starting gold.","1M starting gold":"1M starting gold","5M starting gold":"5M starting gold","25M starting gold":"25M starting gold","Exclude":"Exclude","Maps":"Maps","Clear":"Clear","Search maps":"Search maps","Map filters":"Map filters","Include requires a match. Exclude blocks an option. For starting gold, any included value is enough, while excluded values are always rejected.":"Include requires a match. Exclude blocks an option. For starting gold, any included value is enough, while excluded values are always rejected.","Helper preview":"Helper preview","Close":"Close","Close helper preview":"Close helper preview","Previous image":"Previous image","Next image":"Next image","Close helpers panel":"Close helpers panel","Resize helpers panel":"Resize helpers panel","On":"On","Off":"Off","Show Mark bot nations red preview":"Show Mark bot nations red preview","Show alliances preview":"Show alliances preview","Show Nuke prediction preview":"Show Nuke prediction preview","Show Auto nuke preview":"Show Auto nuke preview","Show Economic heatmap preview":"Show Economic heatmap preview","Game Found!":"Game Found!","Joining now...":"Joining now...","Cologne":"Cologne","Cologne, Germany and European Union":"Cologne, Germany and European Union","Cologne Cathedral":"Cologne Cathedral","Germany":"Germany","European Union":"European Union","Starting…":"Starting…","Disabled":"Disabled","Waiting to enter game…":"Waiting to enter game…","⛔ Blocked in a public lobby":"⛔ Blocked in a public lobby","Waiting for player…":"Waiting for player…","Spawn phase…":"Spawn phase…","💀 Eliminated":"💀 Eliminated","Expanding…":"Expanding…","Massing troops / building…":"Massing troops / building…","Error (see console)":"Error (see console)","Waiting for build-menu (eventBus)…":"Waiting for build-menu (eventBus)…","Spawn intent not found":"Spawn intent not found","No suitable spawn tile found":"No suitable spawn tile found","Spawn tile selected":"Spawn tile selected","Attack intent not found":"Attack intent not found","🛡️ Defending…":"🛡️ Defending…","build-menu not found":"build-menu not found","Waiting for border data (borderTiles)…":"Waiting for border data (borderTiles)…","⛔ Public lobby — auto-disabled":"⛔ Public lobby — auto-disabled","Checking lobby…":"Checking lobby…","⛔ Public lobby — bot locked":"⛔ Public lobby — bot locked","Private lobby":"Private lobby","Singleplayer":"Singleplayer","🚢 Deploy warship":"🚢 Deploy warship","🚢 Dispatch {moved}/{total} ships (keep {home} on guard)":"🚢 Dispatch {moved}/{total} ships (keep {home} on guard)","🚢 Patrol weak point ({moved})":"🚢 Patrol weak point ({moved})","🤝➡️ Send alliance {name}":"🤝➡️ Send alliance {name}","🔁 Renew alliance {name}":"🔁 Renew alliance {name}","🤝 Alliance {name}":"🤝 Alliance {name}","🏗️ Build {type} (score {score})":"🏗️ Build {type} (score {score})","⬆️ Upgrade {type} (level {level})":"⬆️ Upgrade {type} (level {level})","🚫 Embargo {name}":"🚫 Embargo {name}","🎁 Donate troops {name}":"🎁 Donate troops {name}","🤖 Crush {count} bots":"🤖 Crush {count} bots","🛡️ Retaliate {name}":"🛡️ Retaliate {name}","🟩 Grab empty land":"🟩 Grab empty land","☢️🟩 Reclaim irradiated land":"☢️🟩 Reclaim irradiated land","🌊⚔️ Surge from beachhead {name}":"🌊⚔️ Surge from beachhead {name}","🎯 Attack weakened {name}":"🎯 Attack weakened {name}","🤝⚔️ Assist ally vs {name}":"🤝⚔️ Assist ally vs {name}","🗡️ Attack traitor {name}":"🗡️ Attack traitor {name}","🗡️ Betray {name}":"🗡️ Betray {name}","😴 Attack AFK {name}":"😴 Attack AFK {name}","⚔️ Pile on {name}":"⚔️ Pile on {name}","😠 Attack nemesis {name}":"😠 Attack nemesis {name}","🏭⚔️ Seize economy {name}":"🏭⚔️ Seize economy {name}","⚔️ Attack weakest {name}":"⚔️ Attack weakest {name}","⛵ Probe landing {name}":"⛵ Probe landing {name}","☢️ MIRV the dominant enemy":"☢️ MIRV the dominant enemy","☢️ Saturate air defense ({bombs} bombs, {sams} SAMs on path)":"☢️ Saturate air defense ({bombs} bombs, {sams} SAMs on path)","☢️ Nuclear RETALIATION":"☢️ Nuclear RETALIATION","☢️ Launch":"☢️ Launch","Toggle on/off":"Toggle on/off","Collapse":"Collapse","🎮 Controls":"🎮 Controls","📜 Log":"📜 Log","Auto-spawn":"Auto-spawn","Auto-expand / attack":"Auto-expand / attack","Auto-landing (1% boat)":"Auto-landing (1% boat)","Auto-deploy warships":"Auto-deploy warships","Auto-build":"Auto-build","Auto-launch nukes":"Auto-launch nukes","Auto-accept alliances":"Auto-accept alliances","Auto-donate troops (allies)":"Auto-donate troops (allies)","⚙ Advanced settings ▾":"⚙ Advanced settings ▾","⚙ Advanced settings ▴":"⚙ Advanced settings ▴","Troop reserve (vs players)":"Troop reserve (vs players)","Trigger threshold":"Trigger threshold","Reserve when grabbing land":"Reserve when grabbing land","Troops per landing boat":"Troops per landing boat","Tick interval":"Tick interval","Troops":"Troops","Gold":"Gold","Tiles":"Tiles","Attacks":"Attacks","Builds":"Builds","Nukes":"Nukes","All":"All","No actions yet":"No actions yet","Spawn":"Spawn","Combat":"Combat","Naval":"Naval","Building":"Building","Strikes":"Strikes","Diplomacy":"Diplomacy","🏁 Spawn":"🏁 Spawn"};const i={tl:"Tagalog"};let l=null;function s(p,h){return p?.[h]||o[h]||h}function c(p,h){const b=String(h??"").trim();return b?s(p,b):h}function g(p,h){const b=new Set(["SCRIPT","STYLE","SVG","PATH"]);const S=document.createTreeWalker(p,NodeFilter.SHOW_TEXT,{acceptNode(f){const A=f.parentElement;if(!A||b.has(A.tagName)){return NodeFilter.FILTER_REJECT}return f.nodeValue.trim()?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT}});for(let f=S.nextNode();f;f=S.nextNode()){f.__openFrontI18nText||=f.nodeValue.trim();const A=s(h,f.__openFrontI18nText);f.nodeValue=f.nodeValue.replace(f.nodeValue.trim(),A)}for(const f of p.querySelectorAll("*")){for(const A of["aria-label","title","placeholder","data-info-title"]){if(!f.hasAttribute(A)){continue}const F=`data-openfront-i18n-${A}`;if(!f.hasAttribute(F)){f.setAttribute(F,f.getAttribute(A))}f.setAttribute(A,c(h,f.getAttribute(F)))}}}function y(p,h="en"){if(i[p]){return i[p]}try{const b=new Intl.DisplayNames([h],{type:"language"});return b.of(p)||p}catch(b){return p}}function w(p="en"){const h=new Intl.Collator(p,{sensitivity:"base"});return n.map(b=>{const S=y(b,p);const f=y(b,b);return{code:b,name:S,nativeName:f,searchText:`${b} ${S} ${f}`.toLowerCase()}}).sort((b,S)=>h.compare(b.name,S.name))}async function M(p){const h=`locales/${p}/common.json`;const b=a.chrome?.runtime?.getURL instanceof Function?a.chrome.runtime.getURL(h):h;const S=await fetch(b);if(!S.ok){throw new Error(`Unable to load locale ${p}`)}return S.json()}async function E(p){if(!l){l=M("en").catch(()=>o)}const h=await l;if(p==="en"){return{...o,...h}}try{const b=await M(p);return{...o,...h,...b}}catch(b){return{...o,...h}}}a.OpenFrontHelperI18n={LANGUAGE_CODES:n,DEFAULT_TRANSLATIONS:o,createLanguageOptions:w,getLanguageDisplayName:y,getMessage:s,localizeElement:g,loadBundle:E}})(globalThis);;window.__OFH&&window.__OFH.installI18nOverride&&window.__OFH.installI18nOverride();const{STORAGE_KEY,FILTER_KEYS,START_GOLD_FILTER_KEYS,MAPS,MAP_IDS,createDefaultMapFilters,normalizeSettings,normalizeEconomyHeatmapIntensity,getEconomyHeatmapIntensityLabel}=globalThis.OpenFrontHelperSettings;const i18n=globalThis.OpenFrontHelperI18n;const BRIDGE_SOURCE_PAGE="openfront-autojoin-page";const BRIDGE_SOURCE_EXTENSION="openfront-autojoin-extension";const FLOATING_HELPERS_PANEL_ID="openfront-helper-floating-helpers-panel";const FLOATING_HELPERS_STYLE_ID="openfront-helper-floating-helpers-styles";const FLOATING_AUTOJOIN_PANEL_ID="openfront-helper-floating-autojoin-panel";const FLOATING_AUTOJOIN_STYLE_ID="openfront-helper-floating-autojoin-styles";const JOIN_ATTEMPT_TIMEOUT_MS=12e3;const LOBBY_RETRY_COOLDOWN_MS=3e4;const PREFERRED_GROUP_ORDER=["special","ffa","team"];const lobbyCooldowns=new Map;let settings=normalizeSettings();let latestLobbySnapshot=null;let pendingJoin=null;let wasOccupied=false;let joinAlertAudio=null;let customJoinAlertAudio=null;let hasCustomNotificationSound=false;let customNotificationSoundData=null;let lastProcessedSelectiveTradePolicyRequestAt=null;let selectiveTradePolicyAvailable=false;let cheatsAvailable=false;let translations=i18n?.DEFAULT_TRANSLATIONS||{};function t(e){return i18n?.getMessage(translations,e)||e}async function loadContentTranslations(){if(!i18n){return}translations=await i18n.loadBundle(settings.language)}function syncBotNationMarkers(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"MARK_BOT_NATIONS_RED",payload:{enabled:Boolean(settings.markBotNationsRed)}},"*")}function syncGoldPerMinuteHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_GOLD_PER_MINUTE",payload:{enabled:Boolean(settings.showGoldPerMinute)}},"*")}function syncTopGoldPerMinuteHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_TOP_GOLD_PER_MINUTE",payload:{enabled:Boolean(settings.showTopGoldPerMinute)}},"*")}function syncTeamBuildStatsHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_TEAM_BUILD_STATS",payload:{enabled:Boolean(settings.showTeamBuildStats)}},"*")}function syncHoveredAlliesHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"MARK_HOVERED_ALLIES_GREEN",payload:{enabled:Boolean(settings.markHoveredAlliesGreen)}},"*")}function syncAllianceRequestsPanelHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_ALLIANCE_REQUESTS_PANEL",payload:{enabled:Boolean(settings.showAllianceRequestsPanel)}},"*")}function syncTradeBalancesHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_TRADE_BALANCES",payload:{enabled:Boolean(settings.showTradeBalances)}},"*")}function syncNukePredictionHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_NUKE_PREDICTION",payload:{enabled:Boolean(settings.showNukePrediction)}},"*")}function syncBoatPredictionHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_BOAT_PREDICTION",payload:{enabled:Boolean(settings.showBoatPrediction),alwaysOwnRoutes:Boolean(settings.alwaysShowOwnBoatRoutes),alwaysTeamRoutes:Boolean(settings.alwaysShowTeamBoatRoutes),alwaysAllyRoutes:Boolean(settings.alwaysShowAllyBoatRoutes),alwaysEnemyRoutes:Boolean(settings.alwaysShowEnemyBoatRoutes)}},"*")}function syncWarshipRoutesHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_WARSHIP_ROUTES",payload:{enabled:Boolean(settings.showWarshipRoutes)}},"*")}function syncBoatPanelHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_BOAT_PANEL",payload:{enabled:Boolean(settings.showBoatPanel)}},"*")}function syncBoatIncomingWarningHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_BOAT_INCOMING_WARNING",payload:{enabled:Boolean(settings.warnIncomingBoats)}},"*")}function syncEstatePanelHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_ESTATE_PANEL",payload:{enabled:Boolean(settings.showEstatePanel)}},"*")}function syncAutoLeaveOnTeamWinHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_AUTO_LEAVE_ON_TEAM_WIN",payload:{enabled:Boolean(settings.autoLeaveOnTeamWin)}},"*")}function syncNukeSuggestionsHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_NUKE_SUGGESTIONS",payload:{enabled:Boolean(settings.showNukeSuggestions&&cheatsAvailable)}},"*")}function syncAutoNukeHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_AUTO_NUKE",payload:{enabled:Boolean(settings.autoNuke&&cheatsAvailable),includeAllies:Boolean(settings.autoNukeIncludeAllies)}},"*")}function syncSend1PercentBoatHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_SEND_1_PERCENT_BOAT",payload:{enabled:Boolean(settings.send1PercentBoat),contextMenu:settings.send1PercentBoatContextMenu!==false}},"*")}function syncEconomyHeatmapHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_ECONOMY_HEATMAP",payload:{enabled:Boolean(settings.showEconomyHeatmap),intensity:settings.economyHeatmapIntensity}},"*")}function syncExportPartnerHeatmapHelper(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SHOW_EXPORT_PARTNER_HEATMAP",payload:{enabled:Boolean(settings.showExportPartnerHeatmap)}},"*")}function syncAutoBotI18n(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_AUTO_BOT_I18N",payload:{language:settings.language,bundle:translations}},"*")}function syncSelectiveTradePolicyToggle(){window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"SET_SELECTIVE_TRADE_POLICY",payload:{enabled:Boolean(settings.selectiveTradePolicyEnabled)}},"*")}function updateAutoCancelDeniedTradesAvailability(e){const a=Boolean(e);const n=a!==selectiveTradePolicyAvailable||a!==Boolean(settings.autoCancelDeniedTradesAvailable);selectiveTradePolicyAvailable=a;if(n){saveSettings({...settings,autoCancelDeniedTradesAvailable:a,selectiveTradePolicyEnabled:settings.selectiveTradePolicyEnabled&&a}).catch(o=>{console.error("Failed to sync denied trade cancellation availability:",o)})}syncFloatingHelpersPanel();syncFloatingAutoJoinPanel()}function updateCheatsAvailability(e){const a=true;const n=a!==cheatsAvailable||a!==Boolean(settings.cheatsAvailable);cheatsAvailable=a;if(n){saveSettings({...settings,cheatsAvailable:a}).catch(o=>{console.error("Failed to sync cheats availability:",o)})}syncHelpers()}function syncHelpers(){syncBotNationMarkers();syncGoldPerMinuteHelper();syncTopGoldPerMinuteHelper();syncTeamBuildStatsHelper();syncHoveredAlliesHelper();syncAllianceRequestsPanelHelper();syncTradeBalancesHelper();syncSelectiveTradePolicyToggle();syncAutoBotI18n();syncNukePredictionHelper();syncBoatPredictionHelper();syncWarshipRoutesHelper();syncBoatPanelHelper();syncBoatIncomingWarningHelper();syncEstatePanelHelper();syncAutoLeaveOnTeamWinHelper();syncNukeSuggestionsHelper();syncAutoNukeHelper();syncSend1PercentBoatHelper();syncEconomyHeatmapHelper();syncExportPartnerHeatmapHelper()}async function loadSettings(){const e=await chrome.storage.local.get(STORAGE_KEY);settings=normalizeSettings(e[STORAGE_KEY]);await loadContentTranslations();selectiveTradePolicyAvailable=Boolean(settings.autoCancelDeniedTradesAvailable);cheatsAvailable=true;console.info("[openfront-helper] cheats override active");if(settings.cheatsAvailable!==true){settings.cheatsAvailable=true;chrome.storage.local.set({[STORAGE_KEY]:settings}).catch(a=>console.error("Failed to persist cheats override:",a))}lastProcessedSelectiveTradePolicyRequestAt=settings.applySelectiveTradePolicyRequestAt;syncHelpers();syncFloatingHelpersPanel();syncFloatingAutoJoinPanel()}async function saveSettings(e){settings=normalizeSettings(e);await chrome.storage.local.set({[STORAGE_KEY]:settings})}chrome.runtime.onMessage.addListener((e,a,n)=>{if(e?.type==="OPENFRONT_HELPER_PING"){n({ok:true,source:"openfront-helper-content"})}});function ensureFloatingAutoJoinStyles(){if(document.getElementById(FLOATING_AUTOJOIN_STYLE_ID)){return}const e=document.createElement("style");e.id=FLOATING_AUTOJOIN_STYLE_ID;e.textContent=` #${FLOATING_AUTOJOIN_PANEL_ID} { position: fixed; left: var(--ofh-aj-left, 18px); top: var(--ofh-aj-top, 92px); z-index: 2147483647; width: min(300px, calc(100vw - 24px)); height: auto; max-height: calc(100vh - 24px); display: flex; flex-direction: column; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.95); color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 11px; font-weight: 700; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); user-select: none; } #${FLOATING_AUTOJOIN_PANEL_ID}.ofh-aj-collapsed { grid-template-rows: auto; min-height: 0; height: auto; } #${FLOATING_AUTOJOIN_PANEL_ID}.ofh-aj-collapsed .ofh-aj-body { display: none; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 10px; border-bottom: 1px solid rgba(148, 163, 184, 0.18); cursor: move; } #${FLOATING_AUTOJOIN_PANEL_ID}.ofh-aj-collapsed .ofh-aj-header { border-bottom: 0; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-title { margin: 0; display: inline-flex; align-items: center; gap: 6px; color: rgba(226, 232, 240, 0.85); font-size: 10px; font-weight: 900; letter-spacing: 0.04em; text-transform: uppercase; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-header-actions { display: inline-flex; gap: 6px; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-iconbtn { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 7px; border: 1px solid rgba(148, 163, 184, 0.3); background: rgba(8, 22, 36, 0.8); color: #cbd5e1; cursor: pointer; font-size: 13px; font-weight: 900; line-height: 1; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-iconbtn:hover { border-color: rgba(125, 211, 252, 0.55); color: #f1f5f9; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-close { border-color: rgba(248, 113, 113, 0.34); background: rgba(69, 10, 10, 0.6); color: #fecaca; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-close:hover { border-color: rgba(248, 113, 113, 0.6); color: #fff1f2; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-body { display: grid; gap: 10px; flex: 0 1 auto; min-height: 0; overflow: auto; padding: 12px; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; padding: 13px 16px; border: 1px solid rgba(148, 163, 184, 0.28); border-radius: 12px; background: rgba(10, 22, 36, 0.86); color: #e2e8f0; font: inherit; font-size: 14px; font-weight: 900; letter-spacing: 0.04em; text-transform: uppercase; cursor: pointer; transition: border-color 150ms ease, background 150ms ease, box-shadow 150ms ease; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power:hover:not(:disabled) { border-color: rgba(125, 211, 252, 0.5); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power:disabled { opacity: 0.5; cursor: not-allowed; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power-dot { flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%; background: #64748b; box-shadow: 0 0 0 4px rgba(100, 116, 139, 0.18); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power[data-enabled="true"] { border-color: rgba(74, 222, 128, 0.62); background: radial-gradient(circle at 30% 24%, rgba(220, 252, 231, 0.18), transparent 40%), linear-gradient(135deg, rgba(34, 197, 94, 0.32), rgba(22, 101, 52, 0.42)); color: #dcfce7; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 0 18px rgba(34, 197, 94, 0.16); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-power[data-enabled="true"] .ofh-aj-power-dot { background: #4ade80; box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.22), 0 0 12px rgba(74, 222, 128, 0.5); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-notif { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border: 1px solid rgba(148, 163, 184, 0.18); border-radius: 10px; background: rgba(8, 22, 36, 0.6); cursor: pointer; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-notif-text { min-width: 0; font-size: 12px; font-weight: 700; color: #e2e8f0; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-notif input { position: absolute; opacity: 0; pointer-events: none; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-switch { position: relative; flex: 0 0 auto; width: 38px; height: 22px; border-radius: 999px; border: 1px solid rgba(148, 163, 184, 0.32); background: rgba(12, 20, 28, 0.92); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.45); transition: background 200ms ease, border-color 200ms ease; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-switch::after { content: ""; position: absolute; top: 50%; left: 3px; transform: translateY(-50%); width: 16px; height: 16px; border-radius: 50%; background: #94a3b8; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55); transition: left 200ms cubic-bezier(0.34, 1.4, 0.5, 1), background 200ms ease; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-notif input:checked + .ofh-aj-switch { border-color: rgba(56, 189, 248, 0.7); background: linear-gradient(180deg, #38bdf8, #0e7490); box-shadow: 0 0 10px rgba(56, 189, 248, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.25); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-notif input:checked + .ofh-aj-switch::after { left: 19px; background: #f8fafc; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast { display: grid; gap: 6px; padding: 10px 12px; border: 1px solid rgba(56, 189, 248, 0.2); border-radius: 10px; background: rgba(6, 22, 33, 0.6); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast-title { margin: 0 0 2px; color: #7dd3fc; font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 11px; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast-row span { color: rgba(203, 213, 225, 0.72); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast-row strong { color: #e0f2fe; font-size: 12px; font-weight: 900; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-timer { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 13px; border: 1px solid rgba(74, 222, 128, 0.34); border-radius: 10px; background: linear-gradient(135deg, rgba(34, 197, 94, 0.16), transparent 60%), rgba(8, 28, 22, 0.62); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-timer[hidden] { display: none; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-timer-label { display: inline-flex; align-items: center; gap: 7px; color: #bbf7d0; font-size: 11px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-timer-dot { width: 8px; height: 8px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 8px rgba(74, 222, 128, 0.7); animation: ofhAjPulse 1.4s ease-in-out infinite; } @keyframes ofhAjPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-timer strong { color: #f0fdf4; font-size: 15px; font-weight: 900; font-variant-numeric: tabular-nums; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary { display: grid; gap: 6px; padding: 10px 12px; border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 10px; background: rgba(10, 18, 30, 0.55); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary-title { margin: 0 0 2px; color: #cbd5e1; font-size: 10px; font-weight: 900; letter-spacing: 0.08em; text-transform: uppercase; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary-title, #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-forecast-title { display: flex; align-items: center; justify-content: space-between; cursor: pointer; user-select: none; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-chev { font-size: 9px; opacity: 0.6; transition: transform 0.15s; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-sec-collapsed .ofh-aj-chev { transform: rotate(-90deg); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-sec-rows { display: grid; gap: 6px; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-sec-collapsed .ofh-aj-sec-rows { display: none; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-editbtn { margin-top: 2px; width: 100%; cursor: pointer; font: 700 11px/1 "Aptos", "Segoe UI", sans-serif; color: #bbf7d0; background: rgba(34, 197, 94, 0.12); border: 1px solid rgba(134, 239, 172, 0.3); border-radius: 8px; padding: 7px; transition: 0.14s; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-editbtn:hover { background: rgba(34, 197, 94, 0.22); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; font-size: 11px; } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary-row span { flex: 0 0 auto; padding-top: 1px; color: rgba(203, 213, 225, 0.72); } #${FLOATING_AUTOJOIN_PANEL_ID} .ofh-aj-summary-row strong { color: #e2e8f0; font-size: 11.5px; font-weight: 800; line-height: 1.3; text-align: right; overflow-wrap: anywhere; } `;(document.head||document.documentElement).appendChild(e)}function formatAutoJoinDuration(e){const a=Math.max(0,Math.round(Number(e)||0));const n=Math.floor(a/60);const o=a%60;if(n<=0){return`${o}s`}return`${n}m ${o}s`}function formatFloatingAutoJoinElapsed(e){const a=Math.max(0,Math.floor((Date.now()-e)/1e3));const n=Math.floor(a/3600);const o=Math.floor(a%3600/60);const i=a%60;const l=s=>String(s).padStart(2,"0");return n>0?`${n}:${l(o)}:${l(i)}`:`${l(o)}:${l(i)}`}function floatingAutoJoinRangeSummary(e,a){if(e==null&&a==null){return t("Any")}if(e!=null&&a==null){return`${e}+`}if(e==null&&a!=null){return`≤${a}`}if(e===a){return`${e}`}return`${e}–${a}`}function floatingAutoJoinTeamSizeSummary(){return floatingAutoJoinRangeSummary(settings.minTeamSize,settings.maxTeamSize)}function floatingAutoJoinTeamCountSummary(){return floatingAutoJoinRangeSummary(settings.minTeamCount,settings.maxTeamCount)}function floatingAutoJoinCondenseNames(e){if(e.length<=3){return e.join(", ")}return`${e.slice(0,3).join(", ")} +${e.length-3}`}function floatingAutoJoinMapsSummary(){const e=Array.isArray(globalThis.OPENFRONT_MAPS)?globalThis.OPENFRONT_MAPS:[];const a=e.filter(o=>settings.mapFilters&&settings.mapFilters[o.id]).map(o=>o.name);const n=e.filter(o=>settings.mapExcludeFilters&&settings.mapExcludeFilters[o.id]).map(o=>o.name);if(a.length){return floatingAutoJoinCondenseNames(a)}if(n.length){return`${t("All")} − ${floatingAutoJoinCondenseNames(n)}`}return t("All")}function floatingAutoJoinMinLobbySummary(){return settings.minLobbySize==null?t("Any"):`> ${settings.minLobbySize}`}function updateFloatingAutoJoinSearchTime(e){const a=e.querySelector('[data-role="timer-wrap"]');const n=e.querySelector('[data-role="timer"]');const o=Boolean(settings.enabled)&&Number.isFinite(settings.searchStartedAt);if(a instanceof HTMLElement){a.hidden=!o}if(o&&n){n.textContent=formatFloatingAutoJoinElapsed(settings.searchStartedAt)}}function shouldShowFloatingAutoJoinPanel(){if(!settings.showFloatingAutoJoinPanel){return false}const e=document.body;if(!e){return false}if(e.classList.contains("in-game")){return false}if(e.style.overflow==="hidden"){return false}return true}let floatingAutoJoinWatcherId=null;function createFloatingAutoJoinPanel(){ensureFloatingAutoJoinStyles();const e=document.createElement("div");e.id=FLOATING_AUTOJOIN_PANEL_ID;e.innerHTML=`

${t("Auto-Join")}

${t("Filters")}

${t("Players per team")}${t("Any")}
${t("Teams in match")}${t("Any")}
${t("Maps")}${t("All")}
${t("Min lobby size")}${t("Any")}

${t("Lobby forecast")}

${t("ETA (estimate)")}
${t("Hit chance next 10 lobbies")}
${t("Median to match")}
`;e.querySelector('[data-role="close"]')?.addEventListener("click",()=>{saveSettings({...settings,showFloatingAutoJoinPanel:false}).catch(a=>{console.error("Failed to close floating auto-join panel:",a)})});e.querySelector('[data-role="collapse"]')?.addEventListener("click",()=>{const a=!settings.floatingAutoJoinPanelCollapsed;e.classList.toggle("ofh-aj-collapsed",a);saveSettings({...settings,floatingAutoJoinPanelCollapsed:a}).catch(n=>{console.error("Failed to collapse floating auto-join panel:",n)})});e.querySelector('[data-role="power"]')?.addEventListener("click",()=>{const a=e.querySelector('[data-role="power"]');if(a instanceof HTMLButtonElement&&a.disabled){return}const n=!settings.enabled;saveSettings({...settings,enabled:n,searchStartedAt:n?Date.now():null}).catch(o=>{console.error("Failed to toggle auto-join from floating panel:",o)})});e.querySelector('[data-role="notif"]')?.addEventListener("change",a=>{const n=a.target;if(!(n instanceof HTMLInputElement)){return}if(n.checked&&typeof window.__OFH_requestNotifyPermission==="function"){window.__OFH_requestNotifyPermission()}saveSettings({...settings,joinNotification:n.checked}).catch(o=>{console.error("Failed to toggle notification from floating panel:",o)})});e.querySelector('[data-role="settings"]')?.addEventListener("click",()=>{if(typeof window.__OFH_openPopup==="function")window.__OFH_openPopup()});e.querySelector('[data-role="edit-filters"]')?.addEventListener("click",()=>{if(typeof window.__OFH_openPopup==="function"){window.__OFH_openPopup("autojoin","filters")}});e.querySelector('[data-role="filters-toggle"]')?.addEventListener("click",()=>{const a=!settings.floatingAutoJoinFiltersCollapsed;e.querySelector(".ofh-aj-summary")?.classList.toggle("ofh-aj-sec-collapsed",a);saveSettings({...settings,floatingAutoJoinFiltersCollapsed:a}).catch(n=>{console.error("Failed to toggle filters section:",n)})});e.querySelector('[data-role="forecast-toggle"]')?.addEventListener("click",()=>{const a=!settings.floatingAutoJoinForecastCollapsed;e.querySelector(".ofh-aj-forecast")?.classList.toggle("ofh-aj-sec-collapsed",a);saveSettings({...settings,floatingAutoJoinForecastCollapsed:a}).catch(n=>{console.error("Failed to toggle forecast section:",n)})});installFloatingAutoJoinDrag(e);return e}function installFloatingAutoJoinDrag(e){const a=e.querySelector(".ofh-aj-header");if(!(a instanceof HTMLElement)){return}let n=null;a.addEventListener("pointerdown",i=>{if(i.button!==0||i.target instanceof HTMLButtonElement){return}const l=e.getBoundingClientRect();n={pointerId:i.pointerId,offsetX:i.clientX-l.left,offsetY:i.clientY-l.top};a.setPointerCapture(i.pointerId);i.preventDefault()});a.addEventListener("pointermove",i=>{if(!n||n.pointerId!==i.pointerId){return}const l=Math.max(8,window.innerWidth-e.offsetWidth-8);const s=Math.max(8,window.innerHeight-e.offsetHeight-8);const c=Math.max(8,Math.min(l,i.clientX-n.offsetX));const g=Math.max(8,Math.min(s,i.clientY-n.offsetY));setFloatingAutoJoinPosition(e,c,g)});function o(i){if(!n||n.pointerId!==i.pointerId){return}n=null;const l=e.getBoundingClientRect();saveSettings({...settings,floatingAutoJoinPanelPosition:{left:Math.round(l.left),top:Math.round(l.top)}}).catch(s=>{console.error("Failed to save floating auto-join position:",s)})}a.addEventListener("pointerup",o);a.addEventListener("pointercancel",o)}function setFloatingAutoJoinPosition(e,a,n){e.style.setProperty("--ofh-aj-left",`${Math.round(a)}px`);e.style.setProperty("--ofh-aj-top",`${Math.round(n)}px`)}function positionFloatingAutoJoinPanel(e){const a=settings.floatingAutoJoinPanelPosition||{};const n=a.left??window.innerWidth-320;const o=a.top??92;const i=Math.max(8,window.innerWidth-e.offsetWidth-8);const l=Math.max(8,window.innerHeight-e.offsetHeight-8);setFloatingAutoJoinPosition(e,Math.max(8,Math.min(i,n)),Math.max(8,Math.min(l,o)))}function updateFloatingAutoJoinPanel(e){const a=Boolean(settings.floatingAutoJoinPanelCollapsed);e.classList.toggle("ofh-aj-collapsed",a);const n=e.querySelector('[data-role="collapse"]');if(n instanceof HTMLElement){n.textContent=a?"▢":"—";n.title=a?t("Expand"):t("Collapse")}const o=typeof hasSelectedCriteria==="function"?hasSelectedCriteria():true;const i=e.querySelector('[data-role="power"]');if(i instanceof HTMLButtonElement){const h=Boolean(settings.enabled);i.dataset.enabled=String(h);i.disabled=!h&&!o;const b=i.querySelector(".ofh-aj-power-label");if(b){b.textContent=h?t("autoJoinOn"):t("autoJoinOff")}i.title=!h&&!o?t("Select at least one filter in the popup to enable auto-join."):""}const l=e.querySelector('[data-role="notif"]');if(l instanceof HTMLInputElement){l.checked=Boolean(settings.joinNotification)}const s=settings.lobbyForecast||{};const c=e.querySelector('[data-role="eta"]');const g=e.querySelector('[data-role="hit"]');const y=e.querySelector('[data-role="median"]');if(c){c.textContent=Number.isFinite(s.etaMinSeconds)&&Number.isFinite(s.etaMaxSeconds)?`${formatAutoJoinDuration(s.etaMinSeconds)} – ${formatAutoJoinDuration(s.etaMaxSeconds)}`:"—"}if(g){g.textContent=Number.isFinite(s.hitChanceNext10)?`${Math.round(s.hitChanceNext10*100)}%`:"—"}if(y){y.textContent=Number.isFinite(s.medianLobbiesToMatch)?`${Math.round(s.medianLobbiesToMatch)} ${t("lobbies")}`:"—"}const w=e.querySelector('[data-role="teamsize"]');if(w){w.textContent=floatingAutoJoinTeamSizeSummary()}const M=e.querySelector('[data-role="teamcount"]');if(M){M.textContent=floatingAutoJoinTeamCountSummary()}const E=e.querySelector('[data-role="maps"]');if(E){E.textContent=floatingAutoJoinMapsSummary()}const p=e.querySelector('[data-role="minlobby"]');if(p){p.textContent=floatingAutoJoinMinLobbySummary()}updateFloatingAutoJoinSearchTime(e)}function applyFloatingAutoJoinPanelState(){const e=document.getElementById(FLOATING_AUTOJOIN_PANEL_ID);if(!shouldShowFloatingAutoJoinPanel()){e?.remove();return}let a=e;if(!a){a=createFloatingAutoJoinPanel();(document.body||document.documentElement).appendChild(a);positionFloatingAutoJoinPanel(a)}updateFloatingAutoJoinPanel(a)}function syncFloatingAutoJoinPanel(){if(settings.showFloatingAutoJoinPanel){if(floatingAutoJoinWatcherId===null){floatingAutoJoinWatcherId=window.setInterval(applyFloatingAutoJoinPanelState,1e3)}}else if(floatingAutoJoinWatcherId!==null){window.clearInterval(floatingAutoJoinWatcherId);floatingAutoJoinWatcherId=null}applyFloatingAutoJoinPanelState()}function normalizeComparableText(e){return String(e??"").trim().toLowerCase().replace(/\s+/g," ")}function normalizeMapToken(e){return normalizeComparableText(e).replace(/[^a-z0-9]+/g,"")}function isExtensionContextInvalidatedError(e){return String(e?.message||e||"").includes("Extension context invalidated")}function isExtensionContextAlive(){try{return typeof chrome!=="undefined"&&Boolean(chrome.runtime?.id)}catch(e){return false}}const LOBBY_FORECAST_MAX_RATE_SAMPLES=240;const LOBBY_FORECAST_MAX_DISTANCE_SAMPLES=120;const LOBBY_FORECAST_PERSIST_MS=4e3;const LOBBY_FORECAST_MODEL_DRAWS=3500;const LOBBY_FORECAST_PRIOR_STRENGTH=36;const LOBBY_FORECAST_MIN_GROUP_SAMPLES_FOR_WEIGHTING=12;const LOBBY_FORECAST_AVERAGE_WINDOW=100;const LOBBY_FORECAST_MIN_MATCH_SAMPLES_FOR_ROLLING=20;let lobbyForecastSeenIds=new Set;let lobbyForecastNewLobbyTimestamps=[];let lobbyForecastDistanceSamples=[];let lobbyForecastTotalNew=0;let lobbyForecastMatchedNew=0;let lobbyForecastSinceLastMatch=0;let lobbyForecastNewByGroup={special:0,ffa:0,team:0};let lobbyForecastRecentLobbyIntervalsMs=[];let lobbyForecastRecentMatchOutcomes=[];let lobbyForecastLastSeenLobbyAt=null;let lobbyForecastRecentLobbyCount=0;let lobbyForecastFilterSignature="";let lobbyForecastLastPersistedAt=0;let lobbyForecastLastPersistedPayload="";let lobbyForecastModelCache={signature:"",values:{special:0,ffa:0,team:0}};const FORECAST_MAP_FREQUENCY=Object.freeze({africa:7,asia:6,australia:4,achiran:5,baikal:5,betweentwoseas:5,blacksea:6,britannia:5,britanniaclassic:4,deglaciatedantarctica:4,eastasia:5,europe:7,falklandislands:4,faroeislands:4,fourislands:4,gatewaytotheatlantic:5,gulfofstlawrence:4,halkidiki:4,iceland:4,italia:6,japan:6,lisbon:4,manicouagan:4,mars:3,mena:6,montreal:6,newyorkcity:3,northamerica:5,pangaea:5,pluto:6,southamerica:5,straitofgibraltar:5,svalmel:8,world:20,lemnos:3,passage:4,twolakes:6,straitofhormuz:4,surrounded:4,didierfrance:1,didier:1,amazonriver:3,bosphorusstraits:3,beringstrait:2,sierpinski:10,thebox:3,yenisei:6,tradersdream:4,hawaii:4,alps:4,antarctica:1,archipelagosea:3,bajacalifornia:4,beringsea:5,caucasus:5,conakry:3,losangeles:8,luna:6,marenostrum:6,niledelta:4,arctic:6,sanfrancisco:3,aegean:6,milkyway:8,dyslexdria:8,greatlakes:6,straitofmalacca:4,balkans:5,caribbean:5,choppingblock:2,danishstraits:4,giantworldmap:2,hongkong:4,indiansubcontinent:5,juandefucastrait:3,korea:4,labyrinth:2,middleeast:5,mississippiriver:3,northwestpassage:3,onion:2,southeastasia:5,taiwanstrait:3,titan:3,venice:4,warshipwarship:2,worldinverted:3,yellowsea:4});const FORECAST_ARCADE_MAP_IDS=new Set(["thebox","didier","didierfrance","sierpinski"]);const FORECAST_SPECIAL_ONLY_MAP_IDS=new Set(["archipelagosea"]);const FORECAST_WATER_NUKES_BOOSTED_MAPS=new Set(["fourislands","baikal","alps","thebox","luna","archipelagosea"]);const FORECAST_SPECIAL_COUNT_ROLLS=[1,1,1,2,2,2,2,2,3,3];const FORECAST_TEAM_WEIGHTS=Object.freeze([{config:2,weight:10},{config:3,weight:10},{config:4,weight:10},{config:5,weight:10},{config:6,weight:10},{config:7,weight:10},{config:"duos",weight:5},{config:"trios",weight:7.5},{config:"quads",weight:7.5},{config:"humansVsNations",weight:20}]);const FORECAST_MUTUALLY_EXCLUSIVE_MODIFIERS=Object.freeze([["startingGold5M","startingGold25M"],["startingGold5M","startingGold1M"],["startingGold25M","startingGold1M"],["isHardNations","startingGold25M"],["isNukesDisabled","isSAMsDisabled"],["isNukesDisabled","isWaterNukes"]]);const FORECAST_SPECIAL_MODIFIER_POOL=Object.freeze([...Array(2).fill("isRandomSpawn"),...Array(4).fill("isCompact"),...Array(2).fill("isCrowded"),...Array(1).fill("isHardNations"),...Array(3).fill("startingGold1M"),...Array(5).fill("startingGold5M"),...Array(1).fill("startingGold25M"),...Array(4).fill("goldMultiplier"),...Array(1).fill("isAlliancesDisabled"),...Array(1).fill("isPortsDisabled"),...Array(1).fill("isNukesDisabled"),...Array(1).fill("isSAMsDisabled"),...Array(1).fill("isPeaceTime"),...Array(3).fill("isWaterNukes")]);function addMapTokensFromValue(e,a,n=0){if(n>4||e==null){return}if(typeof e==="string"||typeof e==="number"){const o=normalizeComparableText(e);const i=normalizeMapToken(e);if(o){a.add(o)}if(i){a.add(i)}return}if(Array.isArray(e)){for(const o of e){addMapTokensFromValue(o,a,n+1)}return}if(typeof e==="object"){for(const o of Object.values(e)){addMapTokensFromValue(o,a,n+1)}}}function addMapTokensFromMapFields(e,a,n=0){if(n>4||e==null||typeof e!=="object"){return}for(const[o,i]of Object.entries(e)){if(normalizeMapToken(o).includes("map")){addMapTokensFromValue(i,a)}if(i!=null&&typeof i==="object"){addMapTokensFromMapFields(i,a,n+1)}}}function getLobbyMapTokens(e){const a=e?.gameConfig||{};const n=new Set;const o=[a.map,a.mapId,a.mapID,a.mapName,a.mapInfo,a.mapConfig,a.mapMetadata,a.gameMap,a.gameMapId,a.gameMapID,a.gameMapName,a.terrain,e?.map,e?.mapId,e?.mapID,e?.mapName,e?.gameMap,e?.gameMapId,e?.gameMapID,e?.gameMapName];for(const i of o){addMapTokensFromValue(i,n)}addMapTokensFromMapFields(e,n);return n}function mapMatchesTokens(e,a){const n=[normalizeComparableText(e.id),normalizeComparableText(e.name),normalizeMapToken(e.id),normalizeMapToken(e.name)];return n.some(o=>o&&a.has(o))}function lobbyMatchesMapFilters(e){const a=MAPS.filter(i=>settings.mapFilters[i.id]);const n=MAPS.filter(i=>settings.mapExcludeFilters[i.id]);if(a.length===0&&n.length===0){return true}const o=getLobbyMapTokens(e);if(o.size===0){return a.length===0}if(n.some(i=>mapMatchesTokens(i,o))){return false}if(a.length===0){return true}return a.some(i=>mapMatchesTokens(i,o))}function pruneCooldowns(){const e=Date.now();for(const[a,n]of lobbyCooldowns.entries()){if(n<=e){lobbyCooldowns.delete(a)}}}function resetPendingJoinIfExpired(){if(!pendingJoin){return}if(isOccupied()||Date.now()-pendingJoin.startedAt>JOIN_ATTEMPT_TIMEOUT_MS){pendingJoin=null}}function isInActiveGame(){if(document.body?.classList.contains("in-game")){return true}return/\/game\/[^/]+/.test(window.location.pathname)&&window.location.search.includes("live")}function isInLobbyWaiting(){try{const e=document.querySelector("join-lobby-modal");return Boolean(e&&e.isModalOpen===true)}catch(e){return false}}function isOccupied(){return isInActiveGame()||isInLobbyWaiting()}function objectContainsFreeForAll(e,a=0){if(a>4||e==null){return false}if(typeof e==="string"){const n=normalizeComparableText(e);return n==="free for all"||n.includes("free for all")}if(Array.isArray(e)){return e.some(n=>objectContainsFreeForAll(n,a+1))}if(typeof e==="object"){return Object.values(e).some(n=>objectContainsFreeForAll(n,a+1))}return false}function objectContainsPhrase(e,a,n=0){if(n>4||e==null){return false}if(typeof e==="string"){return normalizeComparableText(e).includes(a)}if(Array.isArray(e)){return e.some(o=>objectContainsPhrase(o,a,n+1))}if(typeof e==="object"){return Object.values(e).some(o=>objectContainsPhrase(o,a,n+1))}return false}function isFfaLobby(e,a){const n=normalizeComparableText(e?.gameConfig?.gameMode);if(n==="free for all"||n==="ffa"){return true}if(n==="team"){return false}if(objectContainsFreeForAll(e)){return true}return a==="ffa"}function getTeamSizePerLobby(e){const a=e?.gameConfig||{};const n=normalizeComparableText(a.gameMode);if(n!=="team"){return null}const o=a.playerTeams;const i=normalizeComparableText(o);if(i==="duos"){return 2}if(i==="trios"){return 3}if(i==="quads"){return 4}const l=Number(a.maxPlayers);if(typeof o==="number"&&Number.isFinite(o)&&o>0&&Number.isFinite(l)&&l>0){return Math.floor(l/o)}if(objectContainsPhrase(e,"teams of 2")){return 2}if(objectContainsPhrase(e,"teams of 3")){return 3}if(objectContainsPhrase(e,"teams of 4")){return 4}if(objectContainsPhrase(e,"teams of 5")){return 5}if(objectContainsPhrase(e,"teams of 6")){return 6}return null}function getLobbyTeamSizeForFilter(e,a){if(isFfaLobby(e,a)){return 1}const n=getTeamSizePerLobby(e);return Number.isFinite(n)?n:null}function lobbyMatchesTeamSizeRange(e,a){if(settings.minTeamSize==null&&settings.maxTeamSize==null){return true}const n=getLobbyTeamSizeForFilter(e,a);if(!Number.isFinite(n)){return false}if(settings.minTeamSize!=null&&nsettings.maxTeamSize){return false}return true}function getTeamCountPerLobby(e){const a=e?.gameConfig||{};const n=normalizeComparableText(a.gameMode);if(n!=="team"){return null}const o=a.playerTeams;if(typeof o==="number"&&Number.isFinite(o)&&o>0){return Math.floor(o)}const i=getTeamSizePerLobby(e);const l=getLobbyMaxPlayers(e);if(Number.isFinite(i)&&i>0&&Number.isFinite(l)&&l>0){return Math.floor(l/i)}return null}function getLobbyTeamCountForFilter(e,a){if(isFfaLobby(e,a)){return null}const n=getTeamCountPerLobby(e);return Number.isFinite(n)?n:null}function lobbyMatchesTeamCountRange(e,a){if(settings.minTeamCount==null&&settings.maxTeamCount==null){return true}const n=getLobbyTeamCountForFilter(e,a);if(!Number.isFinite(n)){return false}if(settings.minTeamCount!=null&&nsettings.maxTeamCount){return false}return true}function getLobbyMaxPlayers(e){const a=e?.gameConfig||{};const n=[a.maxPlayers,a.maxPlayerCount,e?.maxPlayers,e?.maxPlayerCount];for(const o of n){const i=Number(o);if(Number.isFinite(i)&&i>0){return i}}return null}function readBooleanModifier(e,a,n,...o){const i=e?.[n];if(i!=null){return Boolean(i)}return o.some(l=>Boolean(a?.[l]))}function readPeaceTimeModifier(e,a){if(e?.isPeaceTime!=null){return Boolean(e.isPeaceTime)}const n=Number(e?.peaceTimeSeconds??e?.peaceTime??a?.peaceTimeSeconds??a?.peaceTime);if(Number.isFinite(n)&&n>0){return n===240}return Boolean(a?.isPeaceTime||a?.peaceTimeEnabled)}function extractTrackedFilters(e,a){const n=e?.gameConfig||{};const o=n.publicGameModifiers||{};const i=o.startingGold??n.startingGold;const l=Number(i);const s=Number.isFinite(l)?l:0;const c=Number(o.goldMultiplier??n.goldMultiplier??0);return{startingGold0M:s===0,randomSpawn:readBooleanModifier(o,n,"isRandomSpawn","randomSpawn","isRandomSpawn"),alliancesDisabled:readBooleanModifier(o,n,"isAlliancesDisabled","disableAlliances","alliancesDisabled","isAlliancesDisabled"),portsDisabled:readBooleanModifier(o,n,"isPortsDisabled","disablePorts","portsDisabled","isPortsDisabled"),nukesDisabled:readBooleanModifier(o,n,"isNukesDisabled","disableNukes","nukesDisabled","isNukesDisabled"),samsDisabled:readBooleanModifier(o,n,"isSAMsDisabled","disableSAMs","disableSams","samsDisabled","isSAMsDisabled","isSamsDisabled"),waterNukes:readBooleanModifier(o,n,"isWaterNukes","waterNukes","isWaterNukes"),peaceTime4m:readPeaceTimeModifier(o,n),startingGold1M:s===1e6,startingGold5M:s===5e6,startingGold25M:s===25e6,goldMultiplier2x:c===2}}function lobbyMatchesFilters(e,a){if(settings.minLobbySize!=null){const c=getLobbyMaxPlayers(e);if(!Number.isFinite(c)||c<=settings.minLobbySize){return false}}if(!lobbyMatchesMapFilters(e)){return false}if(!lobbyMatchesTeamSizeRange(e,a)){return false}if(!lobbyMatchesTeamCountRange(e,a)){return false}const n=extractTrackedFilters(e,a);const o=FILTER_KEYS.every(c=>{if(START_GOLD_FILTER_KEYS.includes(c)){return true}if(settings.includeFilters[c]&&!n[c]){return false}if(settings.excludeFilters[c]&&n[c]){return false}return true});if(!o){return false}const i=START_GOLD_FILTER_KEYS.filter(c=>settings.includeFilters[c]);const l=START_GOLD_FILTER_KEYS.filter(c=>settings.excludeFilters[c]);const s=l.some(c=>n[c]===true);if(s){return false}if(i.length===0){return true}return i.some(c=>n[c]===true)}function hasSelectedCriteria(){return FILTER_KEYS.some(e=>settings.includeFilters[e]||settings.excludeFilters[e])||settings.minLobbySize!=null||settings.minTeamSize!=null||settings.maxTeamSize!=null||settings.minTeamCount!=null||settings.maxTeamCount!=null||MAP_IDS.some(e=>settings.mapFilters[e]||settings.mapExcludeFilters[e])}function createForecastFilterSignature(){return JSON.stringify({include:settings.includeFilters,exclude:settings.excludeFilters,mapFilters:settings.mapFilters,mapExcludeFilters:settings.mapExcludeFilters,minLobbySize:settings.minLobbySize,minTeamSize:settings.minTeamSize,maxTeamSize:settings.maxTeamSize,minTeamCount:settings.minTeamCount,maxTeamCount:settings.maxTeamCount})}function resetLobbyForecastTracking(e=[]){lobbyForecastSeenIds=new Set(e.filter(a=>a));lobbyForecastNewLobbyTimestamps=[];lobbyForecastDistanceSamples=[];lobbyForecastTotalNew=0;lobbyForecastMatchedNew=0;lobbyForecastSinceLastMatch=0;lobbyForecastNewByGroup={special:0,ffa:0,team:0};lobbyForecastLastPersistedAt=0;lobbyForecastLastPersistedPayload=""}function updateLast100LobbyAverages(e,a=null){if(Number.isFinite(lobbyForecastLastSeenLobbyAt)){const n=Math.max(0,e-lobbyForecastLastSeenLobbyAt);if(n>0){lobbyForecastRecentLobbyIntervalsMs.push(n);lobbyForecastRecentLobbyIntervalsMs=withForecastWindow(lobbyForecastRecentLobbyIntervalsMs,LOBBY_FORECAST_AVERAGE_WINDOW)}}lobbyForecastLastSeenLobbyAt=e;lobbyForecastRecentLobbyCount=Math.min(LOBBY_FORECAST_AVERAGE_WINDOW,lobbyForecastRecentLobbyCount+1);if(typeof a==="boolean"){lobbyForecastRecentMatchOutcomes.push(a?1:0);lobbyForecastRecentMatchOutcomes=withForecastWindow(lobbyForecastRecentMatchOutcomes,LOBBY_FORECAST_AVERAGE_WINDOW)}}function computeLast100LobbyAverages(){const e=lobbyForecastRecentLobbyCount;const a=lobbyForecastRecentMatchOutcomes.length;const n=lobbyForecastRecentMatchOutcomes.reduce((y,w)=>y+w,0);const o=a>0?n/a:null;const i=lobbyForecastRecentLobbyIntervalsMs.length;const l=i>0?Math.round(lobbyForecastRecentLobbyIntervalsMs.reduce((y,w)=>y+w,0)/i):null;const s=Number.isFinite(l)&&l>0?6e4/l:null;const c=Number.isFinite(o)&&o>0?estimateLobbiesUntilMatch(o,.5):null;const g=c!=null&&Number.isFinite(l)&&l>0?Math.max(1,Math.round(c*l/1e3)):null;return{windowSize:LOBBY_FORECAST_AVERAGE_WINDOW,sampleSize:e,hitSampleSize:a,hitRate:Number.isFinite(o)?o:null,avgLobbyIntervalMs:Number.isFinite(l)&&l>0?l:null,avgLobbiesPerMinute:Number.isFinite(s)&&s>0?s:null,etaSeconds:g,updatedAt:e>0?Date.now():null}}function getForecastMapsForType(e){const a=[];for(const n of MAPS){let o=Number(FORECAST_MAP_FREQUENCY[n.id]||0);if(o<=0){continue}if(e!=="special"&&(FORECAST_ARCADE_MAP_IDS.has(n.id)||FORECAST_SPECIAL_ONLY_MAP_IDS.has(n.id))){continue}if(e==="team"&&(n.id==="baikal"||n.id==="fourislands")){o*=2}a.push({id:n.id,weight:o})}return a}function pickWeighted(e){if(!Array.isArray(e)||e.length===0){return null}const a=e.reduce((o,i)=>o+i.weight,0);if(!(a>0)){return null}let n=Math.random()*a;for(const o of e){n-=o.weight;if(n<=0){return o}}return e[e.length-1]}function shuffleInPlace(e){for(let a=e.length-1;a>0;a-=1){const n=Math.floor(Math.random()*(a+1));[e[a],e[n]]=[e[n],e[a]]}}function randomTeamConfigForMap(e){if(e==="baikal"&&Math.random()<.75){return 2}if(e==="fourislands"&&Math.random()<.75){return 4}if(e==="luna"&&Math.random()<.75){return 2}const a=pickWeighted(FORECAST_TEAM_WEIGHTS);return a?a.config:2}function teamConfigToTeamSize(e){if(e==="duos"){return 2}if(e==="trios"){return 3}if(e==="quads"){return 4}return typeof e==="number"?e:null}function getRandomSpecialModifiers(e,a=0){const n=new Set(e);const o=FORECAST_SPECIAL_COUNT_ROLLS[Math.floor(Math.random()*FORECAST_SPECIAL_COUNT_ROLLS.length)];const i=Math.max(0,o-a);const l=FORECAST_SPECIAL_MODIFIER_POOL.filter(c=>!n.has(c));shuffleInPlace(l);const s=new Set;for(const c of l){if(s.size>=i){break}const g=FORECAST_MUTUALLY_EXCLUSIVE_MODIFIERS.some(([y,w])=>c===y&&s.has(w)||c===w&&s.has(y));if(!g){s.add(c)}}return{isRandomSpawn:s.has("isRandomSpawn"),startingGold:s.has("startingGold25M")?25e6:s.has("startingGold5M")?5e6:s.has("startingGold1M")?1e6:0,goldMultiplier:s.has("goldMultiplier")?2:1,isAlliancesDisabled:s.has("isAlliancesDisabled"),isPortsDisabled:s.has("isPortsDisabled"),isNukesDisabled:s.has("isNukesDisabled"),isSAMsDisabled:s.has("isSAMsDisabled"),isWaterNukes:s.has("isWaterNukes"),isPeaceTime:s.has("isPeaceTime")}}function createForecastScenario(e){const a=pickWeighted(getForecastMapsForType(e));if(!a){return null}if(e==="ffa"){return{mapId:a.id,teamSize:1,startingGold0M:true,randomSpawn:false,alliancesDisabled:false,portsDisabled:false,nukesDisabled:false,samsDisabled:false,waterNukes:false,peaceTime4m:false,startingGold1M:false,startingGold5M:false,startingGold25M:false,goldMultiplier2x:false}}if(e==="team"){const w=randomTeamConfigForMap(a.id);const M=teamConfigToTeamSize(w);return{mapId:a.id,teamSize:M,startingGold0M:true,randomSpawn:false,alliancesDisabled:false,portsDisabled:false,nukesDisabled:false,samsDisabled:false,waterNukes:false,peaceTime4m:false,startingGold1M:false,startingGold5M:false,startingGold25M:false,goldMultiplier2x:false}}const n=Math.random()>=.5;const o=n?randomTeamConfigForMap(a.id):null;const i=[];if(o==="duos"||o==="trios"||o==="quads"){i.push("isRandomSpawn")}if(a.id==="fourislands"&&n){i.push("goldMultiplier","startingGold25M")}if(n){i.push("isHardNations")}if(o==="humansVsNations"){i.push("startingGold25M","isPeaceTime")}let l=false;if(FORECAST_WATER_NUKES_BOOSTED_MAPS.has(a.id)&&Math.random()<.5){i.push("isWaterNukes","isNukesDisabled");l=true}const s=getRandomSpecialModifiers(i,l?1:0);const c=teamConfigToTeamSize(o);const g=s.startingGold;const y=n?c:1;return{mapId:a.id,teamSize:y,startingGold0M:g===0,randomSpawn:s.isRandomSpawn,alliancesDisabled:s.isAlliancesDisabled,portsDisabled:s.isPortsDisabled,nukesDisabled:s.isNukesDisabled,samsDisabled:s.isSAMsDisabled,waterNukes:l||s.isWaterNukes,peaceTime4m:s.isPeaceTime,startingGold1M:g===1e6,startingGold5M:g===5e6,startingGold25M:g===25e6,goldMultiplier2x:s.goldMultiplier===2}}function scenarioMatchesTeamSizeRange(e){if(settings.minTeamSize==null&&settings.maxTeamSize==null){return true}const a=e?.teamSize;if(!Number.isFinite(a)){return false}if(settings.minTeamSize!=null&&asettings.maxTeamSize){return false}return true}function scenarioMatchesFilters(e){if(!e||!e.mapId){return false}const a=MAP_IDS.filter(s=>settings.mapFilters[s]);const n=MAP_IDS.filter(s=>settings.mapExcludeFilters[s]);if(n.includes(e.mapId)){return false}if(a.length>0&&!a.includes(e.mapId)){return false}if(!scenarioMatchesTeamSizeRange(e)){return false}const o=FILTER_KEYS.every(s=>{if(START_GOLD_FILTER_KEYS.includes(s)){return true}if(settings.includeFilters[s]&&!e[s]){return false}if(settings.excludeFilters[s]&&e[s]){return false}return true});if(!o){return false}const i=START_GOLD_FILTER_KEYS.filter(s=>settings.includeFilters[s]);const l=START_GOLD_FILTER_KEYS.filter(s=>settings.excludeFilters[s]);if(l.some(s=>e[s]===true)){return false}if(i.length===0){return true}return i.some(s=>e[s]===true)}function estimateModelHitRateByType(e){let a=0;let n=0;for(let o=0;o=LOBBY_FORECAST_MIN_GROUP_SAMPLES_FOR_WEIGHTING){return{special:lobbyForecastNewByGroup.special/e,ffa:lobbyForecastNewByGroup.ffa/e,team:lobbyForecastNewByGroup.team/e}}return{special:1/3,ffa:1/3,team:1/3}}function getModelHitRate(){const e=getModelHitRates();const a=getForecastGroupWeights();return e.special*a.special+e.ffa*a.ffa+e.team*a.team}function withForecastWindow(e,a){if(e.length<=a){return e}return e.slice(e.length-a)}function quantile(e,a){if(!Array.isArray(e)||e.length===0){return null}const n=[...e].sort((i,l)=>i-l);const o=Math.max(0,Math.min(n.length-1,Math.floor((n.length-1)*a)));return n[o]}function estimateLobbiesUntilMatch(e,a){if(!(e>0&&e<1)){return e>=1?1:null}const n=1-a;if(!(n>0&&n<1)){return null}const o=Math.log(n)/Math.log(1-e);return Number.isFinite(o)&&o>0?Math.max(1,Math.ceil(o)):null}function buildLobbyForecastPayload(){const e=computeLast100LobbyAverages();if(!hasSelectedCriteria()){return{available:false,sampleSize:lobbyForecastTotalNew,etaMinSeconds:null,etaMaxSeconds:null,hitChanceNext10:null,medianLobbiesToMatch:null,last100Averages:e}}const a=getModelHitRate();const n=e.hitRate;const o=Number.isFinite(n)&&e.hitSampleSize>=LOBBY_FORECAST_MIN_MATCH_SAMPLES_FOR_ROLLING?e.sampleSize:0;const i=Number.isFinite(n)?n*o:0;const l=o===0?lobbyForecastTotalNew:0;const s=o===0?lobbyForecastMatchedNew:0;const c=(i+s+a*LOBBY_FORECAST_PRIOR_STRENGTH)/(o+l+LOBBY_FORECAST_PRIOR_STRENGTH);const g=Math.max(0,Math.min(1,c));const y=g>0?g:Math.max(a,1/LOBBY_FORECAST_MODEL_DRAWS);const w=1-(1-g)**10;const M=quantile(lobbyForecastDistanceSamples,.5);const E=estimateLobbiesUntilMatch(y,.5);const p=M??E;const h=quantile(lobbyForecastDistanceSamples,.25);const b=quantile(lobbyForecastDistanceSamples,.75);const S=estimateLobbiesUntilMatch(y,.25);const f=estimateLobbiesUntilMatch(y,.75);const A=h??S;const F=b??f;let v=null;let N=null;let k=null;if(lobbyForecastNewLobbyTimestamps.length>=2){const _=lobbyForecastNewLobbyTimestamps[0];const L=lobbyForecastNewLobbyTimestamps[lobbyForecastNewLobbyTimestamps.length-1];const P=Math.max(1,L-_);k=(lobbyForecastNewLobbyTimestamps.length-1)/(P/1e3)}else if(Number.isFinite(e.avgLobbyIntervalMs)&&e.avgLobbyIntervalMs>0){k=1e3/e.avgLobbyIntervalMs}if(k>0){v=A!=null?Math.max(1,Math.round(A/k)):null;N=F!=null?Math.max(1,Math.round(F/k)):null}return{available:true,sampleSize:lobbyForecastTotalNew,etaMinSeconds:Number.isFinite(v)&&v>0?v:null,etaMaxSeconds:Number.isFinite(N)&&N>0?N:null,hitChanceNext10:Number.isFinite(w)?w:null,medianLobbiesToMatch:p!=null?Math.max(1,Math.round(p)):null,last100Averages:e}}function persistLobbyForecast(e=false){if(!isExtensionContextAlive()){return}let a;let n;try{a=buildLobbyForecastPayload();n=JSON.stringify(a)}catch(i){if(isExtensionContextInvalidatedError(i)){return}console.error("Failed to build lobby forecast payload:",i);return}if(!e&&n===lobbyForecastLastPersistedPayload){return}const o=Date.now();if(!e&&o-lobbyForecastLastPersistedAt{try{if(!isExtensionContextAlive()){return}const i=await chrome.storage.local.get(STORAGE_KEY);if(!isExtensionContextAlive()){return}const l=normalizeSettings(i[STORAGE_KEY]);await chrome.storage.local.set({[STORAGE_KEY]:{...l,lobbyForecast:a}})}catch(i){if(isExtensionContextInvalidatedError(i)){return}console.error("Failed to persist lobby forecast:",i)}})().catch(i=>{if(isExtensionContextInvalidatedError(i)){return}console.error("Failed to persist lobby forecast:",i)})}function updateLobbyForecast(e){if(!isExtensionContextAlive()){return}const a=createForecastFilterSignature();const n=hasSelectedCriteria();const o=flattenLobbies(e);const i=o.map(({lobby:s})=>String(s?.gameID||"")).filter(Boolean);if(lobbyForecastFilterSignature!==a){lobbyForecastFilterSignature=a;lobbyForecastRecentMatchOutcomes=[];resetLobbyForecastTracking(i);persistLobbyForecast(true);return}const l=Date.now();for(const{lobby:s,groupKey:c}of o){const g=String(s?.gameID||"");if(!g||lobbyForecastSeenIds.has(g)){continue}lobbyForecastSeenIds.add(g);if(!n){updateLast100LobbyAverages(l,null);continue}lobbyForecastTotalNew+=1;lobbyForecastSinceLastMatch+=1;if(c==="special"||c==="ffa"||c==="team"){lobbyForecastNewByGroup[c]+=1}lobbyForecastNewLobbyTimestamps.push(l);lobbyForecastNewLobbyTimestamps=withForecastWindow(lobbyForecastNewLobbyTimestamps,LOBBY_FORECAST_MAX_RATE_SAMPLES);const y=lobbyMatchesFilters(s,c);updateLast100LobbyAverages(l,y);if(y){lobbyForecastMatchedNew+=1;lobbyForecastDistanceSamples.push(lobbyForecastSinceLastMatch);lobbyForecastDistanceSamples=withForecastWindow(lobbyForecastDistanceSamples,LOBBY_FORECAST_MAX_DISTANCE_SAMPLES);lobbyForecastSinceLastMatch=0}}if(!n){lobbyForecastRecentMatchOutcomes=[]}persistLobbyForecast()}function flattenLobbies(e){const a=e?.games;if(!a||typeof a!=="object"){return[]}const n=[...PREFERRED_GROUP_ORDER];for(const i of Object.keys(a)){if(!n.includes(i)){n.push(i)}}const o=[];for(const i of n){const l=a[i];if(Array.isArray(l)){o.push(...l.map(s=>({groupKey:i,lobby:s})))}}return o}function findMatchingLobby(e){pruneCooldowns();const a=flattenLobbies(e).find(({lobby:n,groupKey:o})=>{if(!n?.gameID||lobbyCooldowns.has(n.gameID)){return false}return lobbyMatchesFilters(n,o)});return a?.lobby??null}function ensureJoinAlertAudio(){if(joinAlertAudio){return joinAlertAudio}joinAlertAudio=new Audio(chrome.runtime.getURL("assets/autojoin.mp3"));joinAlertAudio.preload="auto";return joinAlertAudio}function ensureCustomJoinAlertAudio(){if(!customNotificationSoundData){customJoinAlertAudio=null;return null}if(customJoinAlertAudio?.src===customNotificationSoundData){return customJoinAlertAudio}customJoinAlertAudio=new Audio(customNotificationSoundData);customJoinAlertAudio.preload="auto";return customJoinAlertAudio}function tryPlayAudio(e,a){if(!e){return false}try{e.currentTime=0;const n=e.play();if(n&&typeof n.catch==="function"){n.catch(()=>{a?.()})}return true}catch(n){a?.();return false}}function playJoinAlert(){if(hasCustomNotificationSound&&tryPlayAudio(ensureCustomJoinAlertAudio(),()=>{tryPlayAudio(ensureJoinAlertAudio())})){return}try{tryPlayAudio(ensureJoinAlertAudio())}catch(e){}}async function disableAutoJoin(){settings={...settings,enabled:false,searchStartedAt:null};await chrome.storage.local.set({[STORAGE_KEY]:settings})}async function requestJoin(e){pendingJoin={gameID:e.gameID,startedAt:Date.now()};lobbyCooldowns.set(e.gameID,Date.now()+LOBBY_RETRY_COOLDOWN_MS);if(settings.joinNotification){try{await chrome.runtime.sendMessage({type:"SHOW_JOIN_NOTIFICATION",gameID:e.gameID,text:t("Lobby found — joining!")})}catch(a){}}if(!settings.keepAutoJoinAfterMatch){await disableAutoJoin()}if(settings.joinNotification){playJoinAlert()}window.postMessage({source:BRIDGE_SOURCE_EXTENSION,type:"JOIN_PUBLIC_LOBBY",payload:{gameID:e.gameID,publicLobbyInfo:e}},"*")}function tryAutoJoin(){resetPendingJoinIfExpired();if(!settings.enabled||!latestLobbySnapshot||pendingJoin||isOccupied()){return}const e=findMatchingLobby(latestLobbySnapshot);if(!e){return}requestJoin(e).catch(a=>{pendingJoin=null;console.error("Failed to auto-join matching lobby:",a)})}function pollAutoJoinResume(){const e=isOccupied();if(wasOccupied&&!e&&settings.enabled&&settings.keepAutoJoinAfterMatch){settings={...settings,searchStartedAt:Date.now()};chrome.storage.local.set({[STORAGE_KEY]:settings}).catch(()=>{});resetLobbyForecastTracking();tryAutoJoin()}wasOccupied=e}function handleBridgeMessage(e){if(e.source!==window){return}const a=e.data;if(!a||a.source!==BRIDGE_SOURCE_PAGE){return}if(a.type==="PUBLIC_LOBBIES_UPDATE"){latestLobbySnapshot=a.payload;try{updateLobbyForecast(latestLobbySnapshot);tryAutoJoin()}catch(n){if(isExtensionContextInvalidatedError(n)){return}throw n}return}if(a.type==="SELECTIVE_TRADE_POLICY_AVAILABILITY"){updateAutoCancelDeniedTradesAvailability(a.payload?.available);return}if(a.type==="CHEATS_AVAILABILITY"){updateCheatsAvailability(a.payload?.available);return}}async function handleStorageChange(e,a){if(a!=="local"||!e[STORAGE_KEY]){return}const n=settings.language;const o=createForecastFilterSignature();settings=normalizeSettings(e[STORAGE_KEY].newValue);if(settings.language!==n){await loadContentTranslations();document.getElementById(FLOATING_HELPERS_PANEL_ID)?.remove();document.getElementById(FLOATING_AUTOJOIN_PANEL_ID)?.remove()}selectiveTradePolicyAvailable=Boolean(settings.autoCancelDeniedTradesAvailable);cheatsAvailable=Boolean(settings.cheatsAvailable);syncHelpers();syncFloatingHelpersPanel();syncFloatingAutoJoinPanel();if(!settings.enabled){pendingJoin=null}const i=createForecastFilterSignature();if(i!==o){lobbyForecastFilterSignature=i;lobbyForecastRecentMatchOutcomes=[];resetLobbyForecastTracking(latestLobbySnapshot?flattenLobbies(latestLobbySnapshot).map(({lobby:l})=>String(l?.gameID||"")).filter(Boolean):[]);persistLobbyForecast(true)}tryAutoJoin()}async function init(){window.addEventListener("message",handleBridgeMessage);chrome.storage.onChanged.addListener(handleStorageChange);chrome.storage.onChanged.addListener(a=>{if("joinNotificationSoundData"in a){customNotificationSoundData=typeof a.joinNotificationSoundData.newValue==="string"?a.joinNotificationSoundData.newValue:null;hasCustomNotificationSound=Boolean(customNotificationSoundData);if(!hasCustomNotificationSound){customJoinAlertAudio=null}}});await loadSettings();if(settings.enabled&&settings.keepAutoJoinAfterMatch){settings={...settings,searchStartedAt:Date.now()};await chrome.storage.local.set({[STORAGE_KEY]:settings})}lobbyForecastFilterSignature=createForecastFilterSignature();resetLobbyForecastTracking();persistLobbyForecast(true);const e=await chrome.storage.local.get("joinNotificationSoundData");customNotificationSoundData=typeof e.joinNotificationSoundData==="string"?e.joinNotificationSoundData:null;hasCustomNotificationSound=Boolean(customNotificationSoundData);window.addEventListener("resize",()=>{const a=document.getElementById(FLOATING_HELPERS_PANEL_ID);if(a){positionFloatingHelpersPanel(a)}const n=document.getElementById(FLOATING_AUTOJOIN_PANEL_ID);if(n){positionFloatingAutoJoinPanel(n)}});ensureJoinAlertAudio();ensureCustomJoinAlertAudio();tryAutoJoin();window.setInterval(resetPendingJoinIfExpired,1e3);window.setInterval(pollAutoJoinResume,1e3)}init().catch(e=>{console.error("OpenFront Auto Join failed to initialize:",e)}); } catch (e) { console.error("[ofh] lobby section error:", e); } })(); ;(function(){ try { const PAGE_SOURCE="openfront-autojoin-page";const EXTENSION_SOURCE="openfront-autojoin-extension";const BOT_MARKER_CONTAINER_ID="openfront-helper-bot-marker-layer";const BOT_MARKER_STYLE_ID="openfront-helper-bot-marker-styles";const ALLY_MARKER_CONTAINER_ID="openfront-helper-ally-marker-layer";const ALLY_MARKER_STYLE_ID="openfront-helper-ally-marker-styles";const ALLIANCE_REQUEST_PANEL_ID="openfront-helper-alliance-request-panel";const ALLIANCE_REQUEST_PANEL_STYLE_ID="openfront-helper-alliance-request-panel-styles";const ALLIANCE_FOCUS_FLASH_CONTAINER_ID="openfront-helper-alliance-focus-flash-layer";const ALLIANCE_FOCUS_FLASH_STYLE_ID="openfront-helper-alliance-focus-flash-styles";const ALLIANCE_FOCUS_FLASH_CANVAS_CLASS="openfront-helper-alliance-focus-flash-canvas";const ECONOMY_HEATMAP_CONTAINER_ID="openfront-helper-economy-heatmap-layer";const ECONOMY_HEATMAP_STYLE_ID="openfront-helper-economy-heatmap-styles";const ECONOMY_HEATMAP_CANVAS_CLASS="openfront-helper-economy-heatmap-canvas";const EXPORT_PARTNER_HEATMAP_CONTAINER_ID="openfront-helper-export-partner-heatmap-layer";const EXPORT_PARTNER_HEATMAP_STYLE_ID="openfront-helper-export-partner-heatmap-styles";const EXPORT_PARTNER_HEATMAP_CANVAS_CLASS="openfront-helper-export-partner-heatmap-canvas";const HELPER_STATS_CONTAINER_ID="openfront-helper-stats-container";const HELPER_STATS_STYLE_ID="openfront-helper-stats-container-styles";const GOLD_PER_MINUTE_BADGE_ID="openfront-helper-gpm-badge";const GOLD_PER_MINUTE_STYLE_ID="openfront-helper-gpm-styles";const TEAM_GOLD_PER_MINUTE_BADGE_ID="openfront-helper-team-gpm-badge";const TEAM_GOLD_PER_MINUTE_STYLE_ID="openfront-helper-team-gpm-styles";const TOP_GOLD_PER_MINUTE_BADGE_ID="openfront-helper-top-gpm-badge";const TOP_GOLD_PER_MINUTE_STYLE_ID="openfront-helper-top-gpm-styles";const HELPER_STATS_POS_KEY="openfront-helper-stats-container-pos";const TOP_GOLD_PER_MINUTE_POS_KEY="openfront-helper-top-gpm-pos";const TRADE_BALANCE_BADGE_ID="openfront-helper-trade-balance-badge";const TRADE_BALANCE_STYLE_ID="openfront-helper-trade-balance-styles";const NUKE_LANDING_CONTAINER_ID="openfront-helper-nuke-landing-layer";const NUKE_LANDING_STYLE_ID="openfront-helper-nuke-landing-styles";const BOAT_LANDING_CONTAINER_ID="openfront-helper-boat-landing-layer";const BOAT_LANDING_STYLE_ID="openfront-helper-boat-landing-styles";const BOAT_PANEL_ID="openfront-helper-boat-panel";const BOAT_PANEL_STYLE_ID="openfront-helper-boat-panel-styles";const BOAT_ALERT_CONTAINER_ID="openfront-helper-boat-alert-layer";const BOAT_ALERT_STYLE_ID="openfront-helper-boat-alert-styles";const NUKE_SUGGESTION_CONTAINER_ID="openfront-helper-nuke-suggestion-layer";const NUKE_SUGGESTION_STYLE_ID="openfront-helper-nuke-suggestion-styles";const NUKE_SUGGESTION_REFRESH_MS=1800;const GOLD_PER_MINUTE_SAMPLE_MS=1e3;const GOLD_PER_MINUTE_RENDER_MS=250;const GOLD_PER_MINUTE_WINDOW_MS=6e4;const ECONOMY_HEATMAP_DRAW_MS=16;const EXPORT_PARTNER_HEATMAP_DRAW_MS=16;const EXPORT_PARTNER_HEATMAP_SOURCE_CACHE_MS=500;const ECONOMY_HEATMAP_DATA_REFRESH_MS=1e3;const TRADE_BALANCE_RENDER_MS=250;const ECONOMY_HEATMAP_VIEWPORT_PADDING=120;const ECONOMY_HEATMAP_REVENUE_WINDOW_MS=18e4;const HEATMAP_REFERENCE_ZOOM=1.8;const TEAM_COLORS={Red:"#eb3333",Blue:"#2962ff",Teal:"#2bd4bd",Purple:"#9234ea",Yellow:"#e7b008",Orange:"#f97415",Green:"#41be52",Bot:"#d1cdc7",Humans:"#2962ff",Nations:"#eb3333"};let botMarkersEnabled=false;let botMarkerAnimationFrame=null;let allyMarkersEnabled=false;let allyMarkerAnimationFrame=null;let allianceRequestsPanelEnabled=false;let allianceRequestsPanelAnimationFrame=null;let allianceFocusFlashAnimationFrame=null;let goldPerMinuteEnabled=false;let goldPerMinuteInterval=null;let goldPerMinuteAnimationFrame=null;let lastGoldPerMinuteSampleAt=0;let lastGoldPerMinuteRenderAt=0;let goldPerMinuteRenderSignature="";let lastProcessedIncomingGoldTransferTick=null;let teamGoldPerMinuteEnabled=false;let teamGoldPerMinuteAnimationFrame=null;let lastTeamGoldPerMinuteRenderAt=0;let teamGoldPerMinuteRenderSignature="";let topGoldPerMinuteEnabled=false;let topGoldPerMinuteAnimationFrame=null;let lastTopGoldPerMinuteRenderAt=0;let topGoldPerMinuteRenderSignature="";let tradeBalancesEnabled=false;let tradeBalanceAnimationFrame=null;let lastProcessedTradeBalanceTick=null;let lastTradeBalanceRenderAt=0;let tradeBalanceRenderSignature="";let nukePredictionEnabled=false;let nukeLandingAnimationFrame=null;let boatPredictionEnabled=false;let boatPredictionAlwaysOwnRoutes=false;let boatPredictionAlwaysTeamRoutes=false;let boatPredictionAlwaysAllyRoutes=false;let boatPredictionAlwaysEnemyRoutes=false;let boatLandingAnimationFrame=null;let boatPanelOpen=false;let boatPanelFocusedId=null;let boatWarnIncoming=false;let nukeSuggestionsEnabled=false;let nukeSuggestionAnimationFrame=null;let autoNukeEnabled=false;let autoNukePatchTimeout=null;let lastNukeSuggestionComputedAt=0;let lastNukeSuggestionSignature="";let lastNukeSuggestionSignatureCheckAt=0;let currentNukeSuggestionResults=[];let nukeSuggestionTileComputeKey=null;let economyHeatmapEnabled=false;let economyHeatmapAnimationFrame=null;let lastEconomyHeatmapDrawAt=0;let lastEconomyHeatmapDataAt=0;let economyHeatmapDataGame=null;let economyHeatmapSources=[];let economyHeatmapIntensity=1;let exportPartnerHeatmapEnabled=false;let exportPartnerHeatmapAnimationFrame=null;let lastExportPartnerHeatmapDrawAt=0;let exportPartnerHeatmapSourceCacheGame=null;let exportPartnerHeatmapSourceCachePlayerId=null;let exportPartnerHeatmapSourceCacheAt=0;let exportPartnerHeatmapSourceCache=[];let selectiveTradePolicyEnabled=false;let selectiveTradePolicyNeedsEmbargoSync=false;let selectiveTradePolicyMyPlayerId=null;let lastSelectiveTradePolicyRequestAt=null;let lastReportedSelectiveTradePolicyAvailability=null;let lastReportedCheatsAvailability=null;let lastOpenFrontGameContext=null;const goldTrackers=new Map;const incomingGoldTransfers=new Map;const tradeBalanceTrackers=new Map;const exportPartnerSourceTrackers=new Map;const economyRevenueSourceTrackers=new Map;const factoryPortSpendTrackers=new Map;const factoryPortUnitTrackers=new Map;const trainTradeTrackers=new Map;const tradeShipSourceTrackers=new Map;const autoNukeOriginalRootSubMenus=new Map;const autoNukeOriginalAttackSubMenus=new Map;const selectiveTradePolicyAllowedPartnerIds=new Set;const originalPlayerHasEmbargoMethods=new WeakMap;const allianceRequestsPanelEvents=[];const capturedAllianceRequestEvents=new WeakSet;let allianceRequestsPanelRenderSignature="";const ALLIANCE_REQUEST_PANEL_POS_KEY="openfront-helper-alliance-request-panel-pos";const ALLIANCE_REQUEST_PANEL_AUTORENEW_KEY="openfront-helper-alliance-request-autorenew";let allianceRequestsAutoRenewEnabled=(()=>{try{return window.localStorage.getItem(ALLIANCE_REQUEST_PANEL_AUTORENEW_KEY)==="1"}catch(t){return false}})();const autoRenewedAllianceEvents=new WeakSet;function postToExtension(t,e){window.postMessage({source:PAGE_SOURCE,type:t,payload:e},"*")}document.addEventListener("public-lobbies-update",t=>{postToExtension("PUBLIC_LOBBIES_UPDATE",t.detail?.payload??null)});function toFiniteNumber(t,e=0){const n=typeof t==="bigint"?Number(t):Number(t);return Number.isFinite(n)?n:e}function getUnitLevel(t){return Math.max(1,toFiniteNumber(t?.level?.(),1))}function canStationTradeWith(t,e){if(!t||!e){return false}if(getPlayerSmallId(t)===getPlayerSmallId(e)){return true}return canPlayersTrade(t,e)}function getHeatmapTypePriority(t){if(t==="Factory"){return 4}if(t==="Port"){return 3}if(t==="City"){return 2}return 1}const _economicSourceIndexes=new WeakMap;function _getEconomicSourceIndex(t){let e=_economicSourceIndexes.get(t);if(e){return e}e=new Map;for(const n of t){if(n?.tile!=null){e.set(String(n.tile),n)}}_economicSourceIndexes.set(t,e);return e}function addEconomicSource(t,e,n,o="Industry"){if(e==null||!Number.isFinite(n)||n<=0){return}const r=_getEconomicSourceIndex(t);const i=String(e);const a=r.get(i);if(a){a.weight+=n;if(getHeatmapTypePriority(o)>getHeatmapTypePriority(a.type)){a.type=o}return}const s={tile:e,weight:n,type:o};r.set(i,s);t.push(s)}const HELPER_TICK_INTERVAL_MS=250;const _helperTickListeners=new Set;let _helperTickIntervalId=null;function _runHelperTick(){for(const t of _helperTickListeners){try{t()}catch(e){console.error("OpenFront helper tick listener failed:",e)}}}function _ensureHelperTickRunning(){if(_helperTickIntervalId!==null){return}_helperTickIntervalId=window.setInterval(_runHelperTick,HELPER_TICK_INTERVAL_MS)}function registerHelperTickListener(t){if(typeof t!=="function"){return}_helperTickListeners.add(t);_ensureHelperTickRunning();try{t()}catch(e){console.error("OpenFront helper tick listener (initial) failed:",e)}}function unregisterHelperTickListener(t){_helperTickListeners.delete(t);if(_helperTickListeners.size===0&&_helperTickIntervalId!==null){window.clearInterval(_helperTickIntervalId);_helperTickIntervalId=null}}function normalizeEconomyHeatmapIntensity(t){const e=Number(t);if(!Number.isFinite(e)){return 1}return Math.max(0,Math.min(2,Math.round(e)))}function escapeCssIdentifier(t){if(globalThis.CSS?.escape){return CSS.escape(t)}return t.replace(/["\\]/g,"\\$&")}function getPlayerSmallId(t,e=0){try{return Number(t?.smallID?.()??t?.data?.smallID??e)}catch(n){return Number(e)}}function getPlayerDisplayName(t){try{return String(t?.displayName?.()??t?.name?.()??t?.data?.displayName??t?.data?.name??"Unknown")}catch(e){return"Unknown"}}function getPlayerRelationToMyPlayer(t,e){let n=null;try{n=t?.myPlayer?.();if(!e?.isPlayer?.()||!n?.isPlayer?.()){return null}}catch(i){return null}const o=getPlayerSmallId(e,NaN);const r=getPlayerSmallId(n,NaN);if(Number.isFinite(o)&&o===r){return"self"}try{if(e.isFriendly?.(n)||n.isFriendly?.(e)){return"ally"}}catch(i){return"enemy"}return"enemy"}function getPlayerTeamName(t){try{const e=t?.team?.();return e==null?null:String(e)}catch(e){return null}}function _playerTerritoryHex(t){try{const e=t?.territoryColor?.()?.toHex?.();if(typeof e==="string"&&/^#[0-9a-fA-F]{3,8}$/.test(e)){return e}}catch(e){}return null}let _teamRepColorGame=null;let _teamRepColorTick=-1;const _teamRepColors=new Map;const _teamRepSmallIds=new Map;function _ensureTeamRepColors(t){let e=Number.NaN;try{e=Number(t?.ticks?.())}catch(n){e=Number.NaN}if(t===_teamRepColorGame&&Number.isFinite(e)&&e===_teamRepColorTick){return _teamRepColors}_teamRepColorGame=t;_teamRepColorTick=Number.isFinite(e)?e:-1;_teamRepColors.clear();_teamRepSmallIds.clear();for(const n of getCachedPlayerViews(t)){let o=false;try{o=Boolean(n?.isAlive?.())}catch(l){o=false}if(!o){continue}const r=getPlayerTeamName(n);if(!r||r==="Bot"){continue}const i=getPlayerSmallId(n,Number.MAX_SAFE_INTEGER);const a=_teamRepSmallIds.get(r);if(a!==void 0&&a<=i){continue}const s=_playerTerritoryHex(n);if(s){_teamRepColors.set(r,s);_teamRepSmallIds.set(r,i)}}return _teamRepColors}function getPlayerColor(t,e=null){const n=_playerTerritoryHex(t);if(n){return n}const o=getPlayerTeamName(t);return o?getTeamColor(o,e):null}let _cachedTeamColorGame=null;const _cachedTeamColors=new Map;let _teamColorsLowerCaseIndex=null;function _getTeamColorsLowerCaseIndex(){if(_teamColorsLowerCaseIndex!==null){return _teamColorsLowerCaseIndex}_teamColorsLowerCaseIndex=new Map;for(const[t,e]of Object.entries(TEAM_COLORS)){_teamColorsLowerCaseIndex.set(t.toLowerCase(),e)}return _teamColorsLowerCaseIndex}function _hslToHex(t,e,n){const o=e/100;const r=n/100;const i=(1-Math.abs(2*r-1))*o;const a=i*(1-Math.abs(t/60%2-1));const s=r-i/2;let l=0;let c=0;let u=0;if(t<60){l=i;c=a}else if(t<120){l=a;c=i}else if(t<180){c=i;u=a}else if(t<240){c=a;u=i}else if(t<300){l=a;u=i}else{l=i;u=a}const d=f=>{const m=Math.round((f+s)*255);return Math.max(0,Math.min(255,m)).toString(16).padStart(2,"0")};return`#${d(l)}${d(c)}${d(u)}`}function _generateTeamColor(t){const e=String(t??"");const n=/(\d+)\s*$/.exec(e);if(n){const s=Number(n[1]);return _hslToHex(Math.floor(s*137.508%360),68,60)}let o=2166136261;for(let s=0;s>>0;o=Math.imul(o,16777619)>>>0}const r=o%360;const i=62+(o>>>9)%18;const a=56+(o>>>17)%12;return _hslToHex(r,i,a)}function _computeTeamColor(t){const e=String(t??"");const n=_getTeamColorsLowerCaseIndex().get(e.trim().toLowerCase());if(n){return n}if(!e){return"#4ade80"}return _generateTeamColor(e)}function getTeamColor(t,e=null){if(e){const i=_ensureTeamRepColors(e).get(String(t??""));if(i){return i}}if(e!==_cachedTeamColorGame){_cachedTeamColorGame=e;_cachedTeamColors.clear()}const n=String(t??"");const o=_cachedTeamColors.get(n);if(o!==void 0){return o}const r=_computeTeamColor(t);_cachedTeamColors.set(n,r);return r}function getTeamColorBackground(t,e=null){const n=getTeamColor(t,e);return`${n}2b`}function isNationBotPlayer(t){try{const e=t?.type?.()??t?.data?.playerType;return e==="NATION"}catch(e){return false}}function getPlayerMarkerId(t,e){try{return String(t?.id?.()??t?.smallID?.()??t?.data?.id??t?.displayName?.()??e)}catch(n){return String(e)}}function getPlayerGoldNumber(t){try{const e=t?.gold?.();if(typeof e==="bigint"){return Number(e)}return Number(e)}catch(e){return NaN}}let _cachedInfoOverlayEl=null;function getHoveredPlayerInfoOverlay(){if(!_cachedInfoOverlayEl?.isConnected){_cachedInfoOverlayEl=document.querySelector("player-info-overlay")??null}const t=_cachedInfoOverlayEl;if(!t?.player){return null}const e=t._isInfoVisible??t.isInfoVisible;if(e===false){return null}return t}function getPlayerInfoPanelRect(t){const e=t.querySelector('[class*="bg-gray-800"]')??t.querySelector('[class*="backdrop-blur"]')??t;const n=e.getBoundingClientRect?.();if(n&&(n.width>0||n.height>0)){return n}return null}function normalizeTradeName(t){return String(t??"").trim().toLowerCase().replace(/\s+/g," ")}function findPlayerByTradeName(t,e){const n=normalizeTradeName(e);if(!n){return null}return t.find(o=>normalizeTradeName(getPlayerDisplayName(o))===n)??t.find(o=>normalizeTradeName(o?.name?.())===n)??null}let _cachedPlayerViewsGame=null;let _cachedPlayerViewsTick=-1;let _cachedPlayerViewsArray=[];function getCachedPlayerViews(t){if(!t){return[]}let e=Number.NaN;try{e=Number(t.ticks?.())}catch(n){e=Number.NaN}if(t===_cachedPlayerViewsGame&&Number.isFinite(e)&&e===_cachedPlayerViewsTick&&_cachedPlayerViewsArray.length>0){return _cachedPlayerViewsArray}_cachedPlayerViewsGame=t;_cachedPlayerViewsTick=Number.isFinite(e)?e:-1;try{_cachedPlayerViewsArray=Array.from(t.playerViews?.()||[])}catch(n){_cachedPlayerViewsArray=[]}return _cachedPlayerViewsArray}function mixHashNumber(t,e){let n=t>>>0;let o=e|0;if(o<0){o=o+4294967296>>>0}n=(n^o&255)>>>0;n=n+((n<<1)+(n<<4)+(n<<7)+(n<<8)+(n<<24))>>>0;n=(n^o>>>8&255)>>>0;n=n+((n<<1)+(n<<4)+(n<<7)+(n<<8)+(n<<24))>>>0;n=(n^o>>>16&255)>>>0;n=n+((n<<1)+(n<<4)+(n<<7)+(n<<8)+(n<<24))>>>0;n=(n^o>>>24&255)>>>0;n=n+((n<<1)+(n<<4)+(n<<7)+(n<<8)+(n<<24))>>>0;return n}function mixHashString(t,e){const n=String(e??"");let o=t>>>0;for(let r=0;r>>0;o=o+((o<<1)+(o<<4)+(o<<7)+(o<<8)+(o<<24))>>>0}return o}function isObject(t){return t!==null&&typeof t==="object"}function isUsableOpenFrontGameContext(t,e){return Boolean(t?.playerViews&&e?.worldToScreenCoordinates)}function rememberOpenFrontGameContext(t,e){if(!isUsableOpenFrontGameContext(t,e)){return null}lastOpenFrontGameContext={game:t,transform:e};if(selectiveTradePolicyEnabled){syncSelectiveTradePolicyPatches(t);if(selectiveTradePolicyNeedsEmbargoSync){applySelectiveTradePolicy(t)}}return lastOpenFrontGameContext}function findOpenFrontGameContextInDom(){const t=[];const e=[];for(const n of document.querySelectorAll("*")){if(n?.game){t.push(n.game)}if(n?.g){t.push(n.g)}if(n?.transformHandler){e.push(n.transformHandler)}if(n?.transform){e.push(n.transform)}}for(const n of t){for(const o of e){const r=rememberOpenFrontGameContext(n,o);if(r){return r}}}return null}function isTeamGame(t){const e=String(t?.config?.().gameMode??t?.config?.().gameMode?.()??"").trim().toLowerCase();if(e==="team"){return true}if(e==="free for all"||e==="ffa"){return false}const n=Array.from(t?.playerViews?.()||[]).filter(o=>o?.isAlive?.());return n.some(o=>{const r=getPlayerTeamName(o);return r&&r!=="Bot"})}function getGameConfig(t){try{const e=t?.config;const n=typeof e==="function"?e.call(t):e;return isObject(n)?n:null}catch(e){return null}}function getAliveHumanPlayers(t){return Array.from(t?.playerViews?.()||[]).filter(e=>e?.isAlive?.()&&isHumanPlayer(e))}function hasTruthyFlag(t,e){return e.some(n=>Boolean(t?.[n]))}function hasStringFieldMatching(t,e,n,o=[]){for(const r of e){const i=t?.[r];if(typeof i!=="string"){continue}const a=i.trim().toLowerCase();if(!a){continue}if(o.some(s=>a.includes(s))){continue}if(n.some(s=>a.includes(s))){return true}}return false}function isCustomGame(t){const e=getGameConfig(t);if(!e){return false}if(hasTruthyFlag(e,["isCustom","customGame","customLobby","isPrivate","privateGame","privateLobby"])){return true}return hasStringFieldMatching(e,["gameType","lobbyType","matchType","source","queueType","visibility"],["custom","private","friend"],["public","ranked","matchmaking"])}function isSoloGame(t){return getAliveHumanPlayers(t).length<=1}function isCheatsAvailable(t){if(!t){return false}return isSoloGame(t)||isCustomGame(t)}function reportCheatsAvailability(t=null){const e=isCheatsAvailable(t);if(e===lastReportedCheatsAvailability){return}lastReportedCheatsAvailability=e;postToExtension("CHEATS_AVAILABILITY",{available:e})}function isHumanPlayer(t){try{const e=t?.type?.()??t?.data?.playerType;return e==="HUMAN"}catch(e){return false}}function isSelectiveTradePolicyAvailable(t){const e=t?.myPlayer?.();return Boolean(t&&isTeamGame(t)&&e?.isPlayer?.()&&isHumanPlayer(e))}function reportSelectiveTradePolicyAvailability(t=null){const e=isSelectiveTradePolicyAvailable(t);if(e===lastReportedSelectiveTradePolicyAvailability){return}lastReportedSelectiveTradePolicyAvailability=e;postToExtension("SELECTIVE_TRADE_POLICY_AVAILABILITY",{available:e})}function isAllowedTradePartnerForMyPlayer(t,e,n){if(!t||!e){return false}if(!isHumanPlayer(t)||!isHumanPlayer(e)){return true}const o=getPlayerSmallId(t);const r=getPlayerSmallId(e);if(!Number.isFinite(o)||!Number.isFinite(r)){return false}if(o===r){return true}try{if(isTeamGame(n)){return Boolean(t?.isOnSameTeam?.(e))}return Boolean(t?.isAlliedWith?.(e))}catch(i){return false}}function rebuildSelectiveTradePolicyCache(t,e){selectiveTradePolicyAllowedPartnerIds.clear();selectiveTradePolicyMyPlayerId=null;const n=getPlayerSmallId(e);if(!Number.isFinite(n)){return}selectiveTradePolicyMyPlayerId=n;selectiveTradePolicyAllowedPartnerIds.add(n);for(const o of Array.from(t?.playerViews?.()||[])){if(!o?.isAlive?.()||!isHumanPlayer(o)){continue}const r=getPlayerSmallId(o);if(!Number.isFinite(r)||r===n){continue}if(isAllowedTradePartnerForMyPlayer(e,o,t)){selectiveTradePolicyAllowedPartnerIds.add(r)}}}function isBlockedBySelectiveTradePolicy(t,e,n=null){if(!selectiveTradePolicyEnabled||!t||!e){return false}if(!Number.isFinite(selectiveTradePolicyMyPlayerId)||selectiveTradePolicyAllowedPartnerIds.size===0){return false}if(!isHumanPlayer(t)||!isHumanPlayer(e)){return false}const o=getPlayerSmallId(t);const r=getPlayerSmallId(e);if(!Number.isFinite(o)||!Number.isFinite(r)){return false}if(o===selectiveTradePolicyMyPlayerId){return!selectiveTradePolicyAllowedPartnerIds.has(r)}if(r===selectiveTradePolicyMyPlayerId){return!selectiveTradePolicyAllowedPartnerIds.has(o)}return false}function ensureSelectiveTradePolicyPatchForPlayer(t){if(!t||typeof t?.hasEmbargo!=="function"){return}if(originalPlayerHasEmbargoMethods.has(t)){return}const e=t.hasEmbargo.bind(t);originalPlayerHasEmbargoMethods.set(t,e);t.hasEmbargo=function n(o){if(isBlockedBySelectiveTradePolicy(t,o)){return true}try{return Boolean(e(o))}catch(r){return false}}}function syncSelectiveTradePolicyPatches(t){for(const e of Array.from(t?.playerViews?.()||[])){ensureSelectiveTradePolicyPatchForPlayer(e)}}function callEmbargoMethod(t,e,n){const o=t?.[e];if(typeof o!=="function"){return false}try{o.apply(t,n);return true}catch(r){return false}}function trySetEmbargo(t,e,n,o){const r=n?[[e],[e,true],[getPlayerSmallId(e)],[getPlayerSmallId(e),true]]:[[e,false],[e],[getPlayerSmallId(e),false],[getPlayerSmallId(e)]];const i=n?["setEmbargo","embargo","embargoPlayer","addEmbargo","setTradeEmbargo"]:["setEmbargo","removeEmbargo","clearEmbargo","unembargo","setTradeEmbargo"];for(const l of i){for(const c of r){if(callEmbargoMethod(t,l,c)){return true}}}const a=n?["setEmbargo","embargoPlayer","embargo"]:["setEmbargo","removeEmbargo","clearEmbargo"];const s=n?[[t,e],[getPlayerSmallId(t),getPlayerSmallId(e)],[t,e,true]]:[[t,e,false],[getPlayerSmallId(t),getPlayerSmallId(e),false],[t,e]];for(const l of a){for(const c of s){if(callEmbargoMethod(o,l,c)){return true}}}return false}function applySelectiveTradePolicy(t=null){const e=t?{game:t}:getOpenFrontGameContext();const n=e?.game??t;if(!isSelectiveTradePolicyAvailable(n)){selectiveTradePolicyEnabled=false;selectiveTradePolicyNeedsEmbargoSync=false;selectiveTradePolicyAllowedPartnerIds.clear();selectiveTradePolicyMyPlayerId=null;reportSelectiveTradePolicyAvailability(n);return}selectiveTradePolicyEnabled=true;selectiveTradePolicyNeedsEmbargoSync=true;const o=n?.myPlayer?.();reportSelectiveTradePolicyAvailability(n);rebuildSelectiveTradePolicyCache(n,o);syncSelectiveTradePolicyPatches(n);for(const r of Array.from(n?.playerViews?.()||[])){if(!r?.isAlive?.()||!isHumanPlayer(r)){continue}const i=getPlayerSmallId(r);const a=getPlayerSmallId(o);if(!Number.isFinite(i)||!Number.isFinite(a)||i===a){continue}const s=isAllowedTradePartnerForMyPlayer(o,r,n);trySetEmbargo(o,r,!s,n);trySetEmbargo(r,o,!s,n)}selectiveTradePolicyNeedsEmbargoSync=false}function clearSelectiveTradePolicy(t=null){const e=t?{game:t}:getOpenFrontGameContext();const n=e?.game??t;const o=n?.myPlayer?.();selectiveTradePolicyEnabled=false;selectiveTradePolicyNeedsEmbargoSync=false;selectiveTradePolicyAllowedPartnerIds.clear();selectiveTradePolicyMyPlayerId=null;reportSelectiveTradePolicyAvailability(n);if(!n||!o?.isPlayer?.()||!isHumanPlayer(o)){return}for(const r of Array.from(n?.playerViews?.()||[])){if(!r?.isAlive?.()||!isHumanPlayer(r)){continue}const i=getPlayerSmallId(r);const a=getPlayerSmallId(o);if(!Number.isFinite(i)||!Number.isFinite(a)||i===a){continue}trySetEmbargo(o,r,false,n);trySetEmbargo(r,o,false,n)}}function setSelectiveTradePolicyEnabled(t,e=null){if(t){applySelectiveTradePolicy(e);return}clearSelectiveTradePolicy(e)}const _OPEN_FRONT_CONTEXT_REFRESH_MS=1e3;let _lastOpenFrontContextDiscoveryAt=0;function _discoverOpenFrontGameContext(){const t=document.querySelector("player-info-overlay");const e=document.querySelector("player-panel");const n=document.querySelector("emoji-table");const o=document.querySelector("build-menu");const r=document.querySelector("game-left-sidebar");const i=document.querySelector("game-right-sidebar");const a=document.querySelector("leader-board");const s=document.querySelector("team-stats");const l=document.querySelector("spawn-timer");const c=document.querySelector("unit-display");const u=document.querySelector("control-panel");const d=[t?.game,e?.g,n?.game,a?.game,s?.game,r?.game,i?.game,o?.game,l?.game,c?.game,u?.game];const f=[t?.transform,n?.transformHandler,o?.transformHandler,l?.transformHandler,c?.transformHandler,u?.transformHandler];for(const p of d){for(const h of f){const y=rememberOpenFrontGameContext(p,h);if(y){return y}}}const m=findOpenFrontGameContextInDom();if(m){return m}return null}function getOpenFrontGameContext(){const t=performance.now();if(t-_lastOpenFrontContextDiscoveryAt<_OPEN_FRONT_CONTEXT_REFRESH_MS&&isUsableOpenFrontGameContext(lastOpenFrontGameContext?.game,lastOpenFrontGameContext?.transform)){return lastOpenFrontGameContext}_lastOpenFrontContextDiscoveryAt=t;const e=_discoverOpenFrontGameContext();if(e){return e}if(isUsableOpenFrontGameContext(lastOpenFrontGameContext?.game,lastOpenFrontGameContext?.transform)){return lastOpenFrontGameContext}return null}function refreshSelectiveTradePolicyAvailability(){const t=getOpenFrontGameContext();reportSelectiveTradePolicyAvailability(t?.game??null)}function refreshCheatsAvailability(){const t=getOpenFrontGameContext();reportCheatsAvailability(t?.game??null)}function ensureAllyMarkerStyles(){if(document.getElementById(ALLY_MARKER_STYLE_ID)){return}const t=document.createElement("style");t.id=ALLY_MARKER_STYLE_ID;t.textContent=` #${ALLY_MARKER_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483647; pointer-events: none; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-lines { position: fixed; inset: 0; width: 100vw; height: 100vh; overflow: visible; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-line { stroke: rgba(34, 197, 94, 0.58); stroke-width: 2.5; stroke-linecap: round; filter: drop-shadow(0 0 5px rgba(34, 197, 94, 0.72)); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot { position: fixed; display: flex; flex-direction: column; align-items: center; gap: 4px; width: max-content; margin: -7.5px 0 0 -50px; color: #dcfce7; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 10px; font-weight: 900; line-height: 1; text-align: center; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.92); transform: translate(var(--ally-x), var(--ally-y)); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot::before { content: ""; width: 15px; height: 15px; border-radius: 50%; background: rgba(34, 197, 94, 0.72); box-shadow: 0 0 0 2px rgba(20, 83, 45, 0.38), 0 0 13px rgba(34, 197, 94, 0.78); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-time { display: grid; gap: 2px; min-width: 100px; padding: 4px 6px; border: 1px solid rgba(134, 239, 172, 0.34); border-radius: 8px; background: rgba(5, 46, 22, 0.78); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.28); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-time span { white-space: nowrap; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-label { color: rgba(220, 252, 231, 0.82); font-size: 9px; letter-spacing: 0; text-transform: uppercase; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-line.openfront-helper-ally-warning { stroke: rgba(249, 115, 22, 0.68); filter: drop-shadow(0 0 5px rgba(249, 115, 22, 0.76)); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-warning { color: #ffedd5; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-warning::before { background: rgba(249, 115, 22, 0.76); box-shadow: 0 0 0 2px rgba(154, 52, 18, 0.4), 0 0 13px rgba(249, 115, 22, 0.82); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-warning .openfront-helper-ally-time { border-color: rgba(253, 186, 116, 0.42); background: rgba(67, 20, 7, 0.82); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-warning .openfront-helper-ally-label { color: rgba(255, 237, 213, 0.84); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-line.openfront-helper-ally-critical { stroke: rgba(239, 68, 68, 0.72); filter: drop-shadow(0 0 5px rgba(239, 68, 68, 0.82)); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-critical { color: #fee2e2; } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-critical::before { background: rgba(239, 68, 68, 0.82); box-shadow: 0 0 0 2px rgba(127, 29, 29, 0.44), 0 0 13px rgba(239, 68, 68, 0.86); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-critical .openfront-helper-ally-time { border-color: rgba(252, 165, 165, 0.46); background: rgba(69, 10, 10, 0.84); } #${ALLY_MARKER_CONTAINER_ID} .openfront-helper-ally-dot.openfront-helper-ally-critical .openfront-helper-ally-label { color: rgba(254, 226, 226, 0.86); } `;(document.head||document.documentElement).appendChild(t)}function ensureAllyMarkerContainer(){ensureAllyMarkerStyles();let t=document.getElementById(ALLY_MARKER_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=ALLY_MARKER_CONTAINER_ID;(document.body||document.documentElement).appendChild(t)}return t}function ensureAllyLineSvg(t){let e=t.querySelector(".openfront-helper-ally-lines");if(!e){e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.classList.add("openfront-helper-ally-lines");e.setAttribute("aria-hidden","true");t.prepend(e)}return e}function isAlliedPlayer(t,e){try{return t!==e&&Boolean(t?.isAlliedWith?.(e))}catch(n){return false}}function getAllianceView(t,e){try{return Array.from(t?.alliances?.()||[]).find(n=>n?.other===e?.id?.())}catch(n){return null}}function formatAllianceDuration(t){const e=Math.max(0,Math.floor(t));const n=Math.floor(e/60);const o=e%60;if(n>0){return`${n}m ${o}s`}return`${o}s`}function getAllianceUrgencyClass(t){if(!Number.isFinite(t)){return""}if(t<30){return"openfront-helper-ally-critical"}if(t<=60){return"openfront-helper-ally-warning"}return""}function getAllianceTimingText(t,e){const n=Number(t?.ticks?.());const o=Number(e?.expiresAt);if(!Number.isFinite(n)||!Number.isFinite(o)){return{remaining:"unknown left",urgencyClass:""}}const r=(o-n)/10;return{remaining:`${formatAllianceDuration(r)} left`,urgencyClass:getAllianceUrgencyClass(r)}}function syncAllyMarkers(){if(!allyMarkersEnabled){document.getElementById(ALLY_MARKER_CONTAINER_ID)?.remove();allyMarkerAnimationFrame=null;return}const t=ensureAllyMarkerContainer();const e=ensureAllyLineSvg(t);const n=getHoveredPlayerInfoOverlay();const o=n?.transform;if(!n?.game||!o){t.replaceChildren();allyMarkerAnimationFrame=requestAnimationFrame(syncAllyMarkers);return}const r=n.player?.nameLocation?.();if(!r){e.replaceChildren();t.replaceChildren(e);allyMarkerAnimationFrame=requestAnimationFrame(syncAllyMarkers);return}const i=o.worldToScreenCoordinates(r);if(!Number.isFinite(i?.x)||!Number.isFinite(i?.y)){e.replaceChildren();t.replaceChildren(e);allyMarkerAnimationFrame=requestAnimationFrame(syncAllyMarkers);return}const a=new Set;const s=Array.from(n.game.playerViews?.()||[]);for(let l=0;lwindow.innerWidth+30||d.y>window.innerHeight+30){continue}const f=getPlayerMarkerId(c,l);a.add(f);let m=e.querySelector(`.openfront-helper-ally-line[data-ally-id="${escapeCssIdentifier(f)}"]`);if(!m){m=document.createElementNS("http://www.w3.org/2000/svg","line");m.classList.add("openfront-helper-ally-line");m.dataset.allyId=f;e.appendChild(m)}m.setAttribute("x1",String(i.x));m.setAttribute("y1",String(i.y));m.setAttribute("x2",String(d.x));m.setAttribute("y2",String(d.y));let p=t.querySelector(`.openfront-helper-ally-dot[data-ally-id="${escapeCssIdentifier(f)}"]`);if(!p){p=document.createElement("div");p.className="openfront-helper-ally-dot";p.dataset.allyId=f;p.innerHTML=` Alliance `;t.appendChild(p)}const h=getAllianceView(n.player,c);const y=getAllianceTimingText(n.game,h);p.classList.remove("openfront-helper-ally-warning","openfront-helper-ally-critical");m.classList.remove("openfront-helper-ally-warning","openfront-helper-ally-critical");if(y.urgencyClass){p.classList.add(y.urgencyClass);m.classList.add(y.urgencyClass)}const g=p.querySelector(".openfront-helper-ally-remaining");if(g){g.textContent=y.remaining}p.style.setProperty("--ally-x",`${d.x}px`);p.style.setProperty("--ally-y",`${d.y}px`)}for(const l of t.querySelectorAll(".openfront-helper-ally-dot")){if(!a.has(l.dataset.allyId||"")){l.remove()}}for(const l of e.querySelectorAll(".openfront-helper-ally-line")){if(!a.has(l.dataset.allyId||"")){l.remove()}}allyMarkerAnimationFrame=requestAnimationFrame(syncAllyMarkers)}function setAllyMarkersEnabled(t){allyMarkersEnabled=Boolean(t);if(!allyMarkersEnabled){if(allyMarkerAnimationFrame!==null){cancelAnimationFrame(allyMarkerAnimationFrame)}allyMarkerAnimationFrame=null;document.getElementById(ALLY_MARKER_CONTAINER_ID)?.remove();return}if(allyMarkerAnimationFrame===null){syncAllyMarkers()}}const ALLIANCE_REQUEST_MESSAGE_TYPES=new Set([15,16]);const RENEW_ALLIANCE_MESSAGE_TYPES=new Set([22,24]);const ALLIANCE_PROMPT_MESSAGE_TYPES=new Set([...ALLIANCE_REQUEST_MESSAGE_TYPES,...RENEW_ALLIANCE_MESSAGE_TYPES]);function ensureAllianceRequestPanelStyles(){if(document.getElementById(ALLIANCE_REQUEST_PANEL_STYLE_ID)){return}const t=document.createElement("style");t.id=ALLIANCE_REQUEST_PANEL_STYLE_ID;t.textContent=` #${ALLIANCE_REQUEST_PANEL_ID} { position: fixed; right: max(14px, env(safe-area-inset-right, 0px)); top: 50%; z-index: 2147483647; display: none; width: min(258px, calc(100vw - 28px)); max-height: min(46vh, 420px); overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.95); color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 11px; font-weight: 700; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); pointer-events: auto; transform: translateY(-50%); } #${ALLIANCE_REQUEST_PANEL_ID}[data-visible="true"] { display: grid; grid-template-rows: auto minmax(0, 1fr); } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-header { display: flex; align-items: center; gap: 7px; padding: 6px 8px; border-bottom: 1px solid rgba(148, 163, 184, 0.18); cursor: grab; touch-action: none; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-header:active { cursor: grabbing; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-title { margin: 0; flex: 1; color: rgba(226, 232, 240, 0.85); font-size: 10px; font-weight: 900; text-transform: uppercase; letter-spacing: 0.4px; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-count { display: grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; background: rgba(148, 163, 184, 0.26); color: #f1f5f9; font-size: 10px; font-weight: 900; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-autorenew { display: inline-flex; align-items: center; gap: 3px; height: 20px; padding: 0 7px; border: 1px solid rgba(148, 163, 184, 0.3); border-radius: 999px; background: rgba(255, 255, 255, 0.05); color: #9ca3af; font: inherit; font-size: 10px; font-weight: 900; text-transform: uppercase; cursor: pointer; transition: background 120ms ease, color 120ms ease, border-color 120ms ease; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-autorenew:hover { background: rgba(255, 255, 255, 0.09); } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-autorenew[data-on="true"] { border-color: rgba(74, 222, 128, 0.5); background: rgba(34, 197, 94, 0.2); color: #86efac; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-list { display: grid; gap: 6px; min-height: 0; overflow: auto; padding: 7px; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-card { display: grid; gap: 6px; padding: 7px; border: 1px solid rgba(151, 181, 214, 0.18); border-radius: 8px; background: rgba(8, 31, 28, 0.82); } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-card[data-sender-type="nation"] { border-color: rgba(248, 113, 113, 0.34); background: linear-gradient(135deg, rgba(127, 29, 29, 0.2), transparent 54%), rgba(31, 24, 24, 0.84); } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-width: 0; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-sender-type { display: inline-flex; align-items: center; min-height: 18px; padding: 3px 6px; border: 1px solid rgba(248, 113, 113, 0.42); border-radius: 6px; background: rgba(69, 10, 10, 0.48); color: #fecaca; font-size: 9px; font-weight: 900; line-height: 1; text-transform: uppercase; white-space: nowrap; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-time { display: inline-flex; align-items: center; min-height: 18px; padding: 3px 6px; border: 1px solid rgba(125, 211, 252, 0.3); border-radius: 6px; background: rgba(14, 116, 144, 0.2); color: #bae6fd; font-size: 9px; font-weight: 900; line-height: 1; text-transform: uppercase; white-space: nowrap; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-time[data-urgency="soon"] { border-color: rgba(251, 191, 36, 0.42); background: rgba(146, 64, 14, 0.28); color: #fde68a; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-time[data-urgency="critical"] { border-color: rgba(248, 113, 113, 0.48); background: rgba(127, 29, 29, 0.36); color: #fecaca; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-description { flex: 1 1 160px; min-width: 0; border: 0; background: transparent; color: #f3f4f6; padding: 0; font: inherit; font-size: 11px; font-weight: 800; line-height: 1.25; text-align: left; } #${ALLIANCE_REQUEST_PANEL_ID} button.openfront-helper-alliance-request-description { cursor: pointer; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions { display: flex; flex-wrap: wrap; gap: 6px; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions button { border: 0; border-radius: 6px; color: #ffffff; padding: 5px 9px; font: inherit; font-size: 10px; font-weight: 900; cursor: pointer; transition: transform 120ms ease, filter 120ms ease; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions button:hover { filter: brightness(1.08); transform: translateY(-1px); } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions .btn { background: #16a34a; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions .btn-info { background: #3b82f6; } #${ALLIANCE_REQUEST_PANEL_ID} .openfront-helper-alliance-request-actions .btn-gray { background: #6b7280; } `;(document.head||document.documentElement).appendChild(t)}function ensureAllianceRequestPanel(){ensureAllianceRequestPanelStyles();let t=document.getElementById(ALLIANCE_REQUEST_PANEL_ID);if(!t){t=document.createElement("div");t.id=ALLIANCE_REQUEST_PANEL_ID;t.innerHTML=`

${tr("Alliance requests")}

0
`;(document.body||document.documentElement).appendChild(t);const e=t.querySelector(".openfront-helper-alliance-request-header");const n=t.querySelector(".openfront-helper-alliance-request-autorenew");if(n){n.addEventListener("click",o=>{o.stopPropagation();setAllianceRequestsAutoRenewEnabled(!allianceRequestsAutoRenewEnabled)})}if(e){makeAllianceRequestPanelDraggable(t,e,n)}applyStoredAllianceRequestPanelPosition(t)}return t}function setAllianceRequestsAutoRenewEnabled(t){allianceRequestsAutoRenewEnabled=Boolean(t);try{window.localStorage.setItem(ALLIANCE_REQUEST_PANEL_AUTORENEW_KEY,allianceRequestsAutoRenewEnabled?"1":"0")}catch(n){}const e=document.getElementById(ALLIANCE_REQUEST_PANEL_ID)?.querySelector(".openfront-helper-alliance-request-autorenew");if(e){e.dataset.on=allianceRequestsAutoRenewEnabled?"true":"false"}}function applyStoredAllianceRequestPanelPosition(t){let e=null;try{e=JSON.parse(window.localStorage.getItem(ALLIANCE_REQUEST_PANEL_POS_KEY)||"null")}catch(n){e=null}if(!e||!Number.isFinite(e.left)||!Number.isFinite(e.top)){return}t.style.transform="none";t.style.right="auto";t.style.left=Math.max(0,Math.min(e.left,window.innerWidth-60))+"px";t.style.top=Math.max(0,Math.min(e.top,window.innerHeight-30))+"px"}function makeAllianceRequestPanelDraggable(t,e,n){let o=0;let r=0;let i=0;let a=0;let s=false;function l(u){if(!s){return}const d=Math.max(0,Math.min(i+(u.clientX-o),window.innerWidth-60));const f=Math.max(0,Math.min(a+(u.clientY-r),window.innerHeight-30));t.style.left=d+"px";t.style.top=f+"px"}function c(){if(!s){return}s=false;window.removeEventListener("pointermove",l);window.removeEventListener("pointerup",c);try{window.localStorage.setItem(ALLIANCE_REQUEST_PANEL_POS_KEY,JSON.stringify({left:parseInt(t.style.left,10),top:parseInt(t.style.top,10)}))}catch(u){}}e.addEventListener("pointerdown",u=>{if(n&&(u.target===n||n.contains(u.target))){return}s=true;const d=t.getBoundingClientRect();i=d.left;a=d.top;o=u.clientX;r=u.clientY;t.style.transform="none";t.style.right="auto";t.style.left=i+"px";t.style.top=a+"px";window.addEventListener("pointermove",l);window.addEventListener("pointerup",c);u.preventDefault()})}function ensureAllianceFocusFlashStyles(){if(document.getElementById(ALLIANCE_FOCUS_FLASH_STYLE_ID)){return}const t=document.createElement("style");t.id=ALLIANCE_FOCUS_FLASH_STYLE_ID;t.textContent=` #${ALLIANCE_FOCUS_FLASH_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483646; pointer-events: none; } #${ALLIANCE_FOCUS_FLASH_CONTAINER_ID} .${ALLIANCE_FOCUS_FLASH_CANVAS_CLASS} { position: fixed; inset: 0; width: 100vw; height: 100vh; } `;(document.head||document.documentElement).appendChild(t)}function ensureAllianceFocusFlashCanvas(){ensureAllianceFocusFlashStyles();let t=document.getElementById(ALLIANCE_FOCUS_FLASH_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=ALLIANCE_FOCUS_FLASH_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}let e=t.querySelector(`.${ALLIANCE_FOCUS_FLASH_CANVAS_CLASS}`);if(!e){e=document.createElement("canvas");e.className=ALLIANCE_FOCUS_FLASH_CANVAS_CLASS;t.appendChild(e)}const n=Math.max(1,Math.min(2,window.devicePixelRatio||1));const o=Math.max(1,Math.floor(window.innerWidth*n));const r=Math.max(1,Math.floor(window.innerHeight*n));if(e.width!==o||e.height!==r){e.width=o;e.height=r}e.style.width=`${window.innerWidth}px`;e.style.height=`${window.innerHeight}px`;return{canvas:e,pixelRatio:n}}function isAllianceRequestPanelEvent(t){if(!t||!Array.isArray(t.buttons)||t.buttons.length===0){return false}if(ALLIANCE_PROMPT_MESSAGE_TYPES.has(Number(t.type))){return true}return Number.isFinite(Number(t.focusID))}function isRenewAllianceEvent(t){if(t&&t.allianceID!==void 0&&t.allianceID!==null){return true}return RENEW_ALLIANCE_MESSAGE_TYPES.has(Number(t?.type))}function getEventsDisplayElement(){return document.querySelector("actionable-events")||document.querySelector("events-display")}function getAllianceFocusFlashContext(t){const e=typeof getOpenFrontGameContext==="function"?getOpenFrontGameContext():null;const n=e?.game??t?.game??null;const o=e?.transform??null;if(!n||!o?.worldToScreenCoordinates){return null}return{game:n,transform:o}}function findAllianceFocusPlayer(t,e){const{game:n}=getAllianceFocusFlashContext(t)??{};if(!n){return null}try{const o=n.playerBySmallID?.(Number(e));return o?.isPlayer?.()?o:null}catch(o){return null}}function getAllianceFocusTileOwnerId(t,e){try{const n=Number(t.ownerID?.(e));if(Number.isFinite(n)){return n}}catch(n){}try{return Number(t.owner?.(e)?.smallID?.())}catch(n){return NaN}}function createAllianceFocusFlashMask(t,e){const{game:n}=t;const o=Number(e);if(!Number.isFinite(o)){return null}const r=Math.floor(Number(n.width?.()));const i=Math.floor(Number(n.height?.()));if(!Number.isFinite(r)||!Number.isFinite(i)||r<=0||i<=0){return null}const a=document.createElement("canvas");const s=document.createElement("canvas");a.width=r;a.height=i;s.width=r;s.height=i;const l=a.getContext("2d",{alpha:true});const c=s.getContext("2d",{alpha:true});if(!l||!c){return null}const u=l.createImageData(r,i);const d=c.createImageData(r,i);let f=false;const m=p=>{if(getAllianceFocusTileOwnerId(n,p)!==o){return}let h;let y;try{h=Number(n.x?.(p));y=Number(n.y?.(p))}catch(T){return}if(!Number.isFinite(h)||!Number.isFinite(y)){return}const g=(y*r+h)*4;const b=Math.floor((h+y)/3)%2;const w=b===0?u:d;w.data[g]=b===0?34:45;w.data[g+1]=b===0?197:212;w.data[g+2]=b===0?94:191;w.data[g+3]=255;f=true};if(typeof n.forEachTile==="function"){n.forEachTile(m)}else{for(let p=0;p160){a=collectAllianceFocusFlashPoints(u,e);s=c}drawAllianceFocusFlashPoints(m,a,f,p,u.transform)}if(p<1){allianceFocusFlashAnimationFrame=requestAnimationFrame(l);return}allianceFocusFlashAnimationFrame=null;d.parentElement?.remove()}allianceFocusFlashAnimationFrame=requestAnimationFrame(l)}function flashAllianceFocusTerritoryAfterCameraMove(t,e){requestAnimationFrame(()=>{requestAnimationFrame(()=>{flashAllianceFocusTerritory(t,e)})})}function getGameTick(t){const e=Number(t?.game?.ticks?.());return Number.isFinite(e)?e:0}function getAllianceRequestRemainingSeconds(t,e){const n=Number(t?.game?.ticks?.());const o=Number(e?.createdAt);const r=Number(e?.duration??600);if(!Number.isFinite(n)||!Number.isFinite(o)||!Number.isFinite(r)){return null}return Math.max(0,Math.ceil((r-(n-o))/10))}function formatAllianceRequestRemainingTime(t){if(!Number.isFinite(t)){return tr("Expires soon")}if(t<=0){return"Expired"}if(t<60){return`Expires in ${t}s`}const e=Math.floor(t/60);const n=t%60;return`Expires in ${e}:${String(n).padStart(2,"0")}`}function getAllianceRequestTimeUrgency(t){if(!Number.isFinite(t)){return"normal"}if(t<30){return"critical"}if(t<=60){return"soon"}return"normal"}function pruneAllianceRequestPanelEvents(t){const e=getGameTick(t);let n=false;for(let o=allianceRequestsPanelEvents.length-1;o>=0;o-=1){const r=allianceRequestsPanelEvents[o];const i=Number(r?.duration??600);const a=Number.isFinite(e)&&Number.isFinite(Number(r?.createdAt))&&e-Number(r.createdAt)>=i;const s=Boolean(r?.shouldDelete?.(t?.game));if(a||s){r?.onDelete?.();allianceRequestsPanelEvents.splice(o,1);n=true}}return n}function captureAllianceRequestPanelEvents(t){const e=Array.isArray(t?.events)?t.events:[];let n=false;for(const r of e){if(isAllianceRequestPanelEvent(r)&&!capturedAllianceRequestEvents.has(r)){capturedAllianceRequestEvents.add(r);allianceRequestsPanelEvents.push(r);n=true}}const o=e.filter(r=>!isAllianceRequestPanelEvent(r));if(o.length!==e.length){t.events=o;t.requestUpdate?.();n=true}return n}function removeAllianceRequestPanelEvent(t){const e=allianceRequestsPanelEvents.indexOf(t);if(e>=0){allianceRequestsPanelEvents.splice(e,1)}}function autoRenewAllianceRequestPanelEvents(){if(!allianceRequestsAutoRenewEnabled){return false}let t=false;for(const e of allianceRequestsPanelEvents.slice()){if(!isRenewAllianceEvent(e)){continue}if(autoRenewedAllianceEvents.has(e)){continue}const n=Array.isArray(e.buttons)?e.buttons:[];const o=n.find(i=>typeof i?.action==="function"&&getAllianceRequestPanelButtonClass(i.className)==="btn")||(typeof n[1]?.action==="function"?n[1]:null);if(!o){continue}autoRenewedAllianceEvents.add(e);let r=false;try{o.action();r=true}catch(i){}if(r){removeAllianceRequestPanelEvent(e);t=true}}return t}function getAllianceRequestPanelButtonClass(t){if(String(t).includes("btn-info")){return"btn-info"}if(String(t).includes("btn-gray")){return"btn-gray"}return"btn"}function getAllianceRequestSenderInfo(t,e){const n=Number(e?.focusID);if(!Number.isFinite(n)){return{type:"unknown",label:""}}try{const o=t?.game?.playerBySmallID?.(n);if(o?.isPlayer?.()&&isNationBotPlayer(o)){return{type:"nation",label:tr("Nation")}}}catch(o){}return{type:"player",label:""}}function getAllianceRequestPanelRenderSignature(t){return allianceRequestsPanelEvents.map(e=>{const n=getAllianceRequestRemainingSeconds(t,e);return[e.createdAt,e.type,e.focusID??"",e.description??"",Number.isFinite(n)?n:"",Array.isArray(e.buttons)?e.buttons.length:0].join(":")}).join("|")}function renderAllianceRequestPanel(t){const e=ensureAllianceRequestPanel();e.dataset.visible=String(allianceRequestsPanelEvents.length>0);const n=getAllianceRequestPanelRenderSignature(t);if(n===allianceRequestsPanelRenderSignature){return}allianceRequestsPanelRenderSignature=n;const o=e.querySelector(".openfront-helper-alliance-request-count");if(o){o.textContent=String(allianceRequestsPanelEvents.length)}const r=e.querySelector(".openfront-helper-alliance-request-list");if(!r){return}r.replaceChildren();for(const i of allianceRequestsPanelEvents){const a=getAllianceRequestSenderInfo(t,i);const s=getAllianceRequestRemainingSeconds(t,i);const l=document.createElement("div");l.className="openfront-helper-alliance-request-card";l.dataset.senderType=a.type;const c=document.createElement(i.focusID?"button":"div");c.className="openfront-helper-alliance-request-description";c.textContent=String(i.description??"");if(i.focusID){c.type="button";c.addEventListener("click",()=>{t?.emitGoToPlayerEvent?.(i.focusID);flashAllianceFocusTerritoryAfterCameraMove(t,i.focusID)})}const u=document.createElement("div");u.className="openfront-helper-alliance-request-meta";u.appendChild(c);if(a.label){const f=document.createElement("span");f.className="openfront-helper-alliance-request-sender-type";f.textContent=a.label;u.appendChild(f)}const d=document.createElement("span");d.className="openfront-helper-alliance-request-time";d.dataset.urgency=getAllianceRequestTimeUrgency(s);d.textContent=formatAllianceRequestRemainingTime(s);u.appendChild(d);l.appendChild(u);if(Array.isArray(i.buttons)&&i.buttons.length>0){const f=document.createElement("div");f.className="openfront-helper-alliance-request-actions";for(const m of i.buttons){const p=document.createElement("button");p.type="button";p.className=getAllianceRequestPanelButtonClass(m.className);p.textContent=String(m.text??"");p.addEventListener("click",()=>{m.action?.();if(m.preventClose&&i.focusID){flashAllianceFocusTerritoryAfterCameraMove(t,i.focusID)}if(!m.preventClose){removeAllianceRequestPanelEvent(i);renderAllianceRequestPanel(t)}});f.appendChild(p)}l.appendChild(f)}r.appendChild(l)}}function syncAllianceRequestPanel(){if(!allianceRequestsPanelEnabled){return}const t=getEventsDisplayElement();let e=false;if(t){e=captureAllianceRequestPanelEvents(t);e=pruneAllianceRequestPanelEvents(t)||e;e=autoRenewAllianceRequestPanelEvents()||e}if(e||getAllianceRequestPanelRenderSignature(t)!==allianceRequestsPanelRenderSignature){renderAllianceRequestPanel(t)}allianceRequestsPanelAnimationFrame=requestAnimationFrame(syncAllianceRequestPanel)}function setAllianceRequestsPanelEnabled(t){allianceRequestsPanelEnabled=Boolean(t);if(!allianceRequestsPanelEnabled){if(allianceRequestsPanelAnimationFrame!==null){cancelAnimationFrame(allianceRequestsPanelAnimationFrame);allianceRequestsPanelAnimationFrame=null}allianceRequestsPanelEvents.splice(0,allianceRequestsPanelEvents.length);allianceRequestsPanelRenderSignature="";document.getElementById(ALLIANCE_REQUEST_PANEL_ID)?.remove();return}if(allianceRequestsPanelAnimationFrame===null){syncAllianceRequestPanel()}}function ensureBotMarkerStyles(){if(document.getElementById(BOT_MARKER_STYLE_ID)){return}const t=document.createElement("style");t.id=BOT_MARKER_STYLE_ID;t.textContent=` #${BOT_MARKER_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483647; pointer-events: none; } #${BOT_MARKER_CONTAINER_ID} .openfront-helper-bot-dot { position: fixed; width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px; border-radius: 50%; background: rgba(239, 68, 68, 0.68); box-shadow: 0 0 0 2px rgba(127, 29, 29, 0.34), 0 0 10px rgba(239, 68, 68, 0.72); transform: translate(var(--bot-x), var(--bot-y)); } `;(document.head||document.documentElement).appendChild(t)}function ensureBotMarkerContainer(){ensureBotMarkerStyles();let t=document.getElementById(BOT_MARKER_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=BOT_MARKER_CONTAINER_ID;(document.body||document.documentElement).appendChild(t)}return t}const botMarkerEntries=new Map;let botMarkerScanCache=[];let lastBotMarkerScanAt=0;const BOT_MARKER_SCAN_MS=500;function collectBotMarkerScan(t){const e=getCachedPlayerViews(t);const n=[];for(let o=0;o0){for(const i of botMarkerEntries.values()){i.marker.remove()}botMarkerEntries.clear()}botMarkerScanCache=[];lastBotMarkerScanAt=0;botMarkerAnimationFrame=requestAnimationFrame(syncBotMarkers);return}const n=performance.now();if(n-lastBotMarkerScanAt>=BOT_MARKER_SCAN_MS){botMarkerScanCache=collectBotMarkerScan(t.game);pruneBotMarkerCache();lastBotMarkerScanAt=n}const o=window.innerWidth;const r=window.innerHeight;for(let i=0;io+30||c.y>r+30){const d=botMarkerEntries.get(s);if(d){hideBotMarkerEntry(d)}continue}const u=ensureBotMarkerEntry(e,s);if(u.hidden){u.marker.hidden=false;u.hidden=false}if(u.x!==c.x){u.marker.style.setProperty("--bot-x",`${c.x}px`);u.x=c.x}if(u.y!==c.y){u.marker.style.setProperty("--bot-y",`${c.y}px`);u.y=c.y}}botMarkerAnimationFrame=requestAnimationFrame(syncBotMarkers)}function setBotMarkersEnabled(t){botMarkersEnabled=Boolean(t);if(!botMarkersEnabled){if(botMarkerAnimationFrame!==null){cancelAnimationFrame(botMarkerAnimationFrame)}botMarkerAnimationFrame=null;botMarkerEntries.clear();botMarkerScanCache=[];lastBotMarkerScanAt=0;document.getElementById(BOT_MARKER_CONTAINER_ID)?.remove();return}if(botMarkerAnimationFrame===null){syncBotMarkers()}}function ensureHelperStatsContainerStyles(){if(document.getElementById(HELPER_STATS_STYLE_ID)){return}const t=document.createElement("style");t.id=HELPER_STATS_STYLE_ID;t.textContent=` #${HELPER_STATS_CONTAINER_ID} { position: fixed; bottom: var(--helper-stats-bottom, calc(8px + env(safe-area-inset-bottom, 0px))); left: var(--helper-stats-x, 260px); z-index: 2147483647; display: none; width: min(280px, calc(100vw - 16px)); gap: 6px; padding: 7px; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.92); color: #e2e8f0; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); /* Draggable from anywhere on the panel (same feel as the Top-10 badge). Trade-off: the panel captures clicks within its footprint while visible — drag it aside if it ever covers game UI. */ pointer-events: auto; cursor: move; user-select: none; } #${HELPER_STATS_CONTAINER_ID}[data-visible="true"] { display: grid; } #${HELPER_STATS_CONTAINER_ID} > * { min-width: 0; } `;(document.head||document.documentElement).appendChild(t)}let _helperStatsLayoutDirty=true;let _helperStatsResizeObserver=null;let _helperStatsLayoutListenersBound=false;function markHelperStatsLayoutDirty(){_helperStatsLayoutDirty=true}function bindHelperStatsLayoutListeners(t){if(_helperStatsLayoutListenersBound){return}_helperStatsLayoutListenersBound=true;window.addEventListener("resize",markHelperStatsLayoutDirty,{passive:true});const e=window.visualViewport;if(e){e.addEventListener("resize",markHelperStatsLayoutDirty,{passive:true});e.addEventListener("scroll",markHelperStatsLayoutDirty,{passive:true})}if(typeof ResizeObserver!=="undefined"){_helperStatsResizeObserver=new ResizeObserver(markHelperStatsLayoutDirty);_helperStatsResizeObserver.observe(t)}}function applyStoredGoldStatPanelPosition(t,e){let n=null;try{n=JSON.parse(window.localStorage.getItem(e)||"null")}catch(o){n=null}if(!n||!Number.isFinite(n.left)||!Number.isFinite(n.top)){return}t.dataset.userPositioned="true";t.style.bottom="auto";t.style.left=`${Math.max(0,Math.min(n.left,window.innerWidth-60))}px`;t.style.top=`${Math.max(0,Math.min(n.top,window.innerHeight-30))}px`}function makeGoldStatPanelDraggable(t,e,n){let o=0;let r=0;let i=0;let a=0;let s=false;let l=false;function c(d){if(!s){return}const f=d.clientX-o;const m=d.clientY-r;if(!l){if(Math.abs(f)<3&&Math.abs(m)<3){return}l=true;t.dataset.userPositioned="true";t.style.bottom="auto"}const p=Math.max(0,Math.min(i+f,window.innerWidth-60));const h=Math.max(0,Math.min(a+m,window.innerHeight-30));t.style.left=`${p}px`;t.style.top=`${h}px`}function u(){if(!s){return}s=false;window.removeEventListener("pointermove",c);window.removeEventListener("pointerup",u);if(!l){return}try{window.localStorage.setItem(n,JSON.stringify({left:parseInt(t.style.left,10),top:parseInt(t.style.top,10)}))}catch(d){}}e.addEventListener("pointerdown",d=>{if(d.button!==0){return}s=true;l=false;const f=t.getBoundingClientRect();i=f.left;a=f.top;o=d.clientX;r=d.clientY;window.addEventListener("pointermove",c);window.addEventListener("pointerup",u);d.preventDefault()})}function ensureHelperStatsContainer(){ensureHelperStatsContainerStyles();let t=document.getElementById(HELPER_STATS_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=HELPER_STATS_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t);makeGoldStatPanelDraggable(t,t,HELPER_STATS_POS_KEY);applyStoredGoldStatPanelPosition(t,HELPER_STATS_POS_KEY)}bindHelperStatsLayoutListeners(t);return t}function syncHelperStatsLayoutIfDirty(){if(!_helperStatsLayoutDirty){return}_helperStatsLayoutDirty=false;positionHelperStatsContainer();positionTopGoldPerMinuteBadge()}function positionHelperStatsContainer(){const t=ensureHelperStatsContainer();if(t.dataset.userPositioned==="true"){return t}const e=8;const n=Number(window.visualViewport?.offsetLeft)||0;const o=Number(window.visualViewport?.offsetTop)||0;t.style.setProperty("--helper-stats-x",`${Math.round(e+n)}px`);t.style.setProperty("--helper-stats-bottom",`${Math.round(e+o)}px`);return t}function syncHelperStatsContainerVisibility(){const t=ensureHelperStatsContainer();const e=Boolean(t.querySelector('[data-visible="true"]'));const n=String(e);if(t.dataset.visible!==n){markHelperStatsLayoutDirty()}t.dataset.visible=n;t.setAttribute("aria-hidden",String(!e))}function ensureGoldPerMinuteStyles(){if(document.getElementById(GOLD_PER_MINUTE_STYLE_ID)){return}const t=document.createElement("style");t.id=GOLD_PER_MINUTE_STYLE_ID;t.textContent=` #${GOLD_PER_MINUTE_BADGE_ID} { display: none; align-items: center; justify-content: space-between; gap: 6px; width: 100%; padding: 3px 2px; border: none; background: none; color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 12px; font-weight: 800; line-height: 1; box-shadow: none; } #${GOLD_PER_MINUTE_BADGE_ID}[data-visible="true"] { display: flex; } #${GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-gpm-label { color: rgba(226, 232, 240, 0.82); font-size: 10px; letter-spacing: 0; text-transform: uppercase; } #${GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-gpm-value { color: var(--team-accent-color, #e2e8f0); font-variant-numeric: tabular-nums; } `;(document.head||document.documentElement).appendChild(t)}function ensureGoldPerMinuteBadge(){ensureGoldPerMinuteStyles();let t=document.getElementById(GOLD_PER_MINUTE_BADGE_ID);if(!t){t=document.createElement("div");t.id=GOLD_PER_MINUTE_BADGE_ID;t.innerHTML=` ${tr("Gold per minute")} ${tr("tracking")} `}const e=ensureHelperStatsContainer();if(t.parentElement!==e){e.appendChild(t)}return t}function ensureTeamGoldPerMinuteStyles(){if(document.getElementById(TEAM_GOLD_PER_MINUTE_STYLE_ID)){return}const t=document.createElement("style");t.id=TEAM_GOLD_PER_MINUTE_STYLE_ID;t.textContent=` #${TEAM_GOLD_PER_MINUTE_BADGE_ID} { display: none; width: 100%; padding: 3px 2px; border: none; background: none; color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 12px; font-weight: 800; line-height: 1.15; box-shadow: none; } #${TEAM_GOLD_PER_MINUTE_BADGE_ID}[data-visible="true"] { display: grid; gap: 5px; } #${TEAM_GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-team-gpm-title { color: rgba(226, 232, 240, 0.82); font-size: 10px; letter-spacing: 0; text-transform: uppercase; } #${TEAM_GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-team-gpm-rows { display: grid; gap: 4px; } #${TEAM_GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-team-gpm-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; padding-left: 7px; border-left: 3px solid var(--team-accent-color, rgba(148, 163, 184, 0.6)); } #${TEAM_GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-team-gpm-name { overflow: hidden; color: var(--team-accent-color, #cbd5e1); text-overflow: ellipsis; white-space: nowrap; } #${TEAM_GOLD_PER_MINUTE_BADGE_ID} .openfront-helper-team-gpm-value { color: rgba(226, 232, 240, 0.92); font-variant-numeric: tabular-nums; white-space: nowrap; } `;(document.head||document.documentElement).appendChild(t)}function ensureTeamGoldPerMinuteBadge(){ensureTeamGoldPerMinuteStyles();let t=document.getElementById(TEAM_GOLD_PER_MINUTE_BADGE_ID);if(!t){t=document.createElement("div");t.id=TEAM_GOLD_PER_MINUTE_BADGE_ID;t.innerHTML=` ${tr("Team gold per minute")} `}const e=ensureHelperStatsContainer();if(t.parentElement!==e){e.appendChild(t)}return t}function ensureTopGoldPerMinuteStyles(){if(document.getElementById(TOP_GOLD_PER_MINUTE_STYLE_ID)){return}const t=document.createElement("style");t.id=TOP_GOLD_PER_MINUTE_STYLE_ID;t.textContent=` #${TOP_GOLD_PER_MINUTE_BADGE_ID} { position: fixed; bottom: var(--top-gpm-bottom, calc(8px + env(safe-area-inset-bottom, 0px))); left: var(--top-gpm-x, 296px); z-index: 2147483647; display: none; flex-direction: column; width: 340px; height: 280px; min-width: 240px; min-height: 120px; max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); /* Resizable from the bottom-right corner; the body scrolls. */ resize: both; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.95); color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 11px; font-weight: 700; line-height: 1.15; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); pointer-events: auto; } #${TOP_GOLD_PER_MINUTE_BADGE_ID}[data-visible="true"] { display: flex; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-header { flex: none; display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid rgba(148, 163, 184, 0.18); cursor: grab; touch-action: none; user-select: none; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-header:active { cursor: grabbing; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-title { flex: 1; font-size: 10px; font-weight: 900; letter-spacing: 0.4px; text-transform: uppercase; color: rgba(226, 232, 240, 0.85); } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-expand { cursor: pointer; border: 1px solid rgba(148, 163, 184, 0.4); border-radius: 6px; background: rgba(148, 163, 184, 0.14); color: #e2e8f0; font: 800 12px/1 "Aptos", "Segoe UI", sans-serif; padding: 3px 9px; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-expand:hover { background: rgba(148, 163, 184, 0.3); } /* One scroll container = one grid → header and rows share columns exactly. */ #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-table { flex: 1; min-height: 0; overflow: auto; display: grid; align-content: start; /* Fixed row height: rows keep their size regardless of panel height, so resizing only changes how many are visible (scroll), never squishes. */ grid-auto-rows: 26px; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row { display: contents; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-cell { padding: 0 6px; line-height: 25px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; border-bottom: 1px solid rgba(148, 163, 184, 0.08); /* Clears the sticky header when scrollIntoView follows a hovered row. */ scroll-margin-top: 26px; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-num { text-align: right; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-name { color: var(--team-accent-color, #cbd5e1); font-weight: 700; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-rank { color: rgba(148, 163, 184, 0.72); } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head { position: sticky; top: 0; z-index: 1; background: rgba(16, 22, 24, 0.98); color: rgba(226, 232, 240, 0.72); font-size: 10px; text-transform: uppercase; border-bottom: 1px solid rgba(148, 163, 184, 0.22); } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head.aps-sortable { cursor: pointer; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head.aps-sortable:hover { color: #fff; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head.aps-sorted { color: #fff; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-sort-arrow { font-size: 8px; margin-left: 2px; } /* Icon headers (numeric + building columns) sit centered over the column. */ #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head.aps-num { text-align: center; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-head img { width: 14px; height: 14px; object-fit: contain; vertical-align: middle; } /* Row highlights: applied per-cell (rows are display:contents) so the band is continuous across the columns. */ #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row .aps-cell { cursor: pointer; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row.is-self .aps-cell { background: rgba(59, 130, 246, 0.2); } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row.is-hovered .aps-cell { background: rgba(148, 163, 184, 0.24); } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row.is-disconnected .aps-cell { opacity: 0.5; } /* Pinned hovered-nation row at the bottom (after is-* rules → wins ties). */ #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-row.aps-footer .aps-cell { position: sticky; bottom: 0; z-index: 1; background: rgba(28, 38, 48, 0.98); border-top: 1px solid rgba(148, 163, 184, 0.34); border-bottom: none; } #${TOP_GOLD_PER_MINUTE_BADGE_ID} .aps-empty { grid-column: 1 / -1; padding: 12px; text-align: center; color: rgba(226, 232, 240, 0.6); } `;(document.head||document.documentElement).appendChild(t)}function ensureTopGoldPerMinuteBadge(){ensureTopGoldPerMinuteStyles();let t=document.getElementById(TOP_GOLD_PER_MINUTE_BADGE_ID);if(!t){apsExpanded=loadApsExpanded();t=document.createElement("div");t.id=TOP_GOLD_PER_MINUTE_BADGE_ID;const e=document.createElement("div");e.className="aps-header";const n=document.createElement("span");n.className="aps-title";n.textContent=tr("Players");const o=document.createElement("button");o.type="button";o.className="aps-expand";o.textContent=apsExpanded?"−":"+";o.title=tr("Show building counts");o.addEventListener("click",i=>{i.stopPropagation();apsExpanded=!apsExpanded;saveApsExpanded();o.textContent=apsExpanded?"−":"+";topGoldPerMinuteRenderSignature="";updateTopGoldPerMinuteBadge()});e.append(n,o);const r=document.createElement("div");r.className="aps-table";t.append(e,r)}if(!t.parentElement){(document.body||document.documentElement).appendChild(t);const e=t.querySelector(".aps-header");makeGoldStatPanelDraggable(t,e||t,TOP_GOLD_PER_MINUTE_POS_KEY);applyStoredGoldStatPanelPosition(t,TOP_GOLD_PER_MINUTE_POS_KEY);applyStoredApsSize(t);if(window.ResizeObserver){const n=new ResizeObserver(()=>saveApsSize(t));n.observe(t)}}return t}function positionTopGoldPerMinuteBadge(){const t=ensureTopGoldPerMinuteBadge();if(t.dataset.userPositioned==="true"){return}const e=8;const n=Number(window.visualViewport?.offsetLeft)||0;const o=Number(window.visualViewport?.offsetTop)||0;const r=Number(window.visualViewport?.width)||window.innerWidth;const i=Math.round(e+n);const a=ensureHelperStatsContainer();const s=a.dataset.visible==="true"?a.getBoundingClientRect?.():null;const l=s&&s.width>0?Math.round(s.right+e+n):i;const c=Math.max(i,Math.round(n+r-t.offsetWidth-e));t.style.setProperty("--top-gpm-x",`${Math.min(l,c)}px`);t.style.setProperty("--top-gpm-bottom",`${Math.round(e+o)}px`)}function addIncomingGoldTransfer(t,e){if(!Number.isFinite(t)||!Number.isFinite(e)||e<=0){return}const n=String(t);incomingGoldTransfers.set(n,(incomingGoldTransfers.get(n)||0)+e)}function processIncomingGoldTransferUpdates(t){const e=Number(t?.ticks?.());if(!Number.isFinite(e)||e===lastProcessedIncomingGoldTransferTick){return}lastProcessedIncomingGoldTransferTick=e;const n=t?.updatesSinceLastTick?.();if(!n){return}for(const o of Object.values(n)){if(!Array.isArray(o)){continue}for(const r of o){if(r?.message!=="events_display.received_gold_from_player"){continue}addIncomingGoldTransfer(Number(r.playerID),getTradeEventGoldAmount(r.goldAmount))}}}function formatGoldPerMinute(t){if(typeof t!=="number"||!Number.isFinite(t)){return"tracking"}const e=t<0?"-":"";const n=Math.abs(t);if(n>=1e9){return`${e}${(n/1e9).toFixed(1)}B/min`}if(n>=1e6){return`${e}${(n/1e6).toFixed(1)}M/min`}if(n>=1e3){return`${e}${(n/1e3).toFixed(1)}K/min`}return`${e}${Math.round(n)}/min`}function getGoldTracker(t,e){const n=getPlayerMarkerId(t,e);let o=goldTrackers.get(n);if(!o){o={lastGold:null,earnedTotal:0,samples:[]};goldTrackers.set(n,o)}return{playerId:n,tracker:o}}function sampleGoldPerMinute(){const t=getOpenFrontGameContext();if(!t){return}processIncomingGoldTransferUpdates(t.game);const e=Date.now();lastGoldPerMinuteSampleAt=e;const n=new Set;const o=getCachedPlayerViews(t.game);let r=0;for(const i of o){if(!i?.isAlive?.()){r+=1;continue}const a=getPlayerGoldNumber(i);if(!Number.isFinite(a)){r+=1;continue}const{playerId:s,tracker:l}=getGoldTracker(i,r);n.add(s);const c=getPlayerSmallId(i,r);const u=incomingGoldTransfers.get(String(c))||0;if(l.lastGold!==null&&a>l.lastGold){l.earnedTotal+=Math.max(0,a-l.lastGold-u)}incomingGoldTransfers.delete(String(c));l.lastGold=a;l.samples.push({time:e,earnedTotal:l.earnedTotal});const d=e-GOLD_PER_MINUTE_WINDOW_MS;while(l.samples.length>1&&l.samples[0].time=GOLD_PER_MINUTE_SAMPLE_MS){sampleGoldPerMinute()}}function getGoldPerMinuteForPlayer(t,e){const{tracker:n}=getGoldTracker(t,e);if(n.samples.length<2){return null}const o=n.samples[0];const r=n.samples[n.samples.length-1];const i=r.time-o.time;if(i<=0){return null}return(r.earnedTotal-o.earnedTotal)/i*6e4}function getTeamGoldPerMinuteRows(t){const e=getCachedPlayerViews(t);const n=new Map;let o=0;for(const r of e){if(!r?.isAlive?.()){o+=1;continue}const i=getPlayerTeamName(r);if(!i||i==="Bot"){o+=1;continue}const a=getGoldPerMinuteForPlayer(r,o);const s=n.get(i)??{team:i,total:0,trackedPlayers:0,players:0};s.players+=1;if(Number.isFinite(a)){s.total+=a;s.trackedPlayers+=1}n.set(i,s);o+=1}return Array.from(n.values()).sort((r,i)=>i.total-r.total)}const APS_STRUCTURES=[{type:"City",emoji:"🏙️",label:"City"},{type:"Port",emoji:"⚓",label:"Port"},{type:"Factory",emoji:"🏭",label:"Factory"},{type:"Defense Post",emoji:"🛡️",label:"Defense Post"},{type:"SAM Launcher",emoji:"🛰️",label:"SAM Launcher"},{type:"Missile Silo",emoji:"🚀",label:"Missile Silo"}];const APS_BASE_COLUMNS=[{key:"rank",label:"#",cls:"aps-rank",sortable:false},{key:"name",label:"Player",cls:"aps-name",sortable:true},{key:"owned",label:"Owned",glyph:"🗺️",cls:"aps-num",sortable:true},{key:"gold",label:"Gold",glyph:"🪙",cls:"aps-num",sortable:true},{key:"gpm",label:"Gold/min",glyph:"💰",cls:"aps-num",sortable:true},{key:"maxTroops",label:"Max troops",glyph:"⚔️",cls:"aps-num",sortable:true}];const APS_SIZE_KEY="openfrontHelperPlayerPanelSize";const APS_EXPAND_KEY="openfrontHelperPlayerPanelExpanded";let apsSortKey="owned";let apsSortDir=1;let apsExpanded=false;function loadApsExpanded(){try{return window.localStorage.getItem(APS_EXPAND_KEY)==="1"}catch(t){return false}}function saveApsExpanded(){try{window.localStorage.setItem(APS_EXPAND_KEY,apsExpanded?"1":"0")}catch(t){}}function applyStoredApsSize(t){try{const e=JSON.parse(window.localStorage.getItem(APS_SIZE_KEY)||"null");if(e&&Number.isFinite(e.w)&&Number.isFinite(e.h)){t.style.width=`${e.w}px`;t.style.height=`${e.h}px`}}catch(e){}}function saveApsSize(t){const e=parseInt(t.style.width,10);const n=parseInt(t.style.height,10);if(!Number.isFinite(e)||!Number.isFinite(n)){return}try{window.localStorage.setItem(APS_SIZE_KEY,JSON.stringify({w:e,h:n}))}catch(o){}}let apsRenderedExpanded=null;let apsRenderedSortSig="";let apsHeaderCells=null;let apsFooterEntry=null;const apsRowCache=new Map;function setGameLeaderboardHidden(t){const e="openfront-helper-hide-game-leaderboard";let n=document.getElementById(e);if(t){if(!n){n=document.createElement("style");n.id=e;n.textContent="game-left-sidebar{display:none !important;}";(document.head||document.documentElement).appendChild(n)}}else if(n){n.remove()}}function apsFormatNum(t){const e=Number(t)||0;const n=e<0?"-":"";const o=Math.abs(e);if(o>=1e9)return`${n}${(o/1e9).toFixed(1)}B`;if(o>=1e6)return`${n}${(o/1e6).toFixed(1)}M`;if(o>=1e3)return`${n}${(o/1e3).toFixed(1)}K`;return`${n}${Math.round(o)}`}function apsFormatGpm(t){return typeof t==="number"&&Number.isFinite(t)?apsFormatNum(t):"—"}function apsFormatOwned(t,e){let n=0;try{n=(Number(e?.numLandTiles?.())||0)-(Number(e?.numTilesWithFallout?.())||0)}catch(r){n=0}if(n<=0)return"—";const o=(Number(t)||0)/n*100;return o>=10?`${Math.round(o)}%`:`${o.toFixed(1)}%`}function getApsStructureCounts(t){const e=new Map;let n=[];try{n=t.units(...APS_STRUCTURES.map(o=>o.type))||[]}catch(o){n=[]}for(const o of n){let r=NaN;try{r=Number(o.owner?.()?.smallID?.())}catch(c){continue}if(!Number.isFinite(r))continue;let i=null;try{i=o.type?.()}catch(c){continue}let a=false;try{a=Boolean(o.isUnderConstruction?.())}catch(c){a=false}if(a)continue;let s=1;try{const c=Number(o.level?.());if(Number.isFinite(c)&&c>0)s=c}catch(c){s=1}let l=e.get(r);if(!l){l={};e.set(r,l)}l[i]=(l[i]||0)+s}return e}function apsRowSortValue(t,e){switch(e){case"name":return t.name;case"owned":return t.owned;case"gold":return t.gold;case"gpm":return Number.isFinite(t.gpm)?t.gpm:-Infinity;case"maxTroops":return t.maxTroops;default:return t.counts&&t.counts[e]||0}}function getAllPlayerStatsRows(t){const e=getCachedPlayerViews(t);const n=apsExpanded?getApsStructureCounts(t):null;let o=null;try{o=t.config?.()}catch(c){o=null}let r=NaN;try{r=Number(t.myPlayer?.()?.smallID?.())}catch(c){r=NaN}const i=[];let a=0;for(const c of e){if(!c?.isAlive?.()){a+=1;continue}const u=Number(getPlayerSmallId(c,a));let d=0;try{d=Number(c.gold?.())||0}catch(h){d=0}let f=0;try{f=Number(c.numTilesOwned?.())||0}catch(h){f=0}let m=0;try{if(o?.maxTroops)m=(Number(o.maxTroops(c))||0)/10}catch(h){m=0}let p=false;try{p=Boolean(c.isDisconnected?.())}catch(h){p=false}i.push({smallId:u,name:getPlayerDisplayName(c),team:getPlayerTeamName(c),color:getPlayerColor(c,t),owned:f,gold:d,gpm:getGoldPerMinuteForPlayer(c,a),maxTroops:m,disconnected:p,isMe:Number.isFinite(r)&&u===r,counts:n?n.get(u)||{}:null});a+=1}const s=apsSortKey;const l=apsSortDir;i.sort((c,u)=>{const d=apsRowSortValue(c,s);const f=apsRowSortValue(u,s);if(typeof d==="string"){const m=String(d).localeCompare(String(f));return l===1?m:-m}if(d!==f)return l===1?f-d:d-f;return u.owned-c.owned});return i}function getApsColumns(){const t=APS_BASE_COLUMNS.slice();if(apsExpanded){for(const e of APS_STRUCTURES){t.push({key:e.type,structure:e,cls:"aps-num",sortable:true,title:e.label})}}return t}function getApsGridTemplate(){let t="36px minmax(72px, 1fr) repeat(4, 52px)";if(apsExpanded){t+=` repeat(${APS_STRUCTURES.length}, 38px)`}return t}function setApsSort(t){if(apsSortKey===t){apsSortDir=apsSortDir===1?-1:1}else{apsSortKey=t;apsSortDir=1}updateTopGoldPerMinuteBadge()}function focusApsPlayer(t){if(!Number.isFinite(t)){return}try{const e=document.querySelector("actionable-events")||document.querySelector("events-display");if(e&&typeof e.emitGoToPlayerEvent==="function"){e.emitGoToPlayerEvent(Number(t))}}catch(e){}}function buildApsHeader(t,e){if(apsHeaderCells){for(const o of apsHeaderCells)o.remove()}const n=e.map(o=>{const r=document.createElement("span");r.className=`aps-cell aps-head ${o.cls}`;if(o.structure&&typeof makeTeamBuildHeadGlyph==="function"){r.appendChild(makeTeamBuildHeadGlyph(o.structure));r.title=o.title||o.structure.label}else if(o.glyph){r.appendChild(document.createTextNode(o.glyph));r.title=tr(o.label)}else{r.textContent=tr(o.label);if(o.title)r.title=o.title}if(o.sortable){r.classList.add("aps-sortable");r.addEventListener("click",()=>setApsSort(o.key));if(apsSortKey===o.key){r.classList.add("aps-sorted");const i=document.createElement("span");i.className="aps-sort-arrow";i.textContent=apsSortDir===1?"▾":"▴";r.appendChild(i)}}return r});t.prepend(...n);apsHeaderCells=n}function getApsRowEntry(t,e,n){let o=apsRowCache.get(e.smallId);if(!o){const r=document.createElement("div");r.className="aps-row";const i={};for(const a of n){const s=document.createElement("span");s.className=`aps-cell ${a.cls}`;r.appendChild(s);i[a.key]=s}o={row:r,cells:i,smallId:e.smallId};r.addEventListener("click",()=>focusApsPlayer(o.smallId));apsRowCache.set(e.smallId,o);t.appendChild(r)}return o}function updateApsRow(t,e,n,o,r){const i=t.cells;if(i.rank)i.rank.textContent=String(n+1);if(i.name){i.name.textContent=e.disconnected?`${e.name} 💤`:e.name;const a=e.color||(e.team?getTeamColor(e.team,o):"");if(a)i.name.style.setProperty("--team-accent-color",a);else i.name.style.removeProperty("--team-accent-color")}if(i.owned)i.owned.textContent=apsFormatOwned(e.owned,o);if(i.gold)i.gold.textContent=apsFormatNum(e.gold);if(i.gpm)i.gpm.textContent=apsFormatGpm(e.gpm);if(i.maxTroops)i.maxTroops.textContent=apsFormatNum(e.maxTroops);if(e.counts){for(const a of APS_STRUCTURES){const s=i[a.type];if(s)s.textContent=String(e.counts[a.type]||0)}}t.row.classList.toggle("is-self",Boolean(e.isMe));t.row.classList.toggle("is-hovered",r!==null&&e.smallId===r);t.row.classList.toggle("is-disconnected",Boolean(e.disconnected))}function getApsFooter(t,e){if(!apsFooterEntry){const n=document.createElement("div");n.className="aps-row aps-footer";const o={};for(const r of e){const i=document.createElement("span");i.className=`aps-cell aps-foot ${r.cls}`;n.appendChild(i);o[r.key]=i}apsFooterEntry={row:n,cells:o,smallId:null};n.addEventListener("click",()=>focusApsPlayer(apsFooterEntry.smallId));t.appendChild(n)}return apsFooterEntry}function updateTeamGoldPerMinuteBadge(){const t=ensureTeamGoldPerMinuteBadge();if(!teamGoldPerMinuteEnabled){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}teamGoldPerMinuteRenderSignature="";return}const e=getOpenFrontGameContext();if(!e?.game){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}teamGoldPerMinuteRenderSignature="";return}sampleGoldPerMinuteIfDue();const n=getTeamGoldPerMinuteRows(e.game);if(n.length<2){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}teamGoldPerMinuteRenderSignature="";return}const o=t.querySelector(".openfront-helper-team-gpm-rows");if(o){const r=n.map(a=>({team:a.team,color:getTeamColor(a.team,e.game),value:a.trackedPlayers>0?formatGoldPerMinute(a.total):"tracking"}));const i=r.map(a=>`${a.team}|${a.color}|${a.value}`).join(";");if(teamGoldPerMinuteRenderSignature!==i){o.replaceChildren(...r.map(a=>{const s=document.createElement("span");s.className="openfront-helper-team-gpm-row";s.style.setProperty("--team-accent-color",a.color);const l=document.createElement("span");l.className="openfront-helper-team-gpm-name";l.textContent=a.team;const c=document.createElement("span");c.className="openfront-helper-team-gpm-value";c.textContent=a.value;s.append(l,c);return s}));teamGoldPerMinuteRenderSignature=i;markHelperStatsLayoutDirty()}}if(t.dataset.visible!=="true"){t.dataset.visible="true";syncHelperStatsContainerVisibility();markHelperStatsLayoutDirty()}syncHelperStatsLayoutIfDirty()}function hideTopGoldPerMinuteBadge(t){if(t.dataset.visible!=="false"){t.dataset.visible="false";markHelperStatsLayoutDirty()}syncHelperStatsLayoutIfDirty()}function updateTopGoldPerMinuteBadge(){const t=ensureTopGoldPerMinuteBadge();if(!topGoldPerMinuteEnabled){hideTopGoldPerMinuteBadge(t);return}const e=getOpenFrontGameContext();if(!e?.game){hideTopGoldPerMinuteBadge(t);return}sampleGoldPerMinuteIfDue();const n=e.game;const o=t.querySelector(".aps-table");if(!o){return}let r=null;if(goldPerMinuteEnabled){const d=getHoveredPlayerInfoOverlay();if(d?.player){r=Number(getPlayerSmallId(d.player,0))}}const i=getApsColumns();const a=getAllPlayerStatsRows(n);if(apsRenderedExpanded!==apsExpanded){o.style.gridTemplateColumns=getApsGridTemplate();for(const[,d]of apsRowCache){d.row.remove()}apsRowCache.clear();if(apsHeaderCells){for(const d of apsHeaderCells)d.remove()}apsHeaderCells=null;if(apsFooterEntry){apsFooterEntry.row.remove();apsFooterEntry=null}apsRenderedSortSig="";apsRenderedExpanded=apsExpanded}const s=`${apsSortKey}:${apsSortDir}`;if(!apsHeaderCells||apsRenderedSortSig!==s){buildApsHeader(o,i);apsRenderedSortSig=s}const l=new Set;for(let d=0;dd.smallId===r):null;if(u){c.smallId=u.smallId;updateApsRow(c,u,a.indexOf(u),n,r);c.row.style.display=""}else{c.smallId=null;c.row.style.display="none"}if(t.dataset.visible!=="true"){t.dataset.visible="true";markHelperStatsLayoutDirty()}syncHelperStatsLayoutIfDirty()}function updateGoldPerMinuteBadge(){const t=ensureGoldPerMinuteBadge();if(!goldPerMinuteEnabled){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}goldPerMinuteRenderSignature="";return}const e=getHoveredPlayerInfoOverlay();if(!e){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}goldPerMinuteRenderSignature="";return}const n=getGoldPerMinuteForPlayer(e.player,getPlayerSmallId(e.player,0));const o=getPlayerTeamName(e.player);const r=formatGoldPerMinute(n);const i=o?getTeamColor(o,e.game):"";const a=o?getTeamColorBackground(o,e.game):"";const s=`${i}|${a}|${r}`;if(goldPerMinuteRenderSignature!==s){goldPerMinuteRenderSignature=s;if(o){t.style.setProperty("--team-accent-color",i);t.style.setProperty("--team-border-color",i);t.style.setProperty("--team-bg-color",a)}else{t.style.removeProperty("--team-accent-color");t.style.removeProperty("--team-border-color");t.style.removeProperty("--team-bg-color")}const l=t.querySelector(".openfront-helper-gpm-value");if(l){l.textContent=r}markHelperStatsLayoutDirty()}if(t.dataset.visible!=="true"){t.dataset.visible="true";syncHelperStatsContainerVisibility();markHelperStatsLayoutDirty()}syncHelperStatsLayoutIfDirty()}function syncTopGpmTickRegistration(){if(goldPerMinuteEnabled||topGoldPerMinuteEnabled){registerHelperTickListener(updateTopGoldPerMinuteBadge)}else{unregisterHelperTickListener(updateTopGoldPerMinuteBadge)}}function setGoldPerMinuteEnabled(t){goldPerMinuteEnabled=Boolean(t);if(!goldPerMinuteEnabled){if(!topGoldPerMinuteEnabled&&!economyHeatmapEnabled&&!teamBuildStatsEnabled){if(goldPerMinuteInterval!==null){clearInterval(goldPerMinuteInterval)}goldPerMinuteInterval=null;goldTrackers.clear();incomingGoldTransfers.clear();lastProcessedIncomingGoldTransferTick=null}syncTopGpmTickRegistration();updateTopGoldPerMinuteBadge();return}sampleGoldPerMinute();if(goldPerMinuteInterval===null){goldPerMinuteInterval=setInterval(sampleGoldPerMinute,GOLD_PER_MINUTE_SAMPLE_MS)}syncTopGpmTickRegistration()}function setTeamGoldPerMinuteEnabled(t){teamGoldPerMinuteEnabled=Boolean(t);if(!teamGoldPerMinuteEnabled){teamGoldPerMinuteAnimationFrame=null;lastTeamGoldPerMinuteRenderAt=0;teamGoldPerMinuteRenderSignature="";unregisterHelperTickListener(updateTeamGoldPerMinuteBadge);const e=document.getElementById(TEAM_GOLD_PER_MINUTE_BADGE_ID);if(e){e.dataset.visible="false"}if(!goldPerMinuteEnabled&&!topGoldPerMinuteEnabled&&!economyHeatmapEnabled){goldTrackers.clear();incomingGoldTransfers.clear();lastProcessedIncomingGoldTransferTick=null}syncHelperStatsContainerVisibility();return}sampleGoldPerMinute();registerHelperTickListener(updateTeamGoldPerMinuteBadge)}function setTopGoldPerMinuteEnabled(t){topGoldPerMinuteEnabled=Boolean(t);setGameLeaderboardHidden(topGoldPerMinuteEnabled);if(!topGoldPerMinuteEnabled){topGoldPerMinuteAnimationFrame=null;lastTopGoldPerMinuteRenderAt=0;topGoldPerMinuteRenderSignature="";if(!goldPerMinuteEnabled&&!economyHeatmapEnabled&&!teamBuildStatsEnabled){goldTrackers.clear();incomingGoldTransfers.clear();lastProcessedIncomingGoldTransferTick=null}syncTopGpmTickRegistration();updateTopGoldPerMinuteBadge();return}sampleGoldPerMinute();syncTopGpmTickRegistration()}const TEAM_BUILD_PANEL_ID="openfront-helper-team-build-stats";const TEAM_BUILD_STYLE_ID="openfront-helper-team-build-stats-styles";const TEAM_BUILD_POS_KEY="openfront-helper-team-build-stats-pos";const TEAM_BUILD_ALERT_CONTAINER_ID="openfront-helper-team-build-alert";const TEAM_BUILD_SILO_TYPE="Missile Silo";const TEAM_BUILD_WARN_MS=9e3;const TEAM_BUILD_ALERT_MS=15e3;const TEAM_BUILD_STRUCTURES=[{type:"City",emoji:"🏙️",label:"City"},{type:"Port",emoji:"⚓",label:"Port"},{type:"Factory",emoji:"🏭",label:"Factory"},{type:"Defense Post",emoji:"🛡️",label:"Defense Post"},{type:"SAM Launcher",emoji:"🛰️",label:"SAM Launcher"},{type:"Missile Silo",emoji:"🚀",label:"Missile Silo"}];const TEAM_BUILD_STAT_COLUMNS=[{key:"gpm",glyph:"💰",label:"Gold per minute"},{key:"owned",glyph:"🗺️",label:"Owned (% of map)"},{key:"maxTroops",glyph:"⚔️",label:"Max troops"}];function formatTeamBuildCount(t){const e=Number(t)||0;if(e>=1e6){return`${(e/1e6).toFixed(1)}M`}if(e>=1e3){return`${(e/1e3).toFixed(1)}K`}return String(Math.round(e))}function formatTeamBuildGpm(t){if(typeof t!=="number"||!Number.isFinite(t)){return"—"}const e=t<0?"-":"";const n=Math.abs(t);if(n>=1e6){return`${e}${(n/1e6).toFixed(1)}M`}if(n>=1e3){return`${e}${(n/1e3).toFixed(1)}K`}return`${e}${Math.round(n)}`}function formatTeamBuildOwned(t,e){let n=0;try{n=(Number(e?.numLandTiles?.())||0)-(Number(e?.numTilesWithFallout?.())||0)}catch(r){n=0}if(n<=0){return"—"}const o=(Number(t)||0)/n*100;return o>=10?`${Math.round(o)}%`:`${o.toFixed(1)}%`}function teamBuildIconUrl(t){return typeof window.__OFH_gameIconUrl==="function"&&window.__OFH_gameIconUrl(t.type)||""}function makeTeamBuildHeadGlyph(t){const e=teamBuildIconUrl(t);if(!e){const o=document.createElement("span");o.textContent=t.emoji;return o}const n=document.createElement("img");n.className="openfront-helper-tbs-icon";n.src=e;n.alt=t.label;n.draggable=false;n.addEventListener("error",()=>{const o=document.createElement("span");o.textContent=t.emoji;n.replaceWith(o)},{once:true});return n}let teamBuildStatsEnabled=false;let teamBuildSortKey=null;let teamBuildSortDir=1;const teamBuildPrevSiloCounts=new Map;const teamBuildSiloWarnings=new Map;const teamBuildAlertTimers=new Map;let teamBuildSeeded=false;let teamBuildGameRef=null;let teamBuildRenderSignature="";function ensureTeamBuildStatsStyles(){if(document.getElementById(TEAM_BUILD_STYLE_ID)){return}const t=document.createElement("style");t.id=TEAM_BUILD_STYLE_ID;t.textContent=` #${TEAM_BUILD_PANEL_ID} { position: fixed; top: var(--team-build-top, 96px); left: var(--team-build-left, 12px); z-index: 2147483647; display: none; width: max-content; max-width: min(440px, calc(100vw - 16px)); padding: 6px 7px 7px; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.92); color: #e2e8f0; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 11px; font-weight: 700; line-height: 1.1; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); pointer-events: auto; user-select: none; } #${TEAM_BUILD_PANEL_ID}[data-visible="true"] { display: block; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 5px; cursor: grab; touch-action: none; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-header:active { cursor: grabbing; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-title { color: rgba(226, 232, 240, 0.82); font-size: 10px; font-weight: 800; letter-spacing: 0.02em; text-transform: uppercase; white-space: nowrap; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-drag { color: rgba(148, 163, 184, 0.55); font-size: 11px; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-warn { display: none; margin-bottom: 5px; padding: 4px 6px; border: 1px solid rgba(248, 113, 113, 0.7); border-radius: 6px; background: rgba(69, 10, 10, 0.85); color: #fecaca; font-size: 10.5px; font-weight: 800; white-space: normal; animation: openfront-helper-tbs-pulse 1.05s ease-in-out infinite; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-warn[data-active="true"] { display: block; } @keyframes openfront-helper-tbs-pulse { 0%, 100% { border-color: rgba(248, 113, 113, 0.7); box-shadow: 0 0 0 rgba(248, 113, 113, 0); } 50% { border-color: rgba(248, 113, 113, 1); box-shadow: 0 0 10px rgba(248, 113, 113, 0.55); } } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-grid { display: grid; grid-template-columns: minmax(46px, 1fr) 34px repeat(${TEAM_BUILD_STRUCTURES.length}, 26px) repeat(${TEAM_BUILD_STAT_COLUMNS.length}, 46px); gap: 2px 4px; align-items: center; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-cell { text-align: center; font-variant-numeric: tabular-nums; } /* Economy columns: a touch wider, right-aligned, with a divider before them so they read apart from the structure counts. */ #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-stat { text-align: right; padding-left: 3px; font-size: 10px; white-space: nowrap; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-stat-first { border-left: 1px solid rgba(148, 163, 184, 0.22); padding-left: 5px; } /* Click-to-sort headers. */ #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-sortable { cursor: pointer; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-sortable:hover { color: #ffffff; filter: brightness(1.25); } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-sorted { color: #ffffff; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-sort-arrow { margin-left: 1px; font-size: 8px; opacity: 0.95; vertical-align: middle; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-head { display: flex; align-items: center; justify-content: center; padding-bottom: 3px; color: rgba(203, 213, 225, 0.85); font-size: 11px; line-height: 1; border-bottom: 1px solid rgba(148, 163, 184, 0.22); } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-icon { width: 14px; height: 14px; object-fit: contain; /* Game icons ship as black-fill SVGs; invert to white for the dark panel. */ filter: brightness(0) invert(1); opacity: 0.92; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-head.openfront-helper-tbs-silo { color: #fca5a5; } /* Tint the silo column's icon red to match its warning emphasis. */ #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-head.openfront-helper-tbs-silo .openfront-helper-tbs-icon { filter: brightness(0) saturate(100%) invert(67%) sepia(38%) saturate(1352%) hue-rotate(305deg) brightness(101%) contrast(98%); opacity: 1; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-total { color: #fcd34d; font-weight: 800; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-name { overflow: hidden; padding-right: 2px; color: var(--team-accent-color, #cbd5e1); text-align: left; text-overflow: ellipsis; white-space: nowrap; border-left: 3px solid var(--team-accent-color, rgba(148, 163, 184, 0.6)); padding-left: 5px; } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-value { color: rgba(226, 232, 240, 0.92); } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-value.openfront-helper-tbs-zero { color: rgba(148, 163, 184, 0.4); } #${TEAM_BUILD_PANEL_ID} .openfront-helper-tbs-value.openfront-helper-tbs-silo-has { color: #fca5a5; font-weight: 800; text-shadow: 0 0 6px rgba(248, 113, 113, 0.45); } /* ── Big center-screen urgent silo alert ─────────────────────────── */ #${TEAM_BUILD_ALERT_CONTAINER_ID} { position: fixed; top: 22%; left: 50%; transform: translateX(-50%); z-index: 2147483647; display: flex; flex-direction: column; align-items: center; gap: 12px; width: max-content; max-width: min(560px, calc(100vw - 24px)); /* Container is pass-through; each alert card re-enables its own events. */ pointer-events: none; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert { pointer-events: auto; display: flex; align-items: center; gap: 14px; padding: 14px 18px; border: 2px solid rgba(248, 113, 113, 0.95); border-radius: 12px; background: linear-gradient(180deg, rgba(60, 9, 9, 0.96), rgba(30, 6, 6, 0.96)); color: #fee2e2; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 30px rgba(248, 113, 113, 0.5); animation: openfront-helper-tbs-alert-in 0.28s cubic-bezier(0.2, 0.9, 0.3, 1.2), openfront-helper-tbs-alert-glow 1.15s ease-in-out infinite; } @keyframes openfront-helper-tbs-alert-in { from { opacity: 0; transform: translateY(-14px) scale(0.94); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes openfront-helper-tbs-alert-glow { 0%, 100% { box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 22px rgba(248, 113, 113, 0.35); } 50% { box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 40px rgba(248, 113, 113, 0.85); } } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-icon { flex: 0 0 auto; width: 38px; height: 38px; display: flex; align-items: center; justify-content: center; font-size: 30px; line-height: 1; animation: openfront-helper-tbs-alert-shake 0.9s ease-in-out infinite; } @keyframes openfront-helper-tbs-alert-shake { 0%, 100% { transform: rotate(-9deg); } 50% { transform: rotate(9deg); } } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-title { font-size: 12px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #fca5a5; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-msg { font-size: 16px; font-weight: 800; line-height: 1.2; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-team { color: var(--team-accent-color, #fee2e2); text-shadow: 0 0 8px rgba(0, 0, 0, 0.6); } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; margin-left: 6px; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-focus { display: inline-flex; align-items: center; gap: 5px; padding: 8px 13px; border: 1px solid rgba(248, 250, 252, 0.5); border-radius: 8px; background: rgba(248, 113, 113, 0.92); color: #450a0a; font-size: 13px; font-weight: 800; cursor: pointer; white-space: nowrap; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-focus:hover { background: #fecaca; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-close { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: 1px solid rgba(248, 250, 252, 0.25); border-radius: 8px; background: rgba(15, 23, 42, 0.5); color: #fecaca; font-size: 14px; font-weight: 800; cursor: pointer; } #${TEAM_BUILD_ALERT_CONTAINER_ID} .openfront-helper-tbs-alert-close:hover { background: rgba(248, 113, 113, 0.3); } `;(document.head||document.documentElement).appendChild(t)}function ensureTeamBuildAlertContainer(){ensureTeamBuildStatsStyles();let t=document.getElementById(TEAM_BUILD_ALERT_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=TEAM_BUILD_ALERT_CONTAINER_ID;t.setAttribute("aria-live","assertive");(document.body||document.documentElement).appendChild(t)}return t}function focusTeamBuildSilo(t,e){const n=document.querySelector("events-display");try{if(t&&n&&typeof n.emitGoToUnitEvent==="function"){n.emitGoToUnitEvent(t);return true}}catch(o){}try{const o=document.querySelector("actionable-events")||n||document.querySelector("events-display");if(o&&typeof o.emitGoToPlayerEvent==="function"&&Number.isFinite(Number(e))){o.emitGoToPlayerEvent(Number(e));return true}}catch(o){}return false}function dismissTeamBuildSiloAlert(t){const e=teamBuildAlertTimers.get(t);if(e!=null){clearTimeout(e);teamBuildAlertTimers.delete(t)}const n=document.getElementById(TEAM_BUILD_ALERT_CONTAINER_ID);if(n){for(const o of Array.from(n.children)){if(o.dataset&&o.dataset.team===t){o.remove()}}}}function clearAllTeamBuildSiloAlerts(){for(const e of teamBuildAlertTimers.values()){clearTimeout(e)}teamBuildAlertTimers.clear();const t=document.getElementById(TEAM_BUILD_ALERT_CONTAINER_ID);if(t){t.replaceChildren()}}function showTeamBuildSiloAlert(t,e,n,o){const r=ensureTeamBuildAlertContainer();dismissTeamBuildSiloAlert(t);const i=document.createElement("div");i.className="openfront-helper-tbs-alert";i.dataset.team=t;i.style.setProperty("--team-accent-color",e);const a=document.createElement("div");a.className="openfront-helper-tbs-alert-icon";a.textContent="🚨";const s=document.createElement("div");s.className="openfront-helper-tbs-alert-body";const l=document.createElement("span");l.className="openfront-helper-tbs-alert-title";l.textContent=tr("⚠ Missile Silo warning");const c=document.createElement("span");c.className="openfront-helper-tbs-alert-msg";const u=document.createElement("span");u.className="openfront-helper-tbs-alert-team";u.textContent=t;c.append(u,document.createTextNode(" "+tr("just built its first Missile Silo!")));s.append(l,c);const d=document.createElement("div");d.className="openfront-helper-tbs-alert-actions";const f=document.createElement("button");f.type="button";f.className="openfront-helper-tbs-alert-focus";f.textContent="🎯 "+tr("Focus");f.title=tr("Move camera to the silo");f.addEventListener("click",()=>{focusTeamBuildSilo(n,o)});const m=document.createElement("button");m.type="button";m.className="openfront-helper-tbs-alert-close";m.textContent="✕";m.title=tr("Close");m.addEventListener("click",()=>{dismissTeamBuildSiloAlert(t)});d.append(f,m);i.append(a,s,d);r.appendChild(i);const p=setTimeout(()=>{dismissTeamBuildSiloAlert(t)},TEAM_BUILD_ALERT_MS);teamBuildAlertTimers.set(t,p)}function ensureTeamBuildStatsPanel(){ensureTeamBuildStatsStyles();let t=document.getElementById(TEAM_BUILD_PANEL_ID);if(!t){t=document.createElement("div");t.id=TEAM_BUILD_PANEL_ID;t.setAttribute("aria-hidden","true");const e=document.createElement("div");e.className="openfront-helper-tbs-header";const n=document.createElement("span");n.className="openfront-helper-tbs-title";n.textContent="⚒ "+tr("Team build");const o=document.createElement("span");o.className="openfront-helper-tbs-drag";o.textContent="⠿";e.append(n,o);const r=document.createElement("div");r.className="openfront-helper-tbs-warn";const i=document.createElement("div");i.className="openfront-helper-tbs-grid";t.append(e,r,i);(document.body||document.documentElement).appendChild(t);makeTeamBuildStatsDraggable(t,e);applyStoredTeamBuildStatsPosition(t)}return t}function applyStoredTeamBuildStatsPosition(t){let e=null;try{e=JSON.parse(window.localStorage.getItem(TEAM_BUILD_POS_KEY)||"null")}catch(n){e=null}if(!e||!Number.isFinite(e.left)||!Number.isFinite(e.top)){return}t.style.setProperty("--team-build-left",`${Math.max(0,Math.min(e.left,window.innerWidth-60))}px`);t.style.setProperty("--team-build-top",`${Math.max(0,Math.min(e.top,window.innerHeight-30))}px`)}function makeTeamBuildStatsDraggable(t,e){let n=0;let o=0;let r=0;let i=0;let a=false;function s(c){if(!a){return}const u=Math.max(0,Math.min(r+(c.clientX-n),window.innerWidth-60));const d=Math.max(0,Math.min(i+(c.clientY-o),window.innerHeight-30));t.style.setProperty("--team-build-left",`${u}px`);t.style.setProperty("--team-build-top",`${d}px`)}function l(){if(!a){return}a=false;window.removeEventListener("pointermove",s);window.removeEventListener("pointerup",l);try{window.localStorage.setItem(TEAM_BUILD_POS_KEY,JSON.stringify({left:parseInt(t.style.getPropertyValue("--team-build-left"),10),top:parseInt(t.style.getPropertyValue("--team-build-top"),10)}))}catch(c){}}e.addEventListener("pointerdown",c=>{a=true;const u=t.getBoundingClientRect();r=u.left;i=u.top;n=c.clientX;o=c.clientY;t.style.setProperty("--team-build-left",`${r}px`);t.style.setProperty("--team-build-top",`${i}px`);window.addEventListener("pointermove",s);window.addEventListener("pointerup",l);c.preventDefault()})}function getTeamBuildRows(t){const e=TEAM_BUILD_STRUCTURES.map(d=>d.type);let n=[];try{n=t.units(...e)||[]}catch(d){n=[]}const o=new Map;const r=new Set;const i=new Map;const a=getCachedPlayerViews(t);let s=null;try{s=t.config?.()}catch(d){s=null}let l=0;for(const d of a){const f=getPlayerTeamName(d);const m=getPlayerSmallId(d,l);if(f&&f!=="Bot"){o.set(m,f);let p=false;try{p=Boolean(d?.isAlive?.())}catch(h){p=false}if(p){r.add(f);let h=i.get(f);if(!h){h={owned:0,maxTroops:0,gpmTotal:0,gpmTracked:0};i.set(f,h)}try{h.owned+=Number(d.numTilesOwned?.())||0}catch(g){}try{if(s?.maxTroops){h.maxTroops+=(Number(s.maxTroops(d))||0)/10}}catch(g){}const y=getGoldPerMinuteForPlayer(d,l);if(Number.isFinite(y)){h.gpmTotal+=y;h.gpmTracked+=1}}}l+=1}const c=new Map;function u(d){let f=c.get(d);if(!f){f={team:d,total:0,silo:0,siloUnit:null,siloOwnerSmallId:NaN,counts:{},owned:0,maxTroops:0,gpm:null};for(const m of TEAM_BUILD_STRUCTURES){f.counts[m.type]=0}c.set(d,f)}return f}for(const d of r){u(d)}for(const d of n){let f=NaN;try{f=Number(d.owner?.()?.smallID?.())}catch(g){continue}const m=o.get(f);if(!m){continue}let p=null;try{p=d.type?.()}catch(g){p=null}const h=u(m);if(p==null||!(p in h.counts)){continue}let y=false;try{y=Boolean(d.isUnderConstruction?.())}catch(g){y=false}if(!y){let g=1;try{const b=Number(d.level?.());if(Number.isFinite(b)&&b>0){g=b}}catch(b){g=1}h.counts[p]+=g;h.total+=g}if(p===TEAM_BUILD_SILO_TYPE){h.silo+=1;if(!h.siloUnit||!y&&h.siloUnderConstruction){h.siloUnit=d;h.siloOwnerSmallId=f;h.siloUnderConstruction=y}}}for(const d of c.values()){const f=i.get(d.team);if(f){d.owned=f.owned;d.maxTroops=f.maxTroops;d.gpm=f.gpmTracked>0?f.gpmTotal:null}}return sortTeamBuildRows(Array.from(c.values()))}function teamBuildRowSortValue(t,e){switch(e){case"total":return t.total;case"gpm":return Number.isFinite(t.gpm)?t.gpm:-Infinity;case"owned":return t.owned;case"maxTroops":return t.maxTroops;default:return t.counts&&t.counts[e]||0}}function sortTeamBuildRows(t){if(!teamBuildSortKey){return t.sort((o,r)=>r.silo-o.silo||r.total-o.total)}const e=teamBuildSortKey;const n=teamBuildSortDir;return t.sort((o,r)=>{const i=teamBuildRowSortValue(o,e);const a=teamBuildRowSortValue(r,e);if(i!==a){return n===1?a-i:i-a}if(r.total!==o.total){return r.total-o.total}return String(o.team).localeCompare(String(r.team))})}function setTeamBuildSort(t){if(teamBuildSortKey===t){teamBuildSortDir=teamBuildSortDir===1?-1:1}else{teamBuildSortKey=t;teamBuildSortDir=1}updateTeamBuildStatsPanel()}function attachTeamBuildSort(t,e){t.classList.add("openfront-helper-tbs-sortable");t.addEventListener("click",()=>setTeamBuildSort(e));if(teamBuildSortKey===e){t.classList.add("openfront-helper-tbs-sorted");const n=document.createElement("span");n.className="openfront-helper-tbs-sort-arrow";n.textContent=teamBuildSortDir===1?"▾":"▴";t.appendChild(n)}}function getMyTeamName(t){try{const e=t?.myPlayer?.();return getPlayerTeamName(e)}catch(e){return null}}function updateTeamBuildSiloWarnings(t,e,n,o){const r=new Set;for(const i of t){r.add(i.team);const a=teamBuildPrevSiloCounts.get(i.team)??0;if(teamBuildSeeded&&a===0&&i.silo>0&&i.team!==e){teamBuildSiloWarnings.set(i.team,n+TEAM_BUILD_WARN_MS);showTeamBuildSiloAlert(i.team,getTeamColor(i.team,o),i.siloUnit,i.siloOwnerSmallId)}teamBuildPrevSiloCounts.set(i.team,i.silo)}for(const i of Array.from(teamBuildPrevSiloCounts.keys())){if(!r.has(i)){teamBuildPrevSiloCounts.delete(i)}}for(const[i,a]of Array.from(teamBuildSiloWarnings.entries())){if(a<=n){teamBuildSiloWarnings.delete(i)}}}function renderTeamBuildGrid(t,e,n){const o=[];const r=document.createElement("span");r.className="openfront-helper-tbs-cell openfront-helper-tbs-head";r.textContent="";o.push(r);const i=document.createElement("span");i.className="openfront-helper-tbs-cell openfront-helper-tbs-head openfront-helper-tbs-total";i.textContent="Σ";i.title=tr("Total structures");attachTeamBuildSort(i,"total");o.push(i);for(const a of TEAM_BUILD_STRUCTURES){const s=document.createElement("span");s.className="openfront-helper-tbs-cell openfront-helper-tbs-head";if(a.type===TEAM_BUILD_SILO_TYPE){s.classList.add("openfront-helper-tbs-silo")}s.append(makeTeamBuildHeadGlyph(a));s.title=a.label;attachTeamBuildSort(s,a.type);o.push(s)}TEAM_BUILD_STAT_COLUMNS.forEach((a,s)=>{const l=document.createElement("span");l.className="openfront-helper-tbs-cell openfront-helper-tbs-head openfront-helper-tbs-stat";if(s===0){l.classList.add("openfront-helper-tbs-stat-first")}l.textContent=a.glyph;l.title=tr(a.label);attachTeamBuildSort(l,a.key);o.push(l)});for(const a of e){const s=getTeamColor(a.team,n);const l=document.createElement("span");l.className="openfront-helper-tbs-cell openfront-helper-tbs-name";l.style.setProperty("--team-accent-color",s);l.textContent=a.team;l.title=`${a.team} · ${a.total} công trình`;o.push(l);const c=document.createElement("span");c.className="openfront-helper-tbs-cell openfront-helper-tbs-value openfront-helper-tbs-total";c.textContent=String(a.total);o.push(c);for(const m of TEAM_BUILD_STRUCTURES){const p=a.counts[m.type]||0;const h=document.createElement("span");h.className="openfront-helper-tbs-cell openfront-helper-tbs-value";if(p===0){h.classList.add("openfront-helper-tbs-zero")}else if(m.type===TEAM_BUILD_SILO_TYPE){h.classList.add("openfront-helper-tbs-silo-has")}h.textContent=String(p);o.push(h)}const u=document.createElement("span");u.className="openfront-helper-tbs-cell openfront-helper-tbs-value openfront-helper-tbs-stat openfront-helper-tbs-stat-first";u.textContent=formatTeamBuildGpm(a.gpm);o.push(u);const d=document.createElement("span");d.className="openfront-helper-tbs-cell openfront-helper-tbs-value openfront-helper-tbs-stat";d.textContent=formatTeamBuildOwned(a.owned,n);o.push(d);const f=document.createElement("span");f.className="openfront-helper-tbs-cell openfront-helper-tbs-value openfront-helper-tbs-stat";f.textContent=formatTeamBuildCount(a.maxTroops);o.push(f)}t.replaceChildren(...o)}function hideTeamBuildStatsPanel(t){if(t.dataset.visible!=="false"){t.dataset.visible="false";t.setAttribute("aria-hidden","true")}teamBuildRenderSignature=""}function updateTeamBuildStatsPanel(){const t=ensureTeamBuildStatsPanel();if(!teamBuildStatsEnabled){hideTeamBuildStatsPanel(t);return}const e=typeof getOpenFrontGameContext==="function"?getOpenFrontGameContext():null;const n=e?.game;if(!n){hideTeamBuildStatsPanel(t);return}if(typeof sampleGoldPerMinuteIfDue==="function"){sampleGoldPerMinuteIfDue()}if(n!==teamBuildGameRef){teamBuildGameRef=n;teamBuildPrevSiloCounts.clear();teamBuildSiloWarnings.clear();clearAllTeamBuildSiloAlerts();teamBuildSeeded=false}const o=getTeamBuildRows(n);if(o.length===0){hideTeamBuildStatsPanel(t);return}const r=Date.now();const i=getMyTeamName(n);if(!teamBuildSeeded){for(const l of o){teamBuildPrevSiloCounts.set(l.team,l.silo)}teamBuildSeeded=true}else{updateTeamBuildSiloWarnings(o,i,r,n)}const a=o.filter(l=>teamBuildSiloWarnings.has(l.team)).map(l=>l.team);const s=`sort:${teamBuildSortKey||""}:${teamBuildSortDir};`+o.map(l=>{const c=TEAM_BUILD_STRUCTURES.map(d=>l.counts[d.type]||0).join(",");const u=l.gpm==null?"":Math.round(l.gpm);return`${l.team}|${getTeamColor(l.team,n)}|${c}|${u}|${l.owned}|${l.maxTroops}`}).join(";")+`#warn:${a.join(",")}`;if(teamBuildRenderSignature!==s){teamBuildRenderSignature=s;const l=t.querySelector(".openfront-helper-tbs-grid");if(l){renderTeamBuildGrid(l,o,n)}const c=t.querySelector(".openfront-helper-tbs-warn");if(c){if(a.length>0){c.textContent=tr("⚠ First silo: {teams}",{teams:a.join(", ")});c.dataset.active="true"}else{c.dataset.active="false";c.textContent=""}}}if(t.dataset.visible!=="true"){t.dataset.visible="true";t.setAttribute("aria-hidden","false")}}function setTeamBuildStatsEnabled(t){teamBuildStatsEnabled=Boolean(t);if(!teamBuildStatsEnabled){unregisterHelperTickListener(updateTeamBuildStatsPanel);teamBuildPrevSiloCounts.clear();teamBuildSiloWarnings.clear();clearAllTeamBuildSiloAlerts();teamBuildSeeded=false;teamBuildGameRef=null;teamBuildRenderSignature="";const e=document.getElementById(TEAM_BUILD_PANEL_ID);if(e){e.dataset.visible="false";e.setAttribute("aria-hidden","true")}return}registerHelperTickListener(updateTeamBuildStatsPanel)}function ensureTradeBalanceStyles(){if(document.getElementById(TRADE_BALANCE_STYLE_ID)){return}const t=document.createElement("style");t.id=TRADE_BALANCE_STYLE_ID;t.textContent=` #${TRADE_BALANCE_BADGE_ID} { display: none; position: fixed; z-index: 2147483647; bottom: calc(8px + env(safe-area-inset-bottom, 0px)); left: 560px; width: min(280px, calc(100vw - 16px)); padding: 7px 9px; border: 1px solid rgba(45, 212, 191, 0.38); border-radius: 8px; background: rgba(5, 28, 32, 0.9); color: #ccfbf1; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; font-size: 11px; font-weight: 800; line-height: 1.15; cursor: move; user-select: none; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.36), 0 0 16px rgba(45, 212, 191, 0.16); } #${TRADE_BALANCE_BADGE_ID}[data-visible="true"] { display: grid; gap: 5px; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-title { color: rgba(153, 246, 228, 0.82); font-size: 10px; letter-spacing: 0; text-transform: uppercase; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; padding: 4px 0 5px; border-bottom: 1px solid rgba(153, 246, 228, 0.16); } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-total { display: grid; gap: 2px; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-total-label { color: rgba(204, 251, 241, 0.7); font-size: 9px; text-transform: uppercase; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-total-value { color: #5eead4; font-size: 12px; font-variant-numeric: tabular-nums; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-roi[data-status="unprofitable"] { color: #f87171; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-roi[data-status="profitable"] { color: #4ade80; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-roi[data-status="unknown"] { color: rgba(204, 251, 241, 0.58); } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-rows { display: grid; gap: 3px; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-name { overflow: hidden; color: #e0f2fe; text-overflow: ellipsis; white-space: nowrap; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-value { color: #2dd4bf; font-variant-numeric: tabular-nums; white-space: nowrap; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-value-wrap { display: grid; gap: 2px; justify-items: end; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-direction { color: rgba(153, 246, 228, 0.72); font-size: 9px; text-transform: uppercase; white-space: nowrap; } #${TRADE_BALANCE_BADGE_ID} .openfront-helper-trade-empty { color: rgba(204, 251, 241, 0.72); } `;(document.head||document.documentElement).appendChild(t)}let tradeBadgeHovered=false;let tradeBadgeHideAt=0;const TRADE_BADGE_HIDE_GRACE_MS=2500;function hideTradeBadgeSoon(t){if(t.dataset.visible==="false"){tradeBadgeHideAt=0;return}if(tradeBadgeHovered){tradeBadgeHideAt=0;return}const e=Date.now();if(!tradeBadgeHideAt)tradeBadgeHideAt=e+TRADE_BADGE_HIDE_GRACE_MS;if(e>=tradeBadgeHideAt){tradeBadgeHideAt=0;t.dataset.visible="false";tradeBalanceRenderSignature="";syncHelperStatsContainerVisibility()}}function ensureTradeBalanceBadge(){ensureTradeBalanceStyles();let t=document.getElementById(TRADE_BALANCE_BADGE_ID);if(!t){t=document.createElement("div");t.id=TRADE_BALANCE_BADGE_ID;t.innerHTML=` ${tr("Trade balance")} ${tr("Total imports")} +0 ${tr("Total exports")} +0 ${tr("Factory/port spent")} 0 ${tr("Return on investment")} n/a ${tr("Break even")} ${tr("tracking")} `}if(t.parentElement!==document.body){(document.body||document.documentElement).appendChild(t);makeGoldStatPanelDraggable(t,t,"openfront-helper-trade-balances-pos");applyStoredGoldStatPanelPosition(t,"openfront-helper-trade-balances-pos");t.addEventListener("pointerenter",()=>{tradeBadgeHovered=true});t.addEventListener("pointerleave",()=>{tradeBadgeHovered=false})}return t}function getTradeBalanceTracker(t){const e=String(t);let n=tradeBalanceTrackers.get(e);if(!n){n=new Map;tradeBalanceTrackers.set(e,n)}return n}function addExportPartnerProfitSource(t,e,n,o){if(!Number.isFinite(t)||!Number.isFinite(Number(e))||!n||!Number.isFinite(o)||o<=0){return}const r=String(t);let i=exportPartnerSourceTrackers.get(r);if(!i){i=new Map;exportPartnerSourceTrackers.set(r,i)}const a=n.tile?.();const s=`${e}:${n.id?.()??a}`;const l=i.get(s)??{partnerId:Number(e),tile:a,type:String(n?.type?.()??"Industry"),total:0};l.tile=a??l.tile;l.type=String(n?.type?.()??l.type);l.total+=o;i.set(s,l)}function addTradeBalance(t,e,n,o,r="unknown",i=null){if(!Number.isFinite(t)||!Number.isFinite(o)||o<=0){return}const a=getTradeBalanceTracker(t);const s=String(e||n||"unknown");const l=a.get(s)??{partnerId:e,partnerName:n||"Unknown",total:0,imports:0,exports:0,unknown:0,firstExportAt:null,lastExportAt:null};l.partnerId=e??l.partnerId;l.partnerName=n||l.partnerName;l.total+=o;if(r==="import"){l.imports+=o}else if(r==="export"){const c=Date.now();l.firstExportAt??=c;l.lastExportAt=c;l.exports+=o;addExportPartnerProfitSource(t,e,i,o)}else{l.unknown+=o}a.set(s,l)}function getTradeEventGoldAmount(t){if(typeof t==="bigint"){return Number(t)}return Number(t)}function findOwnedStructureAtTile(t,e,n,o=null,r=2){if(e==null){return null}function i(a){try{return a?.isActive?.()&&n.includes(a?.type?.())&&(o===null||getPlayerSmallId(a?.owner?.())===getPlayerSmallId(o))}catch(s){return false}}try{for(const a of t?.units?.(...n)||[]){if(i(a)&&a.tile?.()===e){return a}}}catch(a){}try{const a=t?.nearbyUnits?.(e,r,n);return a?.find(s=>i(s?.unit))?.unit??null}catch(a){return null}}function getTradeShipSourceUnit(t,e){const n=tradeShipSourceTrackers.get(String(e));if(!n){return null}const o=Number(n.sourceUnitId);if(Number.isFinite(o)){try{const r=t?.unit?.(o);if(r?.isActive?.()&&String(r?.type?.()??"")==="Port"){return r}}catch(r){}}return findOwnedStructureAtTile(t,n.sourceTile,["Port"],n.sourceOwner)}function refreshTradeShipSourceTrackers(t){const e=Date.now();const n=new Set;for(const o of t?.units?.("Trade Ship")||[]){const r=o?.id?.();if(!Number.isFinite(r)||!o?.isActive?.()){continue}n.add(String(r));let i=tradeShipSourceTrackers.get(String(r));if(!i){i={sourceUnitId:null,sourceTile:null,sourceOwner:o?.owner?.()??null,targetUnitId:toFiniteNumber(o?.targetUnitId?.(),NaN),lastSeenAt:e}}i.lastSeenAt=e;if(!Number.isFinite(Number(i.sourceUnitId))){const a=findOwnedStructureAtTile(t,o?.tile?.(),["Port"],o?.owner?.());if(a){const s=toFiniteNumber(a?.id?.(),NaN);i.sourceUnitId=Number.isFinite(s)?s:i.sourceUnitId;i.sourceTile=a?.tile?.()??i.sourceTile;i.sourceOwner=a?.owner?.()??i.sourceOwner}}tradeShipSourceTrackers.set(String(r),i)}for(const o of tradeShipSourceTrackers.keys()){if(!n.has(o)){const r=tradeShipSourceTrackers.get(o);if(!Number.isFinite(r?.lastSeenAt)||e-r.lastSeenAt>3e4){tradeShipSourceTrackers.delete(o)}}}}function getTradeDirection(t,e,n){const o=getTradeRoute(t,e,n);if(!o){return"unknown"}return o.sourceId===t?"export":"import"}function collectCompletedBoatTradeRoutes(t,e){const n=[];for(const o of Object.values(e||{})){if(!Array.isArray(o)){continue}for(const r of o){if(r?.unitType!=="Trade Ship"||r?.isActive!==false||r?.targetUnitId===void 0){continue}const i=t?.unit?.(r.targetUnitId);const a=i?.owner?.();const s=a?getPlayerSmallId(a):NaN;const l=Number(r.ownerID);if(!Number.isFinite(l)||!Number.isFinite(s)||l===s){continue}n.push({shipId:String(r.id),sourceId:l,destinationId:s,sourceUnit:getTradeShipSourceUnit(t,r.id),destinationUnit:i})}}return n}function getTradeRoute(t,e,n){return n.find(o=>o.sourceId===t&&o.destinationId===e||o.destinationId===t&&o.sourceId===e)}function takeTradeRoute(t,e,n){const o=n.findIndex(r=>r.sourceId===t&&r.destinationId===e||r.destinationId===t&&r.sourceId===e);if(o<0){return null}return n.splice(o,1)[0]}function isTrainStationUnit(t,e=["City","Port"],n=null){try{const o=t?.type?.();if(!e.includes(o)||!t?.hasTrainStation?.()){return false}if(!n){return true}return getPlayerSmallId(t?.owner?.())===getPlayerSmallId(n)}catch(o){return false}}function findTrainStationAtTile(t,e,n=["City","Port"],o=null){if(e==null){return null}for(const i of t?.units?.(...n)||[]){if(isTrainStationUnit(i,n,o)&&i.tile?.()===e){return i}}const r=t?.nearbyUnits?.(e,2,n);return r?.find(i=>isTrainStationUnit(i?.unit,n,o))?.unit??null}function findNearestOwnedFactory(t,e,n){if(e==null||!n){return null}let o=null;let r=Number.POSITIVE_INFINITY;try{for(const a of n?.units?.("Factory")||[]){if(!a?.isActive?.()||!a?.hasTrainStation?.()){continue}const s=a.tile?.();if(s==null){continue}const l=toFiniteNumber(t?.manhattanDist?.(e,s),NaN);if(Number.isFinite(l)&&l=0?"+":"-";if(e>=1e9){return`${n}${(e/1e9).toFixed(1)}B`}if(e>=1e6){return`${n}${(e/1e6).toFixed(1)}M`}if(e>=1e3){return`${n}${(e/1e3).toFixed(1)}K`}return`${n}${Math.round(e)}`}function formatTradeSpendAmount(t){if(!Number.isFinite(t)||t<=0){return"0"}return formatTradeBalanceAmount(t).replace(/^\+/,"")}function formatTradeRoi(t,e){if(!Number.isFinite(t)||!Number.isFinite(e)||e<=0){return"n/a"}const n=t/e;return`${(n*100).toFixed(n>=1?0:1)}%`}function formatBreakEvenDuration(t){if(!Number.isFinite(t)||t<0){return"tracking"}const e=Math.ceil(t/1e3);if(e<60){return`${e}s`}const n=Math.ceil(e/60);if(n<60){return`${n} minutes`}const o=Math.floor(n/60);const r=n%60;return r>0?`${o}h ${r} minutes`:`${o}h`}function formatBreakEvenEstimate(t,e,n){if(!Number.isFinite(e)||e<=0){return"n/a"}if(t>=e){return"now"}if(!Number.isFinite(t)||t<=0||!Number.isFinite(n)||n<=0){return"tracking"}const o=t/n;return formatBreakEvenDuration((e-t)/o)}function getTradeRoiStatus(t,e){if(!Number.isFinite(t)||!Number.isFinite(e)||e<=0){return"unknown"}return t>=e?"profitable":"unprofitable"}function getFactoryPortSpendForLevels(t,e){let n=0;const o=Math.max(0,Math.floor(Number(t)||0));const r=Math.max(0,Math.floor(Number(e)||0));for(let i=0;il.level){addFactoryPortSpend(i,a-l.level)}l.ownerId=i;l.level=a}for(const n of factoryPortUnitTrackers.keys()){if(!e.has(n)){factoryPortUnitTrackers.delete(n)}}}function getFactoryPortSpendTotal(t){const e=getPlayerSmallId(t);return factoryPortSpendTrackers.get(String(e))?.total??0}function getTradeBalanceTotals(t){const e=getPlayerSmallId(t);const n=tradeBalanceTrackers.get(String(e));const o={imports:0,exports:0,firstExportAt:null,lastExportAt:null};if(!n){return o}for(const r of n.values()){o.imports+=r.imports||0;o.exports+=r.exports||0;if(Number.isFinite(r.firstExportAt)){o.firstExportAt=o.firstExportAt===null?r.firstExportAt:Math.min(o.firstExportAt,r.firstExportAt)}if(Number.isFinite(r.lastExportAt)){o.lastExportAt=o.lastExportAt===null?r.lastExportAt:Math.max(o.lastExportAt,r.lastExportAt)}}return o}function getTradeBalanceDirection(t){if((t.exports||0)>(t.imports||0)){return"Player exports more to them"}if((t.imports||0)>(t.exports||0)){return"Player imports more from them"}return"Imports and exports are balanced"}function getTradeBalanceEntries(t){const e=getPlayerSmallId(t);const n=tradeBalanceTrackers.get(String(e));if(!n){return[]}return Array.from(n.values()).filter(o=>Number.isFinite(o.total)&&o.total>0).sort((o,r)=>r.total-o.total).slice(0,5)}function getTradeBalanceRenderSignature(t,e,n,o){let r=2166136261;r=mixHashNumber(r,getPlayerSmallId(t));r=mixHashNumber(r,Math.round(e.imports||0));r=mixHashNumber(r,Math.round(e.exports||0));r=mixHashNumber(r,Number(e.firstExportAt)||0);r=mixHashNumber(r,Math.round(n||0));r=mixHashNumber(r,o.length);for(const i of o){r=mixHashNumber(r,Number(i.partnerId)||0);r=mixHashString(r,i.partnerName);r=mixHashNumber(r,Math.round(i.total||0));r=mixHashNumber(r,Math.round(i.imports||0));r=mixHashNumber(r,Math.round(i.exports||0))}return r>>>0}function findTradePartnerPlayer(t,e){const n=Number(e?.partnerId);if(Number.isFinite(n)){const o=t.find(r=>getPlayerSmallId(r)===n);if(o){return o}}return findPlayerByTradeName(t,e?.partnerName)}function getExportPartnerEntries(t){const e=getPlayerSmallId(t);const n=tradeBalanceTrackers.get(String(e));if(!n){return[]}return Array.from(n.values()).filter(o=>Number.isFinite(o.exports)&&o.exports>0).sort((o,r)=>r.exports-o.exports)}function collectExportPartnerHeatmapSources(t,e){const n=[];const o=getPlayerSmallId(e);const r=exportPartnerSourceTrackers.get(String(o));if(!r){return n}const i=getCachedPlayerViews(t);const a=new Set(i.filter(c=>c?.isAlive?.()).map(c=>getPlayerSmallId(c)));const s=Array.from(r.values()).filter(c=>c.tile!=null&&Number.isFinite(c.total)&&c.total>0&&a.has(Number(c.partnerId)));const l=Math.max(1,...s.map(c=>c.total||0));for(const c of s){const u=String(c.type??"Industry");if(u!=="City"&&u!=="Port"){continue}const d=Math.max(.2,(c.total||0)/l);const f=u==="City"?1.25:1;addEconomicSource(n,c.tile,(8+(u==="City"?6:8))*d*f,u)}return n}function updateTradeBalanceBadge(){const t=ensureTradeBalanceBadge();if(!tradeBalancesEnabled){if(t.dataset.visible!=="false"){t.dataset.visible="false";syncHelperStatsContainerVisibility()}return}const e=getOpenFrontGameContext();if(e?.game){processTradeBalanceUpdates(e.game)}const n=getHoveredPlayerInfoOverlay();if(!n?.game){hideTradeBadgeSoon(t);return}const o=getTradeBalanceTotals(n.player);const r=getFactoryPortSpendTotal(n.player);const i=getTradeBalanceEntries(n.player);const a=getTradeBalanceRenderSignature(n.player,o,r,i);const s=Number.isFinite(o.firstExportAt)?Date.now()-o.firstExportAt:NaN;if(a!==tradeBalanceRenderSignature){tradeBalanceRenderSignature=a;const c=t.querySelector(".openfront-helper-trade-imports");const u=t.querySelector(".openfront-helper-trade-exports");const d=t.querySelector(".openfront-helper-trade-factory-port-spend");const f=t.querySelector(".openfront-helper-trade-roi");if(c){c.textContent=formatTradeBalanceAmount(o.imports)}if(u){u.textContent=formatTradeBalanceAmount(o.exports)}if(d){d.textContent=formatTradeSpendAmount(r)}if(f){f.textContent=formatTradeRoi(o.exports,r);f.dataset.status=getTradeRoiStatus(o.exports,r)}const m=t.querySelector(".openfront-helper-trade-rows");if(m){if(i.length===0){m.innerHTML=`${tr("No observed trade yet")}`}else{m.replaceChildren(...i.map(p=>{const h=document.createElement("span");h.className="openfront-helper-trade-row";const y=document.createElement("span");y.className="openfront-helper-trade-name";y.textContent=p.partnerName;const g=document.createElement("span");g.className="openfront-helper-trade-value-wrap";const b=document.createElement("span");b.className="openfront-helper-trade-value";b.textContent=formatTradeBalanceAmount(p.total);const w=document.createElement("span");w.className="openfront-helper-trade-direction";w.textContent=getTradeBalanceDirection(p);g.append(b,w);h.append(y,g);return h}))}}markHelperStatsLayoutDirty()}const l=t.querySelector(".openfront-helper-trade-break-even");if(l){const c=formatBreakEvenEstimate(o.exports,r,s);if(l.textContent!==c){l.textContent=c}}tradeBadgeHideAt=0;if(t.dataset.visible!=="true"){t.dataset.visible="true";syncHelperStatsContainerVisibility();markHelperStatsLayoutDirty()}syncHelperStatsLayoutIfDirty()}function setTradeBalancesEnabled(t){tradeBalancesEnabled=Boolean(t);if(!tradeBalancesEnabled){tradeBalanceAnimationFrame=null;lastProcessedTradeBalanceTick=null;lastTradeBalanceRenderAt=0;tradeBalanceRenderSignature="";unregisterHelperTickListener(updateTradeBalanceBadge);if(!exportPartnerHeatmapEnabled&&!economyHeatmapEnabled){tradeBalanceTrackers.clear();exportPartnerSourceTrackers.clear();factoryPortSpendTrackers.clear();factoryPortUnitTrackers.clear();trainTradeTrackers.clear();tradeShipSourceTrackers.clear()}const e=document.getElementById(TRADE_BALANCE_BADGE_ID);if(e){e.dataset.visible="false"}syncHelperStatsContainerVisibility();return}registerHelperTickListener(updateTradeBalanceBadge)}const NUKE_UNIT_TYPES=["Atom Bomb","Hydrogen Bomb","MIRV Warhead"];const NUKE_RELATION_RANK={ally:1,self:2,enemy:3};const NUKE_ETA_TYPES=new Set(["Atom Bomb","Hydrogen Bomb"]);function ensureNukeLandingStyles(){if(document.getElementById(NUKE_LANDING_STYLE_ID)){return}const t=document.createElement("style");t.id=NUKE_LANDING_STYLE_ID;t.textContent=` #${NUKE_LANDING_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483646; pointer-events: none; } #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-zone { position: fixed; left: 0; top: 0; width: var(--nuke-diameter); height: var(--nuke-diameter); border: 2px dashed var(--nuke-color, rgba(248, 113, 113, 0.92)); border-radius: 50%; background: var(--nuke-bg, rgba(127, 29, 29, 0.18)); box-shadow: 0 0 18px var(--nuke-glow, rgba(248, 113, 113, 0.36)), inset 0 0 24px var(--nuke-inner-glow, rgba(248, 113, 113, 0.18)); transform: translate3d(var(--nuke-tx, 0px), var(--nuke-ty, 0px), 0) translate(-50%, -50%); will-change: transform; } #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-zone::before, #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-zone::after { content: ""; position: absolute; left: 50%; top: 50%; background: var(--nuke-cross-color, rgba(254, 202, 202, 0.94)); box-shadow: 0 0 10px var(--nuke-cross-glow, rgba(248, 113, 113, 0.6)); transform: translate(-50%, -50%); } #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-zone::before { width: 28px; height: 2px; } #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-zone::after { width: 2px; height: 28px; } #${NUKE_LANDING_CONTAINER_ID} .openfront-helper-nuke-label { position: fixed; left: 0; top: 0; padding: 4px 8px; border: 1px solid var(--nuke-label-border, rgba(248, 113, 113, 0.52)); border-radius: 8px; background: rgba(7, 12, 18, 0.86); color: var(--nuke-label-color, #fecaca); font: 900 11px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.92); transform: translate3d(var(--nuke-tx, 0px), var(--nuke-label-ty, 0px), 0) translate(-50%, -100%); will-change: transform; white-space: nowrap; } `;(document.head||document.documentElement).appendChild(t)}function ensureNukeLandingContainer(){ensureNukeLandingStyles();let t=document.getElementById(NUKE_LANDING_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=NUKE_LANDING_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}return t}function getNukePredictionRelation(t,e){const n=e?.owner?.();const o=getPlayerRelationToMyPlayer(t,n);return o==="enemy"||o==="ally"||o==="self"?o:null}function getNukePredictionColors(t){if(t==="ally"){return{color:"rgba(74, 222, 128, 0.92)",bg:"rgba(20, 83, 45, 0.18)",glow:"rgba(74, 222, 128, 0.36)",innerGlow:"rgba(74, 222, 128, 0.18)",crossColor:"rgba(187, 247, 208, 0.94)",crossGlow:"rgba(74, 222, 128, 0.6)",labelBorder:"rgba(74, 222, 128, 0.52)",labelColor:"#bbf7d0"}}if(t==="self"){return{color:"rgba(56, 189, 248, 0.92)",bg:"rgba(8, 47, 73, 0.20)",glow:"rgba(56, 189, 248, 0.38)",innerGlow:"rgba(56, 189, 248, 0.18)",crossColor:"rgba(186, 230, 253, 0.96)",crossGlow:"rgba(56, 189, 248, 0.65)",labelBorder:"rgba(56, 189, 248, 0.55)",labelColor:"#bae6fd"}}return{color:"rgba(248, 113, 113, 0.92)",bg:"rgba(127, 29, 29, 0.18)",glow:"rgba(248, 113, 113, 0.36)",innerGlow:"rgba(248, 113, 113, 0.18)",crossColor:"rgba(254, 202, 202, 0.94)",crossGlow:"rgba(248, 113, 113, 0.6)",labelBorder:"rgba(248, 113, 113, 0.52)",labelColor:"#fecaca"}}function getNukeLandingRadius(t,e){try{const n=t?.config?.().nukeMagnitudes?.(e.type());const o=Number(n?.outer??n?.inner);if(Number.isFinite(o)&&o>0){return o}}catch(n){}return e?.type?.()==="Hydrogen Bomb"?160:70}function getNukeLandingScreenRadius(t,e,n){const o=Number(t?.scale);if(Number.isFinite(o)&&o>0){return n*o}try{const r=t.worldToScreenCoordinates({x:e.worldX+n,y:e.worldY});const i=r.x-e.x;const a=r.y-e.y;return Math.hypot(i,a)}catch(r){return n}}const nukeLandingEntries=new Map;let nukeScanCache=[];let lastNukeScanAt=0;const NUKE_SCAN_MS=250;const _nukeWorldQueryArg={x:0,y:0};const _nukeRadiusReusePos={x:0,y:0,worldX:0,worldY:0};const nukeFlightById=new Map;function computeNukeRemainingTicks(t,e,n){try{if(typeof UniversalPathFinding==="undefined"||!UniversalPathFinding.Parabola||e===void 0||e===null||n===void 0||n===null){return null}const o=t.config?.().defaultNukeSpeed?.()??8;const r=UniversalPathFinding.Parabola(t,{increment:o,distanceBasedHeight:true,directionUp:true});const i=r.findPath(e,n);if(!Array.isArray(i)||i.length===0){return null}if(i.length===1){return 1}let a=0;for(let s=1;s0?o:8))+1}catch(o){return null}}function collectNukeScan(t){const e=new Map;const n=new Set;const o=typeof t.ticks==="function"?t.ticks():0;for(const r of t.units(...NUKE_UNIT_TYPES)){if(!r?.isActive?.()){continue}const i=getNukePredictionRelation(t,r);if(!i){continue}const a=r.targetTile?.();if(a===void 0){continue}let s=null;const l=r.type?.();const c=r.id?.();if(NUKE_ETA_TYPES.has(l)&&c!==void 0){n.add(c);let m=nukeFlightById.get(c);if(m===void 0){const p=computeNukeRemainingTicks(t,r.tile?.(),a);if(p!==null){m={firstTick:o,remainTicks:p};nukeFlightById.set(c,m)}}if(m!==void 0){const p=m.remainTicks-(o-m.firstTick);s=Math.round(Math.max(0,p/10)*10)/10}}const u=`tile-${a}`;const d=getNukeLandingRadius(t,r);const f=e.get(u);if(f){f.count+=1;if(d>f.worldRadius){f.worldRadius=d}if((NUKE_RELATION_RANK[i]??0)>(NUKE_RELATION_RANK[f.relation]??0)){f.relation=i}if(s!==null&&(f.eta===null||s0){for(const r of nukeFlightById.keys()){if(!n.has(r)){nukeFlightById.delete(r)}}}return Array.from(e.values())}function pruneNukeEntries(){const t=new Set;for(const e of nukeScanCache){t.add(e.landingId)}for(const[e,n]of nukeLandingEntries){if(!t.has(e)){n.zone.remove();n.label.remove();nukeLandingEntries.delete(e)}}}function ensureNukeLandingEntry(t,e){let n=nukeLandingEntries.get(e);if(n){return n}const o=document.createElement("div");o.className="openfront-helper-nuke-zone";o.dataset.nukeId=e;t.appendChild(o);const r=document.createElement("div");r.className="openfront-helper-nuke-label";r.dataset.nukeId=e;t.appendChild(r);n={zone:o,label:r,hidden:false,tx:NaN,ty:NaN,labelTy:NaN,radius:NaN,relation:"",count:-1,eta:NaN};nukeLandingEntries.set(e,n);return n}function hideNukeEntry(t){if(!t.hidden){t.zone.hidden=true;t.label.hidden=true;t.hidden=true}}function applyNukeColors(t,e,n){t.style.setProperty("--nuke-color",n.color);t.style.setProperty("--nuke-bg",n.bg);t.style.setProperty("--nuke-glow",n.glow);t.style.setProperty("--nuke-inner-glow",n.innerGlow);t.style.setProperty("--nuke-cross-color",n.crossColor);t.style.setProperty("--nuke-cross-glow",n.crossGlow);e.style.setProperty("--nuke-label-border",n.labelBorder);e.style.setProperty("--nuke-label-color",n.labelColor)}function syncNukePrediction(){if(!nukePredictionEnabled){document.getElementById(NUKE_LANDING_CONTAINER_ID)?.remove();nukeLandingEntries.clear();nukeFlightById.clear();nukeScanCache=[];lastNukeScanAt=0;nukeLandingAnimationFrame=null;return}const t=ensureNukeLandingContainer();const e=getOpenFrontGameContext();if(!e?.game||!e?.transform){if(nukeLandingEntries.size>0){for(const i of nukeLandingEntries.values()){i.zone.remove();i.label.remove()}nukeLandingEntries.clear()}nukeFlightById.clear();nukeScanCache=[];lastNukeScanAt=0;nukeLandingAnimationFrame=requestAnimationFrame(syncNukePrediction);return}const n=performance.now();if(n-lastNukeScanAt>=NUKE_SCAN_MS){nukeScanCache=collectNukeScan(e.game);pruneNukeEntries();lastNukeScanAt=n}const o=window.innerWidth;const r=window.innerHeight;for(let i=0;io+300||s.y>r+300){const m=nukeLandingEntries.get(a.landingId);if(m){hideNukeEntry(m)}continue}_nukeRadiusReusePos.x=s.x;_nukeRadiusReusePos.y=s.y;_nukeRadiusReusePos.worldX=a.worldX;_nukeRadiusReusePos.worldY=a.worldY;const l=Math.max(12,getNukeLandingScreenRadius(e.transform,_nukeRadiusReusePos,a.worldRadius));const c=ensureNukeLandingEntry(t,a.landingId);if(c.hidden){c.zone.hidden=false;c.label.hidden=false;c.hidden=false}const u=s.x;const d=s.y;const f=d-l-10;if(c.tx!==u){c.zone.style.setProperty("--nuke-tx",`${u}px`);c.label.style.setProperty("--nuke-tx",`${u}px`);c.tx=u}if(c.ty!==d){c.zone.style.setProperty("--nuke-ty",`${d}px`);c.ty=d}if(c.labelTy!==f){c.label.style.setProperty("--nuke-label-ty",`${f}px`);c.labelTy=f}if(c.radius!==l){c.zone.style.setProperty("--nuke-diameter",`${l*2}px`);c.radius=l}if(c.relation!==a.relation){applyNukeColors(c.zone,c.label,getNukePredictionColors(a.relation));c.relation=a.relation;c.count=-1}if(c.count!==a.count||c.eta!==a.eta){const m=a.relation==="self"?tr("My nuke"):a.relation==="ally"?tr("Ally nuke"):tr("Enemy nuke");const p=a.count>1?` ${a.count}x`:"";const h=a.eta!=null?` · ${a.eta.toFixed(1)}s`:"";c.label.textContent=`${m}${p}${h}`;c.count=a.count;c.eta=a.eta}}nukeLandingAnimationFrame=requestAnimationFrame(syncNukePrediction)}function setNukePredictionEnabled(t){nukePredictionEnabled=Boolean(t);if(!nukePredictionEnabled){if(nukeLandingAnimationFrame!==null){cancelAnimationFrame(nukeLandingAnimationFrame)}nukeLandingAnimationFrame=null;document.getElementById(NUKE_LANDING_CONTAINER_ID)?.remove();nukeLandingEntries.clear();nukeFlightById.clear();return}if(nukeLandingAnimationFrame===null){syncNukePrediction()}}const NUKE_SUGGESTION_TYPES=[{id:"atom",type:"Atom Bomb",label:"Atom",color:"rgba(250, 204, 21, 0.95)",fill:"rgba(250, 204, 21, 0.16)",glow:"rgba(250, 204, 21, 0.36)",maxRawSeeds:72,maxRefineSeeds:10},{id:"hydrogen",type:"Hydrogen Bomb",label:"Hydrogen",color:"rgba(34, 211, 238, 0.95)",fill:"rgba(34, 211, 238, 0.13)",glow:"rgba(34, 211, 238, 0.36)",maxRawSeeds:58,maxRefineSeeds:8}];const NUKE_ECONOMIC_SUGGESTION={id:"economic",label:"Economy",color:"rgba(74, 222, 128, 0.95)",fill:"rgba(74, 222, 128, 0.14)",glow:"rgba(74, 222, 128, 0.36)",maxRawSeeds:64,maxRefineSeeds:8};const NUKE_ECONOMIC_HYDROGEN_MIN_GAIN=5e5;const NUKE_ECONOMIC_HYDROGEN_GAIN_RATIO=1.2;const NUKE_SUGGESTION_FALLBACK_COSTS={"Atom Bomb":75e4,"Hydrogen Bomb":5e6};const AUTO_NUKE_ACTION_COOLDOWN_MS=600;const AUTO_NUKE_PATCH_RETRY_MS=700;const AUTO_NUKE_MENU_ITEM_PREFIX="openfront-helper-auto-nuke";const AUTO_NUKE_CONTEXT_MENU_ID="openfront-helper-auto-nuke-menu";const AUTO_NUKE_CONTEXT_MENU_STYLE_ID="openfront-helper-auto-nuke-menu-styles";const AUTO_NUKE_PROCESS_PANEL_ID="openfront-helper-auto-nuke-process";const AUTO_NUKE_PROCESS_PANEL_STYLE_ID="openfront-helper-auto-nuke-process-styles";const AUTO_NUKE_SEQUENCE_DELAY_MS=60;const AUTO_NUKE_FALLBACK_LANDING_MS=6e4;const AUTO_NUKE_PROCESS_COMPLETE_HIDE_MS=3500;const AUTO_NUKE_GAME_TICK_MS=100;const AUTO_NUKE_MAX_SAM_BURN_SHOTS_PER_STACK=160;const AUTO_NUKE_SAM_STACK_SAFETY_BURN_PER_EXTRA_LEVEL=1;const AUTO_NUKE_SAM_STACK_SAFETY_BURN_LEVEL_FRACTION=.35;const AUTO_NUKE_SAM_STACK_SAFETY_BURN_CAP=10;const AUTO_NUKE_SECOND_PASS_POLL_MS=1e3;const AUTO_NUKE_SECOND_PASS_MAX_WAIT_MS=8*60*1e3;const AUTO_NUKE_SECOND_PASS_READY_SLOT_FRACTION=.6;const AUTO_NUKE_SECOND_PASS_SPREAD_MULTIPLIER=1.65;const AUTO_NUKE_THIRD_PASS_READY_SLOT_FRACTION=.7;const AUTO_NUKE_THIRD_PASS_SPREAD_MULTIPLIER=2.25;const AUTO_NUKE_SECOND_PASS_LAUNCH_SLOT_FRACTION=.58;const AUTO_NUKE_MAX_SAM_CLEAR_WAVES=10;const AUTO_NUKE_MAX_FOLLOWUP_WAVES=4;const AUTO_NUKE_MAX_DESTRUCTION_WAVES=60;const AUTO_NUKE_FOLLOWUP_SPREAD_MULTIPLIER=1.5;const AUTO_NUKE_BLOCKING_STACK_BASE_CAPACITY=10;const AUTO_NUKE_BLOCKING_STACK_SCORE_FRACTION=.55;const AUTO_NUKE_MAX_PREEMPTIVE_SAM_WAVES=4;const AUTO_NUKE_MAX_CANDIDATES_PER_SPEC=28;const AUTO_NUKE_MIN_MARGINAL_SCORE=1;const AUTO_NUKE_DESTRUCTION_BUILDING_BONUS=25e5;const AUTO_NUKE_DESTRUCTION_CONFIRMATION_SHOTS=2;const AUTO_NUKE_DESTRUCTION_SAM_BONUS=35e5;const AUTO_NUKE_DESTRUCTION_SAM_OVERSHOOT_SHOTS=2;const AUTO_NUKE_MAX_SAM_OVERSHOOT_SHOTS=5;const AUTO_NUKE_SAM_CONSTRUCTION_TICKS=300;const AUTO_NUKE_SAM_UPGRADE_BUFFER=1;const AUTO_NUKE_INTENSITY_TIERS=[{id:"low",label:"Low",minShots:1,shotFraction:.25},{id:"medium",label:"Medium",minShots:2,shotFraction:.55},{id:"high",label:"High",minShots:3,shotFraction:1}];const AUTO_NUKE_TIER_ORDER=new Map(AUTO_NUKE_INTENSITY_TIERS.map((t,e)=>[t.id,e]));const NUKE_SUGGESTION_STRUCTURE_TYPES=["City","Defense Post","SAM Launcher","Missile Silo","Port","Factory"];const NUKE_SUGGESTION_BUCKET_SIZE=16;const NUKE_SUGGESTION_SAMPLE_LIMIT=80;const NUKE_SUGGESTION_MAX_FINAL_CANDIDATES=40;const NUKE_SUGGESTION_TILE_INFO_CACHE_MS=2500;const NUKE_SUGGESTION_SIGNATURE_CHECK_MS=250;let nukeSuggestionTileInfoCache=null;let nukeSuggestionDomCache=new Map;let lastAutoNukeActionAt=0;let autoNukeIncludeAllies=false;let autoNukeContextMenuInstalled=false;let autoNukeContextMenuParams=null;let autoNukeContextMenuComputeId=0;let autoNukeProcessHideTimeout=null;let autoNukeWaveLog=[];let nextNukeSuggestionUnitObjectId=1;const nukeSuggestionUnitObjectIds=new WeakMap;const nukeSuggestionPendingSamFirstSeen=new Map;function ensureNukeSuggestionStyles(){if(document.getElementById(NUKE_SUGGESTION_STYLE_ID)){return}const t=document.createElement("style");t.id=NUKE_SUGGESTION_STYLE_ID;t.textContent=` #${NUKE_SUGGESTION_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483646; pointer-events: none; } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone { position: fixed; left: var(--suggestion-x); top: var(--suggestion-y); width: var(--suggestion-diameter); height: var(--suggestion-diameter); border: 2px dashed var(--suggestion-color); border-radius: 50%; background: var(--suggestion-fill); box-shadow: 0 0 18px var(--suggestion-glow), inset 0 0 24px var(--suggestion-fill); transform: translate(-50%, -50%); } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone[data-suggestion-status="blocked"] { opacity: 0.62; border-style: dotted; } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone::before, #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone::after { content: ""; position: absolute; left: 50%; top: 50%; background: var(--suggestion-color); box-shadow: 0 0 10px var(--suggestion-glow); transform: translate(-50%, -50%); } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone::before { width: 28px; height: 2px; } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-zone::after { width: 2px; height: 28px; } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-label { position: fixed; left: var(--suggestion-x); top: calc(var(--suggestion-y) - var(--suggestion-radius) - var(--suggestion-label-gap)); padding: 4px 8px; border: 1px solid var(--suggestion-color); border-radius: 8px; background: rgba(7, 12, 18, 0.86); color: var(--suggestion-color); font: 900 11px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.92); transform: translate(-50%, -100%); white-space: nowrap; } #${NUKE_SUGGESTION_CONTAINER_ID} .openfront-helper-nuke-suggestion-label[data-suggestion-status="blocked"] { opacity: 0.72; } `;(document.head||document.documentElement).appendChild(t)}function ensureNukeSuggestionContainer(){ensureNukeSuggestionStyles();let t=document.getElementById(NUKE_SUGGESTION_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=NUKE_SUGGESTION_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}return t}function getNukeSuggestionMagnitude(t,e){try{const n=t?.config?.().nukeMagnitudes?.(e);const o=Number(n?.inner);const r=Number(n?.outer);if(Number.isFinite(o)&&Number.isFinite(r)&&r>0){return{inner:Math.max(0,o),outer:r}}}catch(n){}return e==="Hydrogen Bomb"?{inner:80,outer:100}:{inner:12,outer:30}}function isNukeSuggestionTypeEnabled(t,e){try{return!t?.config?.().isUnitDisabled?.(e)}catch(n){return true}}function getNukeSuggestionUnitCost(t,e,n){try{const o=t?.unitInfo?.(n)?.cost?.(t,e);const r=typeof o==="bigint"?Number(o):Number(o);if(Number.isFinite(r)&&r>=0){return r}}catch(o){}try{const o=t?.config?.().unitInfo?.(n)?.cost?.(t,e);const r=typeof o==="bigint"?Number(o):Number(o);if(Number.isFinite(r)&&r>=0){return r}}catch(o){}return NUKE_SUGGESTION_FALLBACK_COSTS[n]??Infinity}function isHydrogenCheaperThanFiveAtomNukes(t,e){const n=getNukeSuggestionUnitCost(t,e,"Atom Bomb");const o=getNukeSuggestionUnitCost(t,e,"Hydrogen Bomb");return o0){return n*o}try{const r=t.worldToScreenCoordinates({x:e.worldX+n,y:e.worldY});return Math.hypot(r.x-e.x,r.y-e.y)}catch(r){return n}}function getUnitTile(t){try{const e=Number(t?.tile?.());return Number.isFinite(e)?e:null}catch(e){return null}}function getUnitType(t){try{return String(t?.type?.()??t?.data?.unitType??"")}catch(e){return""}}function getUnitKey(t){try{const n=t?.id?.();if(n!==void 0&&n!==null){return String(n)}}catch(n){}if(t&&(typeof t==="object"||typeof t==="function")){let n=nukeSuggestionUnitObjectIds.get(t);if(!n){n=nextNukeSuggestionUnitObjectId++;nukeSuggestionUnitObjectIds.set(t,n)}return`object:${n}`}const e=getUnitTile(t);return e===null?"":`tile:${e}`}function getMissileTimerQueue(t){try{const e=t?.missileTimerQueue?.();if(Array.isArray(e)){return e}}catch(e){}return Array.isArray(t?.data?.missileTimerQueue)?t.data.missileTimerQueue:[]}function isActiveFinishedUnit(t){try{return Boolean(t?.isActive?.())&&!t?.isUnderConstruction?.()}catch(e){return false}}function isActiveUnderConstructionUnit(t){try{return Boolean(t?.isActive?.())&&Boolean(t?.isUnderConstruction?.())}catch(e){return false}}function getPendingSamExpectedReadyTick(t,e,n){const o=getUnitKey(e);const r=Number.isFinite(n)?n:getCurrentGameTick(t);if(!o){return r+AUTO_NUKE_SAM_CONSTRUCTION_TICKS}let i=nukeSuggestionPendingSamFirstSeen.get(o);if(!Number.isFinite(i)){i=r;nukeSuggestionPendingSamFirstSeen.set(o,i)}return i+AUTO_NUKE_SAM_CONSTRUCTION_TICKS}function pruneFinishedPendingSamTracker(t){if(nukeSuggestionPendingSamFirstSeen.size===0)return;for(const e of Array.from(nukeSuggestionPendingSamFirstSeen.keys())){if(!t.has(e)){nukeSuggestionPendingSamFirstSeen.delete(e)}}}function isMissileUnitReady(t){if(!isActiveFinishedUnit(t)){return false}const e=getUnitLevel(t);return getMissileTimerQueue(t).length0&&d!==r){i.add(d)}}}const a=[];const s=new Set;for(const l of i){const c=getPlayerBySmallIdSafe(t,l);if(!isNukeSuggestionSamOwnerHostileToLauncher(e,c)){continue}for(const u of collectTargetSams(c)){const d=getUnitKey(u);if(s.has(d)){continue}s.add(d);a.push(u)}}o.adjacentHostileSamsCache=a;return a}function collectRelevantSams(t,e,n,o){const r=[];const i=new Set;for(const a of[...collectTargetSams(n),...collectAdjacentHostileSams(t,e,n,o)]){const s=getUnitKey(a);if(i.has(s)){continue}i.add(s);r.push(a)}return r}function collectAllHostileSams(t,e){const n=[];const o=new Set;function r(i){if(!isActiveFinishedUnit(i)){return}let a=null;try{a=i?.owner?.()}catch(l){a=null}if(!isNukeSuggestionSamOwnerHostileToLauncher(e,a)){return}const s=getUnitKey(i);if(!s||o.has(s)){return}o.add(s);n.push(i)}try{for(const i of t?.units?.("SAM Launcher")||[]){r(i)}}catch(i){}try{for(const i of t?.playerViews?.()||[]){if(!isNukeSuggestionSamOwnerHostileToLauncher(e,i)){continue}for(const a of collectTargetSams(i)){r(a)}}}catch(i){}return n}function collectPendingHostileSams(t,e){const n=[];const o=new Set;function r(i){if(!isActiveUnderConstructionUnit(i)){return}let a=null;try{a=i?.owner?.()}catch(l){a=null}if(!isNukeSuggestionSamOwnerHostileToLauncher(e,a)){return}const s=getUnitKey(i);if(!s||o.has(s)){return}o.add(s);n.push(i)}try{for(const i of t?.units?.("SAM Launcher")||[]){r(i)}}catch(i){}try{for(const i of t?.playerViews?.()||[]){if(!isNukeSuggestionSamOwnerHostileToLauncher(e,i)){continue}for(const a of getPlayerUnits(i,"SAM Launcher")){r(a)}}}catch(i){}return n}function getUnitOwner(t){try{return t?.owner?.()||null}catch(e){return null}}function isSamOwnedByNukeTarget(t,e){const n=getUnitOwner(e);return n?.isPlayer?.()&&getPlayerSmallId(n)===getPlayerSmallId(t.targetPlayer)}function getSamStackKey(t){const e=getUnitTile(t);if(e===null){return""}const n=getUnitOwner(t);const o=getPlayerSmallId(n);return`${Number.isFinite(o)?o:"unknown"}:${e}`}function getStackedSamLevel(t,e){const n=getSamStackKey(e);if(!n){return getUnitLevel(e)}let o=0;for(const r of t||[]){if(getSamStackKey(r)===n){o+=getUnitLevel(r)}}return Math.max(getUnitLevel(e),o)}function getSamStackUnitKeys(t,e){return(t||[]).filter(n=>getSamStackKey(n)===e).map(getUnitKey).filter(Boolean)}function collectTargetStructures(t){return getPlayerUnits(t,...NUKE_SUGGESTION_STRUCTURE_TYPES).filter(e=>{try{return Boolean(e?.isActive?.())}catch(n){return false}}).map(e=>({unit:e,tile:getUnitTile(e),type:getUnitType(e)})).filter(e=>e.tile!==null)}function isTeamGame(t){try{return t?.config?.().gameConfig?.().gameMode==="Team"}catch(e){return false}}function collectTeamStructures(t,e){if(!isTeamGame(t)){return[]}try{return Array.from(t.units?.(...NUKE_SUGGESTION_STRUCTURE_TYPES)||[]).filter(n=>{if(!isActiveFinishedUnit(n)){return false}const o=n.owner?.();return o?.isPlayer?.()&&e?.isOnSameTeam?.(o)}).map(n=>({tile:getUnitTile(n)})).filter(n=>n.tile!==null)}catch(n){return[]}}function collectAlliedPlayerSmallIds(t,e){const n=new Set;try{for(const o of e?.allies?.()||[]){const r=getPlayerSmallId(o);if(Number.isFinite(r)){n.add(r)}}}catch(o){}try{for(const o of t?.playerViews?.()||[]){if(o?.isPlayer?.()&&getPlayerSmallId(o)!==getPlayerSmallId(e)&&(e?.isAlliedWith?.(o)||o?.isAlliedWith?.(e))){const r=getPlayerSmallId(o);if(Number.isFinite(r)){n.add(r)}}}}catch(o){}return n}function collectAlliedStructures(t,e){if(!e?.size){return[]}try{return Array.from(t.units?.(...NUKE_SUGGESTION_STRUCTURE_TYPES)||[]).filter(n=>{if(!isActiveFinishedUnit(n)){return false}const o=n.owner?.();return o?.isPlayer?.()&&e.has(getPlayerSmallId(o))}).map(n=>({tile:getUnitTile(n)})).filter(n=>n.tile!==null)}catch(n){return[]}}function getNukeAllianceBreakThreshold(t){try{const e=Number(t?.config?.().nukeAllianceBreakThreshold?.());return Number.isFinite(e)&&e>=0?e:100}catch(e){return 100}}function isEnemyNukeSuggestionTarget(t,e){const n=t?.myPlayer?.();if(!n?.isPlayer?.()||!e?.isPlayer?.()){return false}if(!e?.isAlive?.()){return false}if(getPlayerSmallId(n)===getPlayerSmallId(e)){return false}try{return!n.isFriendly?.(e)&&!e.isFriendly?.(n)}catch(o){return true}}function makeNukeSuggestionBucketKey(t,e,n=NUKE_SUGGESTION_BUCKET_SIZE){return`${Math.floor(t/n)}:${Math.floor(e/n)}`}function addNukeSuggestionPointToBucket(t,e){const n=makeNukeSuggestionBucketKey(e.x,e.y);let o=t.get(n);if(!o){o=[];t.set(n,o)}o.push(e)}function getNukeSuggestionPlayerTileCount(t){return Math.max(0,toFiniteNumber(t?.numTilesOwned?.(),0))}function getNukeSuggestionTileInfoCacheKey(t,e,n,o){const r=Array.from(o||[]).sort((i,a)=>i-a).map(i=>{const a=getPlayerBySmallIdSafe(t,i);return`${i}:${getNukeSuggestionPlayerTileCount(a)}`}).join("|");return[Number(t?.width?.())||0,Number(t?.height?.())||0,getPlayerSmallId(e),getNukeSuggestionPlayerTileCount(e),getPlayerSmallId(n),getNukeSuggestionPlayerTileCount(n),r].join(":")}function runTileScan(t,e,n,o,r,i){const a=[];const s=new Map;const l=new Map;const c=new Map;const u=new Map;let d=Infinity;let f=Infinity;let m=-Infinity;let p=-Infinity;let h=0;let y=0;const g=r*i;for(let b=0;b0){addNukeSuggestionPointToBucket(u,I)}continue}a.push(I);d=Math.min(d,T);f=Math.min(f,A);m=Math.max(m,T);p=Math.max(p,A);h+=T;y+=A;addNukeSuggestionPointToBucket(s,I)}if(a.length===0){return null}return{tiles:a,buckets:s,selfBuckets:l,allyBuckets:c,otherPlayerBuckets:u,adjacentHostileSamsCache:null,bbox:{minX:d,minY:f,maxX:m,maxY:p},centroid:{x:h/a.length,y:y/a.length}}}function collectTargetTileInfo(t,e,n,o){const r=getPlayerSmallId(e);const i=getPlayerSmallId(n);const a=Number(t?.width?.());const s=Number(t?.height?.());if(!Number.isFinite(r)||!Number.isFinite(i)||!Number.isFinite(a)||!Number.isFinite(s)||a<=0||s<=0){return null}const l=getNukeSuggestionTileInfoCacheKey(t,e,n,o);const c=performance.now();if(nukeSuggestionTileInfoCache?.key===l&&c-nukeSuggestionTileInfoCache.updatedAt<=NUKE_SUGGESTION_TILE_INFO_CACHE_MS){return nukeSuggestionTileInfoCache.value}if(nukeSuggestionTileComputeKey!==l){nukeSuggestionTileComputeKey=l;setTimeout(()=>{if(nukeSuggestionTileComputeKey!==l){return}const u=runTileScan(t,r,i,o,a,s);if(nukeSuggestionTileComputeKey!==l){return}nukeSuggestionTileComputeKey=null;nukeSuggestionTileInfoCache={key:l,updatedAt:performance.now(),value:u};lastNukeSuggestionSignatureCheckAt=0;lastNukeSuggestionSignature=""},0)}return nukeSuggestionTileInfoCache?.value??null}function getTileFromXY(t,e,n){const o=Math.round(e);const r=Math.round(n);try{if(!t?.isValidCoord?.(o,r)){return null}return t.ref(o,r)}catch(i){return null}}function addNukeSuggestionSeed(t,e,n){if(n==null){return}try{if(!e?.isValidRef?.(n)){return}}catch(o){return}t.set(String(n),n)}function addNukeSuggestionSeedXY(t,e,n,o){addNukeSuggestionSeed(t,e,getTileFromXY(e,n,o))}function addDensitySeeds(t,e,n,o,r){const i=Math.max(8,Math.round(o/2));const a=new Map;for(const l of n.tiles){const c=makeNukeSuggestionBucketKey(l.x,l.y,i);let u=a.get(c);if(!u){u={count:0,sumX:0,sumY:0};a.set(c,u)}u.count++;u.sumX+=l.x;u.sumY+=l.y}const s=Array.from(a.values()).sort((l,c)=>c.count-l.count).slice(0,r);for(const l of s){addNukeSuggestionSeedXY(t,e,l.sumX/l.count,l.sumY/l.count)}}function buildNukeSuggestionSeeds(t,e,n,o,r,i){const a=new Map;const s=e?.nameLocation?.();if(s){addNukeSuggestionSeedXY(a,t,s.x,s.y)}addNukeSuggestionSeedXY(a,t,n.centroid.x,n.centroid.y);addNukeSuggestionSeedXY(a,t,(n.bbox.minX+n.bbox.maxX)/2,(n.bbox.minY+n.bbox.maxY)/2);const l=i.scoreMode==="destruction"?o.filter(isPopulationOrEconomicNukeObjective):o;for(const d of l){addNukeSuggestionSeed(a,t,d.tile)}addDensitySeeds(a,t,n,r.outer,Math.max(24,Math.floor(i.maxRawSeeds/3)));const c=Math.min(NUKE_SUGGESTION_SAMPLE_LIMIT,i.maxRawSeeds);const u=Math.max(1,Math.floor(n.tiles.length/c));for(let d=0;d=i.maxRawSeeds+l.length+8){break}}return Array.from(a.values())}function getCityTroopIncrease(t){try{const e=Number(t?.config?.().cityTroopIncrease?.());return Number.isFinite(e)&&e>0?e:25e4}catch(e){return 25e4}}function getPlayerTroopsNumber(t){try{const e=Number(t?.troops?.());return Number.isFinite(e)&&e>0?e:0}catch(e){return 0}}function getPlayerMaxTroopsNumber(t,e){try{const r=Number(t?.config?.().maxTroops?.(e));if(Number.isFinite(r)&&r>0){return r}}catch(r){}const n=Math.max(0,toFiniteNumber(e?.numTilesOwned?.(),0));const o=getPlayerUnits(e,"City").filter(r=>!r?.isUnderConstruction?.()).map(getUnitLevel).reduce((r,i)=>r+i,0);return 2*(Math.pow(n,.6)*1e3+5e4)+o*getCityTroopIncrease(t)}function estimateNukePopulationLoss(t,e,n,o){const r=getPlayerTroopsNumber(n);if(r<=0||o<=0){return 0}const i=Math.max(1,toFiniteNumber(n?.numTilesOwned?.(),1));if(e==="MIRV Warhead"){try{return Number(t?.config?.().nukeDeathFactor?.(e,r,i,getPlayerMaxTroopsNumber(t,n)))||0}catch(u){return 0}}const a=Math.min(o,i);const s=Math.floor(a);const l=a-s;let c=1;if(s>0){if(i<=5||i-s<=4){c=0}else{const u=i-s;for(let d=0;d<5;d++){c*=(u-d)/(i-d)}}}if(l>0){const u=Math.max(1,i-s);const d=Math.min(1,5/u*l);c*=Math.max(0,1-d)}return r*(1-c)}function estimateCityPopulationHit(t,e,n,o,r){const i=Math.max(0,Math.min(1,getPlayerTroopsNumber(e.targetPlayer)/Math.max(1,getPlayerMaxTroopsNumber(t,e.targetPlayer))));const a=getCityTroopIncrease(t);let s=0;let l=0;const c=new Map;for(const u of e.structures){if(u.type!=="City"){continue}try{const d=t.x(u.tile);const f=t.y(u.tile);const m=d-n;const p=f-o;if(m*m+p*p0}function estimateNukeEconomicImpact(t,e,n,o){let r=0;let i=0;const a=new Map;for(const s of t.structures){const l=getEconomicStructureWeight(s);if(l<=0){continue}try{const c=t.game.x(s.tile);const u=t.game.y(s.tile);const d=c-e;const f=u-n;if(d*d+f*fr){continue}const b=g<=c?1:.5;const w=(u.get(p.ownerId)||0)+b;if(w>e.allianceBreakThreshold){return true}u.set(p.ownerId,w)}}}return false}function wouldNukeSuggestionAffectLauncher(t,e,n,o,r){const i=getPlayerSmallId(e.myPlayer);if(!Number.isFinite(i)){return true}const a=Math.floor((n-e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const s=Math.floor((n+e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const l=Math.floor((o-e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const c=Math.floor((o+e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);for(let u=l;u<=c;u++){for(let d=a;d<=s;d++){const f=e.tileInfo.selfBuckets.get(`${d}:${u}`);if(!f){continue}for(const m of f){const p=m.x-n;const h=m.y-o;if(p*p+h*h<=r){return true}}}}return false}function wouldHydrogenNukeAffectOtherPlayers(t,e,n,o){if(t.spec?.type!=="Hydrogen Bomb"){return false}const r=Math.floor((e-t.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const i=Math.floor((e+t.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const a=Math.floor((n-t.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const s=Math.floor((n+t.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);for(let l=a;l<=s;l++){for(let c=r;c<=i;c++){const u=`${c}:${l}`;const d=[t.tileInfo.otherPlayerBuckets?.get(u),t.tileInfo.allyBuckets?.get(u),t.tileInfo.selfBuckets?.get(u)].filter(Boolean);for(const f of d){for(const m of f){const p=m.x-e;const h=m.y-n;if(p*p+h*h<=o){return true}}}}}return false}function getOwnerAtTile(t,e){try{return t.owner?.(e)??null}catch(n){return null}}function isNukeSuggestionTileAllowed(t,e,n,o,r){if(t?.isSpawnImmunityActive?.()){return false}const i=getOwnerAtTile(t,n);if(i?.isPlayer?.()&&isTeamGame(t)&&e?.isOnSameTeam?.(i)){return false}if(r.length===0){return true}const a=t.x(n);const s=t.y(n);return!anyTeamStructureInBlast(t,r,a,s,o.outer*o.outer)}function scoreNukeSuggestionTile(t,e,n){if(n==null||!isNukeSuggestionTileAllowed(t,e.myPlayer,n,e.magnitude,e.teamStructures)){return null}let o;let r;try{o=t.x(n);r=t.y(n)}catch(C){return null}const i=e.magnitude.inner*e.magnitude.inner;const a=e.magnitude.outer*e.magnitude.outer;if(wouldNukeSuggestionAffectLauncher(t,e,o,r,a)){return null}if(wouldNukeSuggestionBreakAlliance(t,e,o,r,a)){return null}if(wouldHydrogenNukeAffectOtherPlayers(e,o,r,a)){return null}let s=0;let l=0;let c=0;const u=Math.floor((o-e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const d=Math.floor((o+e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const f=Math.floor((r-e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);const m=Math.floor((r+e.magnitude.outer)/NUKE_SUGGESTION_BUCKET_SIZE);for(let C=f;C<=m;C++){for(let U=u;U<=d;U++){const Y=e.tileInfo.buckets.get(`${U}:${C}`);if(!Y){continue}for(const G of Y){const ee=G.x-o;const H=G.y-r;const te=ee*ee+H*H;if(te<=i){s+=1;l++}else if(te<=a){s+=.5;c++}}}}let p=0;let h=0;let y=0;let g=0;let b=0;let w=new Map;let T=new Map;let A=new Map;let I=0;if(e.scoreMode==="economic"||e.scoreMode==="destruction"){const C=estimateNukeEconomicImpact(e,o,r,a);g=C.economicImpact;b=C.economicStructuresHit;w=C.hitEconomicStructureWeights||new Map}if(e.scoreMode!=="economic"){p=estimateNukePopulationLoss(t,e.spec.type,e.targetPlayer,s);const C=estimateCityPopulationHit(t,e,o,r,a);h=C.cityPopulationHit;y=C.citiesHit;T=C.hitCityPopulationWeights||new Map}if(e.scoreMode==="destruction"){A=combineDestructionStructureWeights(w,T);for(const C of A.values()){I+=C}I+=p*.15}let P=0;const $=[];for(const C of e.targetSams||[]){try{const U=getUnitTile(C);if(U===null){continue}const Y=t.x(U);const G=t.y(U);const ee=Y-o;const H=G-r;if(ee*ee+H*Ho.totalScore+.25){o=u}}}return o}function findClosestReadySilo(t,e,n){let o=null;let r=Infinity;for(const i of n){const a=getUnitTile(i);if(a===null){continue}let s=Infinity;try{s=t.manhattanDist(a,e)}catch(l){continue}if(s0?e:6}catch(e){return 6}}function getDefaultNukeTargetableRange(t){try{const e=Number(t?.config?.().defaultNukeTargetableRange?.());return Number.isFinite(e)&&e>0?e:150}catch(e){return 150}}function getDefaultSamMissileSpeed(t){try{const e=Number(t?.config?.().defaultSamMissileSpeed?.());return Number.isFinite(e)&&e>0?e:12}catch(e){return 12}}function getSamCooldown(t){try{const e=Number(t?.config?.().SAMCooldown?.());return Number.isFinite(e)&&e>0?e:120}catch(e){return 120}}function getMissileAvailabilityTick(t,e,n){const o=getUnitLevel(e);const r=getMissileTimerQueue(e);if(r.lengthNumber(s)).filter(Number.isFinite).slice(0,o);const i=getCurrentGameTick(t);const a=[];for(let s=r.length;ss-l)}function getSamStackMissileSlotAvailableTicks(t,e){const n=getSamCooldown(t);const o=[];for(const r of e||[]){o.push(...getSamMissileSlotAvailableTicks(t,r,n))}return o.sort((r,i)=>r-i)}function getSamRange(t,e,n=null){try{const o=n?getStackedSamLevel(n,e):getUnitLevel(e);const r=Number(t?.config?.().samRange?.(o));return Number.isFinite(r)&&r>0?r:70}catch(o){return 70}}function getMaxSamRange(t){try{const e=Number(t?.config?.().maxSamRange?.());return Number.isFinite(e)&&e>0?e:150}catch(e){return 150}}function clampNukeSuggestionValue(t,e,n){return Math.max(e,Math.min(n,t))}function getCubicBezierPoint(t,e,n,o,r){const i=1-r;const a=i*i;const s=a*i;const l=r*r;const c=l*r;return{x:s*t.x+3*a*r*e.x+3*i*l*n.x+c*o.x,y:s*t.y+3*a*r*e.y+3*i*l*n.y+c*o.y}}function createNukeSuggestionTrajectory(t,e,n,o=true){const r=getDefaultNukeSpeed(t);const i={x:t.x(e),y:t.y(e)};const a={x:t.x(n),y:t.y(n)};const s=a.x-i.x;const l=a.y-i.y;const c=Math.hypot(s,l);const u=Math.max(c/3,50);const d=Number(t?.height?.())||1;const f=o===false?1:-1;const m={x:i.x+s/4,y:clampNukeSuggestionValue(i.y+l/4+f*u,0,d-1)};const p={x:i.x+s*3/4,y:clampNukeSuggestionValue(i.y+l*3/4+f*u,0,d-1)};const h=[i];let y=0;let g=i;let b=0;while(y<1){y=Math.min(y+.002,1);const I=getCubicBezierPoint(i,m,p,a,y);b+=Math.hypot(I.x-g.x,I.y-g.y);if(b>=r){h.push(I);b=0}g=I}const w=getCubicBezierPoint(i,m,p,a,1);const T=h[h.length-1];if(T.x!==w.x||T.y!==w.y){h.push(w)}const A=[];for(const I of h){const P=Math.floor(I.x);const $=Math.floor(I.y);try{if(t.isValidCoord?.(P,$)){A.push(t.ref(P,$))}}catch(k){}}return A.length>0?A:[e,n]}function isNukeSuggestionTrajectoryTargetable(t,e,n,o,r){try{return t.euclideanDistSquared(o,n)=0){return true}}return false}function findSamInterceptionOpportunity(t,e,n,o,r,i,a=true,s=0,l=0){const c=getUnitTile(r);if(c===null||!Array.isArray(o)||o.length===0){return null}const u=getDefaultNukeTargetableRange(t);const d=u*u;const f=getDefaultSamMissileSpeed(t);const m=getSamRange(t,r,i);const p=m*m;const h=getMaxSamRange(t)*2;const y=h*h;const g=Math.max(0,Math.floor(Number(s)||0));const b=Math.max(0,Math.floor(Number(l)||0));const w=Math.max(g,b);let T=null;for(let A=0;Ap){continue}const k=g+A-P;if(k=g+o.length){continue}const C=Math.max(0,Math.min(o.length-1,k-g));try{if(t.euclideanDistSquared(c,o[C])>y){continue}}catch(U){continue}if(!T||kn+o,0)}function chooseBestNukeRocketDirection(t,e,n,o){const r=findInterceptingSams(t,e,n,o,true);const i=findInterceptingSams(t,e,n,o,false);const a=getInterceptingSamsWeight(r);const s=getInterceptingSamsWeight(i);if(so.totalScore){e.set(n.tile,n)}}return Array.from(e.values()).sort((n,o)=>o.totalScore-n.totalScore)}function computeNukeCandidatesForType(t,e,n,o){const r=getNukeSuggestionMagnitude(t,n.type);const i={...e,game:t,magnitude:r,spec:n,scoreMode:n.scoreMode||"population"};const a=buildNukeSuggestionSeeds(t,i.targetPlayer,i.tileInfo,i.structures,r,n);const s=[];for(const d of a){const f=scoreNukeSuggestionTile(t,i,d);if(f){s.push(f)}}s.sort((d,f)=>f.totalScore-d.totalScore);const l=s.slice(0,n.maxRefineSeeds);for(const d of l){s.push(refineNukeSuggestionCandidate(t,i,d))}const c=dedupeNukeSuggestionCandidates(s).slice(0,o||NUKE_SUGGESTION_MAX_FINAL_CANDIDATES);const u=[];for(const d of c){const f=findClosestReadySilo(t,d.tile,i.readySilos);if(!f){continue}const m=getUnitTile(f);if(m===null){continue}const p=chooseBestNukeRocketDirection(t,m,d.tile,i.targetSams);const h=p.interceptingSams[0]||null;const y={...d,sourceTile:m,rocketDirectionUp:p.rocketDirectionUp,interceptingSamTile:getUnitTile(h),intercepted:Boolean(h)};u.push(y)}return u}function computeNukeSuggestionForType(t,e,n){const o=computeNukeCandidatesForType(t,e,n,NUKE_SUGGESTION_MAX_FINAL_CANDIDATES);return o.find(r=>!r.intercepted)||o[0]||null}function computeEconomicNukeSuggestion(t,e){const n=NUKE_SUGGESTION_TYPES.find(d=>d.id==="atom");const o=NUKE_SUGGESTION_TYPES.find(d=>d.id==="hydrogen");function r(d){if(!shouldComputeNukeSuggestionSpec(t,e.myPlayer,d)){return null}return computeNukeSuggestionForType(t,e,{...NUKE_ECONOMIC_SUGGESTION,id:`economic-${d.id}`,label:d.id==="hydrogen"?"Eco hydrogen":"Eco atom",type:d.type,sourceLabel:d.label,maxRawSeeds:Math.min(NUKE_ECONOMIC_SUGGESTION.maxRawSeeds,d.maxRawSeeds),maxRefineSeeds:Math.min(NUKE_ECONOMIC_SUGGESTION.maxRefineSeeds,d.maxRefineSeeds),scoreMode:"economic"})}const i=r(n);const a=r(o);const s=[];if(i){s.push(i)}if(!a){return s}if(!i){return[a]}const l=i.intercepted&&!a.intercepted;const c=a.totalScore-i.totalScore;const u=c>=NUKE_ECONOMIC_HYDROGEN_MIN_GAIN&&a.totalScore>=i.totalScore*NUKE_ECONOMIC_HYDROGEN_GAIN_RATIO;if(l||u){s.push(a)}return s}function buildNukeSuggestionBaseContext(t,e){const n=t?.myPlayer?.();if(!n?.isPlayer?.()||!isEnemyNukeSuggestionTarget(t,e)){return null}const o=collectReadyNukeSilos(n);if(o.length===0){return null}const r=collectAlliedPlayerSmallIds(t,n);const i=collectTargetTileInfo(t,e,n,r);if(!i){return null}return{myPlayer:n,targetPlayer:e,tileInfo:i,readySilos:o,targetSams:collectRelevantSams(t,n,e,i),structures:collectTargetStructures(e),teamStructures:collectTeamStructures(t,n),allySmallIds:r,alliedStructures:collectAlliedStructures(t,r),allianceBreakThreshold:getNukeAllianceBreakThreshold(t)}}function computeNukeSuggestions(t,e,n=null){const o=t?.myPlayer?.();const r=buildNukeSuggestionBaseContext(t,e);if(!r){return[]}const i=n?.mode||"all";const a=i==="all"||i==="population";const s=i==="all"||i==="economic";const l=a?NUKE_SUGGESTION_TYPES.filter(u=>shouldComputeNukeSuggestionSpec(t,o,u)).map(u=>computeNukeSuggestionForType(t,r,u)).filter(Boolean):[];const c=s?computeEconomicNukeSuggestion(t,r):[];return[...l,...c]}function formatNukeSuggestionPopulation(t){const e=Math.round(t);if(e>=1e6){return`${(e/1e6).toFixed(1)}M`}if(e>=1e3){return`${(e/1e3).toFixed(1)}k`}return String(e)}function formatNukeSuggestionLabel(t){if(t.intercepted){return`${t.spec.label}: SAM covered`}if(t.spec.scoreMode==="economic"){const n=[`${t.spec.label}: ${formatNukeSuggestionPopulation(t.totalScore)} econ`];if(t.economicStructuresHit>0){n.push(`${t.economicStructuresHit} structures`)}return n.join(", ")}const e=[`${t.spec.label}: ${formatNukeSuggestionPopulation(t.totalScore)} pop`];if(t.citiesHit>0){e.push(`${t.citiesHit} cities`)}return e.join(", ")}function getNukeSuggestionSignature(t,e){const n=t?.myPlayer?.();const o=getPlayerSmallId(e);const r=getPlayerSmallId(n);const i=Math.floor(toFiniteNumber(t?.ticks?.(),0)/10);const a=collectReadyNukeSilos(n).map(c=>`${c.id?.()}:${getUnitTile(c)}:${getUnitLevel(c)}:${getMissileTimerQueue(c).join(",")}`).join("|");const s=collectTargetSams(e).map(c=>`${c.id?.()}:${getUnitTile(c)}:${getUnitLevel(c)}:${getMissileTimerQueue(c).join(",")}`).join("|");const l=toFiniteNumber(e?.numTilesOwned?.(),0);return`${r}:${o}:${i}:${l}:${a}:${s}`}function renderNukeSuggestions(t,e,n,o){const r=new Set;for(const i of o){let a=null;try{a=n.worldToScreenCoordinates({x:i.x,y:i.y})}catch(p){a=null}if(!Number.isFinite(a?.x)||!Number.isFinite(a?.y)){continue}const s=Math.max(12,getNukeSuggestionScreenRadius(n,{...a,worldX:i.x,worldY:i.y},i.magnitude.outer));if(a.x<-s-220||a.y<-s-220||a.x>window.innerWidth+s+220||a.y>window.innerHeight+s+220){continue}const l=i.id;r.add(l);let c=nukeSuggestionDomCache.get(l);if(!c){const p=document.createElement("div");p.className="openfront-helper-nuke-suggestion-zone";p.dataset.suggestionId=l;t.appendChild(p);const h=document.createElement("div");h.className="openfront-helper-nuke-suggestion-label";h.dataset.suggestionId=l;t.appendChild(h);const y=l.startsWith("economic")?"22px":l==="hydrogen"?"16px":"10px";for(const g of[p,h]){g.style.setProperty("--suggestion-color",i.spec.color);g.style.setProperty("--suggestion-fill",i.spec.fill);g.style.setProperty("--suggestion-glow",i.spec.glow);g.style.setProperty("--suggestion-label-gap",y)}c={zone:p,label:h,lastX:Infinity,lastY:Infinity,lastRadius:Infinity,lastStatus:null};nukeSuggestionDomCache.set(l,c)}const{zone:u,label:d}=c;const f=i.intercepted?"blocked":"active";if(c.lastStatus!==f){c.lastStatus=f;u.dataset.suggestionStatus=f;d.dataset.suggestionStatus=f}if(Math.abs(a.x-c.lastX)>.5||Math.abs(a.y-c.lastY)>.5||Math.abs(s-c.lastRadius)>.5){c.lastX=a.x;c.lastY=a.y;c.lastRadius=s;for(const p of[u,d]){p.style.setProperty("--suggestion-x",`${a.x}px`);p.style.setProperty("--suggestion-y",`${a.y}px`);p.style.setProperty("--suggestion-radius",`${s}px`);p.style.setProperty("--suggestion-diameter",`${s*2}px`)}}const m=formatNukeSuggestionLabel(i);if(d.textContent!==m){d.textContent=m}}for(const[i,{zone:a,label:s}]of nukeSuggestionDomCache){if(!r.has(i)){a.remove();s.remove();nukeSuggestionDomCache.delete(i)}}}function clearNukeSuggestionState(t=null){currentNukeSuggestionResults=[];lastNukeSuggestionSignature="";lastNukeSuggestionComputedAt=0;lastNukeSuggestionSignatureCheckAt=0;nukeSuggestionTileInfoCache=null;nukeSuggestionTileComputeKey=null;nukeSuggestionDomCache.clear();t?.replaceChildren()}function isAutoNukeRadialTarget(t){if(!autoNukeEnabled||!t?.game||!t?.myPlayer?.isPlayer?.()){return false}if(!t.selected?.isPlayer?.()||!t.selected?.isAlive?.()){return false}if(getPlayerSmallId(t.myPlayer)===getPlayerSmallId(t.selected)){return false}if(isEnemyNukeSuggestionTarget(t.game,t.selected)){return true}return autoNukeIncludeAllies}function hasAutoNukeBuildTypeAvailable(t,e){return NUKE_SUGGESTION_TYPES.some(n=>shouldComputeNukeSuggestionSpec(t,e,n))}function isAutoNukeRadialItemDisabled(t){if(!isAutoNukeRadialTarget(t)){return true}if(!hasAutoNukeBuildTypeAvailable(t.game,t.myPlayer)){return true}return collectReadyNukeSilos(t.myPlayer).length===0}function selectAutoNukeCandidate(t,e,n){const o=computeNukeSuggestions(t,e,{mode:n}).filter(i=>!i.intercepted);const r=o.filter(i=>{const a=i.spec?.scoreMode==="economic";return n==="economic"?a:!a});r.sort((i,a)=>a.totalScore-i.totalScore);return r[0]||null}function getReadyNukeSiloSlotCount(t){return collectActiveNukeSilos(t).reduce((e,n)=>{const o=getUnitLevel(n)-getMissileTimerQueue(n).length;return e+Math.max(0,o)},0)}function getTotalNukeSiloSlotCount(t){return collectActiveNukeSilos(t).reduce((e,n)=>e+Math.max(0,getUnitLevel(n)),0)}function getAutoNukeMinimumAvailableCost(t,e){const n=NUKE_SUGGESTION_TYPES.filter(o=>o.type!=="Hydrogen Bomb"&&isNukeSuggestionTypeEnabled(t,o.type)).map(o=>getNukeSuggestionUnitCost(t,e,o.type)).filter(o=>Number.isFinite(o)&&o>0);return n.length>0?Math.min(...n):Infinity}function getAutoNukeCandidateSpecs(t,e,n){const o=NUKE_SUGGESTION_TYPES.filter(r=>r.type!=="Hydrogen Bomb"&&isNukeSuggestionTypeEnabled(t,r.type));if(n==="population"){return o}if(n==="destruction"||n==="sams"){return o.map(r=>({...r,id:`${n}-${r.id}`,label:n==="sams"?`SAM ${r.label}`:`Destroy ${r.label}`,scoreMode:"destruction"}))}return o.map(r=>({...NUKE_ECONOMIC_SUGGESTION,id:`economic-${r.id}`,label:r.id==="hydrogen"?"Eco hydrogen":"Eco atom",type:r.type,sourceLabel:r.label,maxRawSeeds:Math.min(NUKE_ECONOMIC_SUGGESTION.maxRawSeeds,r.maxRawSeeds),maxRefineSeeds:Math.min(NUKE_ECONOMIC_SUGGESTION.maxRefineSeeds,r.maxRefineSeeds),scoreMode:"economic"}))}function scoreAutoNukeSamClearCandidate(t,e,n,o,r){const i=getUnitTile(o);if(i===null){return null}const a=getNukeSuggestionMagnitude(t,n.type);if(!isNukeSuggestionTileAllowed(t,e.myPlayer,i,a,e.teamStructures)){return null}let s;let l;try{s=t.x(i);l=t.y(i)}catch(U){return null}const c=a.outer*a.outer;const u={...e,game:t,magnitude:a,spec:n,scoreMode:"sam-clear"};if(wouldNukeSuggestionAffectLauncher(t,u,s,l,c)){return null}if(wouldNukeSuggestionBreakAlliance(t,u,s,l,c)){return null}if(wouldHydrogenNukeAffectOtherPlayers(u,s,l,c)){return null}const d=[];const f=[...e.targetSams||[],...e.pendingTargetSams||[]];const m=new Set;for(const U of f){const Y=getUnitTile(U);if(Y===null){continue}try{const G=t.x(Y)-s;const ee=t.y(Y)-l;if(G*G+ee*ee<=c){const H=getUnitKey(U);if(H&&!m.has(H)){m.add(H);d.push(H)}}}catch(G){}}const p=getUnitKey(o);if(!p||!d.includes(p)){return null}const h=getSamStackKey(o)||p;const y=Array.from(new Set([...getSamStackUnitKeys(e.targetSams,h),...getSamStackUnitKeys(e.pendingTargetSams,h)]));let g=0;const b=(e.targetSams||[]).filter(U=>y.includes(getUnitKey(U)));for(const U of b){g+=Math.max(1,getUnitLevel(U))}if(g===0){g=y.length||1}const w=findClosestReadySilo(t,i,e.readySilos);const T=getUnitTile(w);if(T===null){return null}const A=chooseBestNukeRocketDirection(t,T,i,e.targetSams);const I={...n,id:`sam-clear-${n.id}`,label:"Clear SAM",scoreMode:"sam-clear"};const P=A.interceptingSams[0]||null;const $=estimateCityPopulationHit(t,u,s,l,c);const k=estimateNukeEconomicImpact(u,s,l,c);const C=combineDestructionStructureWeights(k.hitEconomicStructureWeights,$.hitCityPopulationWeights);return{id:`sam-clear-${p}-${n.id}`,spec:I,type:n.type,tile:i,x:s,y:l,magnitude:a,populationLoss:0,cityPopulationHit:0,economicImpact:0,expectedTilesHit:0,innerTiles:0,outerTiles:0,citiesHit:$.citiesHit,economicStructuresHit:k.economicStructuresHit,hitEconomicStructureWeights:k.hitEconomicStructureWeights,hitCityPopulationWeights:$.hitCityPopulationWeights,hitDestructionStructureWeights:C,hitSamKeys:d,samsHit:d.length,totalScore:Math.max(1,r*.28),sourceTile:T,rocketDirectionUp:A.rocketDirectionUp,interceptingSamTile:getUnitTile(P),intercepted:Boolean(P),isSamClear:true,samClearStackKey:h,samClearStackSamKeys:y.length>0?y:[p],samClearStackLevelSum:g,samClearOwnedByTarget:isSamOwnedByNukeTarget(e,o)}}function isAutoNukeBurnStructureForMode(t,e){if(t?.tile==null){return false}if(e==="destruction"||e==="sams"){return t.type==="City"||getEconomicStructureWeight(t)>0}if(e==="economic"){return getEconomicStructureWeight(t)>0}return t.type==="City"}function getAutoNukeBurnStructureFallbackScore(t,e){if(e==="destruction"||e==="sams"){return(t.type==="City"?getUnitLevel(t.unit)*65e4:0)+getEconomicStructureWeight(t)}if(e==="economic"){return getEconomicStructureWeight(t)}return t.type==="City"?getUnitLevel(t.unit):0}function scoreAutoNukeTargetBuildingBurnCandidate(t,e,n,o,r,i){const a=getUnitTile(o);const s=getUnitKey(o);if(a===null||!s){return null}const l=e.structures.filter(c=>isAutoNukeBurnStructureForMode(c,i)).sort((c,u)=>{let d=Infinity;let f=Infinity;try{d=t.manhattanDist(a,c.tile);f=t.manhattanDist(a,u.tile)}catch(m){}return d-f||getAutoNukeBurnStructureFallbackScore(u,i)-getAutoNukeBurnStructureFallbackScore(c,i)});for(const c of l){const u=c.tile;const d=getNukeSuggestionMagnitude(t,n.type);if(!isNukeSuggestionTileAllowed(t,e.myPlayer,u,d,e.teamStructures)){continue}let f;let m;try{f=t.x(u);m=t.y(u)}catch(A){continue}const p=d.outer*d.outer;const h={...e,game:t,magnitude:d,spec:n,scoreMode:"sam-burn-building"};if(wouldNukeSuggestionAffectLauncher(t,h,f,m,p)){continue}if(wouldNukeSuggestionBreakAlliance(t,h,f,m,p)){continue}if(wouldHydrogenNukeAffectOtherPlayers(h,f,m,p)){continue}const y=findClosestReadySilo(t,u,e.readySilos);const g=getUnitTile(y);if(g===null){continue}const b=chooseBestNukeRocketDirection(t,g,u,e.targetSams);const w=b.interceptingSams.some(A=>getUnitKey(A)===s);if(!w){continue}const T={...n,id:`sam-burn-${n.id}`,label:"Burn SAM",scoreMode:"sam-burn-building"};return{id:`sam-burn-${s}-${i}-${u}-${n.id}`,spec:T,type:n.type,tile:u,x:f,y:m,magnitude:d,populationLoss:0,cityPopulationHit:0,economicImpact:0,expectedTilesHit:0,innerTiles:0,outerTiles:0,citiesHit:0,economicStructuresHit:0,hitEconomicStructureWeights:new Map,hitCityPopulationWeights:new Map,hitDestructionStructureWeights:new Map,hitSamKeys:[],samsHit:0,totalScore:Math.max(1,r*.08),sourceTile:g,rocketDirectionUp:b.rocketDirectionUp,interceptingSamTile:a,intercepted:true,isSamBurnBuilding:true,samClearOwnedByTarget:false}}return null}function getAutoNukeSamBurnSpec(t,e){const n=NUKE_SUGGESTION_TYPES.find(o=>o.id==="atom");if(n&&isNukeSuggestionTypeEnabled(t,n.type)){return n}return NUKE_SUGGESTION_TYPES.find(o=>o.type!=="Hydrogen Bomb"&&isNukeSuggestionTypeEnabled(t,o.type))||null}function addAutoNukeSamClearCandidates(t,e,n,o,r){if(!n.length){return}const i=new Map;const a=new Map;if(o.length>0){for(const c of o){if(c.sourceTile==null){continue}for(const u of findInterceptingSams(t,c.sourceTile,c.tile,e.targetSams,c.rocketDirectionUp)){const d=getUnitKey(u);if(!d){continue}a.set(d,u);i.set(d,(i.get(d)||0)+c.totalScore)}}}const s=getAutoNukeSamBurnSpec(t,e.myPlayer);if(!s){return}if(r==="destruction"||r==="sams"){const c=new Set;for(const u of e.targetSams||[]){if(!isSamOwnedByNukeTarget(e,u)){continue}const d=getSamStackKey(u)||getUnitKey(u);if(!d||c.has(d)){continue}c.add(d);const f=(e.targetSams||[]).filter(h=>getSamStackKey(h)===d);const m=getSamStackInterceptionCapacity(t,f.length>0?f:[u]);const p=getUnitKey(u);if(p){a.set(p,u);i.set(p,Math.max(i.get(p)||0,AUTO_NUKE_DESTRUCTION_SAM_BONUS*Math.max(1,m)))}}for(const u of e.pendingTargetSams||[]){if(!isSamOwnedByNukeTarget(e,u)){continue}const d=getSamStackKey(u)||getUnitKey(u);if(!d||c.has(d)){continue}c.add(d);const f=getUnitKey(u);if(f){a.set(f,u);i.set(f,Math.max(i.get(f)||0,AUTO_NUKE_DESTRUCTION_SAM_BONUS))}}}const l=new Set;{const c=o.filter(p=>!p.isSamClear);const u=Math.min(5,c.length);const d=u>0?Math.round(c.slice(0,u).reduce((p,h)=>p+getAutoNukeCandidateFlightTicks(t,h),0)/u):180;const f=new Set;const m={pendingSamReadyTicks:e.pendingSamReadyTicks,currentTick:e.currentTick};for(const p of e.targetSams||[]){const h=getSamStackKey(p)||getUnitKey(p);if(!h||f.has(h)){continue}f.add(h);const y=(e.targetSams||[]).filter(w=>getSamStackKey(w)===h);const g=getAutoNukePendingStackSams(e,h);const b=estimateSamStackEffectiveCapacity(t,y.length>0?y:[p],d,{...m,pendingSams:g,includeUpgradeBuffer:true});if(b>=AUTO_NUKE_BLOCKING_STACK_BASE_CAPACITY){const w=getUnitKey(p);if(w){a.set(w,p);const T=AUTO_NUKE_DESTRUCTION_SAM_BONUS*Math.max(1,b)*AUTO_NUKE_BLOCKING_STACK_SCORE_FRACTION;if((i.get(w)||0)=AUTO_NUKE_BLOCKING_STACK_BASE_CAPACITY){const b=getUnitKey(p);if(b){a.set(b,p);const w=AUTO_NUKE_DESTRUCTION_SAM_BONUS*Math.max(1,g)*AUTO_NUKE_BLOCKING_STACK_SCORE_FRACTION;if((i.get(b)||0)0?i:r.targetSams,pendingTargetSams:a,pendingSamReadyTicks:l,currentTick:s,readySilos:o?.includeCoolingSilos?collectActiveNukeSilos(r.myPlayer):r.readySilos};const d=[];const f=getAutoNukeCandidateSpecs(t,u.myPlayer,n);for(const m of f){d.push(...computeNukeCandidatesForType(t,u,m,AUTO_NUKE_MAX_CANDIDATES_PER_SPEC))}addAutoNukeSamClearCandidates(t,u,f,d,n);d.sort((m,p)=>p.totalScore-m.totalScore);return{baseContext:u,candidates:d}}function getSamInterceptionCapacity(t,e){const n=Math.max(0,getUnitLevel(e)-getMissileTimerQueue(e).length);if(n>0){return n}const o=getMissileAvailabilityTick(t,e,getSamCooldown(t));return Number.isFinite(o)?1:0}function getSamStackInterceptionCapacity(t,e){return(e||[]).reduce((n,o)=>n+getUnitLevel(o),0)}function estimateSamStackEffectiveCapacity(t,e,n,o=null){let r=getSamStackInterceptionCapacity(t,e);const i=o?.pendingSams||null;if(i&&i.length>0){const s=o?.pendingSamReadyTicks;const l=Number.isFinite(o?.currentTick)?o.currentTick:getCurrentGameTick(t);const c=l+Math.max(0,Number(n)||0);for(const u of i){const d=getUnitKey(u);const f=s?.get?.(d);if(Number.isFinite(f)&&f<=c){r+=getUnitLevel(u)}}}if(o?.includeUpgradeBuffer&&(e||[]).length>0){r+=AUTO_NUKE_SAM_UPGRADE_BUFFER}if(!r||!n)return r||0;const a=getSamCooldown(t);if(!a)return r;return r*Math.max(1,Math.ceil(n/a))}function getAutoNukePendingStackSams(t,e){if(!e)return[];return(t?.pendingTargetSams||[]).filter(n=>getSamStackKey(n)===e)}function getAutoNukeSamStackSams(t,e,n=null){const o=(t.targetSams||[]).filter(r=>getSamStackKey(r)===e);return o.length>0?o:n||[]}function getAutoNukeSamStackSlots(t,e,n,o,r=null){const i=n.samSlotAvailableTicks?.get?.(o);if(Array.isArray(i)){return[...i].sort((a,s)=>a-s)}return getSamStackMissileSlotAvailableTicks(t,getAutoNukeSamStackSams(e,o,r))}function findAutoNukeSamStackInterceptionSlot(t,e,n,o,r,i=null){if(!e?.sams?.length||o?.sourceTile==null||o?.tile==null){return null}const a=createNukeSuggestionTrajectory(t,o.sourceTile,o.tile,o.rocketDirectionUp);const s=getAutoNukeShotLaunchOffsetTicks(r);let l=null;for(let c=0;cr-i))}}function groupInterceptingSamsByStack(t,e){const n=new Map;for(const o of t){const r=getSamStackKey(o)||getUnitKey(o);if(!r){continue}let i=n.get(r);if(!i){i={key:r,sams:[],ownedByTarget:isSamOwnedByNukeTarget(e,o)};n.set(r,i)}i.sams.push(o)}return Array.from(n.values()).sort((o,r)=>{if(o.ownedByTarget===r.ownedByTarget){return 0}return o.ownedByTarget?1:-1})}function getAutoNukeInterceptionPlan(t,e,n,o,r,i=0){if(n.sourceTile==null){return{shotsNeededBeforeLanding:Infinity,shotSamKeys:[],samSlotUpdates:new Map}}const a=[];const s=new Map;const l=groupInterceptingSamsByStack(e.targetSams||[],e);const c=getAutoNukeShotLaunchOffsetTicks(i);const u=new Map;for(const p of l){if(!p.key)continue;const h=getAutoNukeSamStackSams(e,p.key,p.sams);u.set(p.key,{...p,sams:h.length>0?h:p.sams})}const d=(p,h,y,g)=>{const b=findAutoNukeSamStackInterceptionSlot(t,y,g,p,i+a.length,o);if(!b){return null}let w=h;let T=b.shootTick;let A=b.slotIndex;let I=y.ownedByTarget;for(const[P,$]of u){if(P===h)continue;const k=getSamStackUnitKeys(e.targetSams,P);if(k.length>0&&k.every(Y=>isAutoNukeSamDestroyedBeforeTick(o,Y,c))){continue}const C=s.has(P)?[...s.get(P)]:getAutoNukeSamStackSlots(t,e,o,P,$.sams);const U=findAutoNukeSamStackInterceptionSlot(t,$,C,p,i+a.length,o);if(U&&U.shootTick{const{key:g,shootTick:b,slotIndex:w}=p;let T;if(g===h){T=y}else{const A=u.get(g);T=s.has(g)?[...s.get(g)]:getAutoNukeSamStackSlots(t,e,o,g,A?.sams)}T[w]=b+getSamCooldown(t)+1;T=T.sort((A,I)=>A-I);s.set(g,[...T]);return g===h?[...T]:y};for(const p of l){if(!p.key){continue}const h=getSamStackUnitKeys(e.targetSams,p.key);if(h.length>0&&h.every(V=>isAutoNukeSamDestroyedBeforeTick(o,V,c))){continue}const y=u.get(p.key);if(!y)continue;let g=s.has(p.key)?[...s.get(p.key)]:getAutoNukeSamStackSlots(t,e,o,p.key,y.sams);const b=findAutoNukeSamStackInterceptionSlot(t,y,g,n,i+a.length,o);if(!b){continue}const w=p.sams[0];const T=p.ownedByTarget;let A;if(T){const V=getAutoNukeSamBurnSpec(t,e.myPlayer);if(!V){return{shotsNeededBeforeLanding:Infinity,shotSamKeys:[],samSlotUpdates:new Map,burnCost:Infinity}}A=scoreAutoNukeSamClearCandidate(t,e,V,w,n.totalScore)}else{A=n}if(!A){return{shotsNeededBeforeLanding:Infinity,shotSamKeys:[],samSlotUpdates:new Map,burnCost:Infinity}}let I=0;while(IV+Math.max(1,getUnitLevel(q)),0);const C=getAutoNukePendingStackSams(e,p.key);const U=getCurrentGameTick(t)+getAutoNukeCandidateFlightTicks(t,n);let Y=0;for(const V of C){const q=e.pendingSamReadyTicks?.get?.(getUnitKey(V));if(Number.isFinite(q)&&q<=U){Y+=Math.max(1,getUnitLevel(V))}}const G=$>0?AUTO_NUKE_SAM_UPGRADE_BUFFER:0;const ee=Math.max(0,$-1)*AUTO_NUKE_SAM_STACK_SAFETY_BURN_PER_EXTRA_LEVEL;const H=Math.ceil(k*AUTO_NUKE_SAM_STACK_SAFETY_BURN_LEVEL_FRACTION);const te=Math.min(AUTO_NUKE_SAM_STACK_SAFETY_BURN_CAP,Math.max(ee,H)+Y+G);for(let V=0;V{const y=getNukeSuggestionUnitCost(t,e.myPlayer,h.candidate.spec.type);return p+(Number.isFinite(y)&&y>0?y:Infinity)},0);return{shotsNeededBeforeLanding:a.length,shotSamKeys:a,samSlotUpdates:s,burnCost:m}}function getAutoNukeCircleOverlapFactor(t,e,n=1){let o=1;const r=Math.max(1,Number(n)||1);for(const i of e){if(i.isSamClear){continue}const a=Math.hypot(t.x-i.x,t.y-i.y);const s=Math.min(t.magnitude.outer,i.magnitude.outer);const l=Math.max(t.magnitude.outer,i.magnitude.outer);if(a1?.08:.15)}else if(a1?.28:.45)}}return o}function getAutoNukeRemoteObjectiveFactor(t,e){if((e?.destructionPhase||"")!=="remote"){return 1}const n=e.remoteOrigin;if(!n||!Number.isFinite(n.x)||!Number.isFinite(n.y)){return 1.2}const o=Math.hypot(t.x-n.x,t.y-n.y);return 1.1+Math.min(1.75,o/150)}function isAutoNukeSamClearPhase(t,e){return t==="sams"||t==="destruction"&&e?.destructionPhase==="sams"||Boolean(e?.hasPrioritySamClear)}function isAutoNukeSamClearStackCleared(t,e){if(!e){return true}const n=t?.samClearStackUnitKeys?.get?.(e)||[];if(n.length===0){return false}return n.every(o=>t.destroyedSamKeys.has(o))}function chooseAutoNukeFocusedSamStackKey(t,e){if(e.focusedSamStackKey&&!isAutoNukeSamClearStackCleared(e,e.focusedSamStackKey)){return e.focusedSamStackKey}let n=null;for(const o of t||[]){const r=o.samClearStackKey;if(!o.isSamClear||!r){continue}const i=o.samClearStackSamKeys||[];if(i.length>0){e.samClearStackUnitKeys.set(r,i)}const a=i.reduce((l,c)=>l+(e.destroyedSamKeys.has(c)?0:1),0);if(a<=0){continue}const s=a*AUTO_NUKE_DESTRUCTION_SAM_BONUS+(o.totalScore||0);if(!n||s>n.score){n={stackKey:r,score:s}}}e.focusedSamStackKey=n?.stackKey||null;if(!e.focusedSamStackKey){e.hasPrioritySamClear=false}return e.focusedSamStackKey}function getAutoNukeMarginalScore(t,e,n){if(t.isSamClear||t.spec?.scoreMode==="sam-clear"){if(e==="destruction"||e==="sams"){if(e==="destruction"&&n.destructionPhase!=="sams"){return 0}const a=(t.hitSamKeys||[]).reduce((s,l)=>s+(n.destroyedSamKeys.has(l)?0:1),0);return a>0?t.totalScore*4:0}return(t.hitSamKeys||[]).some(a=>!n.destroyedSamKeys.has(a))?t.totalScore:0}if(e==="economic"&&t.hitEconomicStructureWeights?.size){let a=0;for(const[s,l]of t.hitEconomicStructureWeights){if(!n.hitEconomicStructureKeys.has(s)){a+=l}}return a}if(e==="destruction"){const a=n.destructionPhase||"buildings";if(a==="sams"){if(t.intercepted){return 0}}let s=0;for(const[c,u]of t.hitDestructionStructureWeights||[]){if(!n.hitDestructionStructureKeys.has(c)){s+=u}}const l=s>0?(t.populationLoss||0)*.15:0;return(s+l)*getAutoNukeRemoteObjectiveFactor(t,n)*getAutoNukeCircleOverlapFactor(t,n.selectedCandidates,n.spreadMultiplier)}if(e==="sams"){return 0}let o=0;let r=false;for(const[a,s]of t.hitCityPopulationWeights||[]){if(!n.hitCityPopulationKeys.has(a)){o+=Math.max(0,Number(s)||0);r=true}}for(const[a,s]of t.hitEconomicStructureWeights||[]){if(!n.hitEconomicStructureKeys.has(a)){o+=Math.max(0,Number(s)||0);r=true}}const i=r?o+Math.max(0,Number(t.populationLoss)||0):o;return i*getAutoNukeCircleOverlapFactor(t,n.selectedCandidates,n.spreadMultiplier)}function isAutoNukeHydrogenCandidateEfficient(t,e,n,o,r,i,a,s,l,c){if(n.spec?.type!=="Hydrogen Bomb"){return true}if(isHydrogenCheaperThanFiveAtomNukes(t,e.myPlayer)){return true}const u=getNukeSuggestionUnitCost(t,e.myPlayer,"Atom Bomb");if(!Number.isFinite(u)||u<=0){return a>0}const d=[];for(const g of o){if(g===n||g.isSamClear||g.spec?.type!=="Atom Bomb"){continue}const b=`${g.spec.type}:${g.tile}`;if(i.selectedCandidateKeys.has(b)){continue}const w=getAutoNukeInterceptionPlan(t,e,g,i,r);const T=w.shotsNeededBeforeLanding+1;const A=u+w.burnCost;if(!Number.isFinite(A)||T>5||T>c||A>l){continue}const I=getAutoNukeMarginalScore(g,r,i);if(Ib.density-g.density||b.score-g.score);let f=0;let m=0;let p=0;for(const g of d){if(p+g.shots>5){continue}f+=g.score;m+=g.cost;p+=g.shots;if(p>=5){break}}if(f<=0||m<=0){return a>0}const h=a/s;const y=f/m;return a>=f||h>=y*1.05}function markAutoNukeCandidateLanded(t,e,n=0){if(!t.isSamClear){e.selectedCandidates.push(t)}for(const o of t.hitSamKeys||[]){e.destroyedSamKeys.add(o);if(!e.samDestroyedAtTicks){e.samDestroyedAtTicks=new Map}const r=Number(e.samDestroyedAtTicks.get(o));const i=Math.max(0,Math.floor(Number(n)||0));if(!Number.isFinite(r)||i0){for(const[r,i]of e.samSlotAvailableTicks||[]){e.samSlotAvailableTicks.set(r,i.map(a=>Math.max(0,Number(a)-o)))}for(const[r,i]of e.samDestroyedAtTicks||[]){e.samDestroyedAtTicks.set(r,Math.max(0,Number(i)-o))}}}e.plannedAtTick=n}function orderAutoNukeShots(t){const e=[];const n=[];for(const o of t){const r=o.reason==="sam-intercept"&&o.interceptingSamOwnedByTarget===false;const i=o.reason==="sam-clear"&&o.targetSamOwnedByTarget===false;if(r||i){e.push(o)}else{n.push(o)}}return[...e,...n]}function getAutoNukeDestructionObjectiveScore(t){const e=t.hitDestructionStructureWeights?.size||0;return e*AUTO_NUKE_DESTRUCTION_BUILDING_BONUS+(t.totalScore||0)}function createAutoNukePlanMetrics(){return{populationDamage:0,economicDamage:0,samsDestroyed:0,hitPopulationCandidateKeys:new Set,hitCityPopulationKeys:new Set,hitEconomicStructureKeys:new Set,hitSamKeys:new Set}}function addAutoNukeCandidateMetrics(t,e){if(!t||!e){return}for(const[o,r]of e.hitCityPopulationWeights||[]){if(!t.hitCityPopulationKeys.has(o)){t.hitCityPopulationKeys.add(o);t.populationDamage+=Math.max(0,Number(r)||0)}}const n=Math.max(0,Number(e.populationLoss)||0);if(n>0){const o=`${e.spec?.type||""}:${e.tile}`;if(!t.hitPopulationCandidateKeys.has(o)){t.hitPopulationCandidateKeys.add(o);t.populationDamage+=n}}for(const[o,r]of e.hitEconomicStructureWeights||[]){if(!t.hitEconomicStructureKeys.has(o)){t.hitEconomicStructureKeys.add(o);t.economicDamage+=Math.max(0,Number(r)||0)}}for(const o of e.hitSamKeys||[]){if(!t.hitSamKeys.has(o)){t.hitSamKeys.add(o);t.samsDestroyed++}}}function finalizeAutoNukePlanMetrics(t,e,n){return{shotCount:Math.max(0,Math.floor(Number(e)||0)),cost:Math.max(0,Number(n)||0),populationDamage:Math.max(0,Number(t?.populationDamage)||0),economicDamage:Math.max(0,Number(t?.economicDamage)||0),samsDestroyed:Math.max(0,Math.floor(Number(t?.samsDestroyed)||0))}}function appendAutoNukeDestructionConfirmationShots(t,e,n,o,r,i,a,s,l=AUTO_NUKE_DESTRUCTION_CONFIRMATION_SHOTS,c=null){if(n!=="destruction"&&n!=="sams"||l<=0||i<=0||a<=0){return{remainingGold:i,remainingSiloSlots:a,totalCost:s}}const u=o.selectedCandidates.filter(m=>!m.isSamClear&&m.tile!=null).sort((m,p)=>getAutoNukeDestructionObjectiveScore(p)-getAutoNukeDestructionObjectiveScore(m));if(u.length===0){return{remainingGold:i,remainingSiloSlots:a,totalCost:s}}let d=0;let f=0;while(d0&&a>0&&fa||b>i){continue}for(const w of h.shotSamKeys){r.push({tile:w.candidate.tile,sourceTile:w.candidate.sourceTile,rocketDirectionUp:w.candidate.rocketDirectionUp,spec:w.candidate.spec,reason:"sam-intercept",interceptingSamKey:w.key,interceptingSamOwnedByTarget:w.ownedByTarget})}applyAutoNukeInterceptionPlan(o,h);r.push({tile:m.tile,sourceTile:m.sourceTile,rocketDirectionUp:m.rocketDirectionUp,spec:m.spec,reason:"confirmation"});addAutoNukeCandidateMetrics(c,m);markAutoNukeCandidateLanded(m,o,getAutoNukeCandidateLandingTick(t,m,r.length-1));i-=b;a-=y;s+=b;d++}return{remainingGold:i,remainingSiloSlots:a,totalCost:s}}function buildAutoNukePlan(t,e,n,o=null,r=null){const{baseContext:i,candidates:a}=r||computeAutoNukeCandidatePool(t,e,n);if(!i||a.length===0){return{shots:[],score:0,cost:0,budgetGold:0,budgetSiloSlots:0,availableGold:0,availableSiloSlots:0,minimumRequiredGold:Infinity,minimumRequiredSiloSlots:Infinity,metrics:finalizeAutoNukePlanMetrics(null,0,0),followupState:cloneAutoNukePlanState(),destructionPhase:o?.destructionPhase||"buildings"}}const s=getPlayerGoldNumber(i.myPlayer);const l=Number.isFinite(s)?Math.max(0,s):0;const c=Number(o?.availableSiloSlotsOverride);const u=Number.isFinite(c)&&c>=0?Math.floor(c):getReadyNukeSiloSlotCount(i.myPlayer);const d=Math.min(l,Math.max(0,Number(o?.maxGold??l)));const f=Math.min(u,Math.max(0,Math.floor(Number(o?.maxSiloSlots??u))));let m=d;let p=f;const h=cloneAutoNukePlanState(o?.initialState);normalizeAutoNukePlanStateTiming(t,h);h.spreadMultiplier=Math.max(h.spreadMultiplier||1,Number(o?.spreadMultiplier)||1);h.destructionPhase=o?.destructionPhase||h.destructionPhase||"buildings";if(!h.remoteOrigin&&i.tileInfo?.centroid){h.remoteOrigin={x:i.tileInfo.centroid.x,y:i.tileInfo.centroid.y}}const y=[];const g=createAutoNukePlanMetrics();let b=0;let w=0;let T=Infinity;let A=Infinity;const I=n==="destruction"?Math.max(0,Math.floor(Number(o?.confirmationShots??AUTO_NUKE_DESTRUCTION_CONFIRMATION_SHOTS))):0;const P=n==="destruction"?Math.min(I,Math.max(0,f-2)):0;if(!isAutoNukeSamClearPhase(n,h)){if(a.some(k=>k.isSamClear&&k.isBlockingStack)){h.hasPrioritySamClear=true}}while(p>P&&m>0){let k=null;const C=isAutoNukeSamClearPhase(n,h)?chooseAutoNukeFocusedSamStackKey(a,h):null;for(const G of a){if(C&&G.isSamClear&&G.samClearStackKey!==C){continue}const ee=`${G.spec.type}:${G.tile}`;if(h.selectedCandidateKeys.has(ee)){continue}const H=getNukeSuggestionUnitCost(t,i.myPlayer,G.spec.type);if(!Number.isFinite(H)||H<=0){continue}const te=getAutoNukeInterceptionPlan(t,i,G,h,n,y.length);const V=te.shotsNeededBeforeLanding+1;const q=H+te.burnCost;if(!Number.isFinite(q)||!Number.isFinite(V)){continue}const x=getAutoNukeMarginalScore(G,n,h);if(xp||q+E>m){A=Math.min(A,f-p+V+N+P);T=Math.min(T,d-m+q+E);continue}if(!isAutoNukeHydrogenCandidateEfficient(t,i,G,a,n,h,x,q,m,p)){continue}const R=x/q;if(!k||R>k.valueDensity||R===k.valueDensity&&x>k.marginalScore){k={candidate:G,candidateKey:ee,interceptionPlan:te,marginalScore:x,nukeCost:H,shotsNeeded:V,totalCost:q,valueDensity:R}}}if(!k){break}for(const G of k.interceptionPlan.shotSamKeys){y.push({tile:G.candidate.tile,sourceTile:G.candidate.sourceTile,rocketDirectionUp:G.candidate.rocketDirectionUp,spec:G.candidate.spec,reason:"sam-intercept",interceptingSamKey:G.key,interceptingSamOwnedByTarget:G.ownedByTarget})}applyAutoNukeInterceptionPlan(h,k.interceptionPlan);const U=y.length;y.push({tile:k.candidate.tile,sourceTile:k.candidate.sourceTile,rocketDirectionUp:k.candidate.rocketDirectionUp,spec:k.candidate.spec,reason:k.candidate.isSamClear?"sam-clear":"impact",targetSamOwnedByTarget:k.candidate.samClearOwnedByTarget});addAutoNukeCandidateMetrics(g,k.candidate);let Y=0;if(k.candidate.isSamClear&&(n==="destruction"&&h.destructionPhase==="sams"||n==="sams"||h.hasPrioritySamClear&&k.candidate.isBlockingStack&&k.candidate.samsHit>=3)){const G=p-k.shotsNeeded;const ee=m-k.totalCost;const H=Math.max(1,k.candidate.samsHit||1);const te=k.candidate.samClearStackSamKeys||[];const V=te.filter(L=>!h.destroyedSamKeys.has(L));const q=Math.max(H,V.length);const x=Math.max(q,Number(k.candidate.samClearStackLevelSum)||q);let v;if(q<=1){v=x>=2?1:0}else if(q===2){v=1}else if(q===3){v=2}else{v=3}const N=x>=10?1:0;const E=k.candidate.isBlockingStack&&q>=3?1:0;const R=Math.min(AUTO_NUKE_MAX_SAM_OVERSHOOT_SHOTS,v+N+E);Y=Math.min(R,Math.max(0,G),Math.max(0,Math.floor(ee/k.nukeCost)));for(let L=0;L0||i.score>0||Number.isFinite(i.minimumRequiredGold)||Number.isFinite(i.minimumRequiredSiloSlots)){return i}return buildAutoNukePlan(t,e,n,{...o,destructionPhase:"buildings",confirmationShots:AUTO_NUKE_DESTRUCTION_CONFIRMATION_SHOTS,allowDestructionPhaseFallback:false},r)}function waitForAutoNukeDelay(t){return new Promise(e=>window.setTimeout(e,t))}async function getAutoNukeBuildableUnit(t,e){const n=e?.spec?.type;if(!n||e?.tile==null){return null}if(typeof t?.myPlayer?.buildables==="function"){try{const o=await t.myPlayer.buildables(e.tile,[n]);const r=Array.from(o||[]).find(i=>i?.type===n);if(r?.canBuild){return r}}catch(o){}}if(typeof t?.myPlayer?.actions==="function"){try{const o=await t.myPlayer.actions(e.tile,[n]);const r=Array.from(o?.buildableUnits||[]).find(i=>i?.type===n);if(r?.canBuild){return r}}catch(o){return null}}return null}async function launchAutoNukeCandidate(t,e){if(!e||e.intercepted||e.tile==null){return false}const n=await getAutoNukeBuildableUnit(t,e);if(!n?.canBuild){return false}if(e.spec?.type==="Atom Bomb"||e.spec?.type==="Hydrogen Bomb"){const o=e.rocketDirectionUp!==false;if(t?.buildMenu?.uiState){t.buildMenu.uiState.rocketDirectionUp=o}}t?.buildMenu?.sendBuildOrUpgrade?.(n,e.tile);return true}function estimateAutoNukeShotLandingDelayMs(t,e,n){const o=Number(e?.sourceTile);const r=Number(e?.tile);if(!Number.isFinite(o)||!Number.isFinite(r)||o<0||r<0){return AUTO_NUKE_FALLBACK_LANDING_MS+n*AUTO_NUKE_SEQUENCE_DELAY_MS}try{const i=createNukeSuggestionTrajectory(t,o,r,e.rocketDirectionUp).length;return i*AUTO_NUKE_GAME_TICK_MS+n*AUTO_NUKE_SEQUENCE_DELAY_MS}catch(i){return AUTO_NUKE_FALLBACK_LANDING_MS+n*AUTO_NUKE_SEQUENCE_DELAY_MS}}function estimateAutoNukePlanLandingDelayMs(t,e){const n=Array.isArray(e?.shots)?e.shots:[];if(n.length===0){return 0}return n.reduce((o,r,i)=>Math.max(o,estimateAutoNukeShotLandingDelayMs(t.game,r,i)),0)}async function launchAutoNukePlan(t,e,n=null){let o=0;e.estimatedLandingDelayMs=estimateAutoNukePlanLandingDelayMs(t,e);e.estimatedImpactAtMs=performance.now()+e.estimatedLandingDelayMs;const r=ensureAutoNukeProcessPanel();const i=r.querySelector(".openfront-helper-auto-nuke-process-steps");if(i){const a=n?.objective||"";i.innerHTML=e.shots.map((s,l)=>{const c=s?.reason;let u;if(c==="sam-intercept"){u=s.interceptingSamOwnedByTarget?"Burn SAM slot (decoy)":"Bait allied SAM fire"}else if(c==="sam-clear"){u=s.targetSamOwnedByTarget===false?"Route thru allied SAM":"Destroy SAM stack"}else if(c==="sam-clear-overshoot"){u="SAM overshoot (insurance)"}else if(c==="confirmation"){u="Confirm destruction"}else{u=a==="Destroy SAM stacks"?"Strike SAM position":a==="Destroy SAMs + unguarded targets"?"Strike unguarded target":a==="Destroy main buildings"?"Strike key building":a==="Destroy remote buildings"?"Remote building strike":a==="Maximize economic damage"?"Strike economic target":a==="Maximize population damage"?"Strike population center":"Strike target"}return`
  • ${u}
  • `}).join("")}if(n){updateAutoNukeProcessPanel({...n,detail:`Ready — firing 0/${e.shots.length}`,progress:0})}for(const a of e.shots){if(o>0){await waitForAutoNukeDelay(AUTO_NUKE_SEQUENCE_DELAY_MS)}const s=await launchAutoNukeCandidate(t,a);if(!s){break}if(i){const l=i.querySelector(`[data-shot-index="${o}"]`);if(l){l.setAttribute("data-done","");l.scrollIntoView?.({block:"nearest"})}}o++;if(n){const l=e.shots[o];const c=l?.reason;let u;if(!l){u="All nukes away"}else if(c==="sam-intercept"){u="Burning SAM slot"}else if(c==="sam-clear"||c==="sam-clear-overshoot"){u="Striking SAM stack"}else if(c==="confirmation"){u="Confirming hit"}else{u="Striking target"}updateAutoNukeProcessPanel({...n,detail:`${u} — ${o}/${e.shots.length}`,progress:e.shots.length>0?o/e.shots.length:1})}}return o}function hideAutoNukeProcessPanel(t=0){if(autoNukeProcessHideTimeout!==null){window.clearTimeout(autoNukeProcessHideTimeout);autoNukeProcessHideTimeout=null}const e=()=>{const o=document.getElementById(AUTO_NUKE_PROCESS_PANEL_ID);if(o){o.hidden=true}};const n=Math.max(0,Number(t)||0);if(n>0){autoNukeProcessHideTimeout=window.setTimeout(()=>{autoNukeProcessHideTimeout=null;e()},n);return}e()}function getAutoNukePhaseShortLabel(t,e){if(e==="sams")return"SAM clear";if(e==="buildings")return"Building strike";if(e==="remote")return"Cleanup";if(e==="sam-clear")return"SAM burn";if(t==="economic")return"Economy strike";if(t==="population")return"Population strike";if(t==="sams")return"SAM clear";return"Strike"}function renderAutoNukeWaveLog(){const t=document.getElementById(AUTO_NUKE_PROCESS_PANEL_ID);if(!t)return;const e=t.querySelector(".openfront-helper-auto-nuke-process-waves");if(!e)return;if(autoNukeWaveLog.length===0){e.innerHTML="";return}const n=autoNukeWaveLog.filter(c=>c.status==="done").length;const o=autoNukeWaveLog.length;const r=autoNukeWaveLog.findIndex(c=>c.status==="active");const i=r>=0?r+1:n;const a=autoNukeWaveLog.map((c,u)=>{let d="wave-dot";if(c.status==="done")d+=" wave-dot--done";else if(c.status==="active")d+=" wave-dot--active";return`${c.status==="done"?"✓":u+1}`}).join(``);const s=r>=0?autoNukeWaveLog[r]:null;const l=s?s.label.replace(/^Wave \d+ — /,""):n===o?"Done":"";e.innerHTML=`
    ${l} ${i} / ${o}
    ${a}
    `}function resetAutoNukeWaveLog(){autoNukeWaveLog=[];renderAutoNukeWaveLog()}function pushAutoNukeWaveEntry(t,e="pending"){const n=autoNukeWaveLog.length;autoNukeWaveLog.push({label:t,status:e});renderAutoNukeWaveLog();return n}function setAutoNukeWaveEntryStatus(t,e){if(t>=0&&autoNukeWaveLog[t]){autoNukeWaveLog[t].status=e;renderAutoNukeWaveLog()}}function activateAutoNukeWaveEntry(t){setAutoNukeWaveEntryStatus(t,"active")}function completeAutoNukeWaveEntry(t){setAutoNukeWaveEntryStatus(t,"done")}function hideAutoNukeProcessPanelAfterLanding(t){const e=Math.max(0,(t||0)-performance.now());hideAutoNukeProcessPanel(e+AUTO_NUKE_PROCESS_COMPLETE_HIDE_MS)}function ensureAutoNukeProcessPanelStyles(){if(document.getElementById(AUTO_NUKE_PROCESS_PANEL_STYLE_ID)){return}const t=document.createElement("style");t.id=AUTO_NUKE_PROCESS_PANEL_STYLE_ID;t.textContent=` #${AUTO_NUKE_PROCESS_PANEL_ID} { position: fixed; right: 14px; top: calc(50% + 118px); z-index: 2147483647; width: min(320px, calc(100vw - 28px)); padding: 10px; border: 1px solid rgba(56, 189, 248, 0.46); border-radius: 8px; background: rgba(13, 18, 24, 0.97); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.44); color: #f8fafc; font: 800 12px/1.2 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; transform: translateY(-50%); } #${AUTO_NUKE_PROCESS_PANEL_ID}[hidden] { display: none; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-top { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 5px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-wave { color: #bae6fd; font-size: 12px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-target { color: #94a3b8; font-size: 11px; max-width: 128px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-objective { color: #e2e8f0; font-size: 11px; margin-bottom: 7px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-track { height: 7px; overflow: hidden; border-radius: 6px; background: rgba(51, 65, 85, 0.92); margin-bottom: 7px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-fill { width: 0%; height: 100%; border-radius: 6px; background: linear-gradient(90deg, #38bdf8, #facc15); transition: width 140ms linear; } #${AUTO_NUKE_PROCESS_PANEL_ID}.openfront-helper-auto-nuke-process-waiting .openfront-helper-auto-nuke-process-fill { width: 38%; animation: openfront-helper-auto-nuke-process-wait 1.2s ease-in-out infinite alternate; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-detail { color: #cbd5e1; font-size: 11px; font-weight: 750; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps { list-style: none; margin: 6px 0 0; padding: 0; max-height: 120px; overflow-y: auto; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-waves { margin: 0 0 8px; padding: 0; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-waves:empty { display: none; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-summary { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-summary-label { font-size: 11px; font-weight: 600; color: #e2e8f0; letter-spacing: 0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 72%; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-summary-count { font-size: 10px; font-weight: 500; color: #64748b; white-space: nowrap; flex-shrink: 0; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-track { display: flex; align-items: center; gap: 0; flex-wrap: wrap; row-gap: 4px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-connector { flex: 1; min-width: 6px; max-width: 18px; height: 1px; background: #334155; border-radius: 1px; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-dot { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; font-size: 8px; font-weight: 700; background: #1e293b; color: #475569; border: 1.5px solid #334155; flex-shrink: 0; transition: background 200ms, border-color 200ms, color 200ms, box-shadow 200ms; cursor: default; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-dot--done { background: #14532d; border-color: #22c55e; color: #4ade80; } #${AUTO_NUKE_PROCESS_PANEL_ID} .wave-dot--active { background: #0c4a6e; border-color: #38bdf8; color: #7dd3fc; box-shadow: 0 0 6px 1px rgba(56, 189, 248, 0.35); animation: wave-dot-pulse 1.4s ease-in-out infinite; } @keyframes wave-dot-pulse { 0%, 100% { box-shadow: 0 0 6px 1px rgba(56, 189, 248, 0.35); } 50% { box-shadow: 0 0 10px 3px rgba(56, 189, 248, 0.55); } } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps:empty { display: none; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps li { font-size: 10px; color: #64748b; padding: 1px 0; transition: color 100ms; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps li::before { content: "○ "; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps li[data-done] { color: #4ade80; } #${AUTO_NUKE_PROCESS_PANEL_ID} .openfront-helper-auto-nuke-process-steps li[data-done]::before { content: "✓ "; } @keyframes openfront-helper-auto-nuke-process-wait { from { transform: translateX(-16%); } to { transform: translateX(180%); } } `;(document.head||document.documentElement).appendChild(t)}function ensureAutoNukeProcessPanel(){ensureAutoNukeProcessPanelStyles();let t=document.getElementById(AUTO_NUKE_PROCESS_PANEL_ID);if(t){return t}t=document.createElement("div");t.id=AUTO_NUKE_PROCESS_PANEL_ID;t.hidden=true;t.innerHTML=`
      `;(document.body||document.documentElement).appendChild(t);return t}function updateAutoNukeProcessPanel(t={}){if(autoNukeProcessHideTimeout!==null){window.clearTimeout(autoNukeProcessHideTimeout);autoNukeProcessHideTimeout=null}const e=ensureAutoNukeProcessPanel();const n=e.querySelector(".openfront-helper-auto-nuke-process-wave");const o=e.querySelector(".openfront-helper-auto-nuke-process-target");const r=e.querySelector(".openfront-helper-auto-nuke-process-objective");const i=e.querySelector(".openfront-helper-auto-nuke-process-fill");const a=e.querySelector(".openfront-helper-auto-nuke-process-detail");const s=Math.max(0,Math.min(1,Number(t.progress)||0));e.classList.toggle("openfront-helper-auto-nuke-process-waiting",Boolean(t.waiting));if(n){n.textContent=t.waveLabel||"Auto nuke"}if(o){o.textContent=t.targetName||"";o.title=t.targetName||""}if(r){r.textContent=t.objective||"Preparing strike"}if(i&&!t.waiting){i.style.width=`${Math.round(s*100)}%`}if(a){a.textContent=t.detail||""}renderAutoNukeWaveLog();e.hidden=false}function getAutoNukeProcessObjective(t,e=null,n=null){if(t==="economic"){return"Maximize economic damage"}if(t==="population"){return"Maximize population damage"}if(e==="sams"){const o=n?.shots?.some(r=>r.reason==="impact"||r.reason==="confirmation");return o?"Destroy SAMs + unguarded targets":"Destroy SAM stacks"}if(e==="remote"){return"Destroy remote buildings"}if(e==="buildings"){return"Destroy main buildings"}if(t==="destruction"){return"Total destruction"}return"Launch nukes"}function getAutoNukeProcessWaveStatus(t,e,n,o,r,i={},a=null){return{targetName:getPlayerDisplayName(t.selected),waveLabel:`Wave ${n}`,objective:getAutoNukeProcessObjective(e,o,a),detail:r,...i}}function canContinueAutoNukeSecondPass(t){return Boolean(t?.myPlayer?.isPlayer?.()&&t?.selected?.isPlayer?.()&&(isEnemyNukeSuggestionTarget(t.game,t.selected)||autoNukeIncludeAllies&&t.selected?.isAlive?.()&&getPlayerSmallId(t.myPlayer)!==getPlayerSmallId(t.selected)))}function buildAutoNukeWavePotentialPlan(t,e,n,o){const r=getTotalNukeSiloSlotCount(t.myPlayer);const i=getPlayerGoldNumber(t.myPlayer);const a=Number.isFinite(i)?Math.max(0,i):0;if(r<=0||a<=0){return null}const s=computeAutoNukeCandidatePool(t.game,t.selected,e,{includeCoolingSilos:true});const l=buildAutoNukePlan(t.game,t.selected,e,{maxGold:a,maxSiloSlots:r,availableSiloSlotsOverride:r,initialState:n,spreadMultiplier:o.spreadMultiplier,destructionPhase:o.destructionPhase,confirmationShots:o.confirmationShots},s);return l.shots.length>0&&l.score>0?l:null}async function waitForAutoNukeWavePlan(t,e,n,o,r=null){const i=buildAutoNukeWavePotentialPlan(t,e,n,o);if(!i){return null}const a=getTotalNukeSiloSlotCount(t.myPlayer);const s=Number(o.readySlotFraction)||.6;const l=Math.min(a,Math.max(1,Math.min(i.budgetSiloSlots||a,Math.max(2,Math.ceil(a*s)))));const c=performance.now();let u=null;let d=-Infinity;const f=3500;if(r){updateAutoNukeProcessPanel({...r,detail:`Waiting for silos ${getReadyNukeSiloSlotCount(t.myPlayer)}/${l}`,waiting:true})}while(performance.now()-c0&&y<1?Math.max(1,Math.ceil(m*y)):m;const b=performance.now();if(!u||b-d>f){u=computeAutoNukeCandidatePool(t.game,t.selected,e,{includeCoolingSilos:true});d=b}const w=buildAutoNukePlan(t.game,t.selected,e,{maxGold:h,maxSiloSlots:g,initialState:n,spreadMultiplier:o.spreadMultiplier,destructionPhase:o.destructionPhase,confirmationShots:o.confirmationShots},u);if(w.shots.length>0&&w.score>0){return w}u=null;if(r){updateAutoNukeProcessPanel({...r,detail:"Scanning targets...",waiting:true})}await waitForAutoNukeDelay(AUTO_NUKE_SECOND_PASS_POLL_MS)}return null}async function launchAutoNukeWithOptionalSecondPass(t,e,n,o){resetAutoNukeWaveLog();const r=e==="sams";const i=e==="destruction"||r;let a=1;const s=i?o.destructionPhase:e;const l=getAutoNukePhaseShortLabel(e,s);const c=pushAutoNukeWaveEntry(`Wave 1 — ${l}`,"active");const u=await launchAutoNukePlan(t,o,getAutoNukeProcessWaveStatus(t,e,a,s,`Firing ${o.shots.length} nukes`,{},o));let d=o.estimatedImpactAtMs||0;completeAutoNukeWaveEntry(c);if(u===0){console.info(`OpenFront Helper: auto ${e} nukes could not be launched.`);updateAutoNukeProcessPanel(getAutoNukeProcessWaveStatus(t,e,a,i?o.destructionPhase:e,"Launch failed",{progress:1}));hideAutoNukeProcessPanel(AUTO_NUKE_PROCESS_COMPLETE_HIDE_MS);return}if(!i){let T=o.followupState;if(T?.hasPrioritySamClear&&o.shots.some($=>$.reason==="sam-clear")){for(let $=0;$Y.reason==="sam-clear")){a--;completeAutoNukeWaveEntry(k);break}const U=await launchAutoNukePlan(t,C,getAutoNukeProcessWaveStatus(t,e,a,"sam-clear",`Firing ${C.shots.length} SAM-clear nukes`));completeAutoNukeWaveEntry(k);if(U===0){a--;break}d=Math.max(d,C.estimatedImpactAtMs||0);T=C.followupState}}let A=a+1;let I=0;while(I0?A-1:a;updateAutoNukeProcessPanel(getAutoNukeProcessWaveStatus(t,e,P,e,I>0?`Complete — ${1+I} waves`:`Complete — ${u} nukes`,{progress:1}));hideAutoNukeProcessPanelAfterLanding(d);return}a++;let f=o.followupState;let m=o.destructionPhase==="sams"?1:0;while(m>0&&m{if(!l){console.info(`OpenFront Helper: auto ${e} nuke could not be launched.`)}});return}launchAutoNukeWithOptionalSecondPass(t,e,n,a)}function ensureAutoNukeContextMenuStyles(){if(document.getElementById(AUTO_NUKE_CONTEXT_MENU_STYLE_ID)){return}const t=document.createElement("style");t.id=AUTO_NUKE_CONTEXT_MENU_STYLE_ID;t.textContent=` #${AUTO_NUKE_CONTEXT_MENU_ID} { position: fixed; z-index: 2147483647; min-width: 220px; padding: 6px; border: 1px solid rgba(148, 163, 184, 0.45); border-radius: 8px; background: rgba(13, 18, 24, 0.96); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.42); color: #f8fafc; font: 800 12px/1.15 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; } #${AUTO_NUKE_CONTEXT_MENU_ID}[hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-title { padding: 5px 7px 7px; color: #cbd5e1; font-size: 11px; font-weight: 800; border-bottom: 1px solid rgba(148, 163, 184, 0.22); margin-bottom: 5px; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-loading { margin: 5px 4px 7px; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-loading[hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-loading-track { height: 6px; overflow: hidden; border-radius: 6px; background: rgba(51, 65, 85, 0.92); } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-loading-fill { width: 0%; height: 100%; border-radius: 6px; background: linear-gradient(90deg, #38bdf8, #facc15); transition: width 120ms linear; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-loading-text { margin-top: 4px; color: #cbd5e1; font-size: 10px; font-weight: 750; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-steps { list-style: none; margin: 5px 0 0; padding: 0; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-steps li { font-size: 10px; color: #64748b; padding: 1px 0; transition: color 100ms; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-steps li::before { content: "○ "; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-steps li[data-done] { color: #4ade80; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-steps li[data-done]::before { content: "✓ "; } #${AUTO_NUKE_CONTEXT_MENU_ID} button { display: block; width: 100%; margin: 0; padding: 8px 9px; border: 0; border-radius: 6px; background: transparent; color: #f8fafc; font: inherit; letter-spacing: 0; text-align: left; cursor: pointer; } #${AUTO_NUKE_CONTEXT_MENU_ID} button:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button:focus-visible:not(:disabled) { outline: none; background: rgba(239, 68, 68, 0.24); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="economic"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="economic"]:focus-visible:not(:disabled) { background: rgba(34, 197, 94, 0.22); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="destruction"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="destruction"]:focus-visible:not(:disabled) { background: rgba(250, 204, 21, 0.2); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="sams"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="sams"]:focus-visible:not(:disabled) { background: rgba(56, 189, 248, 0.2); } #${AUTO_NUKE_CONTEXT_MENU_ID} button:disabled { cursor: not-allowed; color: rgba(203, 213, 225, 0.48); } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section { margin: 7px 4px 3px; padding-top: 6px; border-top: 1px solid rgba(148, 163, 184, 0.2); color: #94a3b8; font-size: 10px; font-weight: 900; text-transform: uppercase; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section[hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section[data-auto-nuke-section-label="economic"] { color: #86efac; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section[data-auto-nuke-section-label="population"] { color: #fca5a5; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section[data-auto-nuke-section-label="sams"] { color: #bae6fd; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-section[data-auto-nuke-section-label="destruction"] { color: #fde68a; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="economic"] { border-left: 2px solid rgba(34, 197, 94, 0.42); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="population"] { border-left: 2px solid rgba(239, 68, 68, 0.42); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="sams"] { border-left: 2px solid rgba(56, 189, 248, 0.5); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-mode="destruction"] { border-left: 2px solid rgba(250, 204, 21, 0.5); } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-label, #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-meta { display: block; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-label { color: inherit; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-meta { margin-top: 3px; color: #cbd5e1; font-size: 10px; font-weight: 750; } #${AUTO_NUKE_CONTEXT_MENU_ID} button:disabled .openfront-helper-auto-nuke-meta { color: rgba(203, 213, 225, 0.48); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-back][hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-back] { color: #94a3b8; font-size: 11px; font-weight: 750; margin-bottom: 3px; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-back]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-back]:focus-visible:not(:disabled) { background: rgba(148, 163, 184, 0.12); color: #f8fafc; outline: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-row { display: flex; gap: 4px; align-items: stretch; } #${AUTO_NUKE_CONTEXT_MENU_ID} .openfront-helper-auto-nuke-row > button[data-auto-nuke-expand] { flex: 1; min-width: 0; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-build-silo] { flex-shrink: 0; width: auto; padding: 6px 10px; font-size: 11px; font-weight: 850; border: 1px solid rgba(16, 185, 129, 0.5); border-radius: 6px; background: rgba(16, 185, 129, 0.12); color: #6ee7b7; white-space: nowrap; cursor: pointer; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-build-silo]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-build-silo]:focus-visible:not(:disabled) { background: rgba(16, 185, 129, 0.28); outline: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-build-silo]:disabled { opacity: 0.55; cursor: not-allowed; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-build-silo][hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch][hidden] { display: none; } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="economic"] { border-left: 2px solid rgba(34, 197, 94, 0.42); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="population"] { border-left: 2px solid rgba(239, 68, 68, 0.42); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="sams"] { border-left: 2px solid rgba(56, 189, 248, 0.5); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="destruction"] { border-left: 2px solid rgba(250, 204, 21, 0.5); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="economic"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="economic"]:focus-visible:not(:disabled) { background: rgba(34, 197, 94, 0.22); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="population"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="population"]:focus-visible:not(:disabled) { background: rgba(239, 68, 68, 0.24); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="sams"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="sams"]:focus-visible:not(:disabled) { background: rgba(56, 189, 248, 0.2); } #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="destruction"]:hover:not(:disabled), #${AUTO_NUKE_CONTEXT_MENU_ID} button[data-auto-nuke-branch="destruction"]:focus-visible:not(:disabled) { background: rgba(250, 204, 21, 0.2); } `;(document.head||document.documentElement).appendChild(t)}function ensureAutoNukeContextMenu(){ensureAutoNukeContextMenuStyles();let t=document.getElementById(AUTO_NUKE_CONTEXT_MENU_ID);if(t){return t}t=document.createElement("div");t.id=AUTO_NUKE_CONTEXT_MENU_ID;t.hidden=true;t.innerHTML=`
      Auto nuke
      Economy
      Population
      SAMs
      Total destruction
      `;t.addEventListener("click",e=>{const n=e.target;if(!(n instanceof HTMLElement)){return}const o=n.closest("[data-auto-nuke-expand]");if(o instanceof HTMLButtonElement){if(!o.disabled){expandAutoNukeContextMenu(t)}return}const r=n.closest("[data-auto-nuke-build-silo]");if(r instanceof HTMLButtonElement&&!r.disabled){const d=autoNukeContextMenuParams;handleAutoNukeBuildSiloClick(t,d,r).catch(f=>{console.error("OpenFront Helper: failed to build missile silo.",f)});return}const i=n.closest("[data-auto-nuke-back]");if(i instanceof HTMLButtonElement&&!i.disabled){goBackToAutoNukeBranchMenu(t);return}const a=n.closest("[data-auto-nuke-branch]");if(a instanceof HTMLButtonElement&&!a.disabled){expandAutoNukeModeDetails(t,a.dataset.autoNukeBranch).catch(d=>{console.error("OpenFront Helper: failed to calculate auto nuke plan.",d)});return}const s=n.closest("[data-auto-nuke-mode]");if(!(s instanceof HTMLButtonElement)||s.disabled){return}const l=s.dataset.autoNukeMode;const c=s.dataset.autoNukeTier;const u=autoNukeContextMenuParams;hideAutoNukeContextMenu();if(u&&(l==="economic"||l==="population"||l==="destruction"||l==="sams")){executeAutoNuke(u,l,c)}});(document.body||document.documentElement).appendChild(t);return t}function hideAutoNukeContextMenu(){autoNukeContextMenuComputeId++;const t=document.getElementById(AUTO_NUKE_CONTEXT_MENU_ID);if(t){t.hidden=true}autoNukeContextMenuParams=null}function getAutoNukeContextMenuDisableReason(t){if(!t?.selected?.isPlayer?.()){return"No player or nation selected."}const e=isEnemyNukeSuggestionTarget(t.game,t.selected);if(!e&&!autoNukeIncludeAllies){return"Target is not hostile."}if(!hasAutoNukeBuildTypeAvailable(t.game,t.myPlayer)){return"No nuke type is available."}const n=getReadyNukeSiloSlotCount(t.myPlayer);if(n===0){const i=getTotalNukeSiloSlotCount(t.myPlayer);if(i===0){return"No missile silos built yet."}return`0 / ${i} silo slot${i!==1?"s":""} ready.`}const o=getPlayerGoldNumber(t.myPlayer);const r=getAutoNukeMinimumAvailableCost(t.game,t.myPlayer);if(!Number.isFinite(o)||oe.id===t)||AUTO_NUKE_INTENSITY_TIERS[AUTO_NUKE_INTENSITY_TIERS.length-1]}function getAutoNukeTierRank(t){return AUTO_NUKE_TIER_ORDER.has(t)?AUTO_NUKE_TIER_ORDER.get(t):AUTO_NUKE_INTENSITY_TIERS.length}function getAutoNukeTierPlanOptions(t,e){if(!e){return{tier:null,options:null,reason:""}}const n=getAutoNukeTierById(e);const o=getReadyNukeSiloSlotCount(t.myPlayer);const r=getPlayerGoldNumber(t.myPlayer);const i=Number.isFinite(r)?Math.max(0,r):0;const a=getAutoNukeMinimumAvailableCost(t.game,t.myPlayer);if(o0&&r>0){return`Need ${formatAutoNukeGold(r)} more gold and ${o} more missile silo ${o===1?"slot":"slots"} for this tier.`}if(o>0){return`Need ${o} more missile silo ${o===1?"slot":"slots"} for this tier.`}if(r>0){return`Need ${formatAutoNukeGold(r)} more gold for this tier.`}return"No safe plan."}function getAutoNukePlanMetrics(t){return t?.metrics||finalizeAutoNukePlanMetrics(null,t?.shots?.length||0,t?.cost||0)}function formatAutoNukePlanMeta(t,e){const n=getAutoNukePlanMetrics(e);const o=n.shotCount||e?.shots?.length||0;const r=o===1?"nuke":"nukes";const i=[`${o} ${r}`,`${formatAutoNukeGold(n.cost||e?.cost||0)} gold`];if(t==="population"||t==="destruction"){i.push(`${formatAutoNukeMetric(n.populationDamage)} pop`)}if(t==="economic"||t==="destruction"){i.push(`${formatAutoNukeMetric(n.economicDamage)} econ`)}if(t==="sams"||t==="destruction"||n.samsDestroyed>0){i.push(`${n.samsDestroyed||0} SAMs`)}if(t==="destruction"){i.push("multi-wave")}else if(t==="sams"){i.push("SAM waves")}return i.join(" | ")}function enforceAutoNukeTierPlanOrder(t){const e=t.filter(o=>o.tierId).sort((o,r)=>getAutoNukeTierRank(o.tierId)-getAutoNukeTierRank(r.tierId));let n=null;for(const o of e){const r=o.plan;const i=r?.metrics?.shotCount||r?.shots?.length||0;const a=n?.metrics?.shotCount||n?.shots?.length||0;if(!o.reason&&r?.shots?.length&&n&&i=l){n=o.plan}}}}function renderAutoNukeButton(t,e,n,o,r=""){const i=getAutoNukeTierById(n);const a=document.createElement("span");a.className="openfront-helper-auto-nuke-label";a.textContent=e==="sams"?getAutoNukeModeLabel(e):`${getAutoNukeModeLabel(e)} - ${i.label}`;const s=document.createElement("span");s.className="openfront-helper-auto-nuke-meta";if(r){t.disabled=true;t.title=r;s.textContent=r}else if(!o?.shots?.length||o.score<=0){const l="No valid plan.";t.disabled=true;t.title=l;s.textContent=l}else{const l=formatAutoNukePlanMeta(e,o);t.disabled=false;t.title=l;s.textContent=l}t.replaceChildren(a,s)}function renderAutoNukeLoadingButton(t,e,n){const o=n?getAutoNukeTierById(n):null;const r=document.createElement("span");r.className="openfront-helper-auto-nuke-label";r.textContent=e==="sams"?getAutoNukeModeLabel(e):`${getAutoNukeModeLabel(e)} - ${o?.label||"High"}`;const i=document.createElement("span");i.className="openfront-helper-auto-nuke-meta";i.textContent="Calculating...";t.disabled=true;t.title="Calculating auto nuke plan";t.replaceChildren(r,i)}function renderAutoNukeExpandButton(t,e=""){const n=document.createElement("span");n.className="openfront-helper-auto-nuke-label";n.textContent="Auto nuke";const o=document.createElement("span");o.className="openfront-helper-auto-nuke-meta";o.textContent=e||"Show auto nuke options";t.disabled=Boolean(e);t.title=e||"Calculate auto nuke plans";t.replaceChildren(n,o)}function renderAutoNukeBranchButton(t,e,n=""){const o={economic:"Economy",population:"Population",sams:"SAMs",destruction:"Total destruction"};const r=document.createElement("span");r.className="openfront-helper-auto-nuke-label";r.textContent=o[e]||getAutoNukeModeLabel(e);const i=document.createElement("span");i.className="openfront-helper-auto-nuke-meta";i.textContent=n||"Show options →";t.disabled=Boolean(n);t.title=n||o[e]||e;t.replaceChildren(r,i)}function waitForAutoNukeMenuFrame(){return new Promise(t=>window.requestAnimationFrame(()=>window.requestAnimationFrame(t)))}function isAutoNukeMenuComputeActive(t,e,n){return autoNukeContextMenuComputeId===n&&autoNukeContextMenuParams===e&&!t.hidden}function setAutoNukeMenuLoading(t,e,n=0,o=""){const r=t.querySelector("[data-auto-nuke-loading]");const i=t.querySelector("[data-auto-nuke-loading-fill]");const a=t.querySelector("[data-auto-nuke-loading-text]");if(r instanceof HTMLElement){r.hidden=!e}if(i instanceof HTMLElement){i.style.width=`${Math.round(Math.max(0,Math.min(1,n))*100)}%`}if(a instanceof HTMLElement){a.textContent=o}}function initAutoNukeMenuLoadingSteps(t,e){const n=t.querySelector("[data-auto-nuke-steps]");if(!(n instanceof HTMLElement))return;n.innerHTML=e.map(o=>`
    • ${o}
    • `).join("")}function markAutoNukeMenuLoadingStepDone(t,e){const n=t.querySelector("[data-auto-nuke-steps]");if(!(n instanceof HTMLElement))return;const o=n.children[e];if(o instanceof HTMLElement){o.setAttribute("data-done","")}}function setAutoNukeModeButtonsHidden(t,e){for(const n of t.querySelectorAll("[data-auto-nuke-mode]")){if(n instanceof HTMLButtonElement){n.hidden=e}}}function setAutoNukeSectionLabelsHidden(t,e){for(const n of t.querySelectorAll("[data-auto-nuke-section-label]")){if(n instanceof HTMLElement){n.hidden=e}}}function setAutoNukeBranchButtonsHidden(t,e){for(const n of t.querySelectorAll("[data-auto-nuke-branch]")){if(n instanceof HTMLButtonElement){n.hidden=e}}}function goBackToAutoNukeBranchMenu(t){autoNukeContextMenuComputeId++;const e=autoNukeContextMenuParams;if(!e){return}const n=t.querySelector("[data-auto-nuke-back]");if(n instanceof HTMLButtonElement){n.hidden=true}setAutoNukeMenuLoading(t,false);setAutoNukeSectionLabelsHidden(t,true);setAutoNukeModeButtonsHidden(t,true);const o=t.querySelector("[data-auto-nuke-build-silo]");if(o instanceof HTMLButtonElement){o.hidden=true}const r=getAutoNukeContextMenuDisableReason(e);for(const i of t.querySelectorAll("[data-auto-nuke-branch]")){if(i instanceof HTMLButtonElement){i.hidden=false;renderAutoNukeBranchButton(i,i.dataset.autoNukeBranch,r)}}positionAutoNukeContextMenu(t,Number.parseFloat(t.style.left)||6,Number.parseFloat(t.style.top)||6)}function expandAutoNukeContextMenu(t){const e=autoNukeContextMenuParams;if(!e){return}const n=t.querySelector("[data-auto-nuke-expand]");if(n instanceof HTMLButtonElement){n.hidden=true}const o=t.querySelector("[data-auto-nuke-build-silo]");if(o instanceof HTMLButtonElement){o.hidden=true}const r=getAutoNukeContextMenuDisableReason(e);e.autoNukePlans={};for(const i of t.querySelectorAll("[data-auto-nuke-branch]")){if(i instanceof HTMLButtonElement){i.hidden=false;renderAutoNukeBranchButton(i,i.dataset.autoNukeBranch,r)}}positionAutoNukeContextMenu(t,Number.parseFloat(t.style.left)||6,Number.parseFloat(t.style.top)||6)}async function expandAutoNukeModeDetails(t,e){const n=autoNukeContextMenuParams;if(!n){return}const o=++autoNukeContextMenuComputeId;setAutoNukeBranchButtonsHidden(t,true);setAutoNukeSectionLabelsHidden(t,true);setAutoNukeModeButtonsHidden(t,true);const r=t.querySelector("[data-auto-nuke-back]");if(r instanceof HTMLButtonElement){r.hidden=false;r.textContent="← Back"}const i=t.querySelector(`[data-auto-nuke-section-label="${e}"]`);if(i instanceof HTMLElement){i.hidden=false}const a=Array.from(t.querySelectorAll(`[data-auto-nuke-mode="${e}"]`)).filter(p=>p instanceof HTMLButtonElement);const s=1+a.length;let l=0;for(const p of a){p.hidden=false;renderAutoNukeLoadingButton(p,e,p.dataset.autoNukeTier)}setAutoNukeMenuLoading(t,true,.02,`Calculating ${getAutoNukeModeLabel(e)}...`);initAutoNukeMenuLoadingSteps(t,["Computing targets",...a.map(p=>{const h=getAutoNukeTierById(p.dataset.autoNukeTier);return h?`${h.label} intensity`:"Calculating plan"})]);positionAutoNukeContextMenu(t,Number.parseFloat(t.style.left)||6,Number.parseFloat(t.style.top)||6);await waitForAutoNukeMenuFrame();if(!isAutoNukeMenuComputeActive(t,n,o)){return}const c=getAutoNukeContextMenuDisableReason(n);if(c){for(const h of a){renderAutoNukeButton(h,e,h.dataset.autoNukeTier,null,c)}const p=t.querySelector("[data-auto-nuke-build-silo]");if(p instanceof HTMLButtonElement){const h=isAutoNukeSiloShortageReason(c)&&Boolean(n?.buildMenu?.sendBuildOrUpgrade);if(h){p.hidden=false;renderAutoNukeBuildSiloButton(p,n)}}setAutoNukeMenuLoading(t,false);positionAutoNukeContextMenu(t,Number.parseFloat(t.style.left)||6,Number.parseFloat(t.style.top)||6);return}const u=computeAutoNukeCandidatePool(n.game,n.selected,e);l++;markAutoNukeMenuLoadingStepDone(t,0);setAutoNukeMenuLoading(t,true,l/Math.max(1,s),`Calculating ${getAutoNukeModeLabel(e)}...`);await waitForAutoNukeMenuFrame();if(!isAutoNukeMenuComputeActive(t,n,o)){return}const d=[];let f=1;for(const p of a){const h=p.dataset.autoNukeTier;const y=getAutoNukeTierPlanOptions(n,h);const g=y.reason?null:buildAutoNukePlanForMode(n.game,n.selected,e,getAutoNukeBuildPlanOptions(e,y),u);d.push({button:p,mode:e,tierId:h,plan:g,reason:y.reason});markAutoNukeMenuLoadingStepDone(t,f++);await waitForAutoNukeMenuFrame();if(!isAutoNukeMenuComputeActive(t,n,o)){return}}enforceAutoNukeTierPlanOrder(d);for(const p of d){if(!isAutoNukeMenuComputeActive(t,n,o)){return}const h=getAutoNukePlanKey(p.mode,p.tierId);if(p.plan){n.autoNukePlans[h]=p.plan}const y=p.reason||(!p.plan?.shots?.length||p.plan.score<=0?getAutoNukePlanFailureReason(n,p.plan):"");p.displayReason=y;renderAutoNukeButton(p.button,p.mode,p.tierId,p.plan,y);l++;setAutoNukeMenuLoading(t,true,l/Math.max(1,s),`Calculated ${getAutoNukeModeLabel(e)}`)}if(!isAutoNukeMenuComputeActive(t,n,o)){return}const m=t.querySelector("[data-auto-nuke-build-silo]");if(m instanceof HTMLButtonElement){const p=d.length>0&&d.every(y=>isAutoNukeSiloShortageReason(y.displayReason||y.reason));const h=p&&Boolean(n?.buildMenu?.sendBuildOrUpgrade);if(h){m.hidden=false;renderAutoNukeBuildSiloButton(m,n)}else{m.hidden=true}}setAutoNukeMenuLoading(t,false);positionAutoNukeContextMenu(t,Number.parseFloat(t.style.left)||6,Number.parseFloat(t.style.top)||6)}function positionAutoNukeContextMenu(t,e,n){t.hidden=false;t.style.left="0px";t.style.top="0px";const o=t.getBoundingClientRect();const r=Math.min(Math.max(6,e),window.innerWidth-o.width-6);const i=Math.min(Math.max(6,n),window.innerHeight-o.height-6);t.style.left=`${r}px`;t.style.top=`${i}px`}function showAutoNukeContextMenu(t,e,n){const o=ensureAutoNukeContextMenu();autoNukeContextMenuParams=n;const r=getAutoNukeContextMenuDisableReason(n);n.autoNukePlans={};const i=o.querySelector("[data-auto-nuke-expand]");if(i instanceof HTMLButtonElement){i.hidden=false;renderAutoNukeExpandButton(i,r)}const a=o.querySelector("[data-auto-nuke-build-silo]");if(a instanceof HTMLButtonElement){const l=isAutoNukeSiloShortageReason(r)&&Boolean(n?.buildMenu?.sendBuildOrUpgrade);if(l){a.hidden=false;renderAutoNukeBuildSiloButton(a,n)}else{a.hidden=true}}setAutoNukeModeButtonsHidden(o,true);setAutoNukeSectionLabelsHidden(o,true);setAutoNukeBranchButtonsHidden(o,true);const s=o.querySelector("[data-auto-nuke-back]");if(s instanceof HTMLButtonElement){s.hidden=true}positionAutoNukeContextMenu(o,t,e)}function isAutoNukeSiloShortageReason(t){if(!t){return false}return t==="No missile silos built yet."||/silo slot/i.test(t)||/missile silo/i.test(t)}function renderAutoNukeBuildSiloButton(t,e,n=""){t.disabled=false;t.title="Stack missile silos on an existing one";t.textContent=n||"🏗 Build silos for me"}async function handleAutoNukeBuildSiloClick(t,e,n){if(!e?.myPlayer||!e?.buildMenu?.sendBuildOrUpgrade){return}n.disabled=true;n.textContent="⏳ Building...";const o=await autoBuildMissileSilo(e);if(o>0){n.textContent=`✓ ${o} silo${o!==1?"s":""} placed!`;setTimeout(()=>{n.disabled=false;const r=isAutoNukeSiloShortageReason(getAutoNukeContextMenuDisableReason(e));if(r){renderAutoNukeBuildSiloButton(n,e)}else{n.hidden=true}},1500);return}n.textContent="✗ No valid tile";setTimeout(()=>{if(autoNukeContextMenuParams===e){n.disabled=false;renderAutoNukeBuildSiloButton(n,e)}},1800)}const AUTO_NUKE_BUILD_SILO_TARGET=12;const AUTO_NUKE_BUILD_SILO_MAX_PER_CLICK=20;async function autoBuildMissileSilo(t){const e=t.myPlayer;if(!e){return 0}let n=1;for(const f of Object.values(t.autoNukePlans||{})){if(Number.isFinite(f?.minimumRequiredSiloSlots)&&Number.isFinite(f?.budgetSiloSlots)){const m=Math.ceil(f.minimumRequiredSiloSlots-f.budgetSiloSlots);if(m>0)n=Math.max(n,m)}}const o=getReadyNukeSiloSlotCount(e);for(const f of AUTO_NUKE_INTENSITY_TIERS){if(ogetUnitTile(f)).filter(f=>f!==null&&f!==void 0);const a=getPlayerUnits(e,...NUKE_SUGGESTION_STRUCTURE_TYPES).map(f=>getUnitTile(f)).filter(f=>f!==null&&f!==void 0);const s=new Set;const l=[...i,...a].filter(f=>{if(s.has(f))return false;s.add(f);return true});if(l.length===0)return 0;const c=[];const u=Math.max(AUTO_NUKE_BUILD_SILO_MAX_PER_CLICK,l.length*10);for(let f=0;c.length=u)break}let d=0;for(const f of c){if(d>=n)break;const m=await getBuildableMissileSilo(e,f);const p=m?.canBuild!==false&&m?.canBuild!==void 0||m?.canUpgrade!==false&&m?.canUpgrade!==void 0;if(m&&p){try{t.buildMenu.sendBuildOrUpgrade(m,f);d++}catch(h){}}}return d}async function getBuildableMissileSilo(t,e){if(typeof t?.buildables==="function"){try{const n=await t.buildables(e,["Missile Silo"]);const o=Array.from(n||[]).find(r=>r?.type==="Missile Silo");if(o)return o}catch(n){}}if(typeof t?.actions==="function"){try{const n=await t.actions(e,["Missile Silo"]);return Array.from(n?.buildableUnits||[]).find(o=>o?.type==="Missile Silo")||null}catch(n){return null}}return null}function getAutoNukeRadialElement(){return document.querySelector("main-radial-menu")}function getAutoNukeBuildMenu(t){return t?.buildMenu||document.querySelector("build-menu")}function getAutoNukeContextParamsFromEvent(t){const e=getAutoNukeRadialElement();const n=e?.game||lastOpenFrontGameContext?.game;const o=e?.transformHandler||e?.transform||lastOpenFrontGameContext?.transform;const r=n?.myPlayer?.();const i=getAutoNukeBuildMenu(e);if(!n||!o?.screenToWorldCoordinates||!r?.isPlayer?.()||!i?.sendBuildOrUpgrade){return null}let a=null;try{a=o.screenToWorldCoordinates(t.clientX,t.clientY);if(!n.isValidCoord?.(a.x,a.y)){return null}}catch(c){return null}let s=null;let l=null;try{s=n.ref(a.x,a.y);const c=n.owner(s);l=c?.isPlayer?.()?c:null}catch(c){return null}if(!l?.isPlayer?.()){return null}return{myPlayer:r,selected:l,tile:s,playerActions:{buildableUnits:[]},game:n,buildMenu:i,closeMenu:hideAutoNukeContextMenu}}function handleAutoNukeContextMenu(t){if(!autoNukeEnabled){return}const e=getAutoNukeContextParamsFromEvent(t);if(!e){hideAutoNukeContextMenu();return}showAutoNukeContextMenu(t.clientX+108,t.clientY+16,e)}function handleAutoNukeDocumentPointerDown(t){const e=document.getElementById(AUTO_NUKE_CONTEXT_MENU_ID);if(!e||e.hidden||e.contains(t.target)){return}hideAutoNukeContextMenu()}function handleAutoNukeDocumentKeyDown(t){if(t.key==="Escape"){hideAutoNukeContextMenu()}}function installAutoNukeContextMenu(){if(autoNukeContextMenuInstalled){return}autoNukeContextMenuInstalled=true;document.addEventListener("contextmenu",handleAutoNukeContextMenu,true);document.addEventListener("pointerdown",handleAutoNukeDocumentPointerDown,true);document.addEventListener("keydown",handleAutoNukeDocumentKeyDown,true);window.addEventListener("blur",hideAutoNukeContextMenu);window.addEventListener("wheel",hideAutoNukeContextMenu,true)}function uninstallAutoNukeContextMenu(){if(!autoNukeContextMenuInstalled){return}autoNukeContextMenuInstalled=false;document.removeEventListener("contextmenu",handleAutoNukeContextMenu,true);document.removeEventListener("pointerdown",handleAutoNukeDocumentPointerDown,true);document.removeEventListener("keydown",handleAutoNukeDocumentKeyDown,true);window.removeEventListener("blur",hideAutoNukeContextMenu);window.removeEventListener("wheel",hideAutoNukeContextMenu,true);hideAutoNukeContextMenu()}function createAutoNukeMenuItem(t,e){const n=t==="economic";const o=t==="destruction";const r=t==="sams";return{id:`${AUTO_NUKE_MENU_ITEM_PREFIX}-${e}-${t}`,name:n?"auto econ nuke":o?"total destruction nuke":r?"destroy all sams":"auto population nuke",text:n?"Auto Econ":o?"Destroy":r?"SAMs":"Auto Pop",fontSize:"10px",color:n?"#16a34a":o?"#ca8a04":r?"#0284c7":"#dc2626",tooltipItems:[{text:n?"Auto econ nuke":o?"Total destruction nuke":r?"Destroy all SAMs":"Auto population nuke",className:"title"},{text:n?"Fires at the best valid economy suggestion.":o?"Fires at combined population and economy damage, then retries once after silo cooldown.":r?"Fires SAM-clear waves and stops before population or economy targets.":"Fires at the best valid population suggestion.",className:"description"}],disabled:isAutoNukeRadialItemDisabled,action:i=>executeAutoNuke(i,t,o?"high":null)}}function createAutoNukeMenuItems(t,e){if(!isAutoNukeRadialTarget(t)){return[]}return[createAutoNukeMenuItem("economic",e),createAutoNukeMenuItem("population",e),createAutoNukeMenuItem("sams",e),createAutoNukeMenuItem("destruction",e)]}function menuItemsContainManualNukeActions(t){return t.some(e=>{const n=String(e?.text||e?.name||e?.id||"");return/atom|hydrogen|mirv|nuke/i.test(n)})}function patchAutoNukeAttackMenuItem(t){if(!t||typeof t.subMenu!=="function"||autoNukeOriginalAttackSubMenus.has(t)){return}const e=t.subMenu;t.subMenu=function n(o){const r=Array.from(e.call(this,o)||[]);if(!autoNukeEnabled||!menuItemsContainManualNukeActions(r)){return r}const i=new Set(r.map(s=>String(s?.id||"")));const a=createAutoNukeMenuItems(o,"attack").filter(s=>!i.has(s.id));return a.length>0?[...r,...a]:r};autoNukeOriginalAttackSubMenus.set(t,e)}function patchAutoNukeAttackSubMenus(t){for(const e of t){if(typeof e?.subMenu!=="function"){continue}const n=String(e?.id||e?.name||e?.text||"");if(/attack/i.test(n)){patchAutoNukeAttackMenuItem(e)}}}function getAutoNukeRadialRootMenu(){const t=document.querySelector("main-radial-menu");return t?.radialMenu?.rootMenu||null}function patchAutoNukeRootMenu(t){if(!t||typeof t.subMenu!=="function"||autoNukeOriginalRootSubMenus.has(t)){return false}const e=t.subMenu;t.subMenu=function n(o){const r=Array.from(e.call(this,o)||[]);patchAutoNukeAttackSubMenus(r);if(!autoNukeEnabled){return r}const i=new Set(r.map(s=>String(s?.id||"")));const a=createAutoNukeMenuItems(o,"root").filter(s=>!i.has(s.id));return a.length>0?[...r,...a]:r};autoNukeOriginalRootSubMenus.set(t,e);return true}function restoreAutoNukeRootMenus(){for(const[t,e]of autoNukeOriginalAttackSubMenus){if(t?.subMenu){t.subMenu=e}}autoNukeOriginalAttackSubMenus.clear();for(const[t,e]of autoNukeOriginalRootSubMenus){if(t?.subMenu){t.subMenu=e}}autoNukeOriginalRootSubMenus.clear()}function scheduleAutoNukePatchRetry(){if(!autoNukeEnabled||autoNukePatchTimeout!==null){return}autoNukePatchTimeout=window.setTimeout(()=>{autoNukePatchTimeout=null;syncAutoNukeRadialMenuPatch()},AUTO_NUKE_PATCH_RETRY_MS)}function syncAutoNukeRadialMenuPatch(){if(!autoNukeEnabled){restoreAutoNukeRootMenus();return}const t=getAutoNukeRadialRootMenu();if(!t){scheduleAutoNukePatchRetry();return}patchAutoNukeRootMenu(t);scheduleAutoNukePatchRetry()}function setAutoNukeEnabled(t,e=false){autoNukeEnabled=Boolean(t);autoNukeIncludeAllies=Boolean(e);if(autoNukePatchTimeout!==null){window.clearTimeout(autoNukePatchTimeout);autoNukePatchTimeout=null}restoreAutoNukeRootMenus();if(!autoNukeEnabled){uninstallAutoNukeContextMenu();return}installAutoNukeContextMenu()}function syncNukeSuggestions(){if(!nukeSuggestionsEnabled){document.getElementById(NUKE_SUGGESTION_CONTAINER_ID)?.remove();nukeSuggestionAnimationFrame=null;clearNukeSuggestionState();return}const t=ensureNukeSuggestionContainer();const e=getHoveredPlayerInfoOverlay();const n=e?.game;const o=e?.transform;const r=e?.player;if(!n||!o||!r?.isPlayer?.()||!isEnemyNukeSuggestionTarget(n,r)){clearNukeSuggestionState(t);nukeSuggestionAnimationFrame=requestAnimationFrame(syncNukeSuggestions);return}const i=performance.now();if(i-lastNukeSuggestionSignatureCheckAt>=NUKE_SUGGESTION_SIGNATURE_CHECK_MS){lastNukeSuggestionSignatureCheckAt=i;const a=getNukeSuggestionSignature(n,r);if(a!==lastNukeSuggestionSignature||i-lastNukeSuggestionComputedAt>=NUKE_SUGGESTION_REFRESH_MS){currentNukeSuggestionResults=computeNukeSuggestions(n,r);lastNukeSuggestionSignature=a;lastNukeSuggestionComputedAt=i}}renderNukeSuggestions(t,n,o,currentNukeSuggestionResults);nukeSuggestionAnimationFrame=requestAnimationFrame(syncNukeSuggestions)}function setNukeSuggestionsEnabled(t){nukeSuggestionsEnabled=Boolean(t);if(!nukeSuggestionsEnabled){if(nukeSuggestionAnimationFrame!==null){cancelAnimationFrame(nukeSuggestionAnimationFrame)}nukeSuggestionAnimationFrame=null;document.getElementById(NUKE_SUGGESTION_CONTAINER_ID)?.remove();clearNukeSuggestionState();return}if(nukeSuggestionAnimationFrame===null){syncNukeSuggestions()}}const BOAT_UNIT_TYPES=["Transport"];const BOAT_LANDING_HOVER_RADIUS_PX=18;const BOAT_LANDING_HOVER_RADIUS_SQUARED=BOAT_LANDING_HOVER_RADIUS_PX*BOAT_LANDING_HOVER_RADIUS_PX;const BOAT_PREDICTION_SCAN_MS=1e3;let boatLandingMouseX=-9999;let boatLandingMouseY=-9999;let boatMouseListenerInstalled=false;let lastBoatPredictionScanAt=0;let boatPredictionTransports=[];const boatPredictionDomCache=new Map;let boatPredictionHoverElements=null;function ensureBoatLandingStyles(){if(document.getElementById(BOAT_LANDING_STYLE_ID)){return}const t=document.createElement("style");t.id=BOAT_LANDING_STYLE_ID;t.textContent=` #${BOAT_LANDING_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483646; pointer-events: none; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-marker { position: fixed; left: 0; top: 0; width: 20px; height: 20px; border: 2px solid var(--boat-color); border-radius: 50%; background: var(--boat-bg); box-shadow: 0 0 4px var(--boat-color); transform: translate3d(var(--boat-tx, 0px), var(--boat-ty, 0px), 0) translate(-50%, -50%); will-change: transform; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-marker::before, #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-marker::after { content: ""; position: absolute; left: 50%; top: 50%; background: var(--boat-color); transform: translate(-50%, -50%); } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-marker::before { width: 14px; height: 2px; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-marker::after { width: 2px; height: 14px; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-label { position: fixed; left: 0; top: 0; padding: 3px 7px; border: 1px solid var(--boat-color); border-radius: 8px; background: rgba(7, 12, 18, 0.86); color: var(--boat-color); font: 900 11px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.92); transform: translate3d(var(--boat-tx, 0px), var(--boat-label-ty, 0px), 0) translate(-50%, -100%); will-change: transform; white-space: nowrap; } @keyframes openfront-helper-boat-highlight-pulse { 0%, 100% { box-shadow: 0 0 6px var(--boat-color), 0 0 12px var(--boat-color); } 50% { box-shadow: 0 0 10px var(--boat-color), 0 0 18px var(--boat-color); opacity: 0.78; } } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-highlight { position: fixed; left: 0; top: 0; width: 28px; height: 28px; border: 2.5px solid var(--boat-color); border-radius: 50%; background: var(--boat-bg); box-shadow: 0 0 6px var(--boat-color); transform: translate3d(var(--boat-tx, 0px), var(--boat-ty, 0px), 0) translate(-50%, -50%); will-change: transform; animation: openfront-helper-boat-highlight-pulse 1.1s ease-in-out infinite; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-highlight.is-strong { width: 34px; height: 34px; border-width: 3.5px; box-shadow: 0 0 10px var(--boat-color), 0 0 20px var(--boat-color); } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-hover-svg { position: fixed; inset: 0; width: 100vw; height: 100vh; overflow: visible; pointer-events: none; } #${BOAT_LANDING_CONTAINER_ID} .openfront-helper-boat-route-polyline { fill: none; stroke-width: 2; stroke-dasharray: 4 3; stroke-linecap: round; stroke-linejoin: round; opacity: 0.68; } `;(document.head||document.documentElement).appendChild(t)}function ensureBoatLandingContainer(){ensureBoatLandingStyles();let t=document.getElementById(BOAT_LANDING_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=BOAT_LANDING_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}return t}function ensureBoatMouseListener(){if(boatMouseListenerInstalled){return}boatMouseListenerInstalled=true;document.addEventListener("mousemove",t=>{boatLandingMouseX=t.clientX;boatLandingMouseY=t.clientY},{passive:true})}function ensureBoatRouteSvg(t){let e=t.querySelector(".openfront-helper-boat-hover-svg");if(!e){e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.classList.add("openfront-helper-boat-hover-svg");e.setAttribute("aria-hidden","true");t.prepend(e)}return e}function getBoatRouteScreenPoints(t,e,n,o,r,i){const a=[];if(r){a.push({x:r.x,y:r.y})}try{if(Number.isFinite(o)){const s=t?.motionPlans?.()?.get(o);if(s?.path&&s.path.length>0){const l=s.path;const c=Number(t?.ticks?.());const u=Number.isFinite(c)?c-s.startTick:0;const d=Math.max(0,Math.min(Math.floor(u/Math.max(1,s.ticksPerStep)),l.length-1));const f=l.length-d;const m=40;const p=Math.max(1,Math.floor(f/m));for(let h=d;h=-e&&t.y>=-e&&t.x<=window.innerWidth+e&&t.y<=window.innerHeight+e}function collectBoatPredictionTransports(t){const e=[];for(const n of t.units(...BOAT_UNIT_TYPES)){if(!n?.isActive?.()){continue}const o=getBoatPredictionRelation(t,n);if(!o){continue}try{if(n.retreating?.()){continue}}catch(p){}const r=getBoatTargetTile(n);if(r===null){continue}const i=isBoatTargetingMyPlayer(t,r);const a=o==="enemy"&&i;const{color:s,bg:l}=getBoatPredictionColors(o,a);const c=o==="self"?tr("Your landing"):o==="team"?tr("Team landing"):o==="ally"?tr("Ally landing"):a?tr("Landing!"):tr("Landing");const u=getBoatOwnerLabel(n);const d=`${c} · ${u}`;const f=String(n.id?.()??`target:${r}`);const m=Number(n.id?.());e.push({domUnitId:f,motionPlanUnitId:Number.isFinite(m)?m:null,unit:n,relation:o,targetTile:r,targetsMyTerritory:i,ownerName:u,targetWorldX:t.x(r),targetWorldY:t.y(r),color:s,bg:l,labelText:d})}return e}function clearBoatPredictionDomCache(){boatPredictionDomCache.clear();boatPredictionHoverElements=null}function getBoatPredictionDomEntry(t,e,n){let o=boatPredictionDomCache.get(n.domUnitId);if(!o){const i=document.createElement("div");i.className="openfront-helper-boat-marker";i.dataset.boatId=`${n.domUnitId}-marker`;i.hidden=true;t.appendChild(i);const a=document.createElementNS("http://www.w3.org/2000/svg","polyline");a.classList.add("openfront-helper-boat-route-polyline");a.style.filter="none";a.style.display="none";e.appendChild(a);const s=document.createElement("div");s.className="openfront-helper-boat-highlight";s.hidden=true;t.appendChild(s);o={marker:i,routeLine:a,highlight:s,signature:"",markerVisible:false,markerX:"",markerY:"",routeVisible:false,routePoints:"",highlightVisible:false,highlightStrong:false,highlightX:"",highlightY:""};boatPredictionDomCache.set(n.domUnitId,o)}const r=`${n.color}|${n.bg}`;if(o.signature!==r){o.marker.style.setProperty("--boat-color",n.color);o.marker.style.setProperty("--boat-bg",n.bg);o.routeLine.style.stroke=n.color;o.highlight.style.setProperty("--boat-color",n.color);o.highlight.style.setProperty("--boat-bg",n.bg);o.signature=r}return o}function setBoatRouteVisible(t,e){if(t.routeVisible===e){return}t.routeLine.style.display=e?"":"none";t.routeVisible=e}function setBoatMarkerVisible(t,e){if(t.markerVisible===e){return}t.marker.hidden=!e;t.markerVisible=e}function setBoatHighlightVisible(t,e){if(t.highlightVisible===e){return}t.highlight.hidden=!e;t.highlightVisible=e}function setBoatHighlightStrong(t,e){if(t.highlightStrong===e){return}t.highlight.classList.toggle("is-strong",e);t.highlightStrong=e}function hideBoatEntry(t){setBoatMarkerVisible(t,false);setBoatRouteVisible(t,false);setBoatHighlightVisible(t,false)}function updateBoatPredictionEntryPosition(t,e){const n=`${e.x}px`;const o=`${e.y}px`;if(t.markerX!==n){t.marker.style.setProperty("--boat-tx",n);t.markerX=n}if(t.markerY!==o){t.marker.style.setProperty("--boat-ty",o);t.markerY=o}}function updateBoatHighlightPosition(t,e,n){const o=`${e}px`;const r=`${n}px`;if(t.highlightX!==o){t.highlight.style.setProperty("--boat-tx",o);t.highlightX=o}if(t.highlightY!==r){t.highlight.style.setProperty("--boat-ty",r);t.highlightY=r}}function cleanupBoatPredictionDomCache(t){for(const[e,n]of boatPredictionDomCache){if(!t.has(e)){n.marker.remove();n.routeLine.remove();n.highlight.remove();boatPredictionDomCache.delete(e)}}}function clearBoatPredictionHoverElements(){if(!boatPredictionHoverElements){return}boatPredictionHoverElements.label?.remove();boatPredictionHoverElements=null}function getBoatPredictionHoverElements(t){if(!boatPredictionHoverElements){const e=document.createElement("div");e.className="openfront-helper-boat-label";t.appendChild(e);boatPredictionHoverElements={label:e}}return boatPredictionHoverElements}function syncBoatPrediction(){if(!isBoatOverlayActive()){teardownBoatOverlay();return}const t=ensureBoatLandingContainer();ensureBoatMouseListener();const e=getOpenFrontGameContext();if(!e?.game||!e?.transform){t.replaceChildren();lastBoatPredictionScanAt=0;boatPredictionTransports=[];clearBoatPredictionDomCache();resetIncomingBoatWarningBaseline();boatLandingAnimationFrame=requestAnimationFrame(syncBoatPrediction);return}const n=Date.now();if(!lastBoatPredictionScanAt||n-lastBoatPredictionScanAt>=BOAT_PREDICTION_SCAN_MS){boatPredictionTransports=collectBoatPredictionTransports(e.game);const u=new Set;for(const d of boatPredictionTransports){u.add(d.domUnitId)}cleanupBoatPredictionDomCache(u);maybeWarnNewIncomingBoats(boatPredictionTransports);lastBoatPredictionScanAt=n}const o=ensureBoatRouteSvg(t);resetScreenPointPool();const r=boatPredictionEnabled;const i=new Set;let a=null;let s=null;let l=0;let c=0;for(let u=0;u=2){let k="";for(let C=0;C<$.length;C+=1){const U=$[C];k+=C===0?`${U.x},${U.y}`:` ${U.x},${U.y}`}if(g.routePoints!==k){g.routeLine.setAttribute("points",k);g.routePoints=k}setBoatRouteVisible(g,true)}else{setBoatRouteVisible(g,false)}if(A){updateBoatHighlightPosition(g,I,P);setBoatHighlightStrong(g,w);setBoatHighlightVisible(g,true)}else{setBoatHighlightVisible(g,false)}if(w&&!s&&p){s=d;l=h;c=y}}else{setBoatRouteVisible(g,false);setBoatHighlightVisible(g,false)}}for(const[u,d]of boatPredictionDomCache){if(!i.has(u)){hideBoatEntry(d)}}if(s){const{color:u,labelText:d}=s;const f=getBoatPredictionHoverElements(t);f.label.hidden=false;f.label.style.setProperty("--boat-tx",`${l}px`);f.label.style.setProperty("--boat-label-ty",`${c-16}px`);f.label.style.setProperty("--boat-color",u);const m=formatBoatEta(computeBoatEtaMs(e.game,s));f.label.textContent=`${d} · ${m}`}else{clearBoatPredictionHoverElements()}boatLandingAnimationFrame=requestAnimationFrame(syncBoatPrediction)}function teardownBoatOverlay(){if(boatLandingAnimationFrame!==null){cancelAnimationFrame(boatLandingAnimationFrame)}boatLandingAnimationFrame=null;lastBoatPredictionScanAt=0;boatPredictionTransports=[];clearBoatPredictionDomCache();document.getElementById(BOAT_LANDING_CONTAINER_ID)?.remove()}function refreshBoatOverlayActivity(){if(isBoatOverlayActive()){if(boatLandingAnimationFrame===null){syncBoatPrediction()}}else{teardownBoatOverlay()}}function setBoatPredictionEnabled(t,e){const n=e||{};const o=Boolean(n.alwaysOwnRoutes);const r=Boolean(n.alwaysTeamRoutes);const i=Boolean(n.alwaysAllyRoutes);const a=Boolean(n.alwaysEnemyRoutes);const s=o!==boatPredictionAlwaysOwnRoutes||r!==boatPredictionAlwaysTeamRoutes||i!==boatPredictionAlwaysAllyRoutes||a!==boatPredictionAlwaysEnemyRoutes;boatPredictionAlwaysOwnRoutes=o;boatPredictionAlwaysTeamRoutes=r;boatPredictionAlwaysAllyRoutes=i;boatPredictionAlwaysEnemyRoutes=a;boatPredictionEnabled=Boolean(t);if(s){lastBoatPredictionScanAt=0}refreshBoatOverlayActivity()}const WARSHIP_ROUTES_CONTAINER_ID="openfront-helper-warship-routes-layer";const WARSHIP_ROUTES_STYLE_ID="openfront-helper-warship-routes-style";const WARSHIP_ROUTES_SCAN_MS=600;const WARSHIP_ROUTE_MIN_DIST=6;let warshipRoutesEnabled=false;let warshipRoutesAnimationFrame=null;let lastWarshipRouteScanAt=0;let warshipRouteScan=[];const warshipRouteDomCache=new Map;function warshipRouteColor(t){switch(t){case"self":return"rgba(96, 165, 250, 0.95)";case"team":return"rgba(45, 212, 191, 0.95)";case"ally":return"rgba(74, 222, 128, 0.95)";default:return"rgba(248, 113, 113, 0.95)"}}function ensureWarshipRouteStyles(){if(document.getElementById(WARSHIP_ROUTES_STYLE_ID))return;const t=document.createElement("style");t.id=WARSHIP_ROUTES_STYLE_ID;t.textContent=` #${WARSHIP_ROUTES_CONTAINER_ID} { position: fixed; inset: 0; z-index: 2147483645; pointer-events: none; } #${WARSHIP_ROUTES_CONTAINER_ID} .openfront-helper-warship-route-svg { position: fixed; inset: 0; width: 100vw; height: 100vh; overflow: visible; pointer-events: none; } #${WARSHIP_ROUTES_CONTAINER_ID} .openfront-helper-warship-route-line { fill: none; stroke-width: 2; stroke-dasharray: 1 4; stroke-linecap: round; stroke-linejoin: round; opacity: 0.75; } #${WARSHIP_ROUTES_CONTAINER_ID} .openfront-helper-warship-dest { position: fixed; left: 0; top: 0; width: 14px; height: 14px; border: 2px solid var(--ws-color); border-radius: 3px; background: transparent; box-shadow: 0 0 5px var(--ws-color); transform: translate3d(var(--ws-tx, 0px), var(--ws-ty, 0px), 0) translate(-50%, -50%) rotate(45deg); will-change: transform; } `;(document.head||document.documentElement).appendChild(t)}function ensureWarshipRouteContainer(){ensureWarshipRouteStyles();let t=document.getElementById(WARSHIP_ROUTES_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=WARSHIP_ROUTES_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}return t}function ensureWarshipRouteSvg(t){let e=t.querySelector(".openfront-helper-warship-route-svg");if(!e){e=document.createElementNS("http://www.w3.org/2000/svg","svg");e.classList.add("openfront-helper-warship-route-svg");e.setAttribute("aria-hidden","true");t.prepend(e)}return e}function collectWarshipRoutes(t){const e=[];for(const n of t.units("Warship")){if(!n?.isActive?.())continue;const o=getBoatPredictionRelation(t,n);if(!o)continue;let r=null;try{const l=n.warshipState?.()?.patrolTile;if(l!==void 0&&l!==null)r=asFiniteTileRef(l)}catch(l){}const i=Number(n.id?.());if(r===null&&Number.isFinite(i)){try{const l=t.motionPlans?.()?.get(i);if(l?.path?.length){r=asFiniteTileRef(l.path[l.path.length-1])}}catch(l){}}if(r===null)continue;let a=null;let s=null;try{a=t.x(r);s=t.y(r)}catch(l){continue}try{const l=n.tile?.();if(l!==void 0&&l!==null){const c=Math.abs(t.x(l)-a)+Math.abs(t.y(l)-s);if(c=WARSHIP_ROUTES_SCAN_MS){warshipRouteScan=collectWarshipRoutes(e.game);const r=new Set;for(const i of warshipRouteScan)r.add(i.id);cleanupWarshipRouteDom(r);lastWarshipRouteScanAt=o}resetScreenPointPool();for(let r=0;r=2){let g="";for(let b=0;bsetBoatPanelActiveTab("sent"));const i=document.createElement("div");i.className="ofh-boatp-tab";i.addEventListener("click",()=>setBoatPanelActiveTab("incoming"));o.append(r,i);boatPanelTabButtons={sent:r,incoming:i};const a=document.createElement("div");a.className="ofh-boatp-body";t.append(e,o,a);(document.body||document.documentElement).appendChild(t);makeGoldStatPanelDraggable(t,e,BOAT_PANEL_POS_KEY);applyStoredGoldStatPanelPosition(t,BOAT_PANEL_POS_KEY);setBoatPanelActiveTab(boatPanelActiveTab);return t}function getBoatPanelRow(t){let e=boatPanelRowCache.get(t.domUnitId);if(!e){const n=document.createElement("div");n.className="ofh-boat-row";const o=document.createElement("span");o.className="ofh-boat-dot";const r=document.createElement("span");r.className="ofh-boat-name";const i=document.createElement("span");i.className="ofh-boat-eta";n.append(o,r,i);const a=t.domUnitId;n.addEventListener("mouseenter",()=>{boatPanelFocusedId=a});n.addEventListener("mouseleave",()=>{if(boatPanelFocusedId===a){boatPanelFocusedId=null}});e={row:n,dot:o,name:r,eta:i,unit:t.unit};n.addEventListener("click",()=>{if(e.unit){focusIncomingBoat(e.unit)}});boatPanelRowCache.set(t.domUnitId,e)}return e}function renderBoatPanelRows(t,e,n){const o=new Set;for(const i of n){o.add(i.domUnitId);const a=getBoatPanelRow(i);a.unit=i.unit;a.dot.style.color=i.color;a.dot.style.background=i.color;a.name.textContent=i.ownerName||"—";a.eta.textContent=formatBoatEta(computeBoatEtaMs(e,i));t.appendChild(a.row)}for(const[i,a]of boatPanelRowCache){if(!o.has(i)){if(boatPanelFocusedId===i){boatPanelFocusedId=null}a.row.remove();boatPanelRowCache.delete(i)}}let r=t.querySelector(".ofh-boatp-empty");if(!n.length){if(!r){r=document.createElement("div");r.className="ofh-boatp-empty";t.appendChild(r)}r.textContent=tr("No boats")}else if(r){r.remove()}}function clearBoatPanelRows(){for(const[,t]of boatPanelRowCache){t.row.remove()}boatPanelRowCache.clear()}function updateBoatPanel(){const t=document.getElementById(BOAT_PANEL_ID);if(!t){return}const e=getOpenFrontGameContext();const n=e?.game||null;const o=[];const r=[];if(n){for(const s of boatPredictionTransports){try{if(!s.unit?.isActive?.()){continue}}catch(l){continue}if(s.relation==="self"){o.push(s)}else if(s.targetsMyTerritory){r.push(s)}}}const i=boatPanelOpen&&Boolean(n)&&(o.length>0||r.length>0);t.dataset.visible=i?"true":"false";if(!i){boatPanelFocusedId=null;clearBoatPanelRows();return}const a=t.querySelector(".ofh-boatp-body");if(!a){return}if(boatPanelTabButtons){boatPanelTabButtons.sent.textContent=`${tr("Sent")} (${o.length})`;boatPanelTabButtons.incoming.textContent=`${tr("Incoming")} (${r.length})`}renderBoatPanelRows(a,n,boatPanelActiveTab==="sent"?o:r)}function setBoatPanelEnabled(t){boatPanelOpen=Boolean(t);if(boatPanelOpen){ensureBoatPanel();if(boatPanelUpdateTimer===null){boatPanelUpdateTimer=window.setInterval(updateBoatPanel,BOAT_PANEL_UPDATE_MS)}refreshBoatOverlayActivity();updateBoatPanel()}else{const e=document.getElementById(BOAT_PANEL_ID);if(e){e.dataset.visible="false"}if(boatPanelUpdateTimer!==null){clearInterval(boatPanelUpdateTimer);boatPanelUpdateTimer=null}boatPanelFocusedId=null;clearBoatPanelRows();refreshBoatOverlayActivity()}}const BOAT_ALERT_MS=9e3;let boatIncomingSeen=new Set;let boatIncomingWarnInit=false;const boatAlertTimers=new Map;function ensureBoatAlertStyles(){if(document.getElementById(BOAT_ALERT_STYLE_ID)){return}const t=document.createElement("style");t.id=BOAT_ALERT_STYLE_ID;t.textContent=` #${BOAT_ALERT_CONTAINER_ID} { position: fixed; top: 17%; left: 50%; transform: translateX(-50%); z-index: 2147483647; display: flex; flex-direction: column; align-items: center; gap: 10px; width: max-content; max-width: min(520px, calc(100vw - 24px)); pointer-events: none; } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert { pointer-events: auto; display: flex; align-items: center; gap: 13px; padding: 12px 16px; border: 2px solid var(--boat-accent, rgba(248, 113, 113, 0.95)); border-radius: 12px; background: linear-gradient(180deg, rgba(12, 26, 28, 0.96), rgba(6, 16, 18, 0.96)); color: #ecfdf5; font-family: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 26px var(--boat-accent, rgba(248, 113, 113, 0.45)); animation: ofh-boat-alert-in 0.28s cubic-bezier(0.2, 0.9, 0.3, 1.2), ofh-boat-alert-glow 1.2s ease-in-out infinite; } @keyframes ofh-boat-alert-in { from { opacity: 0; transform: translateY(-14px) scale(0.94); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes ofh-boat-alert-glow { 0%, 100% { box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 18px var(--boat-accent, rgba(248, 113, 113, 0.3)); } 50% { box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), 0 0 36px var(--boat-accent, rgba(248, 113, 113, 0.8)); } } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-icon { flex: 0 0 auto; font-size: 28px; line-height: 1; animation: ofh-boat-alert-bob 0.9s ease-in-out infinite; } @keyframes ofh-boat-alert-bob { 0%, 100% { transform: translateY(-2px) rotate(-6deg); } 50% { transform: translateY(2px) rotate(6deg); } } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-title { font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--boat-accent, #fca5a5); } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-msg { font-size: 15px; font-weight: 800; line-height: 1.2; } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-who { color: var(--boat-accent, #fecaca); } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-actions { display: flex; align-items: center; gap: 6px; margin-left: 4px; } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-btn { cursor: pointer; border: 1px solid rgba(153, 246, 228, 0.4); border-radius: 7px; background: rgba(20, 184, 166, 0.18); color: #ccfbf1; font: 700 12px/1 "Aptos", "Segoe UI", sans-serif; padding: 6px 9px; white-space: nowrap; } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-btn:hover { background: rgba(20, 184, 166, 0.32); } #${BOAT_ALERT_CONTAINER_ID} .ofh-boat-alert-close { padding: 6px 8px; } `;(document.head||document.documentElement).appendChild(t)}function ensureBoatAlertContainer(){ensureBoatAlertStyles();let t=document.getElementById(BOAT_ALERT_CONTAINER_ID);if(!t){t=document.createElement("div");t.id=BOAT_ALERT_CONTAINER_ID;t.setAttribute("aria-hidden","true");(document.body||document.documentElement).appendChild(t)}return t}function dismissIncomingBoatAlert(t){const e=boatAlertTimers.get(t);if(e!=null){clearTimeout(e);boatAlertTimers.delete(t)}const n=document.getElementById(BOAT_ALERT_CONTAINER_ID);if(n){for(const o of Array.from(n.children)){if(o.dataset&&o.dataset.boatId===t){o.remove()}}}}function clearAllIncomingBoatAlerts(){for(const e of boatAlertTimers.values()){clearTimeout(e)}boatAlertTimers.clear();const t=document.getElementById(BOAT_ALERT_CONTAINER_ID);if(t){t.replaceChildren()}}function focusIncomingBoat(t){try{const e=document.querySelector("events-display");if(t&&e&&typeof e.emitGoToUnitEvent==="function"){e.emitGoToUnitEvent(t)}}catch(e){}}function showIncomingBoatAlert(t){const e=ensureBoatAlertContainer();dismissIncomingBoatAlert(t.domUnitId);const n=document.createElement("div");n.className="ofh-boat-alert";n.dataset.boatId=t.domUnitId;n.style.setProperty("--boat-accent",t.color);const o=document.createElement("div");o.className="ofh-boat-alert-icon";o.textContent="🚤";const r=document.createElement("div");r.className="ofh-boat-alert-body";const i=document.createElement("span");i.className="ofh-boat-alert-title";i.textContent=tr("⚠ Incoming boat");const a=document.createElement("span");a.className="ofh-boat-alert-msg";const s=document.createElement("span");s.className="ofh-boat-alert-who";s.textContent=t.ownerName||"—";a.append(s,document.createTextNode(" "+tr("is landing in your territory!")));r.append(i,a);const l=document.createElement("div");l.className="ofh-boat-alert-actions";const c=document.createElement("button");c.type="button";c.className="ofh-boat-alert-btn";c.textContent="🎯 "+tr("Focus");c.addEventListener("click",()=>focusIncomingBoat(t.unit));const u=document.createElement("button");u.type="button";u.className="ofh-boat-alert-btn ofh-boat-alert-close";u.textContent="✕";u.title=tr("Close");u.addEventListener("click",()=>dismissIncomingBoatAlert(t.domUnitId));l.append(c,u);n.append(o,r,l);e.appendChild(n);const d=setTimeout(()=>dismissIncomingBoatAlert(t.domUnitId),BOAT_ALERT_MS);boatAlertTimers.set(t.domUnitId,d)}function maybeWarnNewIncomingBoats(t){if(!boatWarnIncoming){return}const e=new Set;for(const n of t){if(n.relation==="self"||!n.targetsMyTerritory){continue}try{if(!n.unit?.isActive?.()){continue}}catch(o){continue}e.add(n.domUnitId);if(boatIncomingWarnInit&&!boatIncomingSeen.has(n.domUnitId)){showIncomingBoatAlert(n)}}boatIncomingSeen=e;boatIncomingWarnInit=true}function resetIncomingBoatWarningBaseline(){boatIncomingSeen=new Set;boatIncomingWarnInit=false}function setBoatIncomingWarningEnabled(t){const e=Boolean(t);const n=e!==boatWarnIncoming;boatWarnIncoming=e;if(n){boatIncomingSeen=new Set;boatIncomingWarnInit=false;if(!e){clearAllIncomingBoatAlerts()}}refreshBoatOverlayActivity()}const ESTATE_PANEL_ID="openfront-helper-estate-panel";const ESTATE_PANEL_STYLE_ID="openfront-helper-estate-panel-styles";const ESTATE_PANEL_POS_KEY="openfrontHelperEstatePanelPos";const ESTATE_COMPUTE_MS=2500;let estatePanelOpen=false;let estateSortDir=1;let estateData=[];let estateComputing=false;let estateIntervalId=null;const estateRowCache=new Map;function ensureEstatePanelStyles(){if(document.getElementById(ESTATE_PANEL_STYLE_ID)){return}const t=document.createElement("style");t.id=ESTATE_PANEL_STYLE_ID;t.textContent=` #${ESTATE_PANEL_ID} { position: fixed; left: 14px; top: 200px; z-index: 2147483600; display: none; flex-direction: column; width: 178px; height: 240px; min-width: 140px; min-height: 110px; max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); resize: both; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.34); border-radius: 8px; background: rgba(12, 18, 20, 0.95); color: #e2e8f0; font: 700 11px/1.15 "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); pointer-events: auto; } #${ESTATE_PANEL_ID}[data-visible="true"] { display: flex; } #${ESTATE_PANEL_ID} .est-header { flex: none; display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid rgba(148, 163, 184, 0.18); cursor: grab; touch-action: none; } #${ESTATE_PANEL_ID} .est-header:active { cursor: grabbing; } #${ESTATE_PANEL_ID} .est-title { flex: 1; font-size: 10px; font-weight: 900; letter-spacing: 0.4px; text-transform: uppercase; color: rgba(226, 232, 240, 0.85); } #${ESTATE_PANEL_ID} .est-count { font-size: 10px; font-weight: 900; color: rgba(148, 163, 184, 0.85); } #${ESTATE_PANEL_ID} .est-table { flex: 1; min-height: 0; overflow: auto; display: grid; grid-template-columns: 40px minmax(0, 1fr); align-content: start; grid-auto-rows: 26px; } #${ESTATE_PANEL_ID} .est-row { display: contents; } #${ESTATE_PANEL_ID} .est-cell { padding: 0 8px; line-height: 25px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; border-bottom: 1px solid rgba(148, 163, 184, 0.08); } #${ESTATE_PANEL_ID} .est-rank { color: rgba(148, 163, 184, 0.72); } #${ESTATE_PANEL_ID} .est-tiles { text-align: right; } #${ESTATE_PANEL_ID} .est-head { position: sticky; top: 0; z-index: 1; background: rgba(16, 22, 24, 0.98); color: rgba(226, 232, 240, 0.72); font-size: 10px; text-transform: uppercase; border-bottom: 1px solid rgba(148, 163, 184, 0.22); } #${ESTATE_PANEL_ID} .est-head.est-sortable { cursor: pointer; } #${ESTATE_PANEL_ID} .est-head.est-sortable:hover { color: #fff; } #${ESTATE_PANEL_ID} .est-sort-arrow { font-size: 8px; margin-left: 2px; } #${ESTATE_PANEL_ID} .est-row .est-cell { cursor: pointer; } #${ESTATE_PANEL_ID} .est-row:hover .est-cell { background: rgba(148, 163, 184, 0.18); } #${ESTATE_PANEL_ID} .est-empty { grid-column: 1 / -1; padding: 12px; text-align: center; color: rgba(226, 232, 240, 0.55); } `;(document.head||document.documentElement).appendChild(t)}function ensureEstatePanel(){ensureEstatePanelStyles();let t=document.getElementById(ESTATE_PANEL_ID);if(t){return t}t=document.createElement("div");t.id=ESTATE_PANEL_ID;const e=document.createElement("div");e.className="est-header";const n=document.createElement("span");n.className="est-title";n.textContent=tr("Estates");const o=document.createElement("span");o.className="est-count";e.append(n,o);const r=document.createElement("div");r.className="est-table";const i=document.createElement("span");i.className="est-cell est-head est-rank";i.textContent="#";const a=document.createElement("span");a.className="est-cell est-head est-tiles est-sortable";a.title=tr("Tiles");a.addEventListener("click",()=>{estateSortDir=estateSortDir===1?-1:1;renderEstatePanel()});r.append(i,a);t.append(e,r);(document.body||document.documentElement).appendChild(t);makeGoldStatPanelDraggable(t,e,ESTATE_PANEL_POS_KEY);applyStoredGoldStatPanelPosition(t,ESTATE_PANEL_POS_KEY);return t}function getEstateRow(t,e){let n=estateRowCache.get(e.key);if(!n){const o=document.createElement("div");o.className="est-row";const r=document.createElement("span");r.className="est-cell est-rank";const i=document.createElement("span");i.className="est-cell est-tiles";o.append(r,i);n={row:o,cells:{rank:r,tiles:i},cx:e.cx,cy:e.cy};o.addEventListener("click",()=>{try{const a=getOpenFrontGameContext()?.transform;if(a&&typeof a.onGoToPosition==="function"){a.onGoToPosition({x:n.cx,y:n.cy})}}catch(a){}});estateRowCache.set(e.key,n);t.appendChild(o)}return n}function renderEstatePanel(){const t=document.getElementById(ESTATE_PANEL_ID);if(!t){return}const e=Boolean(getOpenFrontGameContext()?.game);const n=estatePanelOpen&&e&&estateData.length>0;t.dataset.visible=n?"true":"false";if(!n){return}const o=t.querySelector(".est-table");if(!o){return}const r=estateData.slice().sort((l,c)=>estateSortDir===1?c.tiles-l.tiles:l.tiles-c.tiles);const i=o.querySelector(".est-head.est-tiles");if(i){i.textContent=tr("Tiles");const l=document.createElement("span");l.className="est-sort-arrow";l.textContent=estateSortDir===1?"▾":"▴";i.appendChild(l)}const a=t.querySelector(".est-count");if(a){a.textContent=String(r.length)}const s=new Set;for(let l=0;l⚓ Send 1% Boat [N]`;t.addEventListener("click",e=>{const n=e.target?.closest("button");if(!n){return}const o=boatMacroContextMenuParams;hideBoatMacroContextMenu();if(o){executeSend1PercentBoat(o)}});document.body.appendChild(t);return t}function executeSend1PercentBoat(t){const{myPlayer:e,tile:n,radialMenuElement:o}=t;const r=o?.buildMenu||document.querySelector("build-menu");const i=r?.eventBus;if(i){const c=findBoatAttackEventCtor(i);if(c){const u=.01*(e.troops?.()||0);i.emit(new c(n,u));return}}const a=getBoatMacroUiState(o);const s=findPlayerActionHandler(o);if(!s){console.warn("[OpenFront Helper] Send 1% Boat: could not find playerActionHandler or eventBus");return}const l=a?a.attackRatio:null;if(a)a.attackRatio=.01;try{s.handleBoatAttack(e,n)}finally{if(a&&l!==null)a.attackRatio=l}}function showBoatMacroContextMenu(t,e,n){const o=ensureBoatMacroContextMenu();boatMacroContextMenuParams=n;o.hidden=false;o.style.left="0px";o.style.top="0px";const r=o.getBoundingClientRect();const i=Math.min(Math.max(6,t),window.innerWidth-r.width-6);const a=Math.min(Math.max(6,e),window.innerHeight-r.height-6);o.style.left=`${i}px`;o.style.top=`${a}px`}function handleBoatMacroContextMenu(t){if(!send1PercentBoatEnabled||!send1PercentBoatContextMenu){return}const e=getBoatMacroContextParamsFromEvent(t);if(!e){hideBoatMacroContextMenu();return}showBoatMacroContextMenu(t.clientX+108,t.clientY-58,e)}function handleBoatMacroPointerDown(t){const e=document.getElementById(BOAT_MACRO_CONTEXT_MENU_ID);if(!e||e.hidden||e.contains(t.target)){return}hideBoatMacroContextMenu()}function handleBoatMacroMouseMove(t){boatMacroLastMouseX=t.clientX;boatMacroLastMouseY=t.clientY}function isBoatMacroEditableTarget(t){if(!(t instanceof Element)){return false}return t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement||t.isContentEditable||Boolean(t.closest('[contenteditable="true"], [contenteditable="plaintext-only"], [role="textbox"]'))}function isBoatMacroTypingEvent(t){const e=typeof t.composedPath==="function"?t.composedPath():[];if(e.some(n=>isBoatMacroEditableTarget(n))){return true}return isBoatMacroEditableTarget(document.activeElement)}function isBoatMacroHotkey(t){return t.code==="KeyN"&&!t.shiftKey&&!t.altKey&&!t.ctrlKey&&!t.metaKey&&!isBoatMacroTypingEvent(t)}function handleBoatMacroKeyDown(t){if(t.key==="Escape"){hideBoatMacroContextMenu();return}if(isBoatMacroHotkey(t)){t.preventDefault();t.stopImmediatePropagation();const e={clientX:boatMacroLastMouseX,clientY:boatMacroLastMouseY};const n=getBoatMacroContextParamsFromEvent(e);if(n){hideBoatMacroContextMenu();executeSend1PercentBoat(n)}}}function handleBoatMacroKeyUp(t){if(isBoatMacroHotkey(t)){t.preventDefault();t.stopImmediatePropagation()}}function installBoatMacroContextMenu(){if(boatMacroContextMenuInstalled){return}boatMacroContextMenuInstalled=true;document.addEventListener("contextmenu",handleBoatMacroContextMenu,true);document.addEventListener("pointerdown",handleBoatMacroPointerDown,true);window.addEventListener("keydown",handleBoatMacroKeyDown,true);window.addEventListener("keyup",handleBoatMacroKeyUp,true);document.addEventListener("mousemove",handleBoatMacroMouseMove,{passive:true});window.addEventListener("wheel",hideBoatMacroContextMenu,true)}function uninstallBoatMacroContextMenu(){if(!boatMacroContextMenuInstalled){return}boatMacroContextMenuInstalled=false;document.removeEventListener("contextmenu",handleBoatMacroContextMenu,true);document.removeEventListener("pointerdown",handleBoatMacroPointerDown,true);window.removeEventListener("keydown",handleBoatMacroKeyDown,true);window.removeEventListener("keyup",handleBoatMacroKeyUp,true);document.removeEventListener("mousemove",handleBoatMacroMouseMove);window.removeEventListener("wheel",hideBoatMacroContextMenu,true);hideBoatMacroContextMenu()}function setSend1PercentBoatEnabled(t,e=true){send1PercentBoatEnabled=Boolean(t);send1PercentBoatContextMenu=Boolean(e);if(t){installBoatMacroContextMenu()}else{uninstallBoatMacroContextMenu()}}const heatmapWebGlRenderers=new WeakMap;const heatmapVertexBuffers=new WeakMap;const _economyHeatmapIntensityVariants=[{alpha:.7,radius:.88},{alpha:1,radius:1},{alpha:1.35,radius:1.18}];function _getEconomyHeatmapIntensitySettingsCached(){return _economyHeatmapIntensityVariants[normalizeEconomyHeatmapIntensity(economyHeatmapIntensity)]}function _ensureHeatmapVertexBuffer(t,e){let n=heatmapVertexBuffers.get(t);if(!n||n.length=EXPORT_PARTNER_HEATMAP_SOURCE_CACHE_MS){exportPartnerHeatmapSourceCache=collectExportPartnerHeatmapSources(t,e);exportPartnerHeatmapSourceCacheGame=t;exportPartnerHeatmapSourceCachePlayerId=o;exportPartnerHeatmapSourceCacheAt=n}return exportPartnerHeatmapSourceCache}function toFiniteNumber(t,e=0){const n=typeof t==="bigint"?Number(t):Number(t);return Number.isFinite(n)?n:e}function canStationTradeWith(t,e){if(!t||!e){return false}if(getPlayerSmallId(t)===getPlayerSmallId(e)){return true}return canPlayersTrade(t,e)}function getHeatmapTypePriority(t){if(t==="Factory"){return 4}if(t==="Port"){return 3}if(t==="City"){return 2}return 1}function getRevenueUnitKey(t){const e=toFiniteNumber(t?.id?.(),NaN);if(Number.isFinite(e)){return`unit:${e}`}const n=t?.tile?.();const o=String(t?.type?.()??"Industry");return n==null?null:`tile:${o}:${n}`}function isEconomyRevenueType(t){return t==="City"||t==="Port"||t==="Factory"}function addEconomyRevenueSource(t,e){const n=toFiniteNumber(e);if(!t||!Number.isFinite(n)||n<=0){return}const o=String(t?.type?.()??"Industry");if(!isEconomyRevenueType(o)){return}const r=t?.tile?.();if(r==null){return}const i=getRevenueUnitKey(t);if(!i){return}const a=toFiniteNumber(t?.id?.(),NaN);const s=t?.owner?.();const l=economyRevenueSourceTrackers.get(i)??{unitId:Number.isFinite(a)?a:null,ownerId:s?getPlayerSmallId(s):null,tile:r,type:o,total:0,events:[]};l.unitId=Number.isFinite(a)?a:l.unitId;l.ownerId=s?getPlayerSmallId(s):l.ownerId;l.tile=r;l.type=o;l.events.push({amount:n,at:Date.now()});l.total+=n;economyRevenueSourceTrackers.set(i,l)}function getActiveRevenueUnit(t,e){const n=Number(e?.unitId);if(Number.isFinite(n)){try{const o=t?.unit?.(n);if(o?.isActive?.()&&String(o?.type?.()??"")===String(e.type??"")){return o}}catch(o){}}try{return Array.from(t?.units?.(e.type)||[]).find(o=>o?.isActive?.()&&o?.tile?.()===e.tile)??null}catch(o){return null}}function pruneEconomyRevenueSources(t){const e=Date.now()-ECONOMY_HEATMAP_REVENUE_WINDOW_MS;for(const[n,o]of economyRevenueSourceTrackers.entries()){const r=getActiveRevenueUnit(t,o);if(!r){economyRevenueSourceTrackers.delete(n);continue}o.tile=r.tile?.()??o.tile;o.type=String(r?.type?.()??o.type);o.events=o.events.filter(i=>Number.isFinite(i?.amount)&&i.amount>0&&Number.isFinite(i?.at)&&i.at>=e);o.total=o.events.reduce((i,a)=>i+a.amount,0);if(o.total<=0){economyRevenueSourceTrackers.delete(n)}}}function getHeatmapZoomScale(t){const e=Number(t?.scale);if(!Number.isFinite(e)||e<=0){return 1}return e/HEATMAP_REFERENCE_ZOOM}function projectEconomyHeatmapPoints(t,e,n){const o=[];const r=ECONOMY_HEATMAP_VIEWPORT_PADDING;const i=getHeatmapZoomScale(n);for(const a of t){if(a.tile==null){continue}try{const s={x:e.x(a.tile),y:e.y(a.tile)};const l=n.worldToScreenCoordinates(s);if(Number.isFinite(l?.x)&&Number.isFinite(l?.y)&&l.x>-r&&l.y>-r&&l.x=ECONOMY_HEATMAP_DATA_REFRESH_MS){economyHeatmapSources=collectEconomyHeatmapSources(t);economyHeatmapDataGame=t;lastEconomyHeatmapDataAt=e}return economyHeatmapSources}function normalizeEconomyHeatmapIntensity(t){const e=Number(t);if(!Number.isFinite(e)){return 1}return Math.max(0,Math.min(2,Math.round(e)))}function getEconomyHeatmapIntensitySettings(){return _getEconomyHeatmapIntensitySettingsCached()}function createHeatmapWebGlShader(t,e,n){const o=t.createShader(e);if(!o){return null}t.shaderSource(o,n);t.compileShader(o);if(!t.getShaderParameter(o,t.COMPILE_STATUS)){t.deleteShader(o);return null}return o}function createHeatmapWebGlRenderer(t){const e=t.getContext("webgl",{alpha:true,antialias:false,depth:false,stencil:false,premultipliedAlpha:true,preserveDrawingBuffer:false})||t.getContext("experimental-webgl",{alpha:true,antialias:false,depth:false,stencil:false,premultipliedAlpha:true,preserveDrawingBuffer:false});if(!e){return null}const n=createHeatmapWebGlShader(e,e.VERTEX_SHADER,` attribute vec2 a_center; attribute vec2 a_offset; attribute float a_radius; attribute float a_intensity; uniform vec2 u_resolution; varying vec2 v_offset; varying float v_intensity; void main() { vec2 position = a_center + a_offset * a_radius; vec2 clip = (position / u_resolution) * 2.0 - 1.0; gl_Position = vec4(clip * vec2(1.0, -1.0), 0.0, 1.0); v_offset = a_offset; v_intensity = a_intensity; } `);const o=createHeatmapWebGlShader(e,e.FRAGMENT_SHADER,` precision mediump float; uniform int u_palette; varying vec2 v_offset; varying float v_intensity; vec4 gradientColor(vec3 c0, vec3 c1, vec3 c2, float a0, float a1, float a2, float d) { if (d <= 0.34) { float t = d / 0.34; return vec4(mix(c0, c1, t), mix(a0, a1, t)); } float t = (d - 0.34) / 0.66; return vec4(mix(c1, c2, clamp(t, 0.0, 1.0)), mix(a1, a2, clamp(t, 0.0, 1.0))); } void main() { float distanceFromCenter = length(v_offset); if (distanceFromCenter > 1.0) { discard; } float fade = 1.0 - smoothstep(0.72, 1.0, distanceFromCenter); vec4 color; if (u_palette == 0) { color = gradientColor( vec3(239.0, 68.0, 68.0) / 255.0, vec3(250.0, 204.0, 21.0) / 255.0, vec3(34.0, 197.0, 94.0) / 255.0, 0.9, 0.68, 0.38, distanceFromCenter ); } else { color = gradientColor( vec3(20.0, 184.0, 166.0) / 255.0, vec3(250.0, 204.0, 21.0) / 255.0, vec3(59.0, 130.0, 246.0) / 255.0, 0.94, 0.66, 0.36, distanceFromCenter ); } gl_FragColor = vec4(color.rgb, color.a * v_intensity * fade); } `);if(!n||!o){return null}const r=e.createProgram();if(!r){return null}e.attachShader(r,n);e.attachShader(r,o);e.linkProgram(r);e.deleteShader(n);e.deleteShader(o);if(!e.getProgramParameter(r,e.LINK_STATUS)){e.deleteProgram(r);return null}const i=e.createBuffer();if(!i){e.deleteProgram(r);return null}return{gl:e,program:r,buffer:i,centerLocation:e.getAttribLocation(r,"a_center"),offsetLocation:e.getAttribLocation(r,"a_offset"),radiusLocation:e.getAttribLocation(r,"a_radius"),intensityLocation:e.getAttribLocation(r,"a_intensity"),resolutionLocation:e.getUniformLocation(r,"u_resolution"),paletteLocation:e.getUniformLocation(r,"u_palette")}}function getHeatmapWebGlRenderer(t){const e=heatmapWebGlRenderers.get(t);if(e){return e}const n=createHeatmapWebGlRenderer(t);if(n){heatmapWebGlRenderers.set(t,n)}return n}function clearHeatmapCanvas(t){const e=getHeatmapWebGlRenderer(t);if(e&&!e.gl.isContextLost?.()){e.gl.viewport(0,0,t.width,t.height);e.gl.clearColor(0,0,0,0);e.gl.clear(e.gl.COLOR_BUFFER_BIT);return}const n=t.getContext("2d");n?.clearRect(0,0,t.width,t.height)}function renderHeatmapPointsWebGl(t,e,n,o,r,i){const a=getHeatmapWebGlRenderer(t);if(!a||a.centerLocation<0||a.offsetLocation<0||a.radiusLocation<0||a.intensityLocation<0){return false}const{gl:s,program:l,buffer:c}=a;if(s.isContextLost?.()){heatmapWebGlRenderers.delete(t);return false}const u=[-1,-1,1,-1,-1,1,-1,1,1,-1,1,1];const d=6;const f=6;const m=e.length*f*d;const p=_ensureHeatmapVertexBuffer(t,m);for(let y=0;yi){i=l}}if(renderHeatmapPointsWebGl(n,r,i,o,0,getEconomyHeatmapPointShape)){economyHeatmapAnimationFrame=requestAnimationFrame(drawEconomyHeatmap);return}const a=n.getContext("2d");if(!a){economyHeatmapAnimationFrame=requestAnimationFrame(drawEconomyHeatmap);return}a.clearRect(0,0,n.width,n.height);a.globalCompositeOperation="lighter";for(const s of r){drawEconomyHeatmapPoint(a,s,i,o)}a.globalCompositeOperation="source-over";economyHeatmapAnimationFrame=requestAnimationFrame(drawEconomyHeatmap)}const _exportPartnerShapeResult={intensity:0,radius:0};function getExportPartnerHeatmapPointShape(t,e,n){const o=Math.max(.3,Math.min(1,t.weight/e));const r=t.type==="City"?1.35:t.type==="Factory"?1.15:t.type==="Port"?1.05:.95;const i=t.zoomScale<1?Math.min(1.15,Math.max(.58,t.zoomScale*1.55)):t.zoomScale;_exportPartnerShapeResult.intensity=o;_exportPartnerShapeResult.radius=(20+o*64)*r*i*n;return _exportPartnerShapeResult}function drawExportPartnerHeatmapPoint(t,e,n,o){const r=getExportPartnerHeatmapPointShape(e,n,o);const{intensity:i,radius:a}=r;const s=e.x*o;const l=e.y*o;const c=t.createRadialGradient(s,l,0,s,l,a);c.addColorStop(0,`rgba(20, 184, 166, ${.94*i})`);c.addColorStop(.34,`rgba(250, 204, 21, ${.66*i})`);c.addColorStop(.72,`rgba(59, 130, 246, ${.36*i})`);c.addColorStop(1,"rgba(59, 130, 246, 0)");t.fillStyle=c;t.beginPath();t.arc(s,l,a,0,Math.PI*2);t.fill()}function drawExportPartnerHeatmap(){if(!exportPartnerHeatmapEnabled){exportPartnerHeatmapAnimationFrame=null;return}const t=performance.now();if(t-lastExportPartnerHeatmapDrawAta){a=c}}if(renderHeatmapPointsWebGl(e,i,a,n,1,getExportPartnerHeatmapPointShape)){exportPartnerHeatmapAnimationFrame=requestAnimationFrame(drawExportPartnerHeatmap);return}const s=e.getContext("2d");if(!s){exportPartnerHeatmapAnimationFrame=requestAnimationFrame(drawExportPartnerHeatmap);return}s.clearRect(0,0,e.width,e.height);s.globalCompositeOperation="lighter";for(const l of i){drawExportPartnerHeatmapPoint(s,l,a,n)}s.globalCompositeOperation="source-over";exportPartnerHeatmapAnimationFrame=requestAnimationFrame(drawExportPartnerHeatmap)}function setEconomyHeatmapIntensity(t){economyHeatmapIntensity=normalizeEconomyHeatmapIntensity(t)}function setEconomyHeatmapEnabled(t){economyHeatmapEnabled=Boolean(t);if(!economyHeatmapEnabled){if(economyHeatmapAnimationFrame!==null){cancelAnimationFrame(economyHeatmapAnimationFrame)}economyHeatmapAnimationFrame=null;lastEconomyHeatmapDrawAt=0;lastEconomyHeatmapDataAt=0;economyHeatmapDataGame=null;economyHeatmapSources=[];economyRevenueSourceTrackers.clear();if(!tradeBalancesEnabled&&!exportPartnerHeatmapEnabled){trainTradeTrackers.clear();tradeShipSourceTrackers.clear();lastProcessedTradeBalanceTick=null}if(!goldPerMinuteEnabled&&!teamGoldPerMinuteEnabled&&!topGoldPerMinuteEnabled){goldTrackers.clear();incomingGoldTransfers.clear();lastProcessedIncomingGoldTransferTick=null}document.getElementById(ECONOMY_HEATMAP_CONTAINER_ID)?.remove();return}setExportPartnerHeatmapEnabled(false);if(economyHeatmapAnimationFrame===null){drawEconomyHeatmap()}}function setExportPartnerHeatmapEnabled(t){exportPartnerHeatmapEnabled=Boolean(t);if(!exportPartnerHeatmapEnabled){if(exportPartnerHeatmapAnimationFrame!==null){cancelAnimationFrame(exportPartnerHeatmapAnimationFrame)}exportPartnerHeatmapAnimationFrame=null;lastExportPartnerHeatmapDrawAt=0;resetExportPartnerHeatmapSourceCache();document.getElementById(EXPORT_PARTNER_HEATMAP_CONTAINER_ID)?.remove();if(!tradeBalancesEnabled&&!economyHeatmapEnabled){tradeBalanceTrackers.clear();exportPartnerSourceTrackers.clear();factoryPortSpendTrackers.clear();factoryPortUnitTrackers.clear();trainTradeTrackers.clear();tradeShipSourceTrackers.clear()}return}setEconomyHeatmapEnabled(false);if(exportPartnerHeatmapAnimationFrame===null){drawExportPartnerHeatmap()}}"use strict";const UNIT={City:"City",Port:"Port",Factory:"Factory",SAMLauncher:"SAM Launcher",MissileSilo:"Missile Silo",DefensePost:"Defense Post",TransportShip:"Transport",TradeShip:"Trade",Warship:"Warship",AtomBomb:"Atom Bomb",HydrogenBomb:"Hydrogen Bomb",MIRV:"MIRV",MIRVWarhead:"MIRV Warhead"};const PANEL_ID="openfront-helper-auto-bot-panel";const STYLE_ID="openfront-helper-auto-bot-styles";const STORAGE_KEY="openfront-helper-autobot-v1";const DEFAULTS={enabled:true,difficulty:"Impossible",features:{spawn:true,expand:true,build:true,boat:true,nuke:true,warship:true,alliance:true,donate:true,betray:true},buildStructures:{[UNIT.City]:true,[UNIT.Port]:true,[UNIT.Factory]:true,[UNIT.DefensePost]:true,[UNIT.SAMLauncher]:true,[UNIT.MissileSilo]:true},winFixes:true,combatCadenceScale:.6,sizeReserveScale:1.5,sizeReserveCap:.6,mirvLeaderShare:.4,mirvPreemptFrac:.85,mirvTargetMinShare:.35,mirvTargetTopN:3,mirvEarlyGameTicks:6e3,teamWonShare:null,factoryRailShare:.75,boatProbeFrac:.01,boatProbeMinTroops:1e3,boatSurgeFrac:.25,oppBoatThrottleMs:350,maxConcurrentBoats:3,boatSpreadRadius:30,mirvBoatWindowTicks:150,donateThrottleMs:1200,donateNeedThreshold:.8,donateKeepFrac:.45,farAllyThrottleMs:4e3,warshipPatrolThrottleMs:1500,warshipMoveMaxDist:160,warshipMoveCooldownTicks:60,warshipLossMinDist:25,warshipLossWindowTicks:900,warshipRaidWindowTicks:400,warshipLossZoneMin:2,warshipLossZoneRadius:35,warshipServiceCellSize:40,warshipZoneServiceTicks:90,warshipNukeDodgeMargin:8,warshipNukeDodgeBuffer:20,warshipNukeDodgeSamples:8,warshipNukeDodgeRings:3,donateMinExcessFrac:.05,boatWeakTroopFrac:.6,distantBoatProbeMax:12,boatSurplusFill:.4,boatIslandFill:.15,islandScanRadius:40,islandScanStep:2,islandScanSamples:6,islandProbeMax:4,tickMs:200,minimized:false,hidden:false,statusOpen:true,featsOpen:false,pos:null};const MAX_DEFENSE_RESERVE=.8;const EARLY_EXPAND_COMMIT=.6;const BUILD_THROTTLE_MS=1800;const NUKE_THROTTLE_MS=6e3;const MIRV_COOLDOWN_MS=3e4;const MIRV_DOMINANCE_SHARE=.5;const MIRV_STEAMROLL_MIN_CITIES=10;const MIRV_STEAMROLL_GAP=1.25;const WARSHIP_THROTTLE_MS=4e3;const ALLIANCE_THROTTLE_MS=2e3;const BORDER_CACHE_MS=2200;const UNDER_ATTACK_RATIO=.35;const state={settings:loadSettings(),tickTimer:null,tickIntervalMs:0,tickInFlight:false,tickStartedAt:0,lastCombatMs:0,lastPlayerAttackMs:0,lastRetaliateMs:0,lastBuildMs:0,lastNukeMs:0,lastWarshipMs:0,lastWarshipMoveMs:0,lastWarshipPatrolMs:0,lastAllianceMs:0,lastEmbargoMs:0,lastDonateMs:0,beachhead:null,recentMirvHits:[],lastOppBoatMs:0,lastNuker:null,recentNukes:[],hostility:new Map,nukeReserveGold:0,nukeWantSlots:null,lastMirv:null,navalThreatAt:0,speed:{lastTick:null,lastMs:0,factor:1},border:{tiles:null,atMs:0},landBorderEnemySids:new Set,status:"",lastAction:"—",stats:{spawns:0,attacks:0,builds:0,nukes:0},live:{troops:0,gold:0,tiles:0,fill:0},log:[],activeTab:"control",logFilter:"all"};const LOG_CATS={spawn:{label:"Spawn",emoji:"🏁"},combat:{label:"Combat",emoji:"⚔️"},naval:{label:"Naval",emoji:"🚢"},build:{label:"Building",emoji:"🏗️"},nuke:{label:"Strikes",emoji:"☢️"},diplo:{label:"Diplomacy",emoji:"🤝"}};const LOG_CAP=200;function loadSettings(){const t=["enabled","difficulty","winFixes","minimized","hidden","statusOpen","featsOpen","pos","tickMs"];const e=JSON.parse(JSON.stringify(DEFAULTS));try{const n=window.localStorage.getItem(STORAGE_KEY);if(n){const o=JSON.parse(n);for(const r of t){if(o[r]!==void 0)e[r]=o[r]}e.features={...DEFAULTS.features,...o.features||{}};e.buildStructures={...DEFAULTS.buildStructures,...o.buildStructures||{}}}}catch(n){console.warn("[AutoBot] failed to load settings:",n)}return e}function saveSettings(){try{window.localStorage.setItem(STORAGE_KEY,JSON.stringify(state.settings))}catch(t){console.warn("[AutoBot] failed to save settings:",t)}}"use strict";let autoBotBundle=null;let autoBotLang="en";function tr(t,e){let n=autoBotBundle&&autoBotBundle[t]||t;if(e){for(const o in e){n=n.split("{"+o+"}").join(String(e[o]))}}return n}function setAutoBotI18n(t,e){const n=t||"en";const o=autoBotBundle===null&&e&&typeof e==="object";const r=n!==autoBotLang;autoBotLang=n;if(e&&typeof e==="object"){autoBotBundle=e}if(o||r){relocalizeAutoBotPanel()}}function relocalizeAutoBotPanel(){if(!state.settings.enabled){state.status=""}const t=document.getElementById(PANEL_ID);if(t&&typeof buildPanel==="function"){t.remove();buildPanel()}if(typeof renderStatus==="function")renderStatus();if(typeof renderLog==="function")renderLog()}"use strict";function gameType(t){try{return t?.config?.().gameConfig?.().gameType??null}catch(e){return null}}function isPublicLobby(t){return false}function randInt(t,e){return Math.floor(Math.random()*(e-t+1))+t}function clamp(t,e,n){return Math.max(e,Math.min(n,t))}function shuffleInPlace(t){for(let e=t.length-1;e>0;e--){const n=Math.floor(Math.random()*(e+1));[t[e],t[n]]=[t[n],t[e]]}return t}function toNum(t){if(typeof t==="bigint")return Number(t);const e=Number(t);return Number.isFinite(e)?e:0}function withTimeout(t,e,n){return Promise.race([Promise.resolve(t).catch(()=>n),new Promise(o=>window.setTimeout(()=>o(n),e))])}const WORKER_TIMEOUT_MS=2500;function updateSpeedFactor(t){let e=NaN;try{e=Number(t.ticks?.())}catch(a){e=NaN}if(!Number.isFinite(e))return;const n=performance.now();const o=state.speed;if(o.lastTick===null){o.lastTick=e;o.lastMs=n;return}const r=e-o.lastTick;const i=n-o.lastMs;if(i>=1500){if(r>0){const a=r/i*1e3;const s=clamp(a/10,.25,12);o.factor=o.factor*.6+s*.4}o.lastTick=e;o.lastMs=n}}function scaled(t){return t/Math.max(.25,state.speed.factor||1)}function safeMaxTroops(t,e){try{const n=t.config().maxTroops(e);return Number.isFinite(n)&&n>0?n:0}catch(n){return 0}}function safeName(t){try{return String(t.displayName?.()??t.name?.()??"?")}catch(e){return"?"}}"use strict";function findContext(){try{if(typeof getOpenFrontGameContext==="function"){const e=getOpenFrontGameContext();if(e?.game?.myPlayer)return e}}catch(e){}try{if(lastOpenFrontGameContext?.game?.myPlayer){return lastOpenFrontGameContext}}catch(e){}const t=["main-radial-menu","build-menu","control-panel","leader-board","options-menu"];for(const e of t){const n=document.querySelector(e);if(n?.game?.myPlayer){return{game:n.game,transform:n.transformHandler||n.transform||null}}}return null}function getGame(){return findContext()?.game??null}function getRadialMenu(){return document.querySelector("main-radial-menu")}function getBuildMenu(){return document.querySelector("build-menu")||getRadialMenu()?.buildMenu||null}function getEventBus(){const t=getBuildMenu();if(t?.eventBus)return t.eventBus;const e=document.querySelector("control-panel");if(e?.eventBus)return e.eventBus;const n=document.querySelector("leader-board");if(n?.eventBus)return n.eventBus;return getRadialMenu()?.eventBus??null}const _ctors={bus:null,spawn:null,attack:null,boat:null,moveWarship:null,embargo:null,donateTroops:null,emoji:null,allianceRequest:null};const _alliancesActioned=new WeakSet;function getListenersMap(t){if(t?.listeners instanceof Map)return t.listeners;for(const e of Object.getOwnPropertyNames(t||{})){try{if(t[e]instanceof Map)return t[e]}catch(n){}}return null}function discoverCtors(t){if(_ctors.bus===t&&_ctors.attack&&_ctors.spawn&&_ctors.boat&&_ctors.moveWarship&&_ctors.embargo&&_ctors.donateTroops){return _ctors}const e=getListenersMap(t);if(!e)return _ctors;_ctors.bus=t;for(const n of e.keys()){if(typeof n!=="function")continue;if(!_ctors.moveWarship){try{const o=new n([7,8],99);if(o&&Array.isArray(o.unitIds)&&o.unitIds[0]===7&&o.tile===99){_ctors.moveWarship=n;continue}}catch(o){}}if(!_ctors.embargo){try{const o={__probe:1};const r=new n(o,"start");if(r&&r.target===o&&r.action==="start"){_ctors.embargo=n;continue}}catch(o){}}if(!_ctors.donateTroops){try{const o={__probe:1};const r=new n(o,123);if(r&&r.recipient===o&&r.troops===123){_ctors.donateTroops=n;continue}}catch(o){}}if(!_ctors.emoji){try{const o={__probe:1};const r=new n(o,7);if(r&&r.recipient===o&&r.emoji===7&&r.troops===void 0&&r.action===void 0){_ctors.emoji=n;continue}}catch(o){}}if(!_ctors.attack){try{const o=new n("§p",123);if(o&&o.targetID==="§p"&&o.troops===123&&o.dst===void 0){_ctors.attack=n;continue}}catch(o){}}if(!_ctors.boat){try{const o=new n(77,123);if(o&&o.dst===77&&o.troops===123){_ctors.boat=n;continue}}catch(o){}}if(!_ctors.spawn){try{const o=new n(55);if(o&&o.tile===55&&o.targetID===void 0&&o.dst===void 0&&o.unit===void 0&&o.troops===void 0){_ctors.spawn=n;continue}}catch(o){}}}return _ctors}function emitIntent(t,...e){const n=getEventBus();if(!n||typeof t!=="function")return false;try{n.emit(new t(...e));return true}catch(o){console.error("[AutoBot] emit failed:",o);return false}}function diagnose(){const t=getGame();const e=getEventBus();const n=e?discoverCtors(e):_ctors;const o=getBuildMenu();const r=t?.myPlayer?.();const i={game:!!t,gameType:t?gameType(t):null,inSpawnPhase:t?.inSpawnPhase?.()??null,myPlayer:!!r,hasSpawned:r?.hasSpawned?.()??null,buildMenu:!!o,sendBuildOrUpgrade:typeof o?.sendBuildOrUpgrade,eventBus:!!e,ctorSpawn:!!n.spawn,ctorAttack:!!n.attack,ctorBoat:!!n.boat,ctorMoveWarship:!!n.moveWarship,ctorEmbargo:!!n.embargo,ctorDonateTroops:!!n.donateTroops,ctorEmoji:!!n.emoji,ctorAllianceRequest:!!n.allianceRequest,speedFactor:Math.round((state.speed?.factor??1)*100)/100,gold:r?Math.round(toNum(r.gold?.())):0,troops:r?Math.round(toNum(r.troops?.())):0,tiles:r?toNum(r.numTilesOwned?.()):0,botStatus:(()=>{try{return typeof _bot!=="undefined"&&_bot?_bot.status:"no-bot"}catch(a){return"n/a"}})(),eventsDisplayEvents:(()=>{try{return document.querySelector("events-display")?.events?.length??null}catch(a){return null}})()};console.log("[AutoBot] DIAG",i);return i}window.__autoBotDiag=diagnose;"use strict";async function getBorderTiles(t,e){const n=performance.now();if(state.border.tiles&&n-state.border.atMsr)r=s;if(li)i=l}if(!Number.isFinite(n))return null;return{minX:n,minY:o,maxX:r,maxY:i}}"use strict";const Difficulty={Easy:"Easy",Medium:"Medium",Hard:"Hard",Impossible:"Impossible"};const GameType={Singleplayer:"Singleplayer",Public:"Public",Private:"Private"};const GameMode={FFA:"Free For All",Team:"Team"};const PlayerType={Bot:"BOT",Human:"HUMAN",Nation:"NATION"};const TerrainType={Plains:0,Highland:1,Mountain:2,Lake:3,Ocean:4};function currentDifficulty(){const t=state&&state.settings?state.settings.difficulty:null;switch(t){case"Easy":case"Medium":case"Hard":case"Impossible":return t;default:return Difficulty.Impossible}}class PseudoRandom{constructor(e){let n=Number(e)>>>0||1;this._next01=function(){n|=0;n=n+1831565813|0;let o=Math.imul(n^n>>>15,1|n);o=o+Math.imul(o^o>>>7,61|o)^o;return((o^o>>>14)>>>0)/4294967296}}next(){return this._next01()}nextInt(e,n){const o=Math.floor(e);const r=Math.floor(n);return Math.floor(this._next01()*(r-o))+o}nextFloat(e,n){return this._next01()*(n-e)+e}randElement(e){if(e.length===0)throw new Error("array must not be empty");return e[this.nextInt(0,e.length)]}randFromSet(e){const n=e.size;if(n===0)throw new Error("set must not be empty");const o=this.nextInt(0,n);let r=0;for(const i of e){if(r===o)return i;r++}throw new Error("Unexpected error selecting element from set")}chance(e){return this.nextInt(0,e)===0}shuffleArray(e){const n=[...e];for(let o=n.length-1;o>0;o--){const r=this.nextInt(0,o+1);[n[o],n[r]]=[n[r],n[o]]}return n}}function simpleHash(t){let e=0;for(let n=0;nt.x(c)-t.x(u));const r=Array.from(n).sort((c,u)=>t.x(c)-t.x(u));if(o.length===0||r.length===0)return null;let i=0;let a=0;let s=Infinity;let l={x:o[0],y:r[0]};while(ir)r=p;if(ha)a=h}const s=Math.floor((o+r)/2);const l=Math.floor((i+a)/2);const c=t.ref(s,l);const u=t.owner(c);if(u&&u.isPlayer&&u.isPlayer()&&u.smallID()===e.smallID()){return c}let d=null;let f=Infinity;for(const m of n){const p=t.x(m)-s;const h=t.y(m)-l;const y=p*p+h*h;if(y0&&n.numTilesOwned()<=100&&r.size>0){return t.randElement(Array.from(r))}return null}"use strict";const Relation={Hostile:0,Distrustful:1,Neutral:2,Friendly:3};function relationFromValue(t){if(t<-50)return Relation.Hostile;if(t<0)return Relation.Distrustful;if(t<50)return Relation.Neutral;return Relation.Friendly}function bucketMidpoint(t){switch(t){case Relation.Hostile:return-75;case Relation.Distrustful:return-25;case Relation.Friendly:return 75;default:return 25}}function createGameApi(t){const e=new Map;const n=new Map;const o=new Map;let r=new Map;let i=[];let a=null;let s=-1;const l=x=>x&&x.__src?x.__src:x;function c(x){try{return!!(x&&typeof x.isPlayer==="function"&&x.isPlayer())}catch(v){return false}}function u(x){if(x==null)return x;const v=(N,E)=>(...R)=>typeof x[N]==="function"?x[N](...R):E;return{__src:x,owner:()=>p(x.owner()),type:v("type",null),tile:v("tile",null),level:v("level",1),id:v("id",null),isActive:v("isActive",false),isUnderConstruction:v("isUnderConstruction",false),isInCombat:v("isInCombat",false),hasTrainStation:v("hasTrainStation",false),missileTimerQueue:v("missileTimerQueue",[]),targetTile:v("targetTile",void 0),patrolTile:v("patrolTile",void 0),warshipState:v("warshipState",void 0),transportShipState:v("transportShipState",void 0),health:v("health",void 0),ticksLeftInCooldown:v("ticksLeftInCooldown",0),lastSetSafeFromPirates:v("lastSetSafeFromPirates",void 0)}}function d(x){const v=N=>typeof N==="function"?N():N;return{__src:x,troops:()=>Number(v(x.troops)??0),attacker:()=>p(typeof x.attacker==="function"?x.attacker():f(x.attackerID)),target:()=>p(typeof x.target==="function"?x.target():f(x.targetID)),retreating:()=>v(x.retreating)??false,id:()=>v(x.id),hasSourceTile:()=>x.sourceTile!==void 0,sourceTile:()=>x.sourceTile!==void 0?v(x.sourceTile):null}}function f(x){if(x==null)return null;try{return t.playerBySmallID(x)}catch(v){return null}}function m(x){const v=r.has(x)?r.get(x):Relation.Neutral;const N=o.get(x)??0;return bucketMidpoint(v)+N}function p(x){if(x==null)return null;if(!c(x)){return x}const v=x.smallID();let N=e.get(v);if(N){N.__src=x;return N}N=h(x);e.set(v,N);return N}function h(x){const v=(E,R)=>(...L)=>typeof x[E]==="function"?x[E](...L.map(z=>z&&z.__src?z.__src:z)):R;const N={__src:x,isPlayer:()=>true,isAlive:v("isAlive",false),hasSpawned:v("hasSpawned",false),smallID:()=>x.smallID(),id:()=>x.id(),clientID:v("clientID",null),type:v("type",null),name:v("name","?"),displayName:v("displayName","?"),troops:()=>Number(x.troops?.()??0),gold:()=>x.gold?.()??0n,numTilesOwned:()=>Number(x.numTilesOwned?.()??0),team:v("team",null),maxTroops:()=>te.config().maxTroops(N),isTraitor:v("isTraitor",false),isDisconnected:v("isDisconnected",false),betrayals:v("betrayals",0),getTraitorRemainingTicks:v("getTraitorRemainingTicks",0),units:(...E)=>x.units(...E).map(u),unitCount:E=>x.units(E).length,unitsOwned:E=>x.units(E).filter(R=>!(R.isUnderConstruction?.()??false)).length,unitsConstructed:E=>x.units(E).some(R=>!(R.isUnderConstruction?.()??false)),totalUnitLevels:E=>x.units(E).filter(R=>!(R.isUnderConstruction?.()??false)).reduce((R,L)=>R+(L.level?.()??1),0),incomingAttacks:()=>x.incomingAttacks().map(d),outgoingAttacks:()=>x.outgoingAttacks().map(d),targets:()=>x.targets?x.targets().map(p):[],allies:()=>x.allies?x.allies().map(p):[],transitiveTargets:()=>x.transitiveTargets?x.transitiveTargets().map(p):[],alliances:()=>x.alliances?x.alliances():[],isFriendly:E=>x.isFriendly(l(E)),isAlliedWith:E=>x.isAlliedWith(l(E)),isOnSameTeam:E=>x.isOnSameTeam(l(E)),isRequestingAllianceWith:E=>x.isRequestingAllianceWith?x.isRequestingAllianceWith(l(E)):false,hasEmbargoAgainst:E=>x.hasEmbargoAgainst?x.hasEmbargoAgainst(l(E)):false,nameLocation:()=>x.nameLocation?x.nameLocation():{x:0,y:0},borderTiles:()=>{const E=n.get(x.smallID());return E&&E.set?E.set:new Set},relation:E=>{const R=y(E);return relationFromValue(within(m(R),-100,100))},allRelationsSorted:()=>{const E=new Set;for(const L of r.keys())E.add(L);for(const L of o.keys())E.add(L);const R=[];for(const L of E){if(L===x.smallID())continue;const z=te.playerBySmallID(L);if(!z||!z.isPlayer||!z.isPlayer()||!z.isAlive())continue;R.push({player:z,raw:m(L),sid:L})}R.sort((L,z)=>L.raw-z.raw||L.sid-z.sid);return R.map(L=>({player:L.player,relation:relationFromValue(within(L.raw,-100,100))}))},updateRelation:(E,R)=>{const L=y(E);if(L==null||L===x.smallID())return;o.set(L,within((o.get(L)??0)+R,-100,100))},nearby:()=>g(N),sharesBorderWith:E=>b(N,E),borderTilesAsync:()=>x.borderTiles(),buildables:(E,R)=>x.buildables(E,R),bestTransportShipSpawn:E=>x.bestTransportShipSpawn(E),profile:()=>x.profile(),actions:(E,R)=>x.actions?x.actions(E,R):Promise.resolve(null)};return N}function y(x){if(x==null)return null;if(typeof x==="number")return x;if(typeof x.smallID==="function")return x.smallID();return null}function g(x){const v=new Map;const N=x.smallID();const E=K=>{if(K===N)return;if(K==null){const J=te.terraNullius();v.set("TN",J)}else{const J=te.playerBySmallID(K);if(J)v.set(K,J)}};const R=x.borderTiles();for(const K of R){for(const J of t.neighbors(K)){if(!t.isLand(J))continue;const ie=t.hasOwner(J)?t.ownerID(J):null;if(ie!==N)E(ie)}}const L=[];for(const K of R)if(t.isShore(K))L.push(K);const z=[[0,-1],[0,1],[-1,0],[1,0]];for(let K=0;Kx.maxTroops(l(R));const E=v[N];return typeof E==="function"?E.bind(v):E}});return T}let I=null;let P=null;let $=null;function k(){const x=t.width();const v=t.height();const N=x*v;const E=new Int32Array(N).fill(-2);const R=[];const L=[];let z=0;for(let K=0;K=I.length)return null;const v=I[x];return v>=0?v:null}const Y=-1;function G(x){if(x==null)return null;try{if(x.type&&x.type()===PlayerType.Bot)return null}catch(z){}C();const v=x.borderTiles();const N=x.smallID();let E=false;const R=new Set;for(const z of v){if(!t.isShore(z))continue;for(const K of t.neighbors(z)){if(!t.isWater(K))continue;if(t.isOcean(K)){E=true;continue}const J=I[K];if(J>=0)R.add(J)}}const L=new Set;if(E)L.add(Y);for(const z of R){const K=$[z];if(!K)continue;let J=false;const ie=new Set;for(const he of K){if(!t.hasOwner(he))continue;const se=t.ownerID(he);if(se==null||se===N)continue;if(ie.has(se))continue;ie.add(se);const ce=p(t.playerBySmallID(se));if(!ce||!ce.isPlayer||!ce.isPlayer())continue;if(ce.type&&ce.type()===PlayerType.Bot)continue;if(x.id()!==ce.id()&&!x.hasEmbargoAgainst(ce)&&!ce.hasEmbargoAgainst(x)){J=true;break}}if(J)L.add(z)}return L.size>0?L:null}function ee(){try{if(typeof t.frameData!=="function")return null;const x=t.frameData();return x&&x.railroadState?x.railroadState:null}catch(x){return null}}const H=x=>(...v)=>t[x](...v);const te={__src:t,ticks:()=>t.ticks(),inSpawnPhase:()=>t.inSpawnPhase(),isSpawnImmunityActive:(...x)=>typeof t.isSpawnImmunityActive==="function"?t.isSpawnImmunityActive(...x):false,config:()=>A(),myPlayer:()=>a,players:()=>t.players().map(p),playerViews:()=>t.players().map(p),playerBySmallID:x=>p(t.playerBySmallID(x)),nations:()=>(t.nations?t.nations():t.players().filter(x=>x.type?.()==="Nation")).map(p),terraNullius:()=>{if(typeof t.terraNullius==="function")return t.terraNullius();return{isPlayer:()=>false,smallID:()=>0,id:()=>"TerraNullius"}},owner:x=>p(t.owner(x)),ownerID:x=>t.ownerID(x),hasOwner:x=>t.hasOwner(x),numLandTiles:()=>t.numLandTiles(),numTilesWithFallout:()=>typeof t.numTilesWithFallout==="function"?t.numTilesWithFallout():0,getWinner:()=>typeof t.getWinner==="function"?t.getWinner():null,units:(...x)=>t.units(...x).map(u),unitCount:x=>t.units(x).length,ref:H("ref"),x:H("x"),y:H("y"),cell:H("cell"),width:H("width"),height:H("height"),isValidCoord:H("isValidCoord"),isOnMap:x=>typeof t.isOnMap==="function"?t.isOnMap(x):t.isValidCoord(x.x,x.y),isLand:H("isLand"),isWater:H("isWater"),isOcean:H("isOcean"),isShore:H("isShore"),isOceanShore:H("isOceanShore"),isBorder:x=>typeof t.isBorder==="function"?t.isBorder(x):false,magnitude:H("magnitude"),hasFallout:H("hasFallout"),neighbors:H("neighbors"),manhattanDist:H("manhattanDist"),euclideanDistSquared:H("euclideanDistSquared"),terrainType:x=>typeof t.terrainType==="function"?t.terrainType(x):null,nearbyUnits:(...x)=>typeof t.nearbyUnits==="function"?t.nearbyUnits(...x):[],hasUnitNearby:(...x)=>typeof t.hasUnitNearby==="function"?t.hasUnitNearby(...x):false,ensureBorderTiles:x=>V(x),getWaterComponent:x=>U(x),sharedWaterComponents:x=>G(x),railroadState:()=>ee(),_wrapPlayer:p,_wrapUnit:u};async function V(x){const v=l(x);const N=v.smallID();const E=t.ticks();const R=n.get(N);const L=typeof performance!=="undefined"&&performance.now?performance.now():Date.now();const z=1500;if(R&&R.set&&R.set.size>0&&L-(R.at||0)0)J=se}}catch(ie){}K.inFlight=false;if(J!==null){K.set=J;K.at=typeof performance!=="undefined"&&performance.now?performance.now():Date.now()}n.set(N,K);return K.set||new Set}async function q(x){a=p(x);const v=t.ticks();if(s>=0&&v>s){const N=Math.min(v-s,50);for(const[E,R]of o){let L=R;for(let z=0;z[Number(E),Number(R)]));i=(N.alliances||[]).map(Number)}}catch(N){}return a}return{game:te,beginTick:q,ensureBorderTiles:V,wrapPlayer:p,overlay:o}}"use strict";const emojiTable=[["😀","😊","🥰","😇","😎"],["😞","🥺","😭","😱","😡"],["😈","🤡","🥱","🫡","🖕"],["👋","👏","✋","🙏","💪"],["👍","👎","🫴","🤌","🤦‍♂️"],["🤝","🆘","🕊️","🏳️","⏳"],["🔥","💥","💀","☢️","⚠️"],["↖️","⬆️","↗️","👑","🥇"],["⬅️","🎯","➡️","🥈","🥉"],["↙️","⬇️","↘️","❤️","💔"],["💰","⚓","⛵","🏡","🛡️"],["🏭","🚂","❓","🐔","🐀"]];const flattenedEmojiTable=emojiTable.flat();const emojiId=t=>flattenedEmojiTable.indexOf(t);const EMOJI_ASSIST_ACCEPT=["👍","🤝","🎯"].map(emojiId);const EMOJI_ASSIST_RELATION_TOO_LOW=["🥱","🤦‍♂️"].map(emojiId);const EMOJI_ASSIST_TARGET_ME=["🥺","💀"].map(emojiId);const EMOJI_ASSIST_TARGET_ALLY=["🕊️","👎"].map(emojiId);const EMOJI_AGGRESSIVE_ATTACK=["😈"].map(emojiId);const EMOJI_ATTACK=["😡"].map(emojiId);const EMOJI_WARSHIP_RETALIATION=["⛵"].map(emojiId);const EMOJI_NUKE=["☢️","💥"].map(emojiId);const EMOJI_GOT_INSULTED=["🖕","😡","🤡","😞","😭"].map(emojiId);const EMOJI_LOVE=["❤️","😊","🥰"].map(emojiId);const EMOJI_CONFUSED=["❓","🤡"].map(emojiId);const EMOJI_BRAG=["👑","🥇","💪"].map(emojiId);const EMOJI_CHARM_ALLIES=["🤝","😇","💪"].map(emojiId);const EMOJI_CLOWN=["🤡","🤦‍♂️"].map(emojiId);const EMOJI_RAT=["🐀"].map(emojiId);const EMOJI_OVERWHELMED=["💀","🆘","😱","🥺","😭","😞","🫡","👋"].map(emojiId);const EMOJI_CONGRATULATE=["👏"].map(emojiId);const EMOJI_SCARED_OF_THREAT=["🙏","🥺"].map(emojiId);const EMOJI_BORED=["🥱"].map(emojiId);const EMOJI_HANDSHAKE=["🤝"].map(emojiId);const EMOJI_DONATION_OK=["👍"].map(emojiId);const EMOJI_DONATION_TOO_SMALL=["❓","🥱"].map(emojiId);const EMOJI_GREET=["👋"].map(emojiId);const AllPlayers="AllPlayers";class EmojiBehavior{constructor(e,n,o){this.random=e;this.game=n;this.player=o;this.lastEmojiSent=new Map;this.gameOver=false}maybeSendCasualEmoji(){if(this.gameOver)return;this.checkOverwhelmedByAttacks();this.checkVerySmallAttack();this.congratulateWinner();this.brag();this.charmAllies();this.annoyTraitors();this.findRat();this.greetNearbyPlayers()}checkOverwhelmedByAttacks(){if(!this.random.chance(16))return;const e=this.player.incomingAttacks();if(e.length===0)return;const n=e.reduce((r,i)=>r+i.troops(),0);const o=this.player.troops();if(n>=o*3){this.sendEmoji(AllPlayers,EMOJI_OVERWHELMED)}}checkVerySmallAttack(){if(!this.random.chance(8))return;const e=this.player.incomingAttacks();if(e.length===0)return;const n=this.player.troops();if(n<=0)return;for(const o of e){const r=o.attacker();if(r.type()!==PlayerType.Human)continue;if(o.troops()r.type()===PlayerType.Nation).sort((r,i)=>i.numTilesOwned()-r.numTilesOwned())[0];if(!o||o.smallID()!==this.player.smallID())return;this.sendEmoji(e,EMOJI_CONGRATULATE)}}brag(){if(!this.random.chance(300))return;const e=this.game.players().sort((n,o)=>o.numTilesOwned()-n.numTilesOwned());if(e.length===0||e[0].smallID()!==this.player.smallID())return;this.sendEmoji(AllPlayers,EMOJI_BRAG)}charmAllies(){if(!this.random.chance(250))return;const e=this.player.allies().filter(r=>r.type()===PlayerType.Human);if(e.length===0)return;const n=this.random.randElement(e);const o=this.random.chance(3)?EMOJI_LOVE:EMOJI_CHARM_ALLIES;this.sendEmoji(n,o)}annoyTraitors(){if(!this.random.chance(40))return;const e=this.game.players().filter(o=>o.type()===PlayerType.Human&&!o.isFriendly(this.player)&&o.isTraitor());if(e.length===0)return;const n=this.random.randElement(e);this.sendEmoji(n,EMOJI_CLOWN)}findRat(){if(this.game.ticks()<6e3)return;if(!this.random.chance(1e4))return;const e=this.game.numLandTiles();const n=e*.01;const o=this.game.players().filter(i=>i.type()===PlayerType.Human&&i.numTilesOwned()0);if(o.length===0)return;const r=this.random.randElement(o);this.sendEmoji(r,EMOJI_RAT)}greetNearbyPlayers(){if(this.game.ticks()>600)return;if(!this.random.chance(250))return;const e=this.player.nearby().filter(o=>o.isPlayer()&&o.type()===PlayerType.Human);if(e.length===0)return;const n=this.random.randElement(e);this.sendEmoji(n,EMOJI_GREET)}maybeSendEmoji(e,n){if(!this.shouldSendEmoji(e))return;return this.sendEmoji(e,n)}maybeSendAttackEmoji(e){if(!this.shouldSendEmoji(e))return;if(this.player.relation(e)>=Relation.Neutral){if(!this.random.chance(2))return;this.sendEmoji(e,EMOJI_AGGRESSIVE_ATTACK);return}if(!this.random.chance(4))return;this.sendEmoji(e,EMOJI_ATTACK)}sendEmoji(e,n){if(!this.shouldSendEmoji(e,false))return;if(!this.canSendEmoji(e))return;const o=discoverCtors(getEventBus());if(!o.emoji)return;const r=e===AllPlayers?AllPlayers:e.__src??e;const i=this.random.randElement(n);emitIntent(o.emoji,r,i)}canSendEmoji(e){const n=this.player.__src;if(n&&typeof n.canSendEmoji==="function"){const o=e===AllPlayers?AllPlayers:e.__src??e;try{return n.canSendEmoji(o)}catch(r){return true}}return true}shouldSendEmoji(e,n=true){if(e===AllPlayers)return true;if(this.player.type()===PlayerType.Bot)return false;if(e.type()!==PlayerType.Human)return false;if(n){const o=e.smallID();const r=this.lastEmojiSent.get(o)??-300;if(this.game.ticks()-r<=300)return false;this.lastEmojiSent.set(o,this.game.ticks())}return true}}function respondToEmoji(t,e,n,o,r){if(o===AllPlayers||o.type()!==PlayerType.Nation){return}if(!recipientCanSendEmoji(o,n))return;if(r==="🖕"){o.updateRelation(n,-100);e.randElement(EMOJI_GOT_INSULTED)}if(r==="🤡"){o.updateRelation(n,-10);e.randElement(EMOJI_CONFUSED)}if(["🕊️","🏳️","❤️","🥰","👏"].includes(r)){if(currentDifficulty()===Difficulty.Easy){o.updateRelation(n,15)}n.relation(o)>=Relation.Neutral?e.randElement(EMOJI_LOVE):e.randElement(EMOJI_CONFUSED)}}function respondToMIRV(t,e,n){if(!e.chance(8))return;if(!recipientCanSendEmoji(n,AllPlayers))return;e.randElement(EMOJI_OVERWHELMED)}function recipientCanSendEmoji(t,e){const n=t&&t.__src;if(n&&typeof n.canSendEmoji==="function"){const o=e===AllPlayers?AllPlayers:e&&e.__src||e;try{return n.canSendEmoji(o)}catch(r){return true}}return true}"use strict";const ALLIANCE_REQUEST_TYPES=new Set([15,16]);const RENEW_TYPES=new Set([22,24]);const _allianceBehaviorActioned=new WeakSet;function runAndCaptureAllianceCtor(t){const e=getEventBus();if(!e||typeof e.emit!=="function"||_ctors.allianceRequest){t();return}const n=e.emit;e.emit=function(o){try{if(o&&o.requestor!==void 0&&o.recipient!==void 0&&typeof o.constructor==="function"){_ctors.allianceRequest=o.constructor}}catch(r){}return n.call(this,o)};try{t()}finally{e.emit=n}}function getAllianceShapeCtors(t){if(_ctors.allianceShapeBus===t&&_ctors.allianceShape){return _ctors.allianceShape}const e=[];const n=getListenersMap(t);if(n){const o={__probe:1};const r={__probe:2};for(const i of n.keys()){if(typeof i!=="function")continue;try{const a=new i(o,r);if(a&&a.requestor===o&&a.recipient===r){e.push(i)}}catch(a){}}}_ctors.allianceShape=e;_ctors.allianceShapeBus=t;return e}function collectAllianceEvents(){const t=[];for(const e of["events-display","actionable-events"]){try{const n=document.querySelector(e);if(Array.isArray(n?.events))t.push(...n.events)}catch(n){}}try{if(typeof allianceRequestsPanelEvents!=="undefined"&&Array.isArray(allianceRequestsPanelEvents)){t.push(...allianceRequestsPanelEvents)}}catch(e){}return t}function pickAllianceButton(t,e,n){return t.find(o=>o?.className===e&&typeof o.action==="function")||(typeof t[n]?.action==="function"?t[n]:null)}class AllianceBehavior{constructor(e,n,o,r){this.random=e;this.game=n;this.player=o;this.emojiBehavior=r}handleAllianceRequests(){if(this.game.config().disableAlliances())return;for(const e of this.incomingAllianceRequests()){if(e.createdAt()<=this.game.config().numSpawnPhaseTurns()+1){e.reject();continue}if(this.getAllianceDecision(e.requestor(),true)){e.accept()}else{e.reject()}}}handleAllianceExtensionRequests(){if(this.game.config().disableAlliances())return;for(const e of collectAllianceEvents()){const n=Number(e?.type);if(!RENEW_TYPES.has(n))continue;if(_allianceBehaviorActioned.has(e))continue;const o=Array.isArray(e.buttons)?e.buttons:[];if(o.length===0)continue;const r=this.resolveEventPlayer(e);if(!r||!r.isPlayer||!r.isPlayer())continue;if(!this.getAllianceDecision(r,true))continue;const i=pickAllianceButton(o,"btn",1);if(i){try{i.action();_allianceBehaviorActioned.add(e);setLastAction(tr("🔁 Renew alliance {name}",{name:safeName(r)}),"diplo")}catch(a){}}}}maybeSendAllianceRequests(e){if(this.game.config().disableAlliances())return;const n=o=>o.type()===PlayerType.Bot&¤tDifficulty()===Difficulty.Easy||o.type()!==PlayerType.Bot;for(const o of e){if(this.random.chance(30)&&n(o)&&this.canSendAllianceRequest(o)&&this.getAllianceDecision(o,false)){const r=discoverCtors(getEventBus());if(r.allianceRequest){emitIntent(r.allianceRequest,this.player.__src??this.player,o.__src??o);setLastAction(tr("🤝➡️ Send alliance {name}",{name:safeName(o)}),"diplo")}}}}isFarNation(e){try{const n=e.smallID();for(const o of this.player.nearby()){if(o&&o.isPlayer&&o.isPlayer()&&o.smallID()===n){return false}}return true}catch(n){return false}}isDominantForDiplomacy(){if(!state.settings.winFixes)return false;if(typeof state.dominant==="boolean")return state.dominant;try{const e=String(this.game.config().gameConfig().gameMode)==="Team";const n=typeof this.game.numTilesWithFallout==="function"?this.game.numTilesWithFallout():0;const o=(this.game.numLandTiles()||0)-n;if(o<=0)return false;let r=0;if(e){for(const a of this.game.players()){try{if(a.isPlayer()&&this.player.isOnSameTeam(a)){r+=a.numTilesOwned()}}catch(s){}}}else{r=this.player.numTilesOwned()}const i=Number.isFinite(state.settings.factoryRailShare)?state.settings.factoryRailShare:.75;return r/o>i}catch(e){return false}}reachOutToFarNations(){const e=performance.now();const n=state.settings.farAllyThrottleMs||4e3;if(e-(state.lastFarAllyMs||0)=10){if(n&&this.random.chance(3)){this.emojiBehavior.sendEmoji(e,EMOJI_CONFUSED)}return false}if(state.settings.winFixes&&this.isFarNation(e)){if(this.isDominantForDiplomacy()){return false}if(n){console.log("[Diplo] far accept →",safeName(e))}return true}if(this.hasTooManyAlliances(e)){return false}if(this.isAlliancePartnerThreat(e)){if(!n&&this.random.chance(6)){this.emojiBehavior.sendEmoji(e,EMOJI_SCARED_OF_THREAT)}if(n&&this.random.chance(6)){this.emojiBehavior.sendEmoji(e,EMOJI_LOVE)}return true}if(this.shouldRejectInTeamGame()){return false}if(this.player.relation(e)i.type()!==PlayerType.Bot).length;const r=e.alliances().length;if(n===Difficulty.Hard){return r>=o*.5}else{return r>=o*.25}}isConfused(){const e=currentDifficulty();switch(e){case Difficulty.Easy:return this.random.chance(10);case Difficulty.Medium:return this.random.chance(20);case Difficulty.Hard:return this.random.chance(40);case Difficulty.Impossible:return false;default:return assertNever(e)}}isEarlygame(){const e=this.game.config().numSpawnPhaseTurns();const n=currentDifficulty();switch(n){case Difficulty.Easy:return this.game.ticks()<3e3+e&&this.random.nextInt(0,100)>=10;case Difficulty.Medium:return this.game.ticks()<1800+e&&this.random.nextInt(0,100)>=30;case Difficulty.Hard:return this.game.ticks()<1800+e&&this.random.nextInt(0,100)>=50;case Difficulty.Impossible:return this.game.ticks()<600+e&&this.random.nextInt(0,100)>=70;default:return assertNever(n)}}isAlliancePartnerThreat(e){const n=currentDifficulty();switch(n){case Difficulty.Easy:return false;case Difficulty.Medium:return e.troops()>this.player.troops()*2.5;case Difficulty.Hard:return e.troops()>this.player.troops()&&this.game.config().maxTroops(e)>this.game.config().maxTroops(this.player)*2;case Difficulty.Impossible:{const o=e.troops()>this.player.troops()*1.5;const r=e.troops()>this.player.troops()&&this.game.config().maxTroops(e)>this.game.config().maxTroops(this.player)*1.5;const i=e.troops()>this.player.troops()&&e.numTilesOwned()>this.player.numTilesOwned()*1.5;return o||r||i}default:return assertNever(n)}}shouldRejectInTeamGame(){if(this.game.config().gameConfig().gameMode!==GameMode.Team){return false}const e=currentDifficulty();switch(e){case Difficulty.Easy:return this.random.nextInt(0,100)<25;case Difficulty.Medium:return this.random.nextInt(0,100)<50;case Difficulty.Hard:return this.random.nextInt(0,100)<75;case Difficulty.Impossible:return true;default:return assertNever(e)}}checkAlreadyEnoughAlliances(e){const n=currentDifficulty();switch(n){case Difficulty.Easy:return false;case Difficulty.Medium:return this.player.alliances().length>=this.random.nextInt(4,6);case Difficulty.Hard:case Difficulty.Impossible:{const o=this.player.nearby().filter(i=>i.isPlayer()&&i.type()!==PlayerType.Bot);const r=o.filter(i=>this.player?.isFriendly(i)===true);if(o.length>=2&&o.some(i=>i.smallID()===e.smallID())){return o.length<=r.length+1}if(n===Difficulty.Hard){return this.player.alliances().length>=this.random.nextInt(3,5)}return this.player.alliances().length>=this.random.nextInt(2,4)}default:return assertNever(n)}}isAlliancePartnerFriendly(e){const n=currentDifficulty();switch(n){case Difficulty.Easy:case Difficulty.Medium:return this.player.relation(e)===Relation.Friendly;case Difficulty.Hard:return this.player.relation(e)===Relation.Friendly&&this.random.nextInt(0,100)>=17;case Difficulty.Impossible:return this.player.relation(e)===Relation.Friendly&&this.random.nextInt(0,100)>=33;default:return assertNever(n)}}isAlliancePartnerSimilarlyStrong(e){const n=currentDifficulty();const o={[Difficulty.Easy]:[60,70],[Difficulty.Medium]:[70,80],[Difficulty.Hard]:[75,85],[Difficulty.Impossible]:[80,90]};const r={[Difficulty.Easy]:[70,80],[Difficulty.Medium]:[80,90],[Difficulty.Hard]:[85,95],[Difficulty.Impossible]:[90,100]};const i=o[n];const a=r[n];const s=this.player.outgoingAttacks().reduce((h,y)=>h+y.troops(),0);const l=e.outgoingAttacks().reduce((h,y)=>h+y.troops(),0);const c=this.player.troops()+s;const u=e.troops()+l;const d=c*(this.random.nextInt(i[0],i[1])/100);const f=this.player.numTilesOwned()*(this.random.nextInt(a[0],a[1])/100);const m=u>d;const p=e.numTilesOwned()>f&&u>c*.5;return m||p}maybeBetray(e,n){if(state.settings.features&&state.settings.features.betray===false){return false}if(!this.player.isAlliedWith(e))return false;const o=currentDifficulty();if(o!==Difficulty.Easy&&o!==Difficulty.Medium){const r=this.game.config().maxTroops(e);const i=e.outgoingAttacks().reduce((a,s)=>a+s.troops(),0);if(e.troops()+i=e.troops()*10){this.betray(e);return true}if(o!==Difficulty.Easy&&e.isTraitor()&&e.troops()i!==_ctors.allianceRequest);if(!r)return;emitIntent(r,this.player.__src??this.player,e.__src??e);setLastAction(tr("🗡️ Betray {name}",{name:safeName(e)}),"diplo")}incomingAllianceRequests(){const e=[];const n=(()=>{try{return this.game.inSpawnPhase()===true}catch(o){return false}})();for(const o of collectAllianceEvents()){const r=Number(o?.type);if(!ALLIANCE_REQUEST_TYPES.has(r))continue;if(_allianceBehaviorActioned.has(o))continue;const i=Array.isArray(o.buttons)?o.buttons:[];if(i.length===0)continue;const a=this.resolveEventPlayer(o);if(!a||!a.isPlayer||!a.isPlayer())continue;let s=false;let l;const c=Number(o?.createdAt);if(Number.isFinite(c)){l=c}else if(n){l=0;s=true}else{l=(()=>{try{return this.game.ticks()}catch(u){return this.game.config().numSpawnPhaseTurns()+2}})()}e.push({__ev:o,__createdInSpawnPhase:s,requestor:()=>a,createdAt:()=>l,accept:()=>{const u=pickAllianceButton(i,"btn",1);if(!u)return;try{runAndCaptureAllianceCtor(()=>u.action());_allianceBehaviorActioned.add(o);setLastAction(tr("🤝 Alliance {name}",{name:safeName(a)}),"diplo")}catch(d){console.error("[AutoBot] alliance accept failed:",d)}},reject:()=>{const u=pickAllianceButton(i,"btn-info",2);if(!u)return;try{u.action();_allianceBehaviorActioned.add(o)}catch(d){}}})}return e}canSendAllianceRequest(e){const n=this.player.__src;if(n&&typeof n.canSendAllianceRequest==="function"){try{return n.canSendAllianceRequest(e.__src??e)}catch(o){}}try{if(e.isAlive&&e.isAlive()===false)return false;if(this.player.isAlliedWith(e))return false;if(this.player.isOnSameTeam&&this.player.isOnSameTeam(e)){return false}if(this.player.isRequestingAllianceWith&&this.player.isRequestingAllianceWith(e)){return false}}catch(o){return false}return true}resolveEventPlayer(e){const n=Number(e?.focusID);if(!Number.isFinite(n))return null;let o=null;try{o=this.game.playerBySmallID?this.game.playerBySmallID(n):null}catch(r){o=null}if(o&&o.isPlayer&&o.isPlayer())return o;try{for(const r of this.game.players()){if(r.smallID()===n)return r}}catch(r){}return null}}function assertNever(t){throw new Error("Unexpected difficulty: "+String(t))}"use strict";const MIRV_COOLDOWN_TICKS=300;class MirvBehavior{constructor(e,n,o,r){this.random=e;this.game=n;this.player=o;this.emojiBehavior=r;this.recentMirvTargets=new Map}get hesitationOdds(){switch(currentDifficulty()){case Difficulty.Easy:return 2;case Difficulty.Medium:return 4;case Difficulty.Hard:return 8;case Difficulty.Impossible:return 16;default:return 16}}get victoryDenialTeamThreshold(){switch(currentDifficulty()){case Difficulty.Easy:return .9;case Difficulty.Medium:return .8;case Difficulty.Hard:return .7;case Difficulty.Impossible:return .6;default:return .6}}get victoryDenialIndividualThreshold(){switch(currentDifficulty()){case Difficulty.Easy:return .75;case Difficulty.Medium:return .65;case Difficulty.Hard:return .55;case Difficulty.Impossible:return .4;default:return .4}}get steamrollCityGapMultiplier(){switch(currentDifficulty()){case Difficulty.Easy:return 2;case Difficulty.Medium:return 1.5;case Difficulty.Hard:return 1.25;case Difficulty.Impossible:return 1.15;default:return 1.15}}get steamrollMinLeaderCities(){switch(currentDifficulty()){case Difficulty.Easy:return 20;case Difficulty.Medium:case Difficulty.Hard:return 10;case Difficulty.Impossible:return 8;default:return 8}}async considerMIRV(){if(this.player===null)throw new Error("not initialized");if(this.game.config().isUnitDisabled(UNIT.MIRV)){return false}if(this.player.units(UNIT.MissileSilo).length===0){return false}const e=await this.cost(UNIT.MIRV);if(state.settings.winFixes){if(this.teamHasWon()){if(state.nukeReserveGold)state.nukeReserveGold=0n;return false}const i=this.selectWinFixMirvTarget(e);if(i&&!this.wasRecentlyMirved(i)){if(this.player.gold()>=e){state.nukeReserveGold=0n;await this.maybeSendMIRV(i);return true}const a=state.settings.mirvEarlyGameTicks??6e3;if(this.game.ticks()>=a){state.nukeReserveGold=e}else if(state.nukeReserveGold){state.nukeReserveGold=0n}return false}if(state.nukeReserveGold)state.nukeReserveGold=0n}if(this.player.gold()this.isInboundMIRVFrom(n));if(e.length===0)return null;e.sort((n,o)=>o.numTilesOwned()-n.numTilesOwned());return e[0]}selectVictoryDenialTarget(){if(this.player===null)throw new Error("not initialized");const e=this.game.numLandTiles();if(e===0)return null;let n=null;for(const o of this.getValidMirvTargetPlayers()){let r=0;const i=o.team();if(i!==null){const a=this.game.players().filter(c=>c.team()===i&&c.isPlayer());const s=a.map(c=>c.numTilesOwned()).reduce((c,u)=>c+u,0);const l=s/e;if(l>=this.victoryDenialTeamThreshold){let c=null;let u=-1;for(const d of a){const f=d.numTilesOwned();if(f>u){u=f;c=d}}if(c!==null&&c.smallID()===o.smallID()){r=l}else{r=0}}}else{const a=o.numTilesOwned()/e;if(a>=this.victoryDenialIndividualThreshold)r=a}if(r>0){if(n===null||r>n.severity)n={p:o,severity:r}}}return n?n.p:null}selectSteamrollStopTarget(){if(this.player===null)throw new Error("not initialized");const e=this.getValidMirvTargetPlayers();if(e.length===0)return null;const n=this.game.players().filter(a=>a.isPlayer()).map(a=>({p:a,cityCount:this.countCities(a)})).sort((a,s)=>s.cityCount-a.cityCount);if(n.length<2)return null;const o=n[0];if(o.cityCount<=this.steamrollMinLeaderCities)return null;const r=n[1].cityCount;const i=r*this.steamrollCityGapMultiplier;if(o.cityCount>=i){return e.some(a=>a.smallID()===o.p.smallID())?o.p:null}return null}teamHasWon(){if(this.player===null)return false;try{if(String(this.game.config().gameConfig().gameMode)!=="Team"){return false}}catch(i){return false}const e=typeof this.game.numTilesWithFallout==="function"?this.game.numTilesWithFallout():0;const n=(this.game.numLandTiles()||0)-e;if(n<=0)return false;let o=0;for(const i of this.game.players()){try{if(i.isPlayer()&&this.player.isOnSameTeam(i)){o+=i.numTilesOwned()}}catch(a){}}let r=.95;try{const i=Number(this.game.config().percentageTilesOwnedToWin());if(Number.isFinite(i))r=i/100}catch(i){}if(Number.isFinite(state.settings.teamWonShare)){r=state.settings.teamWonShare}return o/n>r}selectWinFixMirvTarget(e){if(this.player===null)return null;const n=this.getValidMirvTargetPlayers().filter(l=>l.isAlive());if(n.length===0)return null;const o=this.game.numLandTiles()||1;const r=state.settings.mirvTargetMinShare??.35;const i=state.settings.mirvTargetTopN??3;const a=new Set(this.game.players().filter(l=>l.isAlive()).sort((l,c)=>c.numTilesOwned()-l.numTilesOwned()).slice(0,i).map(l=>l.smallID()));const s=n.filter(l=>a.has(l.smallID())&&l.numTilesOwned()/o>r).sort((l,c)=>c.numTilesOwned()-l.numTilesOwned());return s.length>0?s[0]:null}wasRecentlyMirved(e){const n=this.recentMirvTargets.get(e.id());if(n===void 0)return false;return this.game.ticks()-n{return e.smallID()!==this.player.smallID()&&e.isPlayer()&&e.type()!==PlayerType.Bot&&!this.player.isOnSameTeam(e)})}isInboundMIRVFrom(e){if(this.player===null)throw new Error("not initialized");const n=e.units(UNIT.MIRV);for(const o of n){const r=o.targetTile();if(!r)continue;if(!this.game.hasOwner(r))continue;const i=this.game.owner(r);if(i&&i.isPlayer&&i.isPlayer()&&i.smallID()===this.player.smallID()){return true}}return false}async maybeSendMIRV(e){if(this.player===null)throw new Error("not initialized");this.emojiBehavior.maybeSendAttackEmoji(e);const n=await this.calculateTerritoryCenter(e);if(!n)return;let o;try{o=await withTimeout(this.player.buildables(n,[UNIT.MIRV]),WORKER_TIMEOUT_MS,null)}catch(i){return}const r=Array.isArray(o)?o.find(i=>i.type===UNIT.MIRV):null;if(r===null||r===void 0)return;if(r.canBuild!==false&&this.player.gold()>=r.cost){const i=getBuildMenu();if(!i||typeof i.sendBuildOrUpgrade!=="function")return;i.sendBuildOrUpgrade(r,n);this.recordMirvHit(e);if(state.settings.winFixes){if(!Array.isArray(state.recentMirvHits))state.recentMirvHits=[];state.recentMirvHits.push({sid:e.smallID(),tile:n,at:this.game.ticks()})}state.stats.nukes++;setLastAction(tr("☢️ MIRV"),"nuke");this.emojiBehavior.sendEmoji(AllPlayers,EMOJI_NUKE);respondToMIRV(this.game,this.random,e)}}countCities(e){return e.unitCount(UNIT.City)}async calculateTerritoryCenter(e){let n;try{const m=await withTimeout(e.borderTilesAsync(),WORKER_TIMEOUT_MS,null);n=m&&m.borderTiles?m.borderTiles:m}catch(m){return null}if(!n)return null;const o=Array.from(n);if(o.length===0)return null;let r=Infinity,i=-Infinity;let a=Infinity,s=-Infinity;for(const m of o){const p=this.game.x(m);const h=this.game.y(m);if(pi)i=p;if(hs)s=h}const l=Math.floor((r+i)/2);const c=Math.floor((a+s)/2);const u=this.game.ref(l,c);if(this.game.hasOwner(u)&&this.game.ownerID(u)===e.smallID()){return u}let d=null;let f=Infinity;for(const m of o){const p=this.game.x(m)-l;const h=this.game.y(m)-c;const y=p*p+h*h;if(y0?n.values().next().value:null;if(o===null||o===void 0)return 0n;let r;try{r=await withTimeout(this.player.buildables(o,[e]),WORKER_TIMEOUT_MS,null)}catch(a){return 0n}const i=Array.isArray(r)?r.find(a=>a.type===e):null;return i&&i.cost!==void 0&&i.cost!==null?i.cost:0n}}"use strict";class WarshipBehavior{constructor(e,n,o,r){this.random=e;this.game=n;this.player=o;this.emojiBehavior=r;this.trackedTransportShips=new Set;this.trackedTradeShips=new Set;this.trackedIncomingTransportShips=new Set;this.dealtWithTransportShip=new Set}indexById(e){const n=new Map;for(const o of e)n.set(o.id(),o);return n}async maybeSpawnWarship(){if(this.player===null)throw new Error("not initialized");if(this.game.config().isUnitDisabled(UNIT.Warship)){return false}if(!this.random.chance(50)){return false}const e=this.player.units(UNIT.Port);const n=this.player.units(UNIT.Warship);if(e.length>0&&n.length===0&&this.player.gold()>this.cost(UNIT.Warship)){const o=this.random.randElement(e);const r=this.warshipSpawnTile(o.tile(),250);if(r===null){return false}const i=await this.buildableWarship(r);if(i===null||i===void 0||i.canBuild===false){return false}const a=getBuildMenu();if(!a||typeof a.sendBuildOrUpgrade!=="function"){return false}a.sendBuildOrUpgrade(i,r);state.stats.builds++;setLastAction(tr("🚢 Deploy warship"),"naval");return true}return false}warshipSpawnTile(e,n){for(let o=0;o<50;o++){const r=this.random.nextInt(this.game.x(e)-n,this.game.x(e)+n);const i=this.random.nextInt(this.game.y(e)-n,this.game.y(e)+n);if(!this.game.isValidCoord(r,i)){continue}const a=this.game.ref(r,i);if(!this.game.isWater(a)){continue}return a}return null}trackShipsAndRetaliate(){this.trackTransportShipsAndRetaliate();this.trackTradeShipsAndRetaliate();this.trackIncomingTransportsAndRetaliate()}trackTransportShipsAndRetaliate(){if(this.game.config().isUnitDisabled(UNIT.TransportShip)){return}this.player.units(UNIT.TransportShip).forEach(n=>this.trackedTransportShips.add(n.id()));const e=this.indexById(this.game.units(UNIT.TransportShip));for(const n of Array.from(this.trackedTransportShips)){const o=e.get(n);const r=o!==void 0&&o.isActive();if(!r){if(o!==void 0&&typeof o.wasDestroyedByEnemy==="function"&&typeof o.destroyer==="function"&&o.wasDestroyedByEnemy()&&o.destroyer()!==void 0){this.maybeRetaliateWithWarship(o.tile(),o.destroyer(),"transport")}this.trackedTransportShips.delete(n)}}}trackTradeShipsAndRetaliate(){this.player.units(UNIT.TradeShip).forEach(n=>this.trackedTradeShips.add(n.id()));const e=this.indexById(this.game.units(UNIT.TradeShip));for(const n of Array.from(this.trackedTradeShips)){const o=e.get(n);if(o===void 0||!o.isActive()){this.trackedTradeShips.delete(n);continue}if(o.owner().id()!==this.player.id()){this.maybeRetaliateWithWarship(o.tile(),o.owner(),"trade");this.trackedTradeShips.delete(n)}}}trackIncomingTransportsAndRetaliate(){this.game.units(UNIT.TransportShip).filter(n=>{const o=n.targetTile();return o&&n.isActive()&&!n.transportShipState()?.isRetreating&&this.game.ownerID(o)===this.player?.smallID()&&n.owner().smallID()!==this.player?.smallID()}).forEach(n=>this.trackedIncomingTransportShips.add(n.id()));const e=this.indexById(this.game.units(UNIT.TransportShip));for(const n of Array.from(this.trackedIncomingTransportShips)){const o=e.get(n);const r=o!==void 0?o.targetTile():void 0;if(o===void 0||!o.isActive()||r===void 0||o.transportShipState()?.isRetreating){this.trackedIncomingTransportShips.delete(n);this.dealtWithTransportShip.delete(n);continue}if(this.dealtWithTransportShip.has(n)){continue}const i=this.game.manhattanDist(o.tile(),r);if(i<20){this.dealtWithTransportShip.add(n);continue}if(!o.owner().isAlliedWith(this.player)){if(this.game.hasUnitNearby(r,90,UNIT.Warship,this.player.id(),true)||this.player.units(UNIT.Warship).filter(s=>{const l=s.warshipState().patrolTile;return l!==void 0&&this.game.manhattanDist(r,l)<90}).length>0){this.dealtWithTransportShip.add(n);continue}const a=this.warshipSpawnTile(r,30);if(a===null)continue;this.maybeRetaliateWithWarship(a,o.owner(),"transport");this.dealtWithTransportShip.add(n);break}}}maybeRetaliateWithWarship(e,n,o){if(n.smallID()===this.player.smallID()){return}if(this.player.units(UNIT.Warship).length>=10){this.maybeMoveWarship(e);return}const r=currentDifficulty();if(r===Difficulty.Medium&&this.random.nextInt(0,100)<15||r===Difficulty.Hard&&this.random.nextInt(0,100)<50||r===Difficulty.Impossible&&this.random.nextInt(0,100)<80){void this._retaliateBuildAsync(e,n,o)}}async _retaliateBuildAsync(e,n,o){try{const r=await this.buildableWarship(e);if(r===null||r===void 0||r.canBuild===false){this.maybeMoveWarship(e);return}const i=getBuildMenu();if(!i||typeof i.sendBuildOrUpgrade!=="function"){this.maybeMoveWarship(e);return}i.sendBuildOrUpgrade(r,e);state.stats.builds++;setLastAction(tr("🚢 Retaliate warship"),"naval");this.emojiBehavior.maybeSendEmoji(n,EMOJI_WARSHIP_RETALIATION);this.player.updateRelation(n,o==="trade"?-7.5:-15)}catch(r){}}maybeMoveWarship(e){if(this.game.isWater(e)){const n=this.player.units(UNIT.Warship).filter(o=>{const r=o.warshipState().patrolTile;return r!==void 0&&this.game.manhattanDist(o.tile(),r)<130}).sort((o,r)=>{const i=this.game.manhattanDist(o.tile(),e);const a=this.game.manhattanDist(r.tile(),e);return i-a})[0];if(n){const o=discoverCtors(getEventBus());if(o.moveWarship){emitIntent(o.moveWarship,[n.id()],e)}}}}async counterWarshipInfestation(){if(!this.shouldCounterWarshipInfestation()){return}const e=this.player.team()!==null;if(!this.isRichPlayer(e)){return}const n=this.findWarshipInfestationCounterTarget(e);if(n!==null){await this.buildCounterWarship(n)}}shouldCounterWarshipInfestation(){if(this.game.config().isUnitDisabled(UNIT.Warship)){return false}const e=currentDifficulty();if(e!==Difficulty.Hard&&e!==Difficulty.Impossible){return false}if(this.game.unitCount(UNIT.Warship)<=10){return false}if(this.cost(UNIT.Warship)>this.player.gold()){return false}if(this.player.units(UNIT.Port).length===0){return false}if(this.player.units(UNIT.Warship).length>=10){return false}return true}isRichPlayer(e){const n=this.game.players().filter(r=>{if(r.type()===PlayerType.Human)return false;return e?r.team()===this.player.team():true});const o=n.sort((r,i)=>Number(i.gold()-r.gold())).slice(0,3);return o.some(r=>r.id()===this.player.id())}findWarshipInfestationCounterTarget(e){return e?this.findTeamGameWarshipTarget():this.findFreeForAllWarshipTarget()}findTeamGameWarshipTarget(){const e=new Map;for(const n of this.game.players()){if(this.player.isFriendly(n)||n.id()===this.player.id()){continue}const o=n.team();if(o===null)continue;const r=o.toString();const i=n.units(UNIT.Warship).length;if(!e.has(r)){e.set(r,{count:0,team:r,players:[]})}const a=e.get(r);a.count+=i;a.players.push(n)}for(const[,n]of e.entries()){if(n.count>15){const o=n.players.reduce((r,i)=>{const a=i.units(UNIT.Warship).length;const s=r?r.units(UNIT.Warship).length:0;return a>s?i:r},null);if(o){const r=o.units(UNIT.Warship);if(r.length>3){return{player:o,warship:this.random.randElement(r)}}}}}return null}findFreeForAllWarshipTarget(){const e=this.game.players().filter(n=>!this.player.isFriendly(n)&&n.id()!==this.player.id());for(const n of e){const o=n.units(UNIT.Warship);if(o.length>10){return{player:n,warship:this.random.randElement(o)}}}return null}async buildCounterWarship(e){const n=e.warship.tile();const o=await this.buildableWarship(n);if(o===null||o===void 0||o.canBuild===false){this.maybeMoveWarship(n);return}const r=getBuildMenu();if(!r||typeof r.sendBuildOrUpgrade!=="function"){this.maybeMoveWarship(n);return}r.sendBuildOrUpgrade(o,n);state.stats.builds++;setLastAction(tr("🚢 Counter warship"),"naval");this.emojiBehavior.sendEmoji(AllPlayers,EMOJI_WARSHIP_RETALIATION)}async buildableWarship(e){let n;try{n=await withTimeout(this.player.buildables(e,[UNIT.Warship]),WORKER_TIMEOUT_MS,null)}catch(o){return null}return Array.isArray(n)?n.find(o=>o.type===UNIT.Warship)??null:null}cost(e){if(e===UNIT.Warship){const n=this.player.unitsOwned(UNIT.Warship);return BigInt(Math.min(1e6,(n+1)*25e4))}return 0n}ensureSmartState(){return this.smartState=this.smartState||{transportPos:new Map,tradePos:new Map,losses:[],raids:[],cooldown:new Map,serviced:new Map,lastPassMs:0}}smartWarshipPatrol(){if(this.player===null)return;if(this.game.config().isUnitDisabled(UNIT.Warship))return;const e=this.ensureSmartState();const n=performance.now();const o=state.settings.warshipPatrolThrottleMs||1500;if(n-e.lastPassMsl)e.losses.push({x:b.x,y:b.y,at:r})}e.transportPos.delete(g)}const c=this.indexById(this.game.units(UNIT.TradeShip));for(const g of this.player.units(UNIT.TradeShip)){try{e.tradePos.set(g.id(),{x:this.game.x(g.tile()),y:this.game.y(g.tile())})}catch(b){}}for(const[g]of Array.from(e.tradePos)){const b=c.get(g);if(b===void 0||!b.isActive()){e.tradePos.delete(g);continue}if(b.owner().id()!==a){try{e.raids.push({x:this.game.x(b.tile()),y:this.game.y(b.tile()),at:r})}catch(w){}e.tradePos.delete(g)}}const u=state.settings.warshipLossWindowTicks||900;const d=state.settings.warshipRaidWindowTicks||400;e.losses=e.losses.filter(g=>r-g.at<=u);e.raids=e.raids.filter(g=>r-g.at<=d);const f=[];const m=this.findInvasionThreat();if(m!==null)f.push(m);if(e.raids.length>0){const g=e.raids[e.raids.length-1];if(this.game.isValidCoord(g.x,g.y)){const b=this.game.ref(g.x,g.y);if(this.game.isWater(b)){f.push({tile:b,kind:"trade-lane"})}}}const p=this.findLossZone(e.losses);if(p!==null)f.push({tile:p,kind:"loss-zone"});if(f.length===0)return;const h=state.settings.warshipServiceCellSize||40;const y=state.settings.warshipZoneServiceTicks||90;for(const[g,b]of Array.from(e.serviced)){if(r-b>y)e.serviced.delete(g)}for(const g of f){const b=Math.floor(this.game.x(g.tile)/h)+","+Math.floor(this.game.y(g.tile)/h);const w=e.serviced.get(b);if(w!==void 0&&r-w=n&&c>i){const u=Math.round(s/c);const d=Math.round(l/c);if(this.game.isValidCoord(u,d)){const f=this.game.ref(u,d);if(this.game.isWater(f)){i=c;r=f}}}}return r}moveBestWarshipTo(e,n,o,r){if(!this.game.isWater(e))return false;const i=state.settings.warshipMoveMaxDist||160;const a=state.settings.warshipMoveCooldownTicks||60;const s=this.player.units(UNIT.Warship).filter(u=>{try{const d=u.warshipState().patrolTile;if(d===void 0)return false;if(this.game.manhattanDist(u.tile(),d)>=130)return false;const f=o.cooldown.get(u.id());if(f!==void 0&&r-fthis.game.manhattanDist(u.tile(),e)-this.game.manhattanDist(d.tile(),e));const l=s[0];if(this.game.manhattanDist(l.tile(),e)>i)return false;const c=discoverCtors(getEventBus());if(!c.moveWarship)return false;emitIntent(c.moveWarship,[l.id()],e);o.cooldown.set(l.id(),r);setLastAction(tr("🚢 Patrol → {k}",{k:n}),"naval");console.log("[Warship] patrol →",n,"@",this.game.x(e),this.game.y(e));return true}computeNukeRemainingTicks(e,n){try{if(typeof UniversalPathFinding==="undefined"||!UniversalPathFinding.Parabola||e===void 0||e===null||n===void 0||n===null){return null}const o=this.game.config?.().defaultNukeSpeed?.()??8;const r=UniversalPathFinding.Parabola(this.game,{increment:o,distanceBasedHeight:true,directionUp:true});const i=r.findPath(e,n);if(!Array.isArray(i)||i.length===0)return null;if(i.length===1)return 1;let a=0;for(let s=1;s0?o:8))+1}catch(o){return null}}nukeEtaTicks(e,n){const o=e.type?.();if(o===UNIT.MIRVWarhead)return 0;const r=e.id?.();if(r===void 0)return 0;if(!this.nukeEtaById)this.nukeEtaById=new Map;let i=this.nukeEtaById.get(r);if(i===void 0){let a=this.computeNukeRemainingTicks(e.tile?.(),e.targetTile?.());if(a===null){try{const s=this.game.config?.().defaultNukeSpeed?.()??8;const l=this.game.euclideanDistSquared(e.tile(),e.targetTile());a=Math.floor(Math.sqrt(l)/(s>0?s:8))+1}catch(s){a=1}}i={firstTick:n,remainTicks:a};this.nukeEtaById.set(r,i)}return Math.max(0,i.remainTicks-(n-i.firstTick))}nukeOuterRadius(e){try{const n=this.game.config().nukeMagnitudes(e.type());const o=Number(n?.outer??n?.inner);if(Number.isFinite(o)&&o>0)return o}catch(n){}return e.type&&e.type()===UNIT.HydrogenBomb?160:70}findNukeEscapeTile(e,n,o,r){let i=null;let a=Infinity;for(const p of o){const h=e-p.tx;const y=n-p.ty;const g=h*h+y*y;if(gf||P===f&&$0){for(const c of Array.from(this.nukeEtaById.keys())){if(!i.has(c))this.nukeEtaById.delete(c)}}if(r.length===0)return;const a=(c,u)=>{let d=Infinity;for(const f of r){const m=c-f.tx;const p=u-f.ty;if(m*m+p*p<=f.r2&&f.eta1e3){this._dodgeLogAtMs=s;const c=this.player.smallID();console.log("[Warship] nuke-dodge scan:",r.length,"zone(s)",r.map(u=>({owner:u.owner,mine:u.owner===c,eta:u.eta})))}const l=this.ensureSmartState();for(const c of e){try{const u=c.tile();const d=this.game.x(u);const f=this.game.y(u);const m=a(d,f);if(m===Infinity)continue;const p=this.findNukeEscapeTile(d,f,r,a);if(p===null)continue;if(p.eta<=m)continue;const h=c.warshipState().patrolTile;if(h!==void 0&&a(this.game.x(h),this.game.y(h))>=p.eta){continue}const y=discoverCtors(getEventBus());if(!y.moveWarship)continue;emitIntent(y.moveWarship,[c.id()],p.tile);l.cooldown.set(c.id(),n);setLastAction(tr("🚢 Dodge nuke"),"naval")}catch(u){}}}}"use strict";const SAM_RATIO_BY_DIFFICULTY={[Difficulty.Easy]:.15,[Difficulty.Medium]:.2,[Difficulty.Hard]:.25,[Difficulty.Impossible]:.3};function getStructureRatios(t){return{[UNIT.Port]:{ratioPerCity:.75,perceivedCostIncreasePerOwned:1},[UNIT.Factory]:{ratioPerCity:.75,perceivedCostIncreasePerOwned:1},[UNIT.SAMLauncher]:{ratioPerCity:SAM_RATIO_BY_DIFFICULTY[t],perceivedCostIncreasePerOwned:.3},[UNIT.MissileSilo]:{ratioPerCity:.2,perceivedCostIncreasePerOwned:1}}}const CITY_PERCEIVED_COST_INCREASE_PER_OWNED=1;const FACTORY_COASTAL_RATIO_MULTIPLIER=.33;const MAX_MISSILE_SILOS=3;const FIRST_MISSILE_SILO_RATIO=.4;const UPGRADE_DENSITY_THRESHOLD=1/1500;const TILES_PER_CITY_EQUIVALENT=2e3;const HIGH_NATION_DENSITY_THRESHOLD=1/7500;const HIGH_STARTING_GOLD_THRESHOLD=3000000n;const HIGH_GOLD_STRUCTURE_COOLDOWN_TICKS=[0,0,250,150,100];const TEAM_POST_SAVE_UP_PHASE_TICKS=150;const UNDER_ATTACK_THREAT_RATIO=.35;const DEFENSE_POST_RATIO_PER_POST=.4;const STRUCTURES_TYPES=[UNIT.City,UNIT.DefensePost,UNIT.SAMLauncher,UNIT.MissileSilo,UNIT.Port,UNIT.Factory];function autoBotBuildAllowed(t){const e=state.settings&&state.settings.buildStructures;return!e||e[t]!==false}function shimCanTrade(t,e){if(t==null||e==null)return false;if(t.id()===e.id())return false;return!t.hasEmbargoAgainst(e)&&!e.hasEmbargoAgainst(t)}function shimSharedWaterComponents(t,e){try{if(typeof t.sharedWaterComponents==="function"){return t.sharedWaterComponents(e)}}catch(n){}return null}class StationUnionFind{constructor(){this.parent=new Map}add(e){if(!this.parent.has(e))this.parent.set(e,e)}find(e){let n=e;while(this.parent.get(n)!==n)n=this.parent.get(n);let o=e;while(this.parent.get(o)!==n){const r=this.parent.get(o);this.parent.set(o,n);o=r}return n}union(e,n){const o=this.find(e);const r=this.find(n);if(o!==r)this.parent.set(o,r)}}class StructureBehavior{constructor(e,n,o){this.random=e;this.game=n;this.player=o;this.reachableStationsCache=null;this._sharedWaterComponents=null;this.lastStructureTick=null;this.placementsCount=0;this._hasHighStartingGold=null;this._postSaveUpStartTick=null}ownedLevels(e){let n=0;for(const o of this.player.units(e)){if(o.isUnderConstruction()){n+=1}else{n+=o.level()}}return n}async handleStructures(){if(this.placementsCount>0&&!this.game.config().isUnitDisabled(UNIT.DefensePost)){if(await this.tryBuildDefensePost()){return true}if(this.defensePostNeeded()){return false}}if(this.isOnStructureCooldown()){return false}if(this.isInPostSaveUpBlockedPhase()){return false}const e=await this.doHandleStructures();if(e){this.lastStructureTick=this.game.ticks();this.placementsCount++}return e}async tryBuildDefensePost(){if(!autoBotBuildAllowed(UNIT.DefensePost))return false;const e=currentDifficulty();if(e===Difficulty.Easy)return false;if(e===Difficulty.Medium&&!this.random.chance(2)){return false}const n=this.player;const o=n.incomingAttacks().filter(d=>this.isLandAttack(d));if(o.length===0)return false;const r=n.troops();if(r<=0)return false;const i=o.reduce((d,f)=>d+f.troops(),0);const a=i/r;if(a=s){return false}const c=this.cost(UNIT.DefensePost);if(n.gold()this.isLandAttack(i));if(n.length===0)return false;const o=this.player.troops();if(o<=0)return false;const r=n.reduce((i,a)=>i+a.troops(),0);return r/o>=UNDER_ATTACK_THREAT_RATIO}isLandAttack(e){if(e.hasSourceTile&&e.hasSourceTile()){return e.sourceTile()===null}const n=e.attacker();if(!n||!(n.isPlayer&&n.isPlayer()))return true;return this.player.sharesBorderWith(n)}getAttackFrontTiles(e){const n=this.game;const o=this.player;const r=new Set(e.map(a=>{const s=a.attacker();return s&&s.isPlayer&&s.isPlayer()?s.smallID():null}).filter(a=>a!==null));if(r.size===0)return[];const i=[];e:for(const a of o.borderTiles()){for(const s of n.neighbors(a)){const l=n.hasOwner(s)?n.ownerID(s):null;if(l!==null&&r.has(l)){i.push(a);continue e}}}return i}countDefensePostsNearFront(e,n){if(e.length===0)return 0;const o=this.game;const{borderSpacing:r}=this.spacingConstants();const i=(r*1.5)**2;let a=0;for(const s of this.player.units(UNIT.DefensePost)){for(const l of e){if(o.euclideanDistSquared(s.tile(),l)<=i){a++;if(n!==void 0&&a>=n)return a;break}}}return a}sampleTilesNearFront(e,n,o){const r=this.game;const i=this.player;if(e.length===0){return[]}const{borderSpacing:a}=this.spacingConstants();const s=Math.ceil(a*1.5);const l=Math.ceil(a*.75);const c=Math.ceil(a*1.5);const u=i.borderTiles();const d=i.smallID();const f=(a*1.5)**2;const m=i.units(UNIT.DefensePost).map(g=>g.tile());let p;if(m.length>0){p=e.filter(g=>!m.some(b=>r.euclideanDistSquared(g,b)c)continue;h.push(P)}if(h.length>0)return h;const y=[];for(let g=0;g=TEAM_POST_SAVE_UP_PHASE_TICKS}async doHandleStructures(){this.reachableStationsCache=null;const e=this.game.config();const n=e.isUnitDisabled(UNIT.City);const o=n?Math.max(1,Math.floor(this.player.numTilesOwned()/TILES_PER_CITY_EQUIVALENT)):this.ownedLevels(UNIT.City);this._sharedWaterComponents=shimSharedWaterComponents(this.game,this.player);const r=this._sharedWaterComponents!==null;this._dominant=false;if(state.settings.winFixes){const c=this.dominanceShare();const u=Number.isFinite(state.settings.factoryRailShare)?state.settings.factoryRailShare:.75;this._dominant=c!==null&&c>u;try{const d=this.game.ticks();if(d-(state._buildDomDiagTick||-999)>=80){state._buildDomDiagTick=d;console.log("[Build] dominance gate",{share:c===null?null:Number(c.toFixed(3)),trigger:u,dominant:this._dominant})}}catch(d){}}state.dominant=this._dominant;const i=!e.isUnitDisabled(UNIT.MissileSilo);const a=currentDifficulty();if(this.placementsCount===0&&(a===Difficulty.Hard||a===Difficulty.Impossible)&&!e.isUnitDisabled(UNIT.AtomBomb)&&i&&!e.isUnitDisabled(UNIT.SAMLauncher)&&this.hasHighStartingGold()&&await this.maybeSpawnStructure(UNIT.SAMLauncher)){return true}if(!n&&this.ownedLevels(UNIT.City)===0&&this.isHighNationDensity()){const c=r&&!e.isUnitDisabled(UNIT.Port)?UNIT.Port:UNIT.Factory;if(!e.isUnitDisabled(c)&&await this.maybeSpawnStructure(c)){return true}}const s=[UNIT.Port,UNIT.Factory,UNIT.SAMLauncher,UNIT.MissileSilo];const l=!e.isUnitDisabled(UNIT.AtomBomb)||!e.isUnitDisabled(UNIT.HydrogenBomb)||!e.isUnitDisabled(UNIT.MIRV);for(const c of s){if(e.isUnitDisabled(c)){continue}if(c===UNIT.Port&&!r){continue}if(!l&&(c===UNIT.MissileSilo||c===UNIT.SAMLauncher)){continue}if(!i&&c===UNIT.SAMLauncher){continue}if(this.shouldBuildStructure(c,o,r)){if(await this.maybeSpawnStructure(c)){return true}}}if(!n&&await this.maybeSpawnStructure(UNIT.City)){return true}return false}hasHighStartingGold(){if(this._hasHighStartingGold===null){const e=this.player.__src??this.player;const n={playerType:this.player.type(),isLobbyCreator:typeof e.isLobbyCreator==="function"?e.isLobbyCreator():false};this._hasHighStartingGold=this.game.config().startingGold(n)>=HIGH_STARTING_GOLD_THRESHOLD}return this._hasHighStartingGold}isHighNationDensity(){const e=this.game.numLandTiles();if(e<=0)return false;return this.game.nations().length/e>HIGH_NATION_DENSITY_THRESHOLD}dominanceShare(){if(this.player===null)return null;let e=false;try{e=String(this.game.config().gameConfig().gameMode)==="Team"}catch(i){return null}const n=typeof this.game.numTilesWithFallout==="function"?this.game.numTilesWithFallout():0;const o=(this.game.numLandTiles()||0)-n;if(o<=0)return null;let r=0;if(e){for(const i of this.game.players()){try{if(i.isPlayer()&&this.player.isOnSameTeam(i)){r+=i.numTilesOwned()}}catch(a){}}}else{r=this.player.numTilesOwned()}return r/o}shouldBuildStructure(e,n,o){const r=this.game.config();const i=currentDifficulty();const a=getStructureRatios(i);const s=a[e];if(s===void 0){return false}let l=s.ratioPerCity;if(e===UNIT.Factory&&o&&!r.isUnitDisabled(UNIT.Port)&&!this._dominant){l*=FACTORY_COASTAL_RATIO_MULTIPLIER}const c=this.ownedLevels(e);if(e===UNIT.MissileSilo&&c>=MAX_MISSILE_SILOS){return false}if(e===UNIT.MissileSilo&&c===0){l=FIRST_MISSILE_SILO_RATIO}const u=Math.floor(n*l);return c=15000000n){o=o>s?o-s:0n}}if(oUPGRADE_DENSITY_THRESHOLD&&this.game.config().unitInfo(e).upgradable){if(await this.maybeUpgradeStructure(r)){return true}if(r.length>0){return false}}const i=await this.structureSpawnTile(e);if(i===null){return false}const a=getBuildMenu();if(!a||typeof a.sendBuildOrUpgrade!=="function"){return false}a.sendBuildOrUpgrade(i,i.canBuild);state.stats.builds++;setLastAction(tr("🏗️ Build {type}",{type:e}),"build");return true}getPerceivedCost(e){const n=this.cost(e);const o=this.getSaveUpTarget();if(o===0n||this.player.gold()>=o){return n}const r=this.ownedLevels(e);let i;if(e===UNIT.City){i=CITY_PERCEIVED_COST_INCREASE_PER_OWNED}else{const s=currentDifficulty();const l=getStructureRatios(s);const c=l[e];i=c&&c.perceivedCostIncreasePerOwned!==void 0?c.perceivedCostIncreasePerOwned:.1}const a=1+i*r;return BigInt(Math.ceil(Number(n)*a))}getSaveUpTarget(){const e=this.game.config();if(e.isUnitDisabled(UNIT.MissileSilo)){return this.cost(UNIT.SAMLauncher)}if(this.game.config().gameConfig().gameMode===GameMode.Team){return this.cost(UNIT.HydrogenBomb)}const n=!e.isUnitDisabled(UNIT.MIRV);const o=!e.isUnitDisabled(UNIT.HydrogenBomb);const r=!e.isUnitDisabled(UNIT.AtomBomb);if(n){return this.cost(UNIT.MIRV)+this.cost(UNIT.HydrogenBomb)}if(o){return this.cost(UNIT.HydrogenBomb)*5n}if(r){return this.cost(UNIT.AtomBomb)*20n}return this.cost(UNIT.SAMLauncher)}async maybeUpgradeStructure(e){if(this.getTotalStructureDensity()<=UPGRADE_DENSITY_THRESHOLD){return false}if(e.length===0){return false}const n=await this.findBestStructureToUpgrade(e);if(n!==null){const o=getBuildMenu();if(!o||typeof o.sendBuildOrUpgrade!=="function"){return false}o.sendBuildOrUpgrade(n.bu);state.stats.builds++;setLastAction(tr("⬆️ Upgrade {type}",{type:n.structure.type()}),"build");return true}return false}getTotalStructureDensity(){const e=this.player.numTilesOwned();return e>0?this.player.units(...STRUCTURES_TYPES).length/e:0}async findBestStructureToUpgrade(e){const n=this.game;if(e.length===0){return null}const o=[];const r=new Map;for(const u of e){const d=await this.buildableFor(u.type(),u.tile());if(d!==null&&d.canUpgrade!==false){o.push(u);r.set(u.id(),d)}}if(o.length===0){return null}const i=u=>({structure:u,bu:r.get(u.id())});const a=currentDifficulty();let s;switch(a){case Difficulty.Easy:s=70;break;case Difficulty.Medium:s=40;break;case Difficulty.Hard:s=25;break;case Difficulty.Impossible:s=10;break;default:s=10}if(this.random.nextInt(0,100)1){d+=(f.level()-1)*7.5}}}d+=this.random.nextInt(0,5);c.push({structure:u,score:d})}if(c.length===0){return null}c.sort((u,d)=>d.score-u.score);if(c.length>=2&&this.random.chance(2)){const u=c.length>=3?this.random.nextInt(1,3):1;return i(c[u].structure)}return i(c[0].structure)}async structureSpawnTile(e){let n=e===UNIT.Port?this.randCoastalTileArray(25):randTerritoryTileArray(this.random,this.game,this.player,25);if(e===UNIT.MissileSilo&&state.settings.winFixes){try{const i=this.tilesNearFriendlySams(25);if(i.length>0)n=i.concat(n)}catch(i){}}if(n.length===0)return null;const o=this.structureSpawnTileValue(e);if(o===null)return null;const r=n.map(i=>({t:i,v:o(i)}));r.sort((i,a)=>a.v-i.v);for(const{t:i}of r){const a=await this.buildableFor(e,i);if(a!==null&&a.canBuild!==false){return a}}return null}async buildableFor(e,n){let o;try{o=await withTimeout(this.player.buildables(n,[e]),WORKER_TIMEOUT_MS,null)}catch(r){return null}return Array.isArray(o)?o.find(r=>r.type===e)??null:null}randCoastalTileArray(e){const n=this.game;const o=this._sharedWaterComponents;const r=Array.from(this.player.borderTiles()).filter(i=>{if(!n.isShore(i))return false;if(o===null)return false;for(const a of n.neighbors(i)){if(!n.isWater(a))continue;if(n.isOcean(a))return true;const s=typeof n.getWaterComponent==="function"?n.getWaterComponent(a):null;if(s!==null&&o.has(s))return true}return false});return Array.from(this.arraySampler(r,e))}*arraySampler(e,n){if(e.length<=n){yield*e}else{const o=new Set(e);while(n--){const r=this.random.randFromSet(o);o.delete(r);yield r}}}structureSpawnTileValue(e){switch(e){case UNIT.City:return this.cityValue();case UNIT.MissileSilo:return this.missileSiloValue();case UNIT.Factory:return this.factoryValue();case UNIT.Port:return this.portValue();case UNIT.SAMLauncher:return this.samLauncherValue();default:throw new Error(`Value function not implemented for ${e}`)}}missileSiloValue(){const e=this.game;const n=this.player.borderTiles();const o=this.player.units(UNIT.MissileSilo);const{borderSpacing:r,structureSpacing:i}=this.spacingConstants();return a=>{let s=0;s+=e.magnitude(a);const l=closestTile(e,n,a);const c=l[1];s+=Math.min(c,r);const u=new Set(o.map(f=>f.tile()));u.delete(a);const d=closestTwoTiles(e,u,[a]);if(d!==null){const f=e.manhattanDist(d.x,a);s+=Math.min(f,i)}if(state.settings.winFixes){s+=this.samCoverageBonus(a)}return s}}samCoverageBonus(e){const n=this.game;if(typeof n.nearbyUnits!=="function")return 0;let o;try{o=n.config()}catch(c){return 0}const r=o&&o.maxSamRange?Number(o.maxSamRange()):200;let i;try{i=n.nearbyUnits(e,r,UNIT.SAMLauncher)}catch(c){return 0}const a=this.player.smallID();let s=-1;let l=-1;for(const c of i||[]){const u=c.unit||c;const d=u.owner&&u.owner();if(!d)continue;if(u.isUnderConstruction&&u.isUnderConstruction())continue;const f=d.smallID&&d.smallID()===a;const m=!f&&this.player.isFriendly(d)===true;if(!f&&!m)continue;const p=u.level&&Number(u.level())||1;const h=o&&o.samRange?Number(o.samRange(p)):0;if(h<=0)continue;let y=c.distSquared;if(y==null&&n.euclideanDistSquared&&u.tile){y=n.euclideanDistSquared(e,u.tile())}if(y==null||y>h*h)continue;const g=h-Math.sqrt(y);if(f){if(g>s)s=g}else if(g>l){l=g}}if(s>=0)return 1e5+s;if(l>=0)return 5e4+l;return 0}tilesNearFriendlySams(e){const n=this.game;if(typeof n.nearbyUnits!=="function"&&typeof n.units!=="function"){return[]}const o=this.player.smallID();let r=[];try{r=n.units(UNIT.SAMLauncher)||[]}catch(d){return[]}const i=[];const a=[];for(const d of r){try{if(d.isUnderConstruction&&d.isUnderConstruction())continue;const f=d.owner&&d.owner();if(!f)continue;if(f.smallID&&f.smallID()===o)i.push(d);else if(this.player.isFriendly(f)===true)a.push(d)}catch(f){}}const s=i.length>0?i:a;if(s.length===0)return[];let l;try{l=n.config()}catch(d){return[]}const c=[];const u=new Set;for(const d of s){const f=d.tile();const m=n.x(f);const p=n.y(f);const h=d.level&&Number(d.level())||1;const y=l&&l.samRange?Number(l.samRange(h)):0;if(y<=0)continue;const g=Math.floor(y);const b=Math.max(2,Math.floor(g/6));for(let w=-g;w<=g;w+=b){for(let T=-g;T<=g;T+=b){if(w*w+T*T>y*y)continue;const A=m+w;const I=p+T;if(!n.isValidCoord(A,I))continue;const P=n.ref(A,I);if(u.has(P))continue;if(!n.isLand(P))continue;if(n.ownerID(P)!==o)continue;u.add(P);c.push(P);if(c.length>=e)return c}}}return c}portValue(){const e=this.game;const n=this.player.units(UNIT.Port);return o=>{let r=0;const i=new Set(n.map(l=>l.tile()));i.delete(o);const a=closestTile(e,i,o);const s=a[1];r+=s;return r}}factoryValue(){const e=this.game;const n=this.player;const o=this.player.borderTiles();const r=n.units(UNIT.Factory);const{borderSpacing:i,structureSpacing:a}=this.spacingConstants();const s=e.config().trainStationMaxRange();const l=s*s;const c=currentDifficulty();const u=this.shouldUseConnectivityScore(c);const d=u?this.getOrBuildReachableStations():[];const f=e.config().trainStationMinRange()**2;const m=new Set(n.units(UNIT.City).map(p=>p.tile()));return p=>{let h=0;h+=e.magnitude(p);const y=closestTile(e,o,p);const g=y[1];h+=Math.min(g,i);const b=new Set(r.map(A=>A.tile()));b.delete(p);const w=closestTwoTiles(e,b,[p]);if(w!==null){const A=e.manhattanDist(w.x,p);h+=Math.min(A,s)}const T=closestTwoTiles(e,m,[p]);if(T!==null){const A=e.manhattanDist(T.x,p);h+=Math.min(A,a)}if(!u){return h}h+=this.computeConnectivityScore(p,d,f,l)*a;return h}}shouldUseConnectivityScore(e){let n;switch(e){case Difficulty.Easy:n=0;break;case Difficulty.Medium:n=60;break;case Difficulty.Hard:n=75;break;case Difficulty.Impossible:n=100;break;default:n=100}return this.random.nextInt(0,100){const m=l++;const p=[f];s[f]=m;let h=0;while(h=0&&g{if(f<0||f>=a)return null;if(n[f]!==0){if(s[f]===-1)c(f);return s[f]}for(const m of o.neighbors(f)){if(m>=0&&mr)continue;if(c!==null){i.set(c,Math.max(i.get(c)??0,u))}else{a+=u}}let s=a;for(const l of i.values())s+=l;return s}cityValue(){const e=this.game;const n=this.player;const o=n.borderTiles();const r=n.units(UNIT.City);const{borderSpacing:i,structureSpacing:a}=this.spacingConstants();const s=e.config().trainStationMaxRange();const l=s*s;const c=currentDifficulty();const u=this.shouldUseConnectivityScore(c);const d=u?this.getOrBuildReachableStations():[];const f=e.config().trainStationMinRange()**2;const m=new Set(n.units(UNIT.Factory).map(p=>p.tile()));return p=>{let h=0;h+=e.magnitude(p);const y=closestTile(e,o,p);const g=y[1];h+=Math.min(g,i);const b=new Set(r.map(A=>A.tile()));b.delete(p);const w=closestTwoTiles(e,b,[p]);if(w!==null){const A=e.manhattanDist(w.x,p);h+=Math.min(A,a)}const T=closestTwoTiles(e,m,[p]);if(T!==null){const A=e.manhattanDist(T.x,p);h+=Math.min(A,a)}if(!u){return h}h+=this.computeConnectivityScore(p,d,f,l)*a;return h}}samLauncherValue(){const e=this.game;const n=this.player;const o=n.borderTiles();const r=n.units(UNIT.SAMLauncher);const{borderSpacing:i,structureSpacing:a}=this.spacingConstants();const s=currentDifficulty();const l=s===Difficulty.Hard||s===Difficulty.Impossible;const c=[];for(const p of n.units()){switch(p.type()){case UNIT.City:case UNIT.Factory:case UNIT.MissileSilo:case UNIT.Port:c.push({tile:p.tile(),weight:l?p.level():1})}}const u=e.config().defaultSamRange();const d=u*u;const f=s!==Difficulty.Easy&&this.random.nextInt(0,100)<25;let m=null;if(f){m=new Map;const p=n.units(UNIT.SAMLauncher);for(const h of c){let y=0;for(const g of p){const b=e.config().samRange(g.level());const w=e.euclideanDistSquared(h.tile,g.tile());if(w<=b*b){y+=g.level()}}m.set(h.tile,y)}}return p=>{let h=0;h+=e.magnitude(p);const y=closestTwoTiles(e,o,[p]);if(y!==null){const w=e.manhattanDist(y.x,p);h+=Math.min(w,i)}const g=new Set(r.map(w=>w.tile()));g.delete(p);const b=closestTwoTiles(e,g,[p]);if(b!==null){const w=e.manhattanDist(b.x,p);h+=Math.min(w,a)}if(s!==Difficulty.Easy){for(const w of c){const T=e.euclideanDistSquared(p,w.tile);if(T>d)continue;if(f&&m!==null){const A=m.get(w.tile)??0;const I=1/(1+A);h+=a*w.weight*I}else{h+=a*w.weight}}}return h}}spacingConstants(){const e=this.game.config().nukeMagnitudes(UNIT.AtomBomb).outer;return{borderSpacing:e,structureSpacing:e*2}}}"use strict";const AttackStructures=new Set([UNIT.City,UNIT.DefensePost,UNIT.SAMLauncher,UNIT.MissileSilo,UNIT.Port,UNIT.Factory]);const HumansVsNations="Humans Vs Nations";const BOAT_PROBE_CANDIDATES=4;class AttackBehavior{constructor(e,n,o,r,i,a,s,l){this.random=e;this.game=n;this.player=o;this.triggerRatio=r;this.reserveRatio=i;this.expandRatio=a;this.allianceBehavior=s;this.emojiBehavior=l;this.botAttackTroopsSent=0}async maybeAttack(){if(this.player===null||this.allianceBehavior===void 0){throw new Error("not initialized")}const e=Array.from(this.player.borderTiles()).flatMap(f=>this.game.neighbors(f)).filter(f=>this.game.isLand(f)&&this.game.ownerID(f)!==this.player?.smallID());const n=this.player.nearby();const o=new Set(e.map(f=>this.game.playerBySmallID(this.game.ownerID(f))).filter(f=>f!=null&&f.isPlayer()));for(const f of n){if(f.isPlayer())o.add(f)}const r=[...o].sort((f,m)=>f.troops()-m.troops());const i=r.filter(f=>this.player?.isFriendly(f)===true);const a=r.filter(f=>this.player?.isFriendly(f)===false);const s=e.some(f=>!this.game.hasOwner(f)&&!this.game.hasFallout(f))||n.some(f=>!f.isPlayer());const l=state.settings.features||{};const c=!!l.expand;const u=!!l.boat;const d=!!l.alliance;if(c&&state.settings.winFixes){const f=r.filter(m=>{try{return m.isPlayer()&&m.isDisconnected&&m.isDisconnected()}catch(p){return false}});if(f.length>0){f.sort((m,p)=>m.troops()-p.troops());console.log("[Takeover] disconnected neighbour → grabbing land:",f[0].name?.()??f[0].smallID?.());if(await this.sendAttack(f[0]))return}}if(c&&state.settings.winFixes&&a.some(f=>f.isPlayer()&&f.type()===PlayerType.Bot)){if(await this.attackBots())return}if(state.settings.winFixes){await this.maybeOpportunisticBoat()}if(state.settings.winFixes&&state.settings.features.donate){const f=performance.now();if(f-(state.lastDonateMs||0)>(state.settings.donateThrottleMs||3e3)){if(this.donateTroops())state.lastDonateMs=f}}if(d&&state.settings.winFixes){this.allianceBehavior.reachOutToFarNations()}if(c&&s){if(await this.sendAttack(this.game.terraNullius()))return}if(a.length===0){if(u&&this.random.chance(5)){await this.attackWithRandomBoat()}}else{if(u&&this.random.chance(10)){await this.attackWithRandomBoat(a);return}if(d){this.allianceBehavior.maybeSendAllianceRequests(a)}}if(c){await this.attackBestTarget(i,a)}}async attackWithRandomBoat(e=[]){if(this.player===null)throw new Error("not initialized");if(!(state.settings.features&&state.settings.features.boat)){return}if(this.game.config().isUnitDisabled(UNIT.TransportShip)){return}if(state.settings.winFixes){const s=this.game.numLandTiles()||1;if(this.player.numTilesOwned()/s>.25){return}}if(this.player.unitCount(UNIT.TransportShip)>=this.game.config().boatMaxNumber()){return}const n=Array.from(this.player.borderTiles()).filter(s=>this.game.isShore(s));if(n.length===0){return}const o=this.random.randElement(n);let r=await this.findRandomBoatTarget(o,e,true);if(r===null){r=await this.findRandomBoatTarget(o,e,false);if(r===null){return}}const i=discoverCtors(getEventBus());const a=state.settings.winFixes?Math.max(this.player.troops()*(state.settings.boatProbeFrac||.01),state.settings.boatProbeMinTroops||8e3):this.player.troops()/5;if(i.boat&&emitIntent(i.boat,r,a)){state.stats.attacks++;setLastAction(tr("⛵ Random boat"),"naval")}return}async maybeOpportunisticBoat(){if(this.player===null)return false;if(!(state.settings.features&&state.settings.features.boat))return false;if(this.game.config().isUnitDisabled(UNIT.TransportShip))return false;const e=Math.min(this.game.config().boatMaxNumber(),state.settings.maxConcurrentBoats||3);const n=this.player.unitCount(UNIT.TransportShip);const o=performance.now();const r=state.settings.oppBoatThrottleMs||1200;const i=o-(state.lastOppBoatMs||0);const a=n>=e;const s=i0?this.player.troops()/l:1;const u=state.settings.boatSurplusFill||.75;const d=Array.from(this.player.borderTiles()).filter(k=>this.game.isShore(k));try{if(o-(state._boatDiagAt||0)>5e3){state._boatDiagAt=o;console.log("[Boat] diag:",{reason:a?"capped":s?"throttled":d.length===0?"no-shore":"ok",ships:n,boatCap:e,shoreTiles:d.length,fill:Number(c.toFixed(2)),islandFill:state.settings.boatIslandFill||.35,maxShips:this.game.config().boatMaxNumber()})}}catch(k){}if(a)return false;if(s)return false;if(d.length===0)return false;const f=state.beachhead;const m=state.settings.mirvBoatWindowTicks||150;if(f&&f.tile!=null&&c>=u&&this.game.ticks()-(f.at||0)this.game.ownerID($)===a)){continue}}c.add(T);u.push({tile:T,dist:y*y+g*g,owned:P,fallout:A})}}}u.sort((f,m)=>f.dist-m.dist);return u}async findDisconnectedBoatTarget(){if(this.player===null)return null;const e=this.player.smallID();const n=this.getPlayerCenter(this.player);const o=[];for(const i of this.game.players()){try{if(!i.isPlayer||!i.isPlayer())continue;if(!i.isAlive())continue;if(i.smallID()===e)continue;if(!(i.isDisconnected&&i.isDisconnected()))continue;const a=i.nameLocation?i.nameLocation():null;if(!a||a.x==null)continue;if(!this.game.isValidCoord(a.x,a.y))continue;const s=this.game.ref(a.x,a.y);if(s==null)continue;let l=0;if(n&&n.x!=null){const c=a.x-n.x;const u=a.y-n.y;l=c*c+u*u}o.push({tile:s,dist:l})}catch(a){}}o.sort((i,a)=>i.dist-a.dist);const r=state.settings.distantBoatProbeMax||12;for(let i=0;il.dist-c.dist);const r=state.settings.distantBoatProbeMax||12;const i="__probe_timeout__";let a=0;let s=0;for(let l=0;lo-(i.at||0)=0;i--){const a=state.recentMirvHits[i];if(a.tile==null)continue;if(this.game.ownerID(a.tile)===r)continue;return a.tile}return null}async findRandomBoatTarget(e,n,o=false){if(this.player===null)throw new Error("not initialized");const r=this.game.x(e);const i=this.game.y(e);const a=new Set;const s=[];for(let l=0;l<500;l++){const c=this.random.nextInt(r-150,r+150);const u=this.random.nextInt(i-150,i+150);if(!this.game.isValidCoord(c,u)){continue}const d=this.game.ref(c,u);if(!this.game.isLand(d)){continue}const f=this.game.owner(d);if(f.isPlayer()&&f.smallID()===this.player.smallID()){continue}if(f.isPlayer()&&a.has(f.id())){continue}if(f.isPlayer()&&n.some(p=>p.smallID()===f.smallID())){continue}if(this.isFFA()&&f.isPlayer()&&f.troops()>this.player.troops()){continue}let m;if(o){m=!f.isPlayer()||f.type()===PlayerType.Bot}else{m=!f.isPlayer()||!f.isFriendly(this.player)}if(!m){continue}s.push({tile:d,owner:f});if(s.length>=BOAT_PROBE_CANDIDATES){break}}for(const l of s){if(l.owner.isPlayer()&&a.has(l.owner.id())){continue}const c=await withTimeout(this.player.bestTransportShipSpawn(l.tile),WORKER_TIMEOUT_MS,false);if(c===false){if(l.owner.isPlayer()){a.add(l.owner.id())}continue}return l.tile}return null}async attackBestTarget(e,n){if(this.hasNeighboringBotWithStructures()){if(await this.attackBots())return}if(!this.hasReserveRatioTroops())return;if(!this.hasTriggerRatioTroops()&&!this.random.chance(10))return;const o=this.getAttackStrategies(e,n);for(const r of o){if(await r())return}}getAttackStrategies(e,n){const o=currentDifficulty();const r=async()=>{const b=this.findIncomingAttackPlayer();if(b){return await this.sendAttack(b,true)}return false};const i=async()=>await this.attackBots();const a=async()=>await this.assistAllies();const s=async()=>{const b=this.findTraitor(n);if(b){return await this.sendAttack(b)}return false};const l=async()=>{const b=n.find(w=>w.isDisconnected()&&(!this.isFFA()||w.troops()await this.maybeBetrayAndAttack(e,n);const u=async()=>{if(this.isBorderingNukedTerritory()){return await this.sendAttack(this.game.terraNullius())}return false};const d=async()=>{const b=this.findVictim(n);if(b){return await this.sendAttack(b)}return false};const f=async()=>{for(const b of this.player.allRelationsSorted()){if(b.relation!==Relation.Hostile)continue;const w=b.player;if(this.player.isFriendly(w))continue;if(this.isFFA()&&w.troops()>this.player.troops()*3)continue;return await this.sendAttack(w)}return false};const m=async()=>{const b=this.findVeryWeakEnemy(n);if(b){return await this.sendAttack(b)}return false};const p=async()=>{if(n.length>0){const b=n[0];if(!this.isFFA()||b.troops(){if(n.length===0){const b=await this.findNearestIslandEnemy();if(b){return await this.sendAttack(b)}}return false};const y=async()=>await this.donateTroops();let g;switch(o){case Difficulty.Easy:g=[u,i,r,a,c,f,p];break;case Difficulty.Medium:g=[i,u,r,a,c,f,l,s,p,h,y];break;case Difficulty.Hard:g=[i,r,a,c,u,s,l,f,m,d,p,h,y];break;case Difficulty.Impossible:g=[r,i,m,a,s,l,c,d,u,f,p,h,y];break;default:throw new Error("unreachable difficulty: "+o)}if(state.settings.winFixes){const b=g.indexOf(u);if(b!==-1){g.splice(b,1);const w=g.indexOf(p);if(w!==-1)g.splice(w+1,0,u);else g.push(u)}}return g}hasNeighboringBotWithStructures(){return this.player.nearby().some(e=>e.isPlayer()&&e.type()===PlayerType.Bot&&!this.player.isFriendly(e)&&e.units().some(n=>AttackStructures.has(n.type())))}hasReserveRatioTroops(){const e=this.game.config().maxTroops(this.player);const n=this.player.troops()/e;return n>=this.effectiveReserveRatio()}effectiveReserveRatio(){let e=this.reserveRatio;try{if(state.settings.winFixes){const n=this.game.numLandTiles()||1;const o=this.player.numTilesOwned()/n;const r=state.settings.sizeReserveScale??0;const i=state.settings.sizeReserveCap??.6;e=Math.max(e,Math.min(i,o*r))}}catch(n){}return e}hasTriggerRatioTroops(){const e=this.game.config().maxTroops(this.player);const n=this.player.troops()/e;return n>=this.triggerRatio}findIncomingAttackPlayer(){let e=this.player.incomingAttacks().filter(r=>!this.player.isFriendly(r.attacker()));if(this.player.type()!==PlayerType.Bot){e=e.filter(r=>r.attacker().type()!==PlayerType.Bot)}let n=0;let o;for(const r of e){if(r.troops()<=n)continue;n=r.troops();o=r.attacker()}if(o!==void 0){return o}return null}async attackBots(){const e=this.player.nearby().filter(a=>a.isPlayer()&&this.player.isFriendly(a)===false&&a.type()===PlayerType.Bot);if(e.length===0){return false}this.botAttackTroopsSent=0;const n=a=>a.troops()/a.numTilesOwned();const o=a=>a.units().some(s=>AttackStructures.has(s.type()));const r=e.slice().sort((a,s)=>{const l=o(a);const c=o(s);if(l!==c){return l?-1:1}return n(a)-n(s)});const i=r.slice(0,this.getBotAttackMaxParallelism());for(const a of i){await this.sendAttack(a)}return this.botAttackTroopsSent>0}getBotAttackMaxParallelism(){const e=currentDifficulty();switch(e){case Difficulty.Easy:return 1;case Difficulty.Medium:return this.random.chance(2)?1:2;case Difficulty.Hard:return 3;case Difficulty.Impossible:{return 100}default:throw new Error("unreachable difficulty: "+e)}}async assistAllies(){if(this.emojiBehavior===void 0)throw new Error("not initialized");if(this.game.config().disableAlliances())return false;for(const e of this.player.allies()){if(e.targets().length===0)continue;if(this.player.relation(e)n.isTraitor()&&(!this.isFFA()||n.troops()0){for(const o of e){if(this.allianceBehavior.maybeBetray(o,e.length+n.length)){return await this.sendAttack(o,true)}}}return false}isBorderingNukedTerritory(){if(this.game.config().isUnitDisabled(UNIT.MissileSilo)){return false}for(const e of this.player.borderTiles()){for(const n of this.game.neighbors(e)){if(this.game.isLand(n)&&!this.game.hasOwner(n)&&this.game.hasFallout(n)){return true}}}return false}findVictim(e){return e.find(n=>{if(this.isFFA()&&n.troops()>this.player.troops()*1.2){return false}const o=n.incomingAttacks().reduce((r,i)=>r+i.troops(),0);return o>n.troops()*.5})??null}findVeryWeakEnemy(e){const n=e.filter(o=>{const r=this.game.config().maxTroops(o);return o.troops()0?n[0]:null}async findNearestIslandEnemy(){if(this.game.config().isUnitDisabled(UNIT.TransportShip)){return null}if(this.player.unitCount(UNIT.TransportShip)>=this.game.config().boatMaxNumber()){return null}const e=Array.from(this.player.borderTiles()).some(s=>this.game.isShore(s));if(!e)return null;const n=this.game.players().filter(s=>{if(s.smallID()===this.player.smallID())return false;if(this.player.isFriendly(s))return false;return!this.isFFA()||s.troops(){const l=this.getPlayerCenter(s);if(!o||o.x==null||!l||l.x==null){return{player:s,distance:Infinity}}const c=this.game.ref(o.x,o.y);const u=this.game.ref(l.x,l.y);const d=this.game.manhattanDist(c,u);return{player:s,distance:d}}).sort((s,l)=>s.distance-l.distance);const i=Array.from(this.player.borderTiles()).filter(s=>this.game.isShore(s));const a=[];for(const s of r){await this.game.ensureBorderTiles(s.player);const l=closestTwoTiles(this.game,i,Array.from(s.player.borderTiles()).filter(u=>this.game.isShore(u)));if(l===null)continue;const c=await withTimeout(this.player.bestTransportShipSpawn(l.y),WORKER_TIMEOUT_MS,false);if(c!==false){a.push(s.player);if(a.length>=2)break}}if(a.length===0)return null;if(a.length>=2&&this.random.chance(3)){return a[1]}return a[0]}isFFA(){return this.game.config().gameConfig().gameMode===GameMode.FFA}getPlayerCenter(e){if(e.largestClusterBoundingBox){return boundingBoxCenter(e.largestClusterBoundingBox)}const n=e.borderTiles();if(n&&n.size>0){return calculateBoundingBoxCenter(this.game,n)}return e.nameLocation()}async attackRandomTarget(){if(!this.hasTriggerRatioTroops())return;const e=this.findIncomingAttackPlayer();if(e){if(await this.sendAttack(e,true))return}const n=this.getNeighborTraitorToAttack();if(n!==null){if(this.random.chance(3)){if(await this.sendAttack(n))return}}const o=this.player.nearby();for(const r of this.random.shuffleArray(o)){if(!r.isPlayer())continue;if(this.player.isFriendly(r))continue;if(r.type()===PlayerType.Nation||r.type()===PlayerType.Human){if(this.random.chance(2)){continue}}if(await this.sendAttack(r))return}}getNeighborTraitorToAttack(){if(this.game.config().disableAlliances())return null;const e=this.player.nearby().filter(n=>n.isPlayer()&&this.player.isFriendly(n)===false&&n.isTraitor());return e.length>0?this.random.randElement(e):null}async forceSendAttack(e){const n=discoverCtors(getEventBus());const o=this.player.troops()/2;const r=e.isPlayer()?e.id():null;if(n.attack&&emitIntent(n.attack,r,o)){state.stats.attacks++;setLastAction(tr("⚔️ Attack"),"combat")}}async sendAttack(e,n=false){if(!n&&!this.shouldAttack(e))return false;if(e.isPlayer()){if(this.player.sharesBorderWith(e)){return this.sendLandAttack(e)}else{return await this.sendBoatAttack(e)}}else{if(this.hasLandBorderWithTerraNullius()){return this.sendLandAttack(e)}else{return await this.sendBoatAttackToNearbyTerraNullius()}}}hasLandBorderWithTerraNullius(){for(const e of this.player.borderTiles()){for(const n of this.game.neighbors(e)){if(this.game.isLand(n)&&!this.game.hasOwner(n)){return true}}}return false}async sendBoatAttackToNearbyTerraNullius(){if(!(state.settings.features&&state.settings.features.boat))return false;if(this.game.config().isUnitDisabled(UNIT.TransportShip))return false;if(this.player.unitCount(UNIT.TransportShip)>=this.game.config().boatMaxNumber())return false;const e=[[0,-1],[0,1],[-1,0],[1,0]];const n=Array.from(this.player.borderTiles()).filter(r=>this.game.isShore(r));const o=[];e:for(let r=0;r=BOAT_PROBE_CANDIDATES)break e}}for(const r of o){const i=await withTimeout(this.player.bestTransportShipSpawn(r),WORKER_TIMEOUT_MS,false);if(i===false)continue;const a=state.settings.winFixes?Math.max(this.player.troops()*(state.settings.boatProbeFrac||.01),state.settings.boatProbeMinTroops||8e3):this.player.troops()/5;if(a<1)return false;const s=discoverCtors(getEventBus());if(s.boat&&emitIntent(s.boat,r,a)){state.stats.attacks++;setLastAction(tr("⛵ Boat (land grab)"),"naval")}return true}return false}shouldAttack(e){if(e.isPlayer()===false||e.type()!==PlayerType.Human||e.isTraitor()||this.player.type()===PlayerType.Bot||this.game.config().gameConfig().playerTeams===HumansVsNations){return true}const n=currentDifficulty();if(n===Difficulty.Easy&&this.random.nextInt(0,4)!==0){return false}if(n===Difficulty.Medium&&this.random.chance(4)){return false}return true}sendLandAttack(e){const n=this.game.config().maxTroops(this.player);const o=e.isPlayer()&&e.type()===PlayerType.Bot&&e.units().some(u=>AttackStructures.has(u.type()));const r=e.isPlayer()&&!o;const i=r?this.effectiveReserveRatio():this.expandRatio;const a=n*i;let s;if(e.isPlayer()&&e.type()===PlayerType.Bot&&this.player.type()!==PlayerType.Bot){s=this.calculateBotAttackTroops(e,this.player.troops()-a-this.botAttackTroopsSent)}else{s=this.player.troops()-a}if(s<1){return false}if(e.isPlayer()&&this.player.type()===PlayerType.Nation){if(this.emojiBehavior===void 0)throw new Error("not initialized");this.emojiBehavior.maybeSendAttackEmoji(e)}const l=discoverCtors(getEventBus());const c=e.isPlayer()?e.id():null;if(l.attack&&emitIntent(l.attack,c,s)){state.stats.attacks++;setLastAction(tr("⚔️ Attack"),"combat")}return true}async sendBoatAttack(e){if(!(state.settings.features&&state.settings.features.boat)){return false}if(this.game.config().isUnitDisabled(UNIT.TransportShip)){return false}await this.game.ensureBorderTiles(e);const n=closestTwoTiles(this.game,Array.from(this.player.borderTiles()).filter(a=>this.game.isShore(a)),Array.from(e.borderTiles()).filter(a=>this.game.isShore(a)));if(n===null){return false}const o=await withTimeout(this.player.bestTransportShipSpawn(n.y),WORKER_TIMEOUT_MS,false);if(o===false){return false}let r;if(state.settings.winFixes){r=Math.max(this.player.troops()*(state.settings.boatProbeFrac||.01),state.settings.boatProbeMinTroops||8e3)}else if(e.type()===PlayerType.Bot){r=this.calculateBotAttackTroops(e,this.player.troops()/5)}else{r=this.player.troops()/5}if(r<1){return false}if(e.isPlayer()&&this.player.type()===PlayerType.Nation){if(this.emojiBehavior===void 0)throw new Error("not initialized");this.emojiBehavior.maybeSendAttackEmoji(e)}const i=discoverCtors(getEventBus());if(i.boat&&emitIntent(i.boat,n.y,r)){state.stats.attacks++;setLastAction(tr("⛵ Boat attack"),"naval")}return true}calculateBotAttackTroops(e,n){const o=currentDifficulty();if(o===Difficulty.Easy){this.botAttackTroopsSent+=n;return n}let r=e.troops()*4;if(r>n){if(n5e3){state._donDiagAt=m;const p=this.game.config();const h=this.game.players().filter(y=>this.player.isOnSameTeam(y)&&y.smallID()!==this.player.smallID()&&y.isAlive());console.log("[Donate] diag: "+JSON.stringify({featureOn:!!state.settings.features.donate,winFixes:!!state.settings.winFixes,replicatedDifficulty:String(currentDifficulty()),gameType:String(p.gameConfig().gameType),gameMode:String(p.gameConfig().gameMode),donateAllowedByLobby:p.donateTroops(),aliveTeammates:h.length,myTroops:Math.round(this.player.troops())}))}}catch(m){}if(!state.settings.features.donate)return false;if(this.game.config().gameConfig().gameMode!==GameMode.Team){return false}if(this.game.config().donateTroops()===false){return false}if(this.game.getWinner()){console.log("[Donate] skip: game already has a winner");return false}const e=currentDifficulty();if(!state.settings.winFixes){switch(e){case Difficulty.Easy:return false;case Difficulty.Medium:if(!this.random.chance(4)){return false}break;case Difficulty.Hard:if(!this.random.chance(2)){return false}break;case Difficulty.Impossible:break;default:throw new Error("unreachable difficulty: "+e)}}const n=this.game.players().filter(m=>this.player.isOnSameTeam(m)).filter(m=>state.settings.winFixes?true:m.incomingAttacks().length>0||m.outgoingAttacks().length>0);if(n.length===0){console.log("[Donate] skip: no same-team players found");return false}const o=n.map(m=>{const p=this.game.config().maxTroops(m);const h=m.troops()/Math.max(p,1);return{teammate:m,troopPercentage:h}}).sort((m,p)=>m.troopPercentage-p.troopPercentage);const r=state.settings.winFixes?state.settings.donateNeedThreshold??.8:1;const i=o.filter(m=>m.teammate.isAlive()&&m.teammate.smallID()!==this.player.smallID()&&m.troopPercentage{let p=false;try{p=m.teammate.incomingAttacks().length>0||m.teammate.outgoingAttacks().length>0}catch(h){}return{entry:m,frontline:p}});i.sort((m,p)=>{if(m.frontline!==p.frontline)return m.frontline?-1:1;return m.entry.troopPercentage-p.entry.troopPercentage});let a=null;if(i.length>0){a=i[0].entry.teammate;console.log("[Donate] picked ally at "+Math.round(i[0].entry.troopPercentage*100)+"% "+(i[0].frontline?"(FRONTLINE — in combat)":"(rear)"))}if(a===null){console.log("[Donate] skip: no teammate below the need threshold (all allies healthy)");return false}const s=this.game.config().maxTroops(this.player);const l=state.settings.winFixes?Math.max(this.reserveRatio,state.settings.donateKeepFrac||.45):this.reserveRatio;const c=s*l;const u=this.player.troops()-c;const d=state.settings.winFixes?s*(state.settings.donateMinExcessFrac||.05):1;if(ur.euclideanDistSquared(t,i)<=o}else{return(r,i)=>{const a=r.x(t)-.5-r.x(i);const s=r.y(t)-.5-r.y(i);return a*a+s*s<=o}}}class NukeCubicBezierCurve{constructor(e,n,o,r){this.p0=e;this.p1=n;this.p2=o;this.p3=r}getPointAt(e){const n=1-e;const o=n*n;const r=o*n;const i=e*e;const a=i*e;const s=r*this.p0.x+3*o*e*this.p1.x+3*n*i*this.p2.x+a*this.p3.x;const l=r*this.p0.y+3*o*e*this.p1.y+3*n*i*this.p2.y+a*this.p3.y;return{x:s,y:l}}}class NukeDistanceBasedBezierCurve extends NukeCubicBezierCurve{constructor(e,n,o,r,i){super(e,n,o,r);this.totalDistance=0;this.cachedPoints=[];this.currentIndex=0;this.computeAllPoints(i,.002)}getAllPoints(){return this.cachedPoints}increment(e){this.totalDistance+=e;while(this.currentIndex=this.cachedPoints.length-1){return null}return this.cachedPoints[this.currentIndex]}getCurrentIndex(){return this.currentIndex}computeAllPoints(e,n){this.cachedPoints=[];this.totalDistance=0;this.currentIndex=0;let o=0;let r=this.getPointAt(o);this.cachedPoints.push(r);let i=0;while(o<1){o=Math.min(o+n,1);const s=this.getPointAt(o);const l=s.x-r.x;const c=s.y-r.y;const u=Math.sqrt(l*l+c*c);i+=u;if(i>=e){this.cachedPoints.push(s);i=0}r=s}const a=this.getPointAt(1);if(this.cachedPoints.length===0||a.x!==this.cachedPoints[this.cachedPoints.length-1].x||a.y!==this.cachedPoints[this.cachedPoints.length-1].y){this.cachedPoints.push(a)}}getDistanceUpToIndex(e){let n=0;for(let o=1;o<=e;o++){const r=this.cachedPoints[o-1];const i=this.cachedPoints[o];const a=i.x-r.x;const s=i.y-r.y;n+=Math.sqrt(a*a+s*s)}return n}}const PARABOLA_MIN_HEIGHT=50;class NukeParabolaUniversalPathFinder{constructor(e,n){this.gameMap=e;this.options=n;this.curve=null;this.lastTo=null}createCurve(e,n){const o=this.options?.increment??3;const r=this.options?.distanceBasedHeight??true;const i=this.options?.directionUp??true;const a={x:this.gameMap.x(e),y:this.gameMap.y(e)};const s={x:this.gameMap.x(n),y:this.gameMap.y(n)};const l=s.x-a.x;const c=s.y-a.y;const u=Math.sqrt(l*l+c*c);const d=r?Math.max(u/3,PARABOLA_MIN_HEIGHT):0;const f=i?-1:1;const m=this.gameMap.height();const p={x:a.x+l/4,y:within(a.y+c/4+f*d,0,m-1)};const h={x:a.x+l*3/4,y:within(a.y+c*3/4+f*d,0,m-1)};return new NukeDistanceBasedBezierCurve(a,p,h,s,o)}findPath(e,n){if(Array.isArray(e)){throw new Error("ParabolaUniversalPathFinder does not support multiple start points")}const o=this.createCurve(e,n);return o.getAllPoints().map(r=>this.gameMap.ref(Math.floor(r.x),Math.floor(r.y)))}next(e,n,o){if(this.lastTo!==n){this.curve=this.createCurve(e,n);this.lastTo=n}const r=this.curve.increment(o??1);if(!r){return{status:"Complete",node:n}}const i=this.gameMap.ref(Math.floor(r.x),Math.floor(r.y));return{status:"Next",node:i}}invalidate(){this.curve=null;this.lastTo=null}currentIndex(){return this.curve?.getCurrentIndex()??0}}const UniversalPathFinding={Parabola(t,e){return new NukeParabolaUniversalPathFinder(t,e)}};class NukeBehavior{constructor(e,n,o,r,i){this.random=e;this.game=n;this.player=o;this.attackBehavior=r;this.emojiBehavior=i;this.recentlySentNukes=[];this.atomBombsLaunched=0;this.atomBombPerceivedCost=null;this.hydrogenBombPerceivedCost=null;this.hydrogenBombsLaunched=0;this.isHydroNation=this.random.chance(3);this._costCacheTick=-1;this._costCache=new Map}async maybeSendNuke(){if(state.settings.winFixes&&state.nukeReserveGold){let g=0n;try{g=BigInt(state.nukeReserveGold||0)}catch(b){g=0n}if(g>=15000000n)return}const e=this.player.units(UNIT.MissileSilo);const n=this.game.config();if(e.length===0||n.isUnitDisabled(UNIT.MissileSilo)||n.isUnitDisabled(UNIT.AtomBomb)&&n.isUnitDisabled(UNIT.HydrogenBomb)){return}const o=this.findBestNukeTarget();if(o===null){return}if(o.type()===PlayerType.Bot||this.player.isOnSameTeam(o)||this.attackBehavior.shouldAttack(o)===false){return}const r=await this.getPerceivedNukeCost(UNIT.HydrogenBomb);const i=await this.getPerceivedNukeCost(UNIT.AtomBomb);let a;if(!this.game.config().isUnitDisabled(UNIT.HydrogenBomb)&&this.player.gold()>=r){a=UNIT.HydrogenBomb}else if(!this.game.config().isUnitDisabled(UNIT.AtomBomb)&&(!this.isHydroNation||this.isUnderHeavyAttack())&&this.player.gold()>=i){a=UNIT.AtomBomb}else{return}const s=this.game.config().nukeMagnitudes(a).outer;const l=o.units(...NUKE_STRUCTURES_TYPES);const c=l.map(g=>g.tile());const u=currentDifficulty();const d=u===Difficulty.Impossible?30:10;await withTimeout(this.game.ensureBorderTiles(o),WORKER_TIMEOUT_MS,null);const f=randTerritoryTileArray(this.random,this.game,o,d);const m=f.concat(c);let p=null;let h=-1;this.removeOldNukeEvents();const y=[];e:for(const g of new Set(m)){if(g===null)continue;const b=boundingBoxTiles(this.game,g,s).concat(boundingBoxTiles(this.game,g,Math.floor(s/2)));for(const A of b){if(!this.isValidNukeTile(A,o)){continue e}}const w=this.nukeSpawn(a,g);if(w===false)continue;if(this.game.config().gameConfig().gameMode===GameMode.Team&&u!==Difficulty.Easy&&this.isTeammateAlreadyNukingThisSpot(g,a)){continue}if((u===Difficulty.Hard||u===Difficulty.Impossible)&&this.isTrajectoryInterceptableBySam(w,g)){continue}const T=this.nukeTileScore(g,e,l,a);y.push({tile:g,value:T})}y.sort((g,b)=>b.value-g.value);for(const g of y){if(!(g.value>h))break;const b=await this.probeCanBuildNuke(g.tile,a);if(!b)continue;p=g.tile;h=g.value;break}if(p!==null&&(h>0||u!==Difficulty.Impossible)){await this.sendNuke(p,a,o)}else if(u===Difficulty.Impossible){await this.maybeDestroyEnemySam(o)}}findBestNukeTarget(){const e=currentDifficulty();if((e===Difficulty.Hard||e===Difficulty.Impossible)&&this.game.players().length===2){const l=this.game.players().find(c=>c.smallID()!==this.player.smallID());if(l){return l}}const n=this.attackBehavior.findIncomingAttackPlayer();if(n){return n}if(e===Difficulty.Impossible&&this.isRichestNation()&&this.random.chance(2)){const l=this.findHighDensityTarget();if(l!==null){return l}}const o=currentDifficulty();const r=this.game.config().gameConfig().gameMode;if(o===Difficulty.Impossible&&r===GameMode.FFA){const l=this.game.numLandTiles()-this.game.numTilesWithFallout();if(l>0){const c=this.game.players().slice().sort((d,f)=>f.numTilesOwned()-d.numTilesOwned());const u=c[0];if(u&&u.smallID()!==this.player.smallID()&&!this.player.isFriendly(u)){const d=u.numTilesOwned()/l;if(d>.5){return u}}}}for(const l of this.player.allies()){if(l.targets().length===0)continue;if(this.player.relation(l)=u*2)continue;return c}const a=this.findFFACrownTarget();if(a){return a}const s=this.findStrongestTeamTarget();if(s){return s}return null}isRichestNation(){const e=this.player.gold();for(const n of this.game.players()){if(n.smallID()===this.player.smallID())continue;if(n.type()!==PlayerType.Nation)continue;if(n.gold()>e)return false}return true}findHighDensityTarget(){let e=null;let n=HIGH_DENSITY_NUKE_THRESHOLD;for(const o of this.game.players()){if(o.smallID()===this.player.smallID())continue;if(o.type()===PlayerType.Bot)continue;if(this.player.isFriendly(o))continue;const r=o.numTilesOwned();if(r===0)continue;const i=o.units(...NUKE_STRUCTURES_TYPES);let a=0;for(const l of i)a+=l.level();if(an){n=s;e=o}}return e}findFFACrownTarget(){const e=currentDifficulty();const n=this.game.config().gameConfig().gameMode;if(n!==GameMode.FFA){return null}if(this.game.players().length<=1){return null}const o=this.game.players().slice().sort((c,u)=>u.numTilesOwned()-c.numTilesOwned());const r=o[0];if(e===Difficulty.Impossible&&r.smallID()===this.player.smallID()&&o.length>=2){const c=o[1];if(!this.player.isFriendly(c)){return c}}if(r.smallID()===this.player.smallID()||this.player.isFriendly(r)){return null}const i=this.game.numLandTiles()-this.game.numTilesWithFallout();if(i<=0){return null}const a=r.numTilesOwned()/i;const s=this.player.numTilesOwned()/i;let l;switch(e){case Difficulty.Easy:l=.4;break;case Difficulty.Medium:l=.3;break;case Difficulty.Hard:l=.2;break;case Difficulty.Impossible:l=.1;break;default:l=.1}if(a-s>l){return r}return null}findStrongestTeamTarget(){if(this.game.config().gameConfig().gameMode!==GameMode.Team){return null}if(this.game.players().length<=1){return null}const e=new Map;const n=new Map;for(const s of this.game.players()){const l=s.team();if(l===null)continue;e.set(l,(e.get(l)??0)+s.numTilesOwned());let c=n.get(l);if(!c){c=[];n.set(l,c)}c.push(s)}const o=Array.from(e.entries()).sort((s,l)=>l[1]-s[1]);if(o.length===0){return null}let r=o[0][0];if(r===this.player.team()){if(o.length>1){r=o[1][0]}else{return null}}const i=n.get(r);const a=i.filter(s=>!this.player.isFriendly(s));if(a.length===0){return null}if(this.random.chance(2)){return a.reduce((s,l)=>this.game.config().maxTroops(s)>this.game.config().maxTroops(l)?s:l)}else{return this.random.randElement(a)}}async getPerceivedNukeCost(e){if(this.game.players().length===2){return await this.cost(e)}if(this.game.config().isUnitDisabled(UNIT.MIRV)){return await this.cost(e)}if(this.game.config().gameConfig().gameMode===GameMode.Team&&this.player.gold()>await this.cost(UNIT.HydrogenBomb)){return await this.cost(e)}if(this.player.gold()>await this.cost(UNIT.MIRV)+await this.cost(UNIT.HydrogenBomb)){return await this.cost(e)}const n=currentDifficulty();if((n===Difficulty.Hard||n===Difficulty.Impossible)&&this.isUnderHeavyAttack()){return await this.cost(e)}if(e===UNIT.AtomBomb){if(this.atomBombPerceivedCost===null){this.atomBombPerceivedCost=await this.cost(UNIT.AtomBomb)}return this.atomBombPerceivedCost}else{if(this.hydrogenBombPerceivedCost===null){this.hydrogenBombPerceivedCost=await this.cost(UNIT.HydrogenBomb)}return this.hydrogenBombPerceivedCost}}isUnderHeavyAttack(){const e=this.player.incomingAttacks();let n=0;for(const r of e){n+=r.troops()}const o=this.player.troops();return n>=o}removeOldNukeEvents(){const e=600;const n=this.game.ticks();while(this.recentlySentNukes.length>0&&this.recentlySentNukes[0][0]+es){if(this.game.euclideanDistSquared(d,n)a(this.game,y.tile())).map(y=>{const g=y.level();switch(y.type()){case UNIT.City:return 25e3*g;case UNIT.DefensePost:return 5e3*g;case UNIT.MissileSilo:return 5e4*g;case UNIT.Port:return 15e3*g;case UNIT.Factory:return 15e3*g;default:return 0}}).reduce((y,g)=>y+g,0);const l=currentDifficulty();if(l===Difficulty.Medium){const y=euclDistFN(e,50,false);const g=o.some(b=>b.type()===UNIT.SAMLauncher&&y(this.game,b.tile()));if(g)return-1}if(l===Difficulty.Impossible&&r===UNIT.HydrogenBomb){const y=this.game.config().nukeMagnitudes(UNIT.HydrogenBomb);const g=this.game.nearbyUnits(e,y.outer,UNIT.SAMLauncher);for(const b of g){const w=b.unit.level();if(w>=5)continue;const T=this.game.config().samRange(w);const A=Math.sqrt(this.game.euclideanDistSquared(e,b.unit.tile()));if(A>T){s+=1e5*w}}}const c=n.map(y=>y.tile());const u=closestTwoTiles(this.game,c,[e]);if(u===null)throw new Error("Missing result");const d=u.x;const f=this.game.euclideanDistSquared(e,d);const m=Math.sqrt(f);const p=m*30;const h=s;s=Math.max(h*.2,s-p);s-=this.recentlySentNukes.filter(([y,g,b])=>{const w=this.game.config().nukeMagnitudes(b).inner;const T=this.game.euclideanDistSquared(e,g);return T<=w*w}).map(y=>1e6).reduce((y,g)=>y+g,0);return s}async sendNuke(e,n,o,r=0){const i=this.game.ticks();let a;try{a=await withTimeout(this.player.buildables(e,[n]),WORKER_TIMEOUT_MS,null)}catch(c){return}const s=Array.isArray(a)?a.find(c=>c.type===n):null;if(s===null||s===void 0)return;if(!(s.canBuild!==false&&this.player.gold()>=s.cost))return;const l=getBuildMenu();if(!l||typeof l.sendBuildOrUpgrade!=="function")return;l.sendBuildOrUpgrade(s,e);this.recentlySentNukes.push([i,e,n]);if(n===UNIT.AtomBomb){this.atomBombsLaunched++;if(this.atomBombPerceivedCost===null){this.atomBombPerceivedCost=await this.cost(UNIT.AtomBomb)}this.atomBombPerceivedCost=this.atomBombPerceivedCost*150n/100n}else if(n===UNIT.HydrogenBomb){this.hydrogenBombsLaunched++;if(this.hydrogenBombPerceivedCost===null){this.hydrogenBombPerceivedCost=await this.cost(UNIT.HydrogenBomb)}this.hydrogenBombPerceivedCost=this.hydrogenBombPerceivedCost*125n/100n}state.stats.nukes++;setLastAction(tr("☢️ Launch")+" "+n,"nuke");this.emojiBehavior.maybeSendEmoji(o,EMOJI_NUKE)}async maybeDestroyEnemySam(e){if(this.game.config().isUnitDisabled(UNIT.AtomBomb)){return}const n=this.player.units(UNIT.AtomBomb);if(n.length>0){return}const o=await this.cost(UNIT.AtomBomb);const r=e.units(UNIT.SAMLauncher);if(r.length===0){return}const i=this.player.units(UNIT.MissileSilo).filter(c=>!c.isUnderConstruction());if(i.length===0){return}const a=r.slice().sort((c,u)=>c.level()-u.level());let s=false;let l=null;for(const c of a){const u=c.tile();const d=this.findEnemySamsCoveringTile(u);const f=new Set(d.map(N=>N.id()));const m=d.reduce((N,E)=>N+E.level(),0);const p=m+1;const h=this.game.config().defaultNukeSpeed();const y=[];for(const N of i){const E=N.level()-N.missileTimerQueue().length;if(E<=0){continue}const R=this.isTrajectoryInterceptableBySam(N.tile(),u,f);const L=UniversalPathFinding.Parabola(this.game,{increment:h,distanceBasedHeight:true,directionUp:true});const z=L.findPath(N.tile(),u)??[];if(z.length===0)continue;y.push({silo:N,slots:E,flightTicks:z.length,interceptable:R})}y.sort((N,E)=>this.game.manhattanDist(N.silo.tile(),u)-this.game.manhattanDist(E.silo.tile(),u));const g=[];for(const N of y){for(let E=0;EN.flightTicks-E.flightTicks);let $=0;let k=0;for(let N=0;Nk){k=E-N;$=N}}if(kN.index-E.index);const Y=U.slice(0,A);const G=new Set(Y.map(N=>N.index));const ee=Y[Y.length-1].index;const H=ee+1;const te=Math.min(...Y.map(N=>N.flightTicks));const V=Math.max(1,Math.floor(w/A));let q=0;const x=[];for(let N=0;N=MAX_NATION_SILO_UPGRADE_LEVEL)continue;let c=0;for(const u of i){const d=this.game.config().samRange(u.level());const f=this.game.euclideanDistSquared(l.tile(),u.tile());if(f<=d*d){c+=u.level()}}if(c>s){s=c;a=l}}if(a!==null){let l;try{l=await withTimeout(this.player.buildables(a.tile(),[UNIT.MissileSilo]),WORKER_TIMEOUT_MS,null)}catch(u){return}const c=Array.isArray(l)?l.find(u=>u.type===UNIT.MissileSilo):null;if(c===null||c===void 0)return;if(c.canUpgrade!==false){const u=getBuildMenu();if(!u||typeof u.sendBuildOrUpgrade!=="function"){return}u.sendBuildOrUpgrade(c);state.stats.builds++;setLastAction(tr("☢️ Upgrade silo"),"build")}}}async cost(e){const n=this.game.ticks();if(this._costCacheTick!==n){this._costCacheTick=n;this._costCache=new Map}if(this._costCache.has(e))return this._costCache.get(e);const o=this.player.borderTiles();const r=o&&o.size>0?o.values().next().value:null;if(r===null||r===void 0)return 0n;let i;try{i=await withTimeout(this.player.buildables(r,[e]),WORKER_TIMEOUT_MS,null)}catch(l){return 0n}const a=Array.isArray(i)?i.find(l=>l.type===e):null;const s=a&&a.cost!==void 0&&a.cost!==null?a.cost:0n;this._costCache.set(e,s);return s}nukeSpawn(e,n){const o=this.player.units(UNIT.MissileSilo);let r=false;let i=Infinity;for(const a of o){if(a.isActive&&a.isActive()===false)continue;if(a.isUnderConstruction())continue;if(typeof a.ticksLeftInCooldown==="function"&&a.ticksLeftInCooldown()>0){continue}const s=this.game.manhattanDist(a.tile(),n);if(si.type===n):null;return r!==null&&r!==void 0&&r.canBuild!==false}}"use strict";class NationBot{constructor(e,n){this.mg=e;this.player=e.myPlayer();this.active=true;this.behaviorsInitialized=false;this.spawnExecAdded=false;this.status="";this.lastSeenTick=-1;this.embargoMalusApplied=new Set;this.random=new PseudoRandom(simpleHash(String(n&&n.playerId))+simpleHash(String(n&&n.gameId)));this.triggerRatio=this.random.nextInt(50,60)/100;this.reserveRatio=this.random.nextInt(30,40)/100;this.expandRatio=this.random.nextInt(10,20)/100;this.attackRate=this.getAttackRate();this.attackTick=this.random.nextInt(0,this.attackRate)}getAttackRate(){switch(currentDifficulty()){case Difficulty.Easy:return this.random.nextInt(65,100);case Difficulty.Medium:return this.random.nextInt(55,70);case Difficulty.Hard:return this.random.nextInt(45,60);case Difficulty.Impossible:return this.random.nextInt(30,50);default:return this.random.nextInt(30,50)}}feat(e){return!!(state.settings.features&&state.settings.features[e])}async tick(e){this.player=this.mg.myPlayer();const n=this.player;if(this.behaviorsInitialized&&n!==null&&n.isAlive()&&this.feat("warship")&¤tDifficulty()!==Difficulty.Easy&&n.unitsConstructed(UNIT.Port)&&!this.mg.config().isUnitDisabled(UNIT.Warship)){try{this.warshipBehavior.trackShipsAndRetaliate();if(state.settings.winFixes){this.warshipBehavior.dodgeNukes();this.warshipBehavior.smartWarshipPatrol()}}catch(u){console.error("[AutoBot] trackShips error:",u)}}if(n===null)return;if(this.mg.inSpawnPhase()){if(n.hasSpawned()){this.status=tr("Spawn phase…");return}if(this.spawnExecAdded){return}if(this.feat("spawn")){this.doSpawn()}else{this.status=tr("Spawn phase…")}return}if(!n.isAlive()){this.active=false;this.status=tr("💀 Eliminated");return}if(!this.behaviorsInitialized){this.initializeBehaviors();if(this.feat("expand")){await this.attackBehavior.forceSendAttack(this.mg.terraNullius())}this.lastSeenTick=e;return}const o=this.lastSeenTick<0?e:this.lastSeenTick+1;this.lastSeenTick=e;let r=this.attackRate;if(state.settings.winFixes){const u=state.settings.combatCadenceScale??1;if(u>0&&u!==1){r=Math.max(1,Math.round(this.attackRate*u))}}const i=this.attackTick%r;const a=(i+Math.floor(r/3))%r;const s=(i+Math.floor(r*2/3))%r;let l=false;let c=false;for(let u=o;u<=e;u++){const d=(u%r+r)%r;if(d===i)l=true;else if(d===a||d===s)c=true}if(!l){if(c&&this.feat("build")){await this.structureBehavior.handleStructures()}return}this.status=tr("Thinking…");try{this.emojiBehavior.maybeSendCasualEmoji();this.updateRelationsFromEmbargos();if(this.feat("alliance")){this.allianceBehavior.handleAllianceRequests();this.allianceBehavior.handleAllianceExtensionRequests()}if(this.feat("nuke")){await this.mirvBehavior.considerMIRV()}if(this.feat("build")){await this.structureBehavior.handleStructures()}if(this.feat("warship")){await this.warshipBehavior.maybeSpawnWarship()}if(this.feat("alliance")){this.handleEmbargoesToHostileNations()}if(this.feat("expand")||this.feat("boat")){await this.attackBehavior.maybeAttack()}if(this.feat("warship")){await this.warshipBehavior.counterWarshipInfestation()}if(this.feat("nuke")){await this.nukeBehavior.maybeSendNuke()}}catch(u){console.error("[AutoBot] decision chain error:",u)}}initializeBehaviors(){const e=this.random;const n=this.mg;const o=this.player;this.emojiBehavior=new EmojiBehavior(e,n,o);this.mirvBehavior=new MirvBehavior(e,n,o,this.emojiBehavior);this.allianceBehavior=new AllianceBehavior(e,n,o,this.emojiBehavior);this.warshipBehavior=new WarshipBehavior(e,n,o,this.emojiBehavior);this.attackBehavior=new AttackBehavior(e,n,o,this.triggerRatio,this.reserveRatio,this.expandRatio,this.allianceBehavior,this.emojiBehavior);this.nukeBehavior=new NukeBehavior(e,n,o,this.attackBehavior,this.emojiBehavior);this.structureBehavior=new StructureBehavior(e,n,o);this.behaviorsInitialized=true}doSpawn(){const e=this.pickSpawnCenter();if(e==null){this.status=tr("Spawn phase…");return}const n=discoverCtors(getEventBus());if(n.spawn){emitIntent(n.spawn,e);this.spawnExecAdded=true;state.stats.spawns++;setLastAction(tr("🏁 Spawned"),"spawn")}}pickSpawnCenter(){const e=this.mg;const n=e.width();const o=e.height();let r=30;try{const l=e.config().minDistanceBetweenPlayers();if(Number.isFinite(l))r=l}catch(l){}let i=null;try{const l=this.player.team();if(l!==null&&typeof e.__src?.teamSpawnArea==="function"){i=e.__src.teamSpawnArea(l)??null}}catch(l){}const a=e.players().filter(l=>l.smallID()!==this.player.smallID()&&l.hasSpawned());const s=l=>{const c=e.x(l);const u=e.y(l);for(const d of a){const f=d.nameLocation();if(!f)continue;if(e.manhattanDist(e.ref(f.x,f.y),l)r.id()!==e.id());const o=-20;n.forEach(r=>{if(r.hasEmbargoAgainst(e)&&!this.embargoMalusApplied.has(r.smallID())){e.updateRelation(r,o);this.embargoMalusApplied.add(r.smallID())}else if(!r.hasEmbargoAgainst(e)&&this.embargoMalusApplied.has(r.smallID())){e.updateRelation(r,-o);this.embargoMalusApplied.delete(r.smallID())}})}handleEmbargoesToHostileNations(){const e=this.player;if(e===null)return;const n=this.mg.players().filter(a=>a.id()!==e.id());const o=currentDifficulty();const r=o===Difficulty.Hard||o===Difficulty.Impossible;const i=this.mg.config().gameConfig().gameMode===GameMode.Team;n.forEach(a=>{if(i&&r&&a.type()!==PlayerType.Bot&&!e.isOnSameTeam(a)){if(!e.hasEmbargoAgainst(a))this.addEmbargo(a,false);return}if(e.relation(a)<=Relation.Hostile&&!e.hasEmbargoAgainst(a)&&!e.isOnSameTeam(a)){this.addEmbargo(a,false)}else if(e.relation(a)>=Relation.Neutral&&e.hasEmbargoAgainst(a)&&o!==Difficulty.Hard&&o!==Difficulty.Impossible){this.stopEmbargo(a)}else if(e.relation(a)>=Relation.Friendly&&e.hasEmbargoAgainst(a)&&o!==Difficulty.Impossible){this.stopEmbargo(a)}})}addEmbargo(e,n){const o=discoverCtors(getEventBus());if(o.embargo)emitIntent(o.embargo,e.__src??e,"start")}stopEmbargo(e){const n=discoverCtors(getEventBus());if(n.embargo)emitIntent(n.embargo,e.__src??e,"stop")}}"use strict";let _api=null;let _apiSrc=null;let _bot=null;let _botSeedKey=null;function ensureGameApi(t){if(_api&&_apiSrc===t)return _api;_api=createGameApi(t);_apiSrc=t;_bot=null;_botSeedKey=null;return _api}function resolveGameId(t){try{if(typeof t.gameID==="function")return String(t.gameID())}catch(e){}try{const e=t.config().gameConfig();if(e&&e.gameID!=null)return String(e.gameID)}catch(e){}return"openfront"}async function botTick(){if(!state.settings.enabled||state.tickInFlight)return;const t=getGame();if(!t){setStatus(tr("Waiting to enter game…"));return}if(isPublicLobby(t)){setStatus(tr("⛔ Blocked in a public lobby"));refreshGateBanner();return}const e=t.myPlayer?.();if(!e||!e.isPlayer?.()){setStatus(tr("Waiting for player…"));return}state.tickInFlight=true;state.tickStartedAt=performance.now();try{updateSpeedFactor(t);const n=ensureGameApi(t);const o=e.id?.();if((!_bot||_botSeedKey!==o)&&typeof NationBot==="function"){_bot=new NationBot(n.game,{gameId:resolveGameId(t),playerId:String(o)});_botSeedKey=o}await n.beginTick(e);const r=n.game.myPlayer();updateLive(n.game,r);if(_bot){await _bot.tick(n.game.ticks());setStatus(_bot.status||tr("Thinking…"))}else{setStatus(tr("Loading bot…"))}}catch(n){console.error("[AutoBot] tick error:",n);setStatus(tr("Error (see console)"))}finally{state.tickInFlight=false;renderStatus()}}function updateLive(t,e){try{const n=toNum(e.troops?.());const o=safeMaxTroops(t,e);state.live={troops:n,gold:toNum(e.gold?.()),tiles:toNum(e.numTilesOwned?.()),fill:o>0?n/o:0}}catch(n){}}function desiredTickInterval(){const t=state.settings.tickMs||200;return clamp(t/Math.max(.25,state.speed.factor||1),100,t)}function startEngine(){if(state.tickTimer!==null)return;state.tickIntervalMs=desiredTickInterval();state.tickTimer=window.setInterval(botTick,state.tickIntervalMs);botTick()}function stopEngine(){if(state.tickTimer!==null){window.clearInterval(state.tickTimer);state.tickTimer=null}state.tickInFlight=false}function retuneEngine(){if(state.tickTimer===null)return;const t=desiredTickInterval();if(Math.abs(t-(state.tickIntervalMs||0))>60){window.clearInterval(state.tickTimer);state.tickIntervalMs=t;state.tickTimer=window.setInterval(botTick,t)}}function setEnabled(t){state.settings.enabled=Boolean(t);saveSettings();if(state.settings.enabled){setStatus(tr("Starting…"));startEngine()}else{stopEngine();setStatus(tr("Disabled"))}renderHeader()}function setStatus(t){state.status=t}function setLastAction(t,e){const n=Date.now();state.lastAction=t+" · T+"+new Date(n).toLocaleTimeString();pushLog(t,e||"combat",n)}function pushLog(t,e,n){state.log.unshift({t:n||Date.now(),cat:e,text:String(t)});if(state.log.length>LOG_CAP)state.log.length=LOG_CAP;if(state.activeTab==="log")renderLog()}"use strict";function ensureStyles(){if(document.getElementById(STYLE_ID))return;const t=document.createElement("style");t.id=STYLE_ID;t.textContent=` #${PANEL_ID} { --ab-accent: #34d399; --ab-accent-2: #22d3ee; --ab-border: rgba(148, 163, 184, 0.34); --ab-surface: rgba(255, 255, 255, 0.05); --ab-surface-2: rgba(255, 255, 255, 0.09); --ab-muted: #94a3b8; position: fixed; z-index: 2147483646; width: 244px; bottom: 16px; right: 16px; background: rgba(12, 18, 20, 0.95); border: 1px solid var(--ab-border); border-radius: 8px; box-shadow: 0 10px 26px rgba(0, 0, 0, 0.4), 0 0 16px rgba(148, 163, 184, 0.1); color: #e2e8f0; font: 700 11px/1.45 "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; user-select: none; overflow: hidden; } #${PANEL_ID} .ab-head { display: flex; align-items: center; gap: 8px; padding: 6px 8px; cursor: grab; background: transparent; border-bottom: 1px solid rgba(148, 163, 184, 0.18); } #${PANEL_ID} .ab-head:active { cursor: grabbing; } #${PANEL_ID} .ab-title { font-weight: 900; font-size: 10px; letter-spacing: 0.4px; text-transform: uppercase; color: rgba(226, 232, 240, 0.85); flex: 1; } #${PANEL_ID} .ab-head-btns { display: flex; align-items: center; gap: 1px; } #${PANEL_ID} .ab-mini, #${PANEL_ID} .ab-close { background: transparent; border: none; color: var(--ab-muted); cursor: pointer; font-size: 13px; line-height: 1; width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center; transition: .14s; } #${PANEL_ID} .ab-mini:hover { color: #fff; background: var(--ab-surface-2); } #${PANEL_ID} .ab-close:hover { color: #fca5a5; background: rgba(248,113,113,0.15); } #${PANEL_ID} .ab-switch { position: relative; width: 36px; height: 20px; border-radius: 999px; background: rgba(120,130,130,.3); cursor: pointer; transition: .18s; flex: none; border: 1px solid rgba(255,255,255,.08); } #${PANEL_ID} .ab-switch.on { background: linear-gradient(135deg, var(--ab-accent), #10b981); border-color: rgba(187,247,208,.5); } #${PANEL_ID} .ab-switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; transition: transform .18s; box-shadow: 0 1px 3px rgba(0,0,0,.4); } #${PANEL_ID} .ab-switch.on::after { transform: translateX(16px); } #${PANEL_ID} .ab-body { padding: 9px 10px 10px; } #${PANEL_ID}.ab-collapsed .ab-body { display: none; } #${PANEL_ID}.ab-collapsed .ab-tabs { display: none; } #${PANEL_ID} .ab-gate { display: flex; align-items: center; justify-content: center; gap: 7px; font-weight: 700; font-size: 11px; padding: 7px 10px; border-radius: 9px; margin-bottom: 11px; border: 1px solid transparent; } #${PANEL_ID} .ab-gate::before { content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; box-shadow: 0 0 8px currentColor; } #${PANEL_ID} .ab-gate.ok { background: rgba(34,197,94,0.14); color: #86efac; border-color: rgba(134,239,172,.28); } #${PANEL_ID} .ab-gate.bad { background: rgba(239,68,68,0.16); color: #fca5a5; border-color: rgba(248,113,113,.3); } #${PANEL_ID} .ab-gate.wait { background: rgba(148,163,184,0.12); color: #cbd5e1; border-color: rgba(148,163,184,.2); } #${PANEL_ID} .ab-row { display: flex; align-items: center; justify-content: space-between; padding: 5px 0; } #${PANEL_ID} .ab-feat { display: flex; align-items: center; gap: 9px; cursor: pointer; padding: 8px 9px; border-radius: 9px; transition: background 0.12s; } #${PANEL_ID} .ab-feat:hover { background: var(--ab-surface); } #${PANEL_ID} .ab-adv-toggle { cursor: pointer; color: #cdd8d8; font-size: 11px; font-weight: 600; padding: 7px 9px; border-radius: 7px; transition: .12s; letter-spacing: .2px; background: var(--ab-surface); margin-top: 4px; } #${PANEL_ID} .ab-adv-toggle:hover { background: var(--ab-surface-2); color: #fff; } #${PANEL_ID} .ab-feats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; max-height: 0; overflow: hidden; transition: max-height 0.24s ease; } #${PANEL_ID} .ab-feats-grid.open { max-height: 200px; margin: 4px 0 2px; } #${PANEL_ID} .ab-feat-mini { position: relative; flex-direction: column; align-items: center; justify-content: center; gap: 3px; padding: 7px 2px; border-radius: 8px; background: var(--ab-surface); border: 1px solid transparent; opacity: 0.5; transition: .12s; } #${PANEL_ID} .ab-feat-mini:hover { background: var(--ab-surface-2); opacity: 0.85; } #${PANEL_ID} .ab-feat-mini.on { opacity: 1; border-color: rgba(52,211,153,.45); background: rgba(52,211,153,.14); } #${PANEL_ID} .ab-feat-mini .ab-feat-emoji { font-size: 16px; line-height: 1; transition: filter .14s; } #${PANEL_ID} .ab-feat-mini.on .ab-feat-emoji { filter: drop-shadow(0 0 5px rgba(52,211,153,.85)); } .ab-feat-tip { position: fixed; z-index: 2147483647; transform: translate(-50%, -100%); padding: 7px 10px; border-radius: 8px; pointer-events: none; background: rgba(8,14,17,0.98); border: 1px solid rgba(52,211,153,0.35); color: #e8eef0; font: 500 11px/1.4 "Aptos", system-ui, sans-serif; white-space: normal; max-width: 220px; text-align: center; box-shadow: 0 8px 22px rgba(0,0,0,0.5); opacity: 0; transition: opacity 0.12s; } .ab-feat-tip b { color: #6ee7b7; font-weight: 800; } .ab-feat-tip.show { opacity: 1; } #${PANEL_ID} .ab-status { display: none; margin-top: 6px; padding: 11px; border-radius: 11px; background: var(--ab-surface); border: 1px solid rgba(255,255,255,.05); font-size: 11px; } #${PANEL_ID} .ab-status.open { display: block; } #${PANEL_ID} .ab-status .ab-st-line { color: #dce6e6; font-weight: 600; } #${PANEL_ID} .ab-status .ab-st-act { color: #fcd34d; margin-top: 4px; font-size: 10.5px; } #${PANEL_ID} .ab-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 10px; } #${PANEL_ID} .ab-stat { background: rgba(0,0,0,.28); border: 1px solid rgba(255,255,255,.05); border-radius: 9px; padding: 7px 4px; text-align: center; } #${PANEL_ID} .ab-stat b { display: block; color: var(--ab-accent); font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; } #${PANEL_ID} .ab-stat span { color: var(--ab-muted); font-size: 9.5px; text-transform: uppercase; letter-spacing: .4px; } #${PANEL_ID} .ab-tabs { display: flex; gap: 4px; padding: 8px 10px 0; } #${PANEL_ID} .ab-tab { flex: none; cursor: pointer; padding: 4px 11px; font-size: 10px; font-weight: 700; color: var(--ab-muted); border-radius: 7px; border: 1px solid transparent; transition: color 0.14s, background 0.14s; } #${PANEL_ID} .ab-tab:hover { color: #fff; background: var(--ab-surface); } #${PANEL_ID} .ab-tab.on { color: #06281f; background: linear-gradient(135deg, var(--ab-accent), #10b981); box-shadow: 0 4px 12px rgba(16,185,129,.25); } #${PANEL_ID} .ab-pane[data-pane="log"] { display: none; } #${PANEL_ID}.ab-tab-log .ab-pane[data-pane="control"] { display: none; } #${PANEL_ID}.ab-tab-log .ab-pane[data-pane="log"] { display: block; } #${PANEL_ID} .ab-log-filters { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; } #${PANEL_ID} .ab-chip { cursor: pointer; padding: 3px 7px; border-radius: 999px; font-size: 10px; font-weight: 700; color: #cbd5e1; background: rgba(255,255,255,0.05); border: 1px solid transparent; transition: background 0.12s, color 0.12s, border-color 0.12s; } #${PANEL_ID} .ab-chip:hover { background: rgba(255,255,255,0.09); } #${PANEL_ID} .ab-chip.on { background: rgba(34,197,94,0.18); color: #86efac; border-color: rgba(74,222,128,0.4); } #${PANEL_ID} .ab-log-list { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; font-variant-numeric: tabular-nums; } #${PANEL_ID} .ab-log-list::-webkit-scrollbar { width: 7px; } #${PANEL_ID} .ab-log-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.14); border-radius: 4px; } #${PANEL_ID} .ab-log-item { display: flex; gap: 6px; align-items: baseline; padding: 3px 5px; border-radius: 5px; line-height: 1.3; } #${PANEL_ID} .ab-log-item:nth-child(odd) { background: rgba(255,255,255,0.03); } #${PANEL_ID} .ab-log-time { color: #6b7280; font-size: 10px; flex: none; } #${PANEL_ID} .ab-log-text { color: #e5e7eb; font-size: 11px; word-break: break-word; } #${PANEL_ID} .ab-log-empty { color: #6b7280; font-size: 11px; text-align: center; padding: 16px 0; } `;document.head.appendChild(t)}const FEAT_TIP={spawn:["Auto-spawn","Automatically pick a spawn tile."],expand:["Auto-expand / attack","Expand into land and attack targets."],boat:["Auto-landing (1% boat)","Send probe and landing boats overseas."],warship:["Auto-deploy warships","Build and dispatch warships."],build:["Auto-build","Build and upgrade structures."],nuke:["Auto-launch nukes","Fire nukes at high-value targets."],alliance:["Auto-accept alliances","Send and accept alliance requests."],donate:["Auto-donate troops (allies)","Donate troops to needy allies."],betray:["Auto-betray allies","Off = stay loyal (defensive retaliation only)."]};function featRow(t,e,n){const o=state.settings.features[t];return`
      ${n}
      `}function buildPanel(){ensureStyles();let t=document.getElementById(PANEL_ID);if(state.settings.hidden){if(t)t.remove();return null}if(t)return t;t=document.createElement("div");t.id=PANEL_ID;if(state.settings.minimized)t.classList.add("ab-collapsed");if(state.activeTab==="log")t.classList.add("ab-tab-log");t.innerHTML=`
      Auto-Bot
      ${tr("🎮 Controls")}
      ${tr("📜 Log")}
      ${tr("Checking lobby…")}
      ${state.settings.featsOpen?tr("Auto toggles ▴"):tr("Auto toggles ▾")}
      ${featRow("spawn","Auto-spawn","🏁")} ${featRow("expand","Auto-expand / attack","⚔️")} ${featRow("boat","Auto-landing (1% boat)","⛵")} ${featRow("warship","Auto-deploy warships","🚢")} ${featRow("build","Auto-build","🏗️")} ${featRow("nuke","Auto-launch nukes","☢️")} ${featRow("alliance","Auto-accept alliances","🤝")} ${featRow("donate","Auto-donate troops (allies)","🎁")} ${featRow("betray","Auto-betray allies","🗡️")}
      ${state.settings.statusOpen?tr("Status & stats ▴"):tr("Status & stats ▾")}
      0${tr("Troops")}
      0${tr("Gold")}
      0${tr("Tiles")}
      0${tr("Spawn")}
      0${tr("Attacks")}
      0${tr("Builds")}
      0${tr("Nukes")}
      ${tr("All")}
      ${Object.entries(LOG_CATS).map(([e,n])=>`
      ${n.emoji} ${tr(n.label)}
      `).join("")}
      `;document.body.appendChild(t);wirePanel(t);applyStoredPosition(t);return t}function wirePanel(t){t.querySelector('[data-role="switch"]').addEventListener("click",()=>{setEnabled(!state.settings.enabled)});t.querySelector('[data-role="mini"]').addEventListener("click",()=>{state.settings.minimized=!state.settings.minimized;t.classList.toggle("ab-collapsed",state.settings.minimized);t.querySelector('[data-role="mini"]').textContent=state.settings.minimized?"▢":"—";saveSettings()});t.querySelector('[data-role="close"]').addEventListener("click",()=>{state.settings.hidden=true;saveSettings();t.remove()});t.querySelectorAll(".ab-feat").forEach(a=>{a.addEventListener("click",()=>{const s=a.dataset.feat;if(!s)return;const l=!state.settings.features[s];state.settings.features[s]=l;a.classList.toggle("on",l);saveSettings()})});const e=t.querySelector('[data-role="feats-toggle"]');const n=t.querySelector('[data-role="feats"]');if(e&&n){e.addEventListener("click",()=>{state.settings.featsOpen=!state.settings.featsOpen;n.classList.toggle("open",state.settings.featsOpen);e.textContent=state.settings.featsOpen?tr("Auto toggles ▴"):tr("Auto toggles ▾");saveSettings()})}let o=null;t.querySelectorAll(".ab-feat-mini").forEach(a=>{a.addEventListener("mouseenter",()=>{const s=FEAT_TIP[a.dataset.feat];if(!s)return;if(!o){o=document.createElement("div");o.className="ab-feat-tip";document.body.appendChild(o)}o.innerHTML=`${tr(s[0])}
      ${tr(s[1])}`;const l=a.getBoundingClientRect();o.style.left=`${l.left+l.width/2}px`;o.style.top=`${l.top-6}px`;o.classList.add("show")});a.addEventListener("mouseleave",()=>{if(o)o.classList.remove("show")})});const r=t.querySelector('[data-role="status-toggle"]');const i=t.querySelector('[data-role="status-wrap"]');if(r&&i){r.addEventListener("click",()=>{state.settings.statusOpen=!state.settings.statusOpen;i.classList.toggle("open",state.settings.statusOpen);r.textContent=state.settings.statusOpen?tr("Status & stats ▴"):tr("Status & stats ▾");saveSettings()})}t.querySelectorAll(".ab-tab").forEach(a=>{a.addEventListener("click",()=>{const s=a.dataset.tab;if(s===state.activeTab)return;state.activeTab=s;t.classList.toggle("ab-tab-log",s==="log");t.querySelectorAll(".ab-tab").forEach(l=>l.classList.toggle("on",l.dataset.tab===s));if(s==="log")renderLog()})});t.querySelectorAll(".ab-chip").forEach(a=>{a.addEventListener("click",()=>{state.logFilter=a.dataset.filter;t.querySelectorAll(".ab-chip").forEach(s=>s.classList.toggle("on",s.dataset.filter===state.logFilter));renderLog()})});makeDraggable(t,t.querySelector(".ab-head"))}function renderLog(){const t=document.getElementById(PANEL_ID);if(!t)return;const e=t.querySelector('[data-role="log-list"]');if(!e)return;const n=state.logFilter==="all"?state.log:state.log.filter(r=>r.cat===state.logFilter);if(n.length===0){e.innerHTML=`
      ${tr("No actions yet")}
      `;return}const o=document.createDocumentFragment();for(const r of n){const i=document.createElement("div");i.className="ab-log-item";const a=document.createElement("span");a.className="ab-log-time";a.textContent=new Date(r.t).toLocaleTimeString();const s=document.createElement("span");s.className="ab-log-text";s.textContent=r.text;i.appendChild(a);i.appendChild(s);o.appendChild(i)}e.replaceChildren(o)}function makeDraggable(t,e){let n=0;let o=0;let r=0;let i=0;let a=false;function s(u){if(u.target.closest('[data-role="switch"], [data-role="mini"], [data-role="close"]'))return;a=true;const d=t.getBoundingClientRect();r=d.left;i=d.top;n=u.clientX;o=u.clientY;t.style.right="auto";t.style.bottom="auto";t.style.left=r+"px";t.style.top=i+"px";window.addEventListener("pointermove",l);window.addEventListener("pointerup",c);u.preventDefault()}function l(u){if(!a)return;const d=clamp(r+(u.clientX-n),0,window.innerWidth-60);const f=clamp(i+(u.clientY-o),0,window.innerHeight-30);t.style.left=d+"px";t.style.top=f+"px"}function c(){if(!a)return;a=false;window.removeEventListener("pointermove",l);window.removeEventListener("pointerup",c);state.settings.pos={left:parseInt(t.style.left,10),top:parseInt(t.style.top,10)};saveSettings();console.log("[Panel] saved pos",state.settings.pos)}e.addEventListener("pointerdown",s)}function applyStoredPosition(t){const e=state.settings.pos;console.log("[Panel] applyStoredPosition",e);if(e&&Number.isFinite(e.left)&&Number.isFinite(e.top)){t.style.right="auto";t.style.bottom="auto";t.style.left=clamp(e.left,0,window.innerWidth-60)+"px";t.style.top=clamp(e.top,0,window.innerHeight-30)+"px"}}function renderHeader(){const t=document.getElementById(PANEL_ID);if(!t)return;t.querySelector('[data-role="switch"]').classList.toggle("on",state.settings.enabled)}function refreshGateBanner(){const t=document.getElementById(PANEL_ID);if(!t)return;const e=t.querySelector('[data-role="gate"]');if(!e)return;const n=getGame();if(!n){e.className="ab-gate wait";e.textContent=tr("Waiting to enter game…");return}const o=gameType(n);if(o){e.className="ab-gate ok";let r;if(o==="Singleplayer")r=tr("Singleplayer");else if(o==="Public")r=tr("Public lobby");else r=tr("Private lobby");e.textContent="✓ "+r}else{e.className="ab-gate wait";e.textContent=tr("Waiting to enter game…")}}function fmt(t){const e=Math.round(t);if(e>=1e6)return(e/1e6).toFixed(1)+"M";if(e>=1e3)return(e/1e3).toFixed(1)+"k";return String(e)}function renderStatus(){const t=document.getElementById(PANEL_ID);if(!t)return;const e=(n,o)=>{const r=t.querySelector(`[data-role="${n}"]`);if(r)r.textContent=o};e("status",state.status);e("lastaction",state.lastAction);e("s-troops",fmt(state.live.troops));e("s-gold",fmt(state.live.gold));e("s-tiles",fmt(state.live.tiles));e("c-spawns",String(state.stats.spawns));e("c-attacks",String(state.stats.attacks));e("c-builds",String(state.stats.builds));e("c-nukes",String(state.stats.nukes))}"use strict";function watcher(){buildPanel();refreshGateBanner();if(!state.settings.enabled&&!state.status){setStatus(tr("Disabled"))}renderStatus();if(state.tickInFlight&&performance.now()-state.tickStartedAt>8e3){console.warn("[AutoBot] tick watchdog: clearing stuck tickInFlight");state.tickInFlight=false}const t=getGame();if(t&&state.settings.enabled&&isPublicLobby(t)){setEnabled(false);setStatus(tr("⛔ Public lobby — auto-disabled"))}retuneEngine()}if(!window.__openfrontAutoBotLoaded){window.__openfrontAutoBotLoaded=true;window.setInterval(watcher,1200);watcher();if(state.settings.enabled)setEnabled(true);console.info("[AutoBot] page bridge loaded (private-lobby training tool).")}(function(){"use strict";console.log("[AtomMacro] script start — file is injected & executing");if(window.__ofhAtomMacroInstalled){console.log("[AtomMacro] already installed in this page → skip duplicate");return}const t="openfront-helper-atom-macro-dialog";const e="openfront-helper-atom-macro-banner";const n="openfront-helper-atom-macro-styles";const o="openfront-helper-atom-range";const r="openfront-helper-atom-range-styles";const i="Atom Bomb";const a="Hydrogen Bomb";const s="Missile Silo";const l="SAM Launcher";const c=30;const u=100;const d=10;let f=false;let m=false;let p=null;let h=null;let y=0;let g=false;let b=0;let w=0;let T=0;let A=0;function I(){return document.querySelector("main-radial-menu")}function P(){const S=I();const M=S?.game||(typeof lastOpenFrontGameContext!=="undefined"?lastOpenFrontGameContext?.game:null);const _=S?.transformHandler||S?.transform||(typeof lastOpenFrontGameContext!=="undefined"?lastOpenFrontGameContext?.transform:null);const D=S?.buildMenu||document.querySelector("build-menu");const B=M?.myPlayer?.();return{game:M,transform:_,buildMenu:D,myPlayer:B}}function $(S,M,_){const{game:D,transform:B}=S;if(!D||!B?.screenToWorldCoordinates)return null;try{const O=B.screenToWorldCoordinates(M,_);if(!D.isValidCoord?.(O.x,O.y))return null;const F=D.ref(O.x,O.y);return F==null?null:F}catch(O){return null}}function k(S){let M=0;let _=[];try{_=S.units(s)||[]}catch(D){return 0}for(const D of _){try{if(D.isUnderConstruction&&D.isUnderConstruction())continue;if(D.isActive&&D.isActive()===false)continue;const B=D.level&&Number(D.level())||1;const O=D.missileTimerQueue&&D.missileTimerQueue()||[];const F=Array.isArray(O)?O.length:0;M+=Math.max(0,B-F)}catch(B){M+=1}}return M}function C(S,M,_,D,B,O,F){const W=O.unit||O;const Q=W.owner&&W.owner();if(!Q)return null;if(Q.smallID&&Q.smallID()===D)return null;if(!B&&_.isFriendly&&_.isFriendly(Q))return null;if(W.isUnderConstruction&&W.isUnderConstruction())return null;const ae=W.level&&Number(W.level())||1;const X=M&&M.samRange?Number(M.samRange(ae)):0;let j=O.distSquared;if(j==null&&S.euclideanDistSquared&&W.tile){j=S.euclideanDistSquared(F,W.tile())}if(X>0&&j!=null&&j<=X*X){return{unit:W,level:ae}}return null}function U(S,M,_){const D=[];try{const B=Ue(S,M);const O=S.config&&S.config();const F=O&&O.maxSamRange?Number(O.maxSamRange()):200;const W=S.nearbyUnits?S.nearbyUnits(_,F,l):[];const Q=M.smallID?M.smallID():-1;for(const ae of W){const X=C(S,O,M,Q,B,ae,_);if(X)D.push(X)}}catch(B){}return D}function Y(S,M,_){try{const D=M.units&&M.units(s)||[];let B=null;let O=Infinity;for(const F of D){if(F.isUnderConstruction&&F.isUnderConstruction())continue;if(F.isActive&&F.isActive()===false)continue;const W=F.tile&&F.tile();if(W==null)continue;const Q=S.manhattanDist?S.manhattanDist(W,_):0;if(Q=1?(M||0)/S:M||0;return Math.max(ee,Math.floor(_)||0)}function te(S,M,_){const D=Math.max(0,Math.floor(S));if(D<=0)return{recommended:1,achievable:true};const B=Math.max(1,M/100);const O=_>0?_:90;const F=D*B;return{recommended:D+1,achievable:Fj+pe.level,0);let W=90;try{const j=S.config&&S.config().SAMCooldown?Number(S.config().SAMCooldown()):90;if(Number.isFinite(j)&&j>0)W=j}catch(j){}const Q=Number.isFinite(B)&&B>0?B:ee;const ae=te(F,Q,W);const X=te(F,ee,W);return{sams:O.length,intercept:F,recommended:ae.recommended,achievable:ae.achievable,achievableAtMaxRate:X.achievable}}function q(S){try{return typeof S==="bigint"?S:BigInt(Math.floor(Number(S)||0))}catch(M){return 0n}}function x(S){const M=q(S);const _=M<0n;const D=_?-M:M;let B;if(D>=1000000n)B=(Number(D)/1e6).toFixed(2).replace(/\.?0+$/,"")+"M";else if(D>=1000n)B=(Number(D)/1e3).toFixed(1).replace(/\.0$/,"")+"k";else B=D.toString();return(_?"-":"")+B}function v(S){try{if(typeof tr==="function")return tr(S)}catch(M){}return S}function N(){if(document.getElementById(n))return;const S=document.createElement("style");S.id=n;S.textContent=` #${e} { position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 2147483647; padding: 7px 14px; border-radius: 8px; background: rgba(127,29,29,0.95); border: 1px solid rgba(248,113,113,0.6); color: #fee2e2; font: 800 13px/1.2 system-ui, sans-serif; white-space: nowrap; box-shadow: 0 8px 22px rgba(0,0,0,0.45); pointer-events: none; } #${e}[hidden] { display: none; } #${t} { position: fixed; z-index: 2147483647; width: 250px; padding: 0; overflow: hidden; display: flex; flex-direction: column; max-height: calc(100vh - 16px); border: 1px solid rgba(248,113,113,0.34); border-radius: 12px; background: linear-gradient(180deg, rgba(127,29,29,0.24), transparent 40%), rgba(9,14,18,0.98); color: #f1f5f9; font: 500 12px/1.35 "Aptos","Segoe UI",system-ui,-apple-system,sans-serif; box-shadow: 0 18px 46px rgba(0,0,0,0.55), inset 0 1px 0 rgba(254,202,202,0.08); } #${t}[hidden] { display: none; } #${t} .atom-hd { flex: none; display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid rgba(248,113,113,0.16); background: rgba(127,29,29,0.16); } #${t} .atom-ico { font-size: 14px; filter: drop-shadow(0 0 6px rgba(248,113,113,0.6)); } #${t} .atom-ttl { flex: 1; font-size: 11px; font-weight: 800; letter-spacing: 0.8px; text-transform: uppercase; color: #fecaca; } #${t} .atom-key { font: 800 11px/1 "Consolas","Aptos",monospace; color: #fca5a5; padding: 3px 7px; border: 1px solid rgba(248,113,113,0.4); border-bottom-width: 2px; border-radius: 5px; background: rgba(0,0,0,0.3); } #${t} .atom-x { flex: none; width: 22px; height: 22px; padding: 0; border-radius: 6px; display: grid; place-items: center; font-size: 13px; line-height: 1; cursor: pointer; transition: 0.14s; color: #fecaca; background: rgba(127,29,29,0.35); border: 1px solid rgba(248,113,113,0.34); } #${t} .atom-x:hover { background: rgba(190,30,30,0.62); color: #fff; } #${t} .atom-bd { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 11px 12px 12px; } #${t} .atom-bd::-webkit-scrollbar { width: 8px; } #${t} .atom-bd::-webkit-scrollbar-thumb { background: rgba(248,113,113,0.28); border-radius: 999px; } #${t} .atom-qty { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; } #${t} .atom-qty label { flex: none; font-size: 11px; font-weight: 700; letter-spacing: 0.3px; color: #cbd5e1; } #${t} .atom-qty input { flex: 1; width: 100%; min-width: 0; padding: 7px 9px; border-radius: 8px; text-align: center; border: 1px solid rgba(148,163,184,0.32); background: rgba(255,255,255,0.05); color: #f8fafc; font: 800 15px/1 inherit; } #${t} .atom-qty input:focus { outline: none; border-color: rgba(248,113,113,0.6); } #${t} .atom-cad { flex: none; font-size: 10px; font-weight: 800; color: #34d399; white-space: nowrap; padding: 4px 7px; border-radius: 6px; border: 1px solid rgba(52,211,153,0.26); background: rgba(52,211,153,0.10); } #${t} .atom-hydro { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; transition: 0.14s; padding: 7px 9px; border-radius: 8px; margin-bottom: 10px; color: #e2e8f0; font-size: 11px; border: 1px solid rgba(148,163,184,0.16); background: rgba(255,255,255,0.03); } #${t} .atom-hydro:hover { border-color: rgba(251,191,36,0.45); } #${t} .atom-hydro input { flex: none; width: 15px; height: 15px; accent-color: #f59e0b; cursor: pointer; } #${t} .atom-hydro .atom-hico { font-size: 13px; } #${t} .atom-hydro[data-disabled="true"] { opacity: 0.45; cursor: not-allowed; } #${t} .atom-stats { display: flex; flex-direction: column; border-radius: 9px; overflow: hidden; margin-bottom: 10px; border: 1px solid rgba(148,163,184,0.12); background: rgba(0,0,0,0.22); } #${t} .atom-line { display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 6px 10px; } #${t} .atom-line + .atom-line { border-top: 1px solid rgba(148,163,184,0.08); } #${t} .atom-line .atom-k { color: #94a3b8; font-weight: 600; } #${t} .atom-line .atom-v { font-weight: 800; color: #e2e8f0; font-variant-numeric: tabular-nums; } #${t} .atom-hl { background: rgba(34,197,94,0.07); } #${t} .atom-ok { color: #4ade80; } #${t} .atom-bad { color: #f87171; } #${t} .atom-warn { margin-bottom: 10px; padding: 7px 10px; border-radius: 8px; font-size: 10px; line-height: 1.45; color: #fecaca; border: 1px solid rgba(248,113,113,0.28); background: rgba(127,29,29,0.26); } #${t} .atom-note { margin: 0 0 11px; font-size: 9.5px; line-height: 1.5; color: #64748b; } #${t} .atom-btns { display: flex; gap: 8px; } #${t} button { padding: 9px 12px; border: none; border-radius: 8px; cursor: pointer; font: 800 12px/1 "Aptos","Segoe UI",sans-serif; letter-spacing: 0.3px; transition: 0.14s; } #${t} .atom-fire { flex: 1; color: #fff; background: linear-gradient(135deg,#ef4444,#dc2626); box-shadow: 0 4px 14px rgba(220,38,38,0.35); } #${t} .atom-fire:hover { background: linear-gradient(135deg,#f87171,#ef4444); } #${t} .atom-fire:disabled { background: rgba(100,116,139,0.35); color: rgba(226,232,240,0.45); box-shadow: none; cursor: not-allowed; } #${t} .atom-fire:disabled:hover { background: rgba(100,116,139,0.35); } #${t} .atom-cancel { flex: none; color: #e5e7eb; background: rgba(255,255,255,0.08); } #${t} .atom-cancel:hover { background: rgba(255,255,255,0.16); } `;document.head.appendChild(S)}function E(S,M){N();let _=document.getElementById(e);if(!_){_=document.createElement("div");_.id=e;document.body.appendChild(_)}_.textContent=S;_.style.background=M==="ok"?"rgba(22,101,52,0.95)":"rgba(127,29,29,0.95)";_.hidden=false;return _}function R(){const S=document.getElementById(e);if(S)S.hidden=true}function L(){const S=document.getElementById(t);if(S)S.remove()}function z(S,M,_){const D=14;const B=6;const O=window.innerWidth;const F=window.innerHeight;const W=S.getBoundingClientRect();const Q=W.width;const ae=W.height;let X=M+D;if(X+Q>O-B)X=M-D-Q;X=Math.min(Math.max(B,X),Math.max(B,O-Q-B));let j=_+D;if(j+ae>F-B)j=_-D-ae;j=Math.min(Math.max(B,j),Math.max(B,F-ae-B));S.style.left=`${X}px`;S.style.top=`${j}px`;S.style.visibility=""}function K(S,M,_){try{const D=S?.config?.().nukeMagnitudes?.(M);const B=Number(D?.outer??D?.inner);if(Number.isFinite(B)&&B>0)return B}catch(D){}return _}function J(S,M,_,D){const B=S&&S.transform;const O=Number(B&&B.scale);if(Number.isFinite(O)&&O>0)return M*O;try{const F=B.screenToWorldCoordinates(_,D);const W=B.worldToScreenCoordinates({x:F.x,y:F.y});const Q=B.worldToScreenCoordinates({x:F.x+M,y:F.y});return Math.hypot(Q.x-W.x,Q.y-W.y)}catch(F){return M}}function ie(){if(document.getElementById(r))return;const S=document.createElement("style");S.id=r;S.textContent=` #${o} { position: fixed; inset: 0; z-index: 2147483640; pointer-events: none; } #${o}[hidden] { display: none; } #${o} .atom-rng { position: fixed; left: 0; top: 0; border-radius: 50%; box-sizing: border-box; transform: translate(-50%, -50%); will-change: left, top, width, height; } #${o} .atom-rng-h { border: 2px dashed rgba(248,113,113,0.9); background: radial-gradient(closest-side, rgba(248,113,113,0.04), rgba(248,113,113,0.12)); box-shadow: 0 0 20px rgba(248,113,113,0.35), inset 0 0 26px rgba(248,113,113,0.12); } #${o} .atom-rng-a { border: 2px solid rgba(251,191,36,0.95); background: radial-gradient(closest-side, rgba(251,191,36,0.16), rgba(251,191,36,0.05)); box-shadow: 0 0 16px rgba(251,191,36,0.4), inset 0 0 18px rgba(251,191,36,0.16); } #${o} .atom-rng-dot { position: fixed; left: 0; top: 0; width: 8px; height: 8px; border-radius: 50%; transform: translate(-50%, -50%); background: #fde68a; box-shadow: 0 0 8px rgba(251,191,36,0.9), 0 0 2px #000; } #${o} .atom-rng-tag { position: fixed; left: 0; top: 0; transform: translate(14px, 14px); white-space: nowrap; font: 800 11px/1 "Aptos","Segoe UI",system-ui,sans-serif; color: #fee2e2; padding: 5px 9px; border-radius: 8px; border: 1px solid rgba(248,113,113,0.45); background: rgba(7,12,18,0.86); box-shadow: 0 6px 18px rgba(0,0,0,0.5); } #${o} .atom-rng-tag b { color: #fbbf24; } #${o} .atom-rng-tag i { color: #fca5a5; font-style: normal; } `;document.head.appendChild(S)}function he(){ie();let S=document.getElementById(o);if(!S){S=document.createElement("div");S.id=o;S.setAttribute("aria-hidden","true");S.innerHTML=`
      ☢️ ${v("Atom")} · ${v("Hydrogen")} — ${v("click to aim")}
      `;document.body.appendChild(S)}return S}function se(S,M){const _=document.getElementById(o);if(!_||!p||!h)return;const D=J(p,h.a,S,M);const B=J(p,h.h,S,M);const O=(Q,ae)=>{const X=_.querySelector(Q);if(!X)return;const j=Math.max(2,ae*2);X.style.width=`${j}px`;X.style.height=`${j}px`;X.style.left=`${S}px`;X.style.top=`${M}px`};O('[data-role="rng-h"]',B);O('[data-role="rng-a"]',D);const F=_.querySelector('[data-role="rng-dot"]');if(F){F.style.left=`${S}px`;F.style.top=`${M}px`}const W=_.querySelector('[data-role="rng-tag"]');if(W){W.style.display=m?"":"none";W.style.left=`${S}px`;W.style.top=`${M}px`}}function ce(){const S=he();S.hidden=false}function ve(){const S=document.getElementById(o);if(S)S.hidden=true}function xe(S){L();m=true;p=S;h={a:K(S.game,i,c),h:K(S.game,a,u)};ce();se(T,A);E("☢️ "+v("Left-click a target to set up the strike · Esc to cancel"),"arm")}function Fe(){m=false;p=null;h=null;g=false;y=0;ve();R()}const Re="openfront-helper-atom-batch-v1";function we(){const S={batchSize:10,delayMs:150,lastHydrogen:false};try{const M=window.localStorage.getItem(Re);if(M){const _=JSON.parse(M);if(Number.isFinite(_.batchSize)&&_.batchSize>=1)S.batchSize=Math.floor(_.batchSize);if(Number.isFinite(_.delayMs)&&_.delayMs>=0)S.delayMs=Math.floor(_.delayMs);S.lastHydrogen=_.lastHydrogen===true}}catch(M){}return S}function De(S,M,_){try{window.localStorage.setItem(Re,JSON.stringify({batchSize:S,delayMs:M,lastHydrogen:_===true}))}catch(D){}}function Me(S){return new Promise(M=>setTimeout(M,Math.max(0,S|0)))}let Ee=false;function Ue(S,M){try{if(String(S.config().gameConfig().gameMode)!=="Team")return false;const _=typeof S.numTilesWithFallout==="function"?S.numTilesWithFallout():0;const D=(S.numLandTiles()||0)-_;if(D<=0)return false;let B=0;for(const F of S.players()){if(F.isPlayer()&&M.isOnSameTeam(F))B+=F.numTilesOwned()}let O=.95;try{const F=Number(S.config().percentageTilesOwnedToWin());if(Number.isFinite(F))O=F/100}catch(F){}return B/D>O}catch(_){return false}}async function qe(S,M,_,D){N();L();const{myPlayer:B,buildMenu:O}=S;if(!B||!O)return;let F=null;let W=null;try{const ne=await B.buildables(M,[i,a]);F=Array.isArray(ne)?ne.find(me=>me.type===i):null;W=Array.isArray(ne)?ne.find(me=>me.type===a):null}catch(ne){F=null;W=null}const Q=F?q(F.cost):0n;const ae=W?q(W.cost):0n;const X=!!W&&ae>0n;const j=q(B.gold?.()??0);const pe=k(B);const Te=!F||Q<=0n;const ke=we();const Ne=H(ke.batchSize,ke.delayMs);let de=V(S.game,B,M,S.buildMenu?.uiState?.rocketDirectionUp,Ne);const Z=document.createElement("div");Z.id=t;const Ae=Math.max(1,de.recommended);Z.innerHTML=`
      ☢️ ${v("Atom batch-fire")} \\
      ≈ ${(1e3/Ne).toFixed(1)}/s
      ${v("Paced under the server's rate limit so no shots drop, then tops up any the server missed. Pacing is set in the Atom settings tab. Esc aborts.")}
      `;Z.style.left="0px";Z.style.top="0px";Z.style.visibility="hidden";document.body.appendChild(Z);const be=Z.querySelector('[data-role="atom-qty"]');const Se=Z.querySelector('[data-role="atom-validate"]');const ge=Z.querySelector('[data-role="atom-fire"]');const Be=Z.querySelector('[data-role="atom-cad"]');const ye=()=>{const ne=we();const me=H(ne.batchSize,ne.delayMs);if(Be)Be.textContent=`≈ ${(1e3/me).toFixed(1)}/s`;de=V(S.game,B,M,S.buildMenu?.uiState?.rocketDirectionUp,me);let ue=parseInt(be.value,10);if(!Number.isFinite(ue)||ue<1)ue=1;const oe=X&&Z.querySelector('[data-role="atom-last-hydro"]')?.checked===true;const re=oe?Math.max(0,ue-1):ue;const le=Q*BigInt(re)+(oe?ae:0n);const Ce=le>j?le-j:0n;const fe=ue>pe?ue-pe:0;const Ie=Math.max(0,Math.min(ue,pe));const Pe=ue>=de.recommended;if(ge)ge.disabled=ue<1||Ce>0n||fe>0;const $e=de.achievable?"":de.achievableAtMaxRate?`
      ⚠ ${v("Too slow — SAMs reload before impact. Lower the Delay in the Atom settings tab.")}
      `:`
      ⚠ ${v("Too many SAM levels here — atoms can't overwhelm them even at full speed. Use MIRV.")}
      `;Se.innerHTML=`
      🛡 ${v("SAMs covering")}${de.sams} · ${v("int")} ${de.intercept}
      ${v("Recommended")}${de.achievableAtMaxRate?de.recommended:"—"}
      ${v("Cost")} ≈${x(le)} 💰
      ${v("Gold")}${x(j)}${Ce>0n?` (−${x(Ce)})`:""}
      ${v("Silos ready")}${pe}${fe>0?` (−${fe})`:""}
      ${v("Will fire now")}${Ie}
      ${$e}`;if(Z.style.visibility!=="hidden"){const Le=Z.getBoundingClientRect();const Oe=6;if(Le.right>window.innerWidth-Oe||Le.bottom>window.innerHeight-Oe||Le.left${v("Cannot build atom here (no silo / disabled / unreachable).")}`;if(ge)ge.disabled=true}else{ye();be.addEventListener("input",ye);const ne=Z.querySelector('[data-role="atom-last-hydro"]');if(ne)ne.addEventListener("change",ye)}z(Z,_,D);Z.querySelectorAll("input").forEach(ne=>ne.addEventListener("keydown",me=>me.stopPropagation()));const _e=()=>{L();ve()};Z.querySelector('[data-role="atom-cancel"]').addEventListener("click",_e);Z.querySelector('[data-role="atom-x"]').addEventListener("click",_e);ge.addEventListener("click",async()=>{if(ge.disabled)return;let ne=parseInt(be.value,10);if(!Number.isFinite(ne)||ne<1)ne=1;const me=we();const ue=me.batchSize;const oe=me.delayMs;const re=Z.querySelector('[data-role="atom-last-hydro"]')?.checked===true;De(ue,oe,re);_e();Ee=false;E("☢️ "+v("Firing… (Esc to stop)"),"arm");const le=await Ke(S,M,ne,ue,oe,re);E(`☢️ ${v("Fired")} ${le}/${ne} ${v("atom bomb(s)")}`,le>=ne?"ok":"arm");setTimeout(R,2600)});setTimeout(()=>be?.focus?.(),0)}async function Ke(S,M,_,D,B,O){const{myPlayer:F,buildMenu:W}=S;if(!F||!W||typeof W.sendBuildOrUpgrade!=="function"){return 0}const Q=140;const ae=6e4;const X=state.settings.atomStallMs||3e4;const j=H(D,B);const pe=O===true&&_>=1;const Te=pe?Math.max(0,_-1):_;const ke=_+Math.max(3,Math.ceil(_*.15));const Ne=F.smallID?F.smallID():-1;const de=oe=>{const re=()=>{const fe=[];try{const Ie=F.units?F.units(oe):[];for(const Pe of Ie){try{const $e=Pe.owner&&Pe.owner();if($e&&$e.smallID&&$e.smallID()!==Ne)continue;if((Pe.targetTile&&Pe.targetTile())===M)fe.push(Pe)}catch($e){}}}catch(Ie){}return fe};const le=new Set;for(const fe of re()){try{le.add(fe.id?fe.id():fe)}catch(Ie){}}const Ce=new Set;return()=>{for(const fe of re()){try{const Ie=fe.id?fe.id():fe;if(!le.has(Ie))Ce.add(Ie)}catch(Ie){}}return Ce.size}};const Z=de(i);const Ae=pe?de(a):null;const be=[];const Se=()=>{const oe=performance.now();while(be.length&&oe-be[0]>ae)be.shift();return be.length{try{const re=await F.buildables(M,[oe]);return Array.isArray(re)?re.find(le=>le.type===oe):null}catch(re){return null}};const Be=oe=>{try{W.sendBuildOrUpgrade(oe,M);be.push(performance.now());return true}catch(re){return false}};let ye=0;let _e="reached-qty";let ne=performance.now();while(yeX){_e="stalled";break}await Me(Math.min(j,250));continue}if(!Se()){ne=performance.now();await Me(1e3);continue}if(Be(oe)){ye++;ne=performance.now()}Z();if(ye>=Te||Ee)break;await Me(j)}if(!Ee&&_e!=="stalled"&&Te>0){await Me(700);const oe=Math.min(Te-Z(),ke-ye);for(let re=0;re=1)break;const re=await ge(a);if(re&&re.canBuild!==false&&k(F)>0){if(!Se())await Me(1e3);if(Be(re))ye++}await Me(Math.max(j,700))}}const ue=Z()+(Ae?Ae():0);if(Ee)_e="cancelled";console.log("[Atom] fireAtoms done",{emitted:ye,confirmed:ue,qty:_,atomQty:Te,hydro:Ae?Ae():0,trailMs:pe?me:0,stopReason:_e,gapMs:j});return ue}function He(S){return!!(S&&S.closest&&S.closest("#"+t))}function Ge(S){const M=S.target;if(He(M)||He(document.activeElement))return false;if(M instanceof HTMLInputElement||M instanceof HTMLTextAreaElement||M instanceof HTMLSelectElement||M&&M.isContentEditable){return true}const _=document.activeElement;return!!(_ instanceof HTMLInputElement||_ instanceof HTMLTextAreaElement||_&&_.isContentEditable)}function We(S){return S.code==="Backslash"&&!S.shiftKey&&!S.altKey&&!S.ctrlKey&&!S.metaKey&&!Ge(S)}function ze(S){T=S.clientX;A=S.clientY;if(m)se(S.clientX,S.clientY)}function je(S){if(S.key==="Escape"){Ee=true;let _=false;if(document.getElementById(t)){L();ve();_=true}if(m){Fe();_=true}if(_){S.preventDefault();S.stopImmediatePropagation()}return}if(!We(S))return;console.log("[AtomMacro] key '\\' detected");S.preventDefault();S.stopImmediatePropagation();const M=P();if(!M.game||!M.myPlayer?.isPlayer?.()){console.log("[AtomMacro] no game/player context",{radial:!!I(),game:!!M.game,transform:!!M.transform,buildMenu:!!M.buildMenu,myPlayer:!!M.myPlayer});E("☢️ "+v("Start a game first to use atom batch-fire"),"arm");setTimeout(R,1400);return}console.log("[AtomMacro] entering aim mode");xe(M)}function Ye(S){if(We(S)){S.preventDefault();S.stopImmediatePropagation()}}function Ve(){try{window.dispatchEvent(new PointerEvent("pointercancel",{bubbles:true,cancelable:true,button:0,pointerId:1,clientX:-1e4,clientY:-1e4}))}catch(S){}}function Xe(S){if(!m)return;if(S.button!==0)return;const M=document.getElementById(t);if(M&&M.contains(S.target))return;g=true;b=S.clientX;w=S.clientY}function Je(S){if(S.button!==0)return;if(!m||!g)return;g=false;const M=Math.abs(S.clientX-b)+Math.abs(S.clientY-w);if(M>=d){return}const _=document.getElementById(t);if(_&&_.contains(S.target))return;S.preventDefault();S.stopImmediatePropagation();Ve();const D=P();const B=$(D,S.clientX,S.clientY);if(B==null){y=performance.now()+400;E("☢️ "+v("Aim at a valid target on the map, then click"),"arm");return}const O=S.clientX;const F=S.clientY;console.log("[AtomMacro] aim click → tile",B);m=false;se(O,F);R();p=null;h=null;y=performance.now()+400;qe(D,B,O,F)}function Qe(S){if(S.button!==0)return;if(performance.now()>y)return;const M=document.getElementById(t);if(M&&M.contains(S.target))return;S.preventDefault();S.stopImmediatePropagation()}function Ze(){if(f)return;f=true;window.addEventListener("keydown",je,true);window.addEventListener("keyup",Ye,true);document.addEventListener("mousemove",ze,{passive:true});window.addEventListener("pointerdown",Xe,true);window.addEventListener("pointerup",Je,true);window.addEventListener("click",Qe,true)}try{window.__OFH_atomBatch={SAFE_GAP_MS:ee,get(){const S=we();return{batchSize:S.batchSize,delayMs:S.delayMs,lastHydrogen:S.lastHydrogen}},set(S){if(!S||typeof S!=="object")return;const M=we();let{batchSize:_,delayMs:D,lastHydrogen:B}=M;if(Number.isFinite(S.batchSize)&&S.batchSize>=1){_=Math.floor(S.batchSize)}if(Number.isFinite(S.delayMs)&&S.delayMs>=0){D=Math.floor(S.delayMs)}if("lastHydrogen"in S)B=S.lastHydrogen===true;De(_,D,B)},effectiveGapMs(S,M){return H(S,M)}}}catch(S){}try{Ze();window.__ofhAtomMacroInstalled=true;console.log("[AtomMacro] installed (point mouse + press '\\' → dialog)")}catch(S){console.warn("[AtomMacro] install failed:",S)}})();window.__OFH_autobot={FEATURE_KEYS:["spawn","expand","build","boat","nuke","warship","alliance","donate","betray"],DIFFICULTIES:["Easy","Medium","Hard","Impossible"],get(){const t=state.settings;return{enabled:Boolean(t.enabled),difficulty:t.difficulty,winFixes:Boolean(t.winFixes),tickMs:t.tickMs,hidden:Boolean(t.hidden),features:{...t.features},buildStructures:{...t.buildStructures}}},set(t){if(!t||typeof t!=="object")return;const e=state.settings;if("difficulty"in t)e.difficulty=t.difficulty;if("winFixes"in t)e.winFixes=Boolean(t.winFixes);if("hidden"in t)e.hidden=Boolean(t.hidden);if("tickMs"in t){const n=Number(t.tickMs);if(Number.isFinite(n)&&n>0)e.tickMs=n}if(t.features&&typeof t.features==="object"){Object.assign(e.features,t.features)}if(t.buildStructures&&typeof t.buildStructures==="object"){if(!e.buildStructures)e.buildStructures={};Object.assign(e.buildStructures,t.buildStructures)}if("enabled"in t)setEnabled(Boolean(t.enabled));saveSettings();if(typeof retuneEngine==="function")retuneEngine();if(typeof buildPanel==="function")buildPanel()}};window.addEventListener("message",t=>{if(t.source!==window){return}const e=t.data;if(!e||e.source!==EXTENSION_SOURCE){return}if(e.type==="JOIN_PUBLIC_LOBBY"&&e.payload?.gameID){document.dispatchEvent(new CustomEvent("join-lobby",{detail:{gameID:e.payload.gameID,source:"public",publicLobbyInfo:e.payload.publicLobbyInfo},bubbles:true,composed:true}))}if(e.type==="MARK_BOT_NATIONS_RED"){setBotMarkersEnabled(e.payload?.enabled)}if(e.type==="SHOW_GOLD_PER_MINUTE"){setGoldPerMinuteEnabled(e.payload?.enabled)}if(e.type==="SHOW_TOP_GOLD_PER_MINUTE"){setTopGoldPerMinuteEnabled(e.payload?.enabled)}if(e.type==="SHOW_TEAM_BUILD_STATS"){setTeamBuildStatsEnabled(e.payload?.enabled)}if(e.type==="MARK_HOVERED_ALLIES_GREEN"){setAllyMarkersEnabled(e.payload?.enabled)}if(e.type==="SHOW_ALLIANCE_REQUESTS_PANEL"){setAllianceRequestsPanelEnabled(e.payload?.enabled)}if(e.type==="SHOW_TRADE_BALANCES"){setTradeBalancesEnabled(e.payload?.enabled)}if(e.type==="SHOW_MY_GPM_HISTORY"){if(typeof setSelfGpmHistoryPanelPosition==="function"){setSelfGpmHistoryPanelPosition(e.payload?.panelPosition)}if(typeof setSelfGpmHistoryEnabled==="function"){setSelfGpmHistoryEnabled(e.payload?.enabled)}else{console.error("OpenFront helper: self-gpm-history bridge not loaded (check manifest web_accessible_resources)")}}if(e.type==="SHOW_NUKE_PREDICTION"){setNukePredictionEnabled(e.payload?.enabled)}if(e.type==="SHOW_BOAT_PREDICTION"){setBoatPredictionEnabled(e.payload?.enabled,{alwaysOwnRoutes:e.payload?.alwaysOwnRoutes,alwaysTeamRoutes:e.payload?.alwaysTeamRoutes,alwaysAllyRoutes:e.payload?.alwaysAllyRoutes,alwaysEnemyRoutes:e.payload?.alwaysEnemyRoutes})}if(e.type==="SHOW_WARSHIP_ROUTES"){setWarshipRoutesEnabled(e.payload?.enabled)}if(e.type==="SHOW_BOAT_PANEL"){setBoatPanelEnabled(e.payload?.enabled)}if(e.type==="SET_BOAT_INCOMING_WARNING"){setBoatIncomingWarningEnabled(e.payload?.enabled)}if(e.type==="SHOW_ESTATE_PANEL"){setEstatePanelEnabled(e.payload?.enabled)}if(e.type==="SET_AUTO_LEAVE_ON_TEAM_WIN"){setAutoLeaveOnTeamWinEnabled(e.payload?.enabled)}if(e.type==="SHOW_NUKE_SUGGESTIONS"){setNukeSuggestionsEnabled(e.payload?.enabled)}if(e.type==="SET_AUTO_NUKE"){setAutoNukeEnabled(e.payload?.enabled,e.payload?.includeAllies)}if(e.type==="SET_SEND_1_PERCENT_BOAT"){setSend1PercentBoatEnabled(e.payload?.enabled,e.payload?.contextMenu!==false)}if(e.type==="SHOW_ECONOMY_HEATMAP"){setEconomyHeatmapIntensity(e.payload?.intensity);setEconomyHeatmapEnabled(e.payload?.enabled)}if(e.type==="SHOW_EXPORT_PARTNER_HEATMAP"){setExportPartnerHeatmapEnabled(e.payload?.enabled)}if(e.type==="APPLY_SELECTIVE_TRADE_POLICY"){const n=Number(e.payload?.requestedAt);if(Number.isFinite(n)&&n!==lastSelectiveTradePolicyRequestAt){lastSelectiveTradePolicyRequestAt=n;applySelectiveTradePolicy()}}if(e.type==="SET_SELECTIVE_TRADE_POLICY"){setSelectiveTradePolicyEnabled(Boolean(e.payload?.enabled))}if(e.type==="SET_AUTO_BOT_I18N"){if(typeof setAutoBotI18n==="function"){setAutoBotI18n(e.payload?.language,e.payload?.bundle)}}});window.setInterval(()=>{refreshSelectiveTradePolicyAvailability();refreshCheatsAvailability()},1e3);refreshSelectiveTradePolicyAvailability();refreshCheatsAvailability();window.__openfrontAutoJoinBridgeReady=true; } catch (e) { console.error("[ofh] ingame section error:", e); } })();