mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 21:45:22 +00:00
097c42740c559c435a65ba8d31a4ab8fc0168a60
674 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
097c42740c |
Random spawn. Avoid spawning near water. (#3009)
## Description: Fixing https://discord.com/channels/1359946986937258015/1360078040222142564/1463898386854973642 Now, if not all tiles on the spawn circle can be owned, the algorithm tries to select another random spawn tile. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: nikolaj_mykola --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
6a30d2b38b |
Smarter factory placement for Nation AI 🤖 (#3244)
## Description: Introduces a dedicated `factoryValue()` scoring function for AI factory placement, replacing the generic `interiorStructureValue()` previously shared with cities and missile silos. Scoring criteria: - High elevation and spacing from other factories (unchanged from city/silo logic) - Rail connectivity: bonus per distinct rail cluster reachable within `trainStationMaxRange`, weighted by trade gold potential — allied clusters score highest (1.0), team/neutral clusters score ~0.71, own clusters ~0.29 (based on `config.tradeGold()` values). Based on difficulty - Cluster deduplication: connecting to the same cluster multiple times does not inflate the score - Embargoed and bot neighbors are excluded; all other non-embargoed neighbors are included The result is that the AI tends to place factories where they can bridge separate rail networks or connect to high-value trade partners, rather than deep in its own interior. ### EDIT Added a dedicated `cityValue()` scoring function that takes into account the connectivity score. This allows placement of cities in a "factory-aware" way, while also enforcing spreading structures (we want the network to grow, not a cluster of cities and factories all together). ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: deshack_82603 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
f09d9a3a5f |
Nations can overwhelm SAMs now 💥 (+ 3 little nation improvements) (#3246)
## Description: ### SAM Overwhelming (`NationNukeBehavior.ts`) On Impossible difficulty, nations can now destroy enemy SAMs by overwhelming them with coordinated atom bomb salvos. When no good nuke target is found (all trajectories intercepted by SAMs), the nations will: - Identify the easiest enemy SAM to destroy (lowest level first) - Calculate the total interception capacity of all covering SAMs and send enough bombs to overwhelm them (+1 extra per 5 needed to account for enemy building more SAMs during flight) - Plan launches in NukeExecution's Manhattan-distance silo order, tracking which silos have interceptable trajectories (wasted bombs) - Use a sliding window over parabolic flight times to find the best cluster of bombs that can arrive within half the SAM cooldown window - Compute per-bomb wait ticks to synchronize arrivals from silos at different distances - Skip launching if a salvo is already in flight - Upgrade the best SAM-protected silo when silo capacity is insufficient; wait and save gold when only gold is lacking https://github.com/user-attachments/assets/14fa592f-2902-4604-8e37-1eba2b2f0b85 ### 2-Player Endgame Handling (`NationNukeBehavior.ts`) - On Hard/Impossible with only 2 players remaining, `findBestNukeTarget()` directly targets the other player (bypasses all priority logic) - `getPerceivedNukeCost()` returns actual cost (no MIRV saving inflation) when only 2 players are left ### SAM Build Rate (`NationStructureBehavior.ts`) - Reduced SAM perceived cost increase per owned from 1.0 to 0.5, so nations build more SAMs ### Island Attack Variety (`AiAttackBehavior.ts`) - `findNearestIslandEnemy()` now collects up to 2 reachable candidates and has a 33% chance to pick the second-nearest, adding variety to boat attack targeting ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
07b5d6e41f |
Prevent self-retaliation crash when own nuke destroys own ship 🔧 (#3260)
## Description: Noticed this in two singleplayer games: When a nation's nuke destroys its own transport/trade ship in the blast radius, `NationWarshipBehavior` incorrectly tries to retaliate against itself, calling `updateRelation(self)` which throws (GameRunner tick error). Added a self-check in `maybeRetaliateWithWarship` to skip retaliation when the destroyer is the player itself. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
f6a08e16db |
Perf alloc (#3241)
If this PR fixes an issue, link it below. If not, delete these two lines. Resolves #(issue number) ## Description: ## PR Title perf(core): reduce hot-path allocations & safe optimizations This PR brings in a set of allocation-focused optimizations in core hot paths ### Scope - `src/core/execution/NukeExecution.ts` - `src/core/execution/WarshipExecution.ts` - `src/core/game/UnitGrid.ts` - `src/core/game/PlayerImpl.ts` - `src/core/configuration/DefaultConfig.ts` - `src/core/execution/SAMLauncherExecution.ts` ### What Changed - `NukeExecution.detonate`: reduced call overhead/allocations by caching `mg`/`config`, avoiding repeated lookups, and using allocation-free loops (no `forEach` closures) in the diminishing-effect pass. - `WarshipExecution.findTargetUnit`: replaced allocate+sort flow with single-pass best-target selection. - `UnitGrid.nearbyUnits`: reduced call overhead and allocations via single-type fast path and cached query coordinates. - `PlayerImpl.units`: added fast paths for common small-arity type queries (1-3 unit types). - `DefaultConfig.unitInfo`: cached `UnitInfo` objects per `UnitType` to avoid repeated object/closure creation. - `SAMLauncherExecution` targeting: removed sort churn and streamlined target selection with single-pass hydrogen prioritization. ### Rebase - One conflict was resolved in `NukeExecution.detonate` by keeping `main`'s diminishing-effect-per-impacted-tile behavior, while retaining the allocation-reduction refactors. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME |
||
|
|
031a17d880 |
Optimize nuke explosion (#3176)
## Description: When a nuke detonates, the explosion is looping over each tiles. Among other things, it filters the owner units to find the `TransportShips` units. This operation is cpu-intensive and repeated over each tiles can make the tick noticably slower. On this screenshot the detonation function took 100ms: <img width="1617" height="488" alt="image" src="https://github.com/user-attachments/assets/07009b18-4342-4caf-9e82-9ae5147b63f8" /> <img width="1645" height="375" alt="image" src="https://github.com/user-attachments/assets/fe9ead87-550a-4166-96ab-092d0c08be82" /> I suspect it led to a few frame freeze when I MIRVed too. Suggestion: loop over the impacted players rather than the tiles. With this suggestion, the same nuke takes between 4ms to 20ms: <img width="989" height="365" alt="image" src="https://github.com/user-attachments/assets/25c0faf0-cc34-41b7-8091-b14bde6db595" /> However this changes the death formula used, as they were repeated over each tiles with ever-smaller values, and with this change the operation is done all-at-once. This will result in a different outcome. In my opinion the performance gain is seductive enough to maybe tweak the formula to make it work with this revised strategy. What do you think? ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom |
||
|
|
8b66c8bd53 |
Perf/refactor: use StructureTypes, remove territoryBound (#3238)
## Description: PR 6/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already merged https://github.com/openfrontio/OpenFrontIO/pull/3237. Please see if these can be merged for v30. - **PlayerImpl**: validStructureSpawnTiles did a filter on unit types to get isTerroritoryBound units, on every call again. It read this from unit info in DefaultConfig. While having it centrally in DefaultConfig unitInfo is good for maintainability, other code uses hardcoded StructureTypes and isisStructureType from Game.ts. Which has the same purpose and thus contains the same unit types. StructureTypes and isisStructureType do need manual maintainance outside of DefaultConfig. And are more bug prone/less type safe. But, using them gives more speed compared to getting these unit types out of DefaultConfig unitInfo centrally with some cached function in GameImpl for example (tested with buildableUnits and MIRVPerf.ts). So I went with StructureTypes in validStructureSpawnTiles too. - **PlayerExecution**: now validStructureSpawnTiles no longer needs isTerritoryBound (see the point above), PlayerExecution is the last place where it was used. Replaced it for isStructureType here too (since it has the same meaning and outcome). - **Game.ts** and **DefaultConfig** unitInfo: remove the now unused _territoryBound_. As it was only used in validStructureSpawnTiles and PlayerExecution and has been replaced in both (see the two points above). ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
348ccfc2c3 |
Perf/small refactor: NationStructureBehavio (#3237)
## Description: PR 5/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already merged https://github.com/openfrontio/OpenFrontIO/pull/3236. Please see if these can be merged for v30. **NationStructureBehavior**: - maybeSpawnStructure: cache this.game to be used twice. - maybeSpawnStructure: instead of hardcoded ruling out Defense Post for upgrade check, check dynamically if type is upgradable. That way if defense posts ever do become upgradable, we don't run into a bug right away. - maybeUpgradeStructure: removed canUpgradeUnit check. Since it already checked this right before in findBestStructureToUpgrade, so only upgradable units are returned. And canUpgradeUnit is also checked right after in UpgradeStructureExecution. So we're going from 3 times to 2 times canUpgradeUnit, small perf win too. - findBestStructureToUpgrade: cache this.game to be used thrice. - shouldBuildStructure: cache this.game.config() to be used twice. - getTotalStructureDensity: this.player.units can handle an array of unit types to count. Input StructureTypes like this so we don't need a loop and count, and only have to get an array length once. getTotalStructureDensity needs to ignore unit levels so we can't make use of other pre-defined functions in PlayerImpl (which were created to avoid array length calls), but at least this saves a few. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
4f08e57304 |
Small refactor: nearbyUnits readonly UnitType[] (#3236)
## Description: PR 4/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already merged https://github.com/openfrontio/OpenFrontIO/pull/3235. Please see if these can be merged for v30. - **Game**/**GameImpl**/**GameView**: nearbyUnits required "UnitType | UnitType[]" for tiles, but calls UnitGrid nearbyUnits which requires "UnitType | readonly UnitType[]". Made the requirement the same for Game/GameImpl/GameView nearbyUnits. This way, we don't have make a shallow copy of the StructureTypes array everytime we want to send it as an argument. Other callers than listNukeBreakAlliance in Util.ts are unaffected. - **Util.ts**: listNukeBreakAlliance needs no shallow copy of StructureTypes anymore as argument for NearbyUnits ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
2b830e9fcd |
Update tradeship spawn & gold meta (#3232)
## Description: Now that pathfinding is much more efficient with hpa*, we can add more trade ships. This PR does the following: 1. No gold bonus for additional ports, keeps the meta simple 2. cut the gold per trade ship roughly in half. 3. Use a "pity bonus", the more times a port has failed to spawn a tradeship, the higher the likelyhood it will spawn one 4. Increase the sigmoid so the mid-point is 200, with a half life of 50. In tests about ~400 trade ships max. It's pretty difficult to balance on singleplayer so I'm sure the values will be adjusted after playtests. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan |
||
|
|
7bf6bfd4b9 |
🔴 Major SAM Targeting Fix (#3223)
## Description: Bug (before): In multi-nuke scenarios (e.g., 6 incoming nukes), SAMs, when stacked, could behave like they only engage one nuke at a time, letting other nukes slip through and wipe the SAM stack. This was most noticeable when nukes entered from certain angles/entry sides (from what I’ve noticed, it happens more often in the 2 o’clock to 6 o’clock direction). <img width="734" height="743" alt="asadasada" src="https://github.com/user-attachments/assets/7ca6c9ef-b2b4-47ea-bed2-249a84c8f5ed" /> What was fixed: 1- Removed permanent "unreachable" caching: When a nuke was out of SAM range on first evaluation, it was cached as null (permanently unreachable) and never re-evaluated. Since nukes move each tick, they could later enter interception range but would be ignored. The fix removes this permanent cache, so nukes are re-evaluated every tick. 2 - Moved targetedBySAM filter into the targeting system: The targetedBySAM() check was at the launch decision, if the highest-priority nuke was already claimed by another SAM, the launcher skipped firing entirely instead of falling back to the next best target. The fix moves the filter inside getSingleTarget() so claimed nukes are excluded before ranking. 3 - Cleared targetedBySAM flag on SAM missile abort: When a SAM missile aborted (for example target became allied), the targetedBySAM flag was never cleared. This permanently prevented all other SAMs from targeting that nuke. The fix calls setTargetedBySAM(false) on abort so the nuke becomes available for re-targeting. **Videos:** I uploaded a before/after clip showing SAM performance intercepting 6 nukes, before the fix, nukes could break through, but after the fix, SAMs consistently intercept as expected. Before: https://github.com/user-attachments/assets/d5a85354-f35c-4aca-82f8-902f5966312b after: https://github.com/user-attachments/assets/54074c09-fbdf-44d5-a88c-b1d54b20fee2 - Deployed for further testing ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: abodcraft1 --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
86e51ab790 |
Fix nation spawnkilling 🔧 (#3222)
## Description: As far as I can remember, in v28 the spawn immunity applied to both humans and nations. With the configurable spawn immunity (added for v29) the spawn immunity no longer applies to nations... Because its called PVP immunity now. So right now it's possible to spawnkill nations. This is a big problem for the 5M gold modifier games... And you can "cheat" in singleplayer. This PR changes two things: - Nations always have 5 seconds spawn immunity now, no matter whats configured for the PVP immunity - Nations attack TerraNullius earlier (Otherwise the easy nations would sometimes do their first attack after the 5 seconds are over, spawnkills would still be possible) ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
4e62114ea0 |
Improve nations 🤖 (#3206)
## Description: - `AiAttackBehavior`: Because bots delete stolen structures now, nations prioritize attacking bots with structures - `NationMIRVBehavior`: Nations no longer MIRV enemies who already got MIRVed in the last 30 seconds. Some humans complained about getting double-MIRVed by nations. And in games with very high starting gold, ALL nations MIRVed the same player (stop steamroll logic). - `NationAllianceBehavior`: Fixes a comparison logic bug (Thanks to Deshack) - `NationNukeBehavior.ts`: Little atom bomb perceived cost balance change - `MIRVExecution`: To make sure the MIRVing nations are attacking the MIRVed nations (even if they don't share a border), the relation gets updated in both directions now. - `SinglePlayerModal` & `HostLobbyModal`: Update the default difficulty to "Medium" (to synchronize the defaults with the public game default) ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
f362e47413 |
Cancel nukes when accepting alliance via radial menu (#3155)
Resolves #3154 ## Description: #2716 introduced nuke cancellation logic on alliance acceptance via `AllianceRequestReplyExecution`. The radial menu action, though, calls `AllianceRequestExecution` instead, which accepts the alliance if a request has already been made by the other player. This PR moves the nuke cancellation logic to `GameImpl`, hooking into the `acceptAllianceRequest` method, therefore accounting for every alliance acceptance, regardless of the specific action that brought to that. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: deshack_82603 |
||
|
|
a1b3afe534 |
Fix cluster deletion (#3185)
## Description: When a train station is removed, the clusters are recomputed. However the cluster recomputation code has not been changed from the original rail network implementation, which was a tree. The deletion code made assumptions that are not true anymore since we introduced loops in the network. As a result the cluster recomputation was very inefficient, although the data was correct. Changes: - Fix clusters computation when a structure is deleted - Structures are frequently deleted in bulk: atom/hydro/MIRV. Re-computing the clusters when a single structure is deleted would be inefficient because the recomputed cluster would probably need to be recomputed again in the same tick. Instead, when a structure is deleted, flag the cluster as "dirty", and recompute all the dirty clusters once per tick only. Previous performances (hydro over a dense area): <img width="700" height="160" alt="image" src="https://github.com/user-attachments/assets/cd3ceb42-6d5f-4ad1-b35a-f8e5e0513821" /> Now: <img width="450" height="269" alt="image" src="https://github.com/user-attachments/assets/55dec3b9-8619-4a6c-9a16-f5368fe40da1" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom |
||
|
|
07e13b3479 |
Fix: remove alliances on death (#3168)
## Description: - Remove alliances on death: after death, alliances would stay active including countdown timers and (when dead player kept spectating) icons. Now remove them when player becomes inActive. - Moved code to private method within PlayerExecution + added comments in NationExecution and BotExecution for more clarity as to where removals are performed from at death - Remove renewal request from Events Display when Alliance doesn't exist anymore (after death or otherwise). - Also cleanup this.alliancesCheckedAt when alliance doesn't exist anymore. Before, old/broken alliance id's would accumulate in it during a game. - Removed now-redundant isAlive check in EventsDisplay. Both the alliances array as the isAlive are updated in the same tick from PlayerUpdates so now alliance is removed from alliances array on player death, the other.isAlive() check is no longer needed. Of course we could keep it in just to be very safe, so just let me know when you're doubtful about this. - Attack.test.ts: fix failing test. Player B dies because of the attack, meaning the alliance now gets removed. Prevent this by gving both a different, adjecent, starting tile. And to be more clear about what is needed for the test to pass, add isAlive check for both of them after the attacks. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
f7da20ddfd |
Nation build order improvements + Nation structure upgrading 🏠 (#3152)
Resolves #2997 ## Description: ### New stuff - Nations can upgrade structures now. They do it if they have too many structures compared to their territory size - They prefer to upgrade stuff thats protected by SAMs (based on difficulty) - Updated the build order, it also depends a bit on the difficulty now (easy nations build less SAMs) - Nations can handle extreme amounts of gold now. 500M starting gold? no problem. Previously they only built cities - They stop saving up for MIRV if they can afford it (in some old Enzo "impossible difficulty experiment" videos you could see nations with like 300M gold...) - The save-up-target changes when bombs / hydros / MIRVs are disabled - Added many checks for disabled units. For example: Don't build SAMs when missile silos are disabled, focus on factories when ports are disabled - Updated the `structureSpawnTileValue` method, SAM-placement depends a bit on the difficulty now ### Refactor - Moved all structure related nation code into `NationStructureBehavior.ts` - Split up the good old `structureSpawnTileValue` method to make it more readable - Cleaned up NationExecution a bit ### A screenshot <img width="1681" height="755" alt="Screenshot 2026-02-08 001108" src="https://github.com/user-attachments/assets/c9b3df01-41ca-4c68-b450-b20e7d7d910a" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
e93cab3392 |
sam missile immunity (#3167)
## Description: added sam missile immunity (was missing from the immunity list) ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n |
||
|
|
c6c793f6b3 |
Highlight hovering railroad (#3156)
## Description:  The `RailroadLayer` simply displays tiles as instructed by the core worker. While it's practical for the layer to only care about the tiles, it also means it has no understanding of railroads as entities (their paths, connections, or identities). It also means that the core worker is responsible for rendering tasks such as tile orientation and construction animation, which is not expected. To support ID-based events and better separation of concerns, the rendering layer needs to be aware of complete railroads. With this change, the core worker can send the tiles once and subsequently reference railroads only by ID for all other events. #### Changes: - `RailroadLayer` now stores full railroad data instead of only individual tiles - `RailroadLayer` is responsible for animating newly built railroads - Add a new `RailroadSnapUpdate` sent when a new structure is built over an existing railroad. This event is used by `RailroadLayer` to keep railroad ID in sync. - When hovering over a railroad, the render worker is querying the core worker about overlapping railroads. Alternatively, RailroadLayer could compute overlaps itself now that it has full railroad knowledge, but this logic would need to be duplicated and kept in sync across workers. Keeping a single source of truth in the core worker is preferred. #### Edgecases: - When a structure snaps over a railroad, the original railroad is split into two new railroads. If the construction animation is still in progress, instead of resuming the animation at the correct point on the new railroads, all remaining tiles are rendered immediately - Previously, `RailroadUpdate` handled both construction and destruction. This no longer works with `RailroadSnapUpdate`, as event ordering is now pretty important and IDs may be lost before they are consumed. To address this, RailroadUpdate is split in two: `RailroadConstructionUpdate` and `RailroadDestructionUpdate`. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: jrouillard <jon@rouillard.org> |
||
|
|
8dcc7cfb9a |
Fix client reconnection after page refresh (#3117)
## Description: - Removed all code related to generating a client ID on the client. The server now assigns the client ID and sends it to the client in lobby messages. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: w.o.n |
||
|
|
8cc6c2c2aa |
Perf spawn train (#3130)
## Description: Train spawning hot-path optimization (trade destination selection) ## Summary This PR reduces per-tick overhead in train spawning by removing temporary allocations and reducing work in the destination-selection path. The change focuses on `Cluster` trade destination lookup and how `TrainStationExecution` picks a destination. ## What changed ### 1) Maintain a “trade-capable” station subset per cluster `src/core/game/TrainStation.ts` - `Cluster` now maintains: - `stations`: all stations in the cluster (unchanged) - `tradeStations`: maintained subset of stations that can act as trade endpoints (`City` or `Port`) - `tradeStations` is kept in sync in: - `addStation()` - `removeStation()` - `clear()` Impact: - Trade queries no longer scan every station in the cluster; they only scan `tradeStations`. ### 2) Add cheap eligibility helpers `src/core/game/TrainStation.ts` - `hasAnyTradeDestination(player)`: - Fast early-exit check: returns as soon as it finds any eligible trade destination. - `randomTradeDestination(player, random)`: - Picks a random eligible trade destination directly without materializing an intermediate `Set`. ### 3) Use reservoir sampling for single-pass random choice `src/core/game/TrainStation.ts` `Cluster.randomTradeDestination()` uses reservoir sampling: - Iterates `tradeStations` once. - Maintains a running count of eligible stations (`eligibleSeen`). - Replaces the selected station with probability `1/eligibleSeen`. Properties: - Uniform selection among eligible stations. - One pass instead of “count then pick by index” (two pass). - Allocation-free. - Returns `null` when no eligible destination exists. ### 4) Update train spawning to avoid temporary sets `src/core/execution/TrainStationExecution.ts` - Previously: `spawnTrain()` called `cluster.availableForTrade()` and then `random.randFromSet(...)`. - This built a new `Set` on the hot path. - Now: - Early-exit via `cluster.hasAnyTradeDestination(owner)`. - Destination via `cluster.randomTradeDestination(owner, random)`. Net effect: - Less per-tick work and no per-spawn temporary `Set` allocations. ## Why this helps Train spawning happens frequently and can become a hot path in large games / large rail clusters. Avoiding repeated allocations and reducing work inside `tick()` helps keep frame/update time predictable. ## notes - Trade rules are unchanged (`tradeAvailable(player)` still gates eligibility). - Destination selection remains random-uniform over eligible `City`/`Port` stations that satisfy `tradeAvailable(player)`. - `TrainStationExecution` now avoids calling `spawnTrain()` entirely when `spawnTrains` is falsy (it was already guarded inside). ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME |
||
|
|
b68de96c6e |
Perf clusters (#3127)
## Description: This PR reduces server/client tick CPU spent on territory cluster maintenance by: - Cutting redundant work in `PlayerExecution.removeClusters()` (largest-cluster bounding box reuse, fewer allocations in `isSurrounded()` and `removeCluster()`). - Making `calculateBoundingBox()` allocation-free per tile by switching from `gm.cell(tile)` to `gm.x(tile)`/`gm.y(tile)` (it now only allocates the two result `Cell`s, instead of 1 per tile). ## Commits - `51de0a1b` core: reduce PlayerExecution cluster overhead - `6d9d85c5` core: avoid Cell allocations in PlayerExecution isSurrounded - `346f6a8c` core(util): speed up calculateBoundingBox by avoiding Cell allocations ## Notes - This PR is intended to be behavior-preserving; changes are limited to hot-path micro-optimizations. - Follow-up opportunity: `calculateClusters`/flood-fill is now the top hotspot; further wins likely come from reducing traversal work or caching. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME |
||
|
|
a8836f76d3 |
Reduce bot farming problem (#2895)
## Description: Explanation of the bot farming strategy in the discord: https://discord.com/channels/1359946986937258015/1359949371956789289/1460928540575928478 "the result is that a player can build unlimited factories for 125 000 gold discount, trade with themselves with each train being worth 50 000 gold. First the 25 000 for neutral trade and then another 25 000 when the bot is harvested." "If you have a minute and ally people around you it should be trivial to get 10 of both cities and factories for 1.25mil" It's debatable if we want to let people do that (close this PR) or see it as an abusive mechanic. Here is the fix, bots try to delete all structures now. You can simply retake them to stop the deletion: https://github.com/user-attachments/assets/ac1ca846-50bd-42fa-8e25-5ac25a6d627e ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
8ab9cef65b |
Various little nation improvements 🤖 (#3082)
## Description:
- Maybe greet nearby players with a 👋 emoji in the earlygame (make
nations feel a little bit more lively)
- Destroying a transport ship / capturing a trade ship of a nation now
updates the relation (mainly on higher difficulties)
- On hard difficulty: Only lift embargos if we have a friendly relation.
On impossible difficulty: Don't lift embargos
- Improve totalPlayers calculation and return value of
`hasTooManyAlliances` in `NationAllianceBehavior`
## Please complete the following:
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
FloPinguin
|
||
|
|
f20028a2a1 |
Show troop count of troop transport boats ⛵ (#3056)
## Description: Troop count display for naval invasion message: <img width="398" height="131" alt="Screenshot 2026-01-28 204213" src="https://github.com/user-attachments/assets/d7ccf2a8-9974-4f12-9901-e603426a8e56" /> On hover, PlayerInfoOverlay shows the troop count now: <img width="504" height="99" alt="Screenshot 2026-01-28 202916" src="https://github.com/user-attachments/assets/46f7685f-8c0e-4156-8d02-8a68dbcffde0" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
f7d3c2e0bc |
Nations donate troops now 💀 (In team games) (#2984)
## Description: For v29, balances the HvN winrate. In team games, nations now donate troops to their weakest team members (if they have no attack options available). How often they donate depends on the difficulty. This PR also has some other little fixes: - For HvN games, always return true in `shouldAttack()` (make nations a bit more aggressive). - Early exit in `attackWithRandomBoat()` for performance - Early exit in `findNearestIslandEnemy()` for performance AND to make sure nations which are encircled by friends don't run into this method (=> no donation happening!) ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
e5c91945af |
Humans are severely skill issued ⚠️ Change HvN difficulty to Medium (#2971)
## Description: For v29 HvN winrate is between 10 and 15%, but should be around 50%. 1. Change HvN difficulty to Medium 2. Little balance change in `NationAllianceBehavior` ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
18fb513326 |
Pathfinding refinements (#2959)
## Description: ### Short path for multi-source HPA* Math was not mathing, increased the bounds to 260x260, it is a bit slower but should work better. The short path was breaking when player owned a lot of shores. This is because the bounding box of tiles with less than 120 distance + 10 padding could be as big as 260x260 and the optimized array was set to 140x140. I made mistake of calculating it as `2 * (60 + 10)` instead of `2 * (120 + 10)`. ### LoS path refinement Previously, we ran 2 passes of LoS smoothing on the path. However, since we are effectively tracing the same path, the line of sight is essentially the same. This PR makes second line of sight stop on water tiles with magnitude `n + 1` compared to first path. Practically, this means it'll attempt LoS exactly 1 tile after previous corner. See screenshot. <img width="1299" height="1151" alt="image" src="https://github.com/user-attachments/assets/726be236-1ff8-406c-896a-02902a762ab0" /> ### SendBoatAttackIntentEvent The flow of sending transport ships is currently strange. This PR makes the flow more sane. **Old flow** ``` - Player clicks TARGET tile, it can be deep inland - Client asks Worker for the best START tile to TARGET tile - Worker answers `false`, since the tile is inland - Client sends BoatAttackIntent with START=false and TARGET tiles set - Worker accepts BoatAttackIntent, computes DESTINATION as closest shore to TARGET - Worker re-computes best START to DESTINATION - Worker sends boat from START to DESTINATION ``` **New flow** ``` - Player clicks TARGET tile, it can be deep inland - Client sends BoatAttackIntent with TARGET - Worker accepts BoatAttackIntent, computes DESTINATION as closest shore to TARGET - Worker computes START as the best tile to DESTINATION - Worker sends boat from START to DESTINATION ``` ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: moleole |
||
|
|
f8156c550b |
Fix random spawn (#2958)
## Description: "You can pick your spawn in random spawn games in v29. You need to open the menu and click on the attack button. That's it." Thats the fix for this problem. Radial menu no longer allows to attack (pick a spawn) while random spawn is enabled. And SpawnExecution got a check so you cannot send malicious intents. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
3dadfbd23d |
feat: Nuke trajectory prediction now accounts for alliance breakage. (#2912)
## Description: Nuke trajectory prediction now will show interception with allied SAMs if the alliance will break on nuke launch. Code was also refactored to be shared a bit more. In addition, if an incoming alliance would break if accepted, the nuke launch will break the alliance. <img width="1199" height="1002" alt="nukepr" src="https://github.com/user-attachments/assets/c31066d9-66cf-4eaa-be3c-e2fbcfe7965a" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: bibizu |
||
|
|
969b301aac |
Fix: Ally won't conquer last tiles of so-called dead defender (#2954)
## Description: When a player is conquered (has less than 100 tiles left) their gold is transfered to the conqueror. And after that the conqueror gets the last tiles. But if some of those last tiles are not bordered by the conqueror, they are given to their neighbour player. However that neighbour player can be an ally. It is percieved as a bug if an ally conquers/annexes tiles. This PR fixes that by adding an isFriendly check to `handleDeadDefender` in `AttackExecution`. Now, there are already scenarios possible currently, where a player survives being conquered. If they have some tiles on a small island for example. Going from that, there should be no unexpected bugs following this change. A player can be conquered twice in a game already in the stats too. https://discord.com/channels/1359946986937258015/1359946989046989063/1462595261204533248 Example of this happening in an Enzo vid (with his surprised reaction) and explanation posted here: https://discord.com/channels/1359946986937258015/1359946989046989063/1460483209308536925 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
b2ba37e0ab |
Destroy incoming nukes when alliance is created (#2716)
Resolves #2484 ## Description: - When an alliance is created between two players, any incoming nukes between them are destroyed mid-air. This prevents the traitor debuff from being applied on impact, even if the nukes were launched before the alliance was formed. - If a player has launched nukes at multiple nations, only the nukes targeting the newly allied nation are destroyed. This is what the players will see after the alliance is created (in case they have launched nukes at each other): <img width="423" height="125" alt="Screenshot 2026-01-04 092907" src="https://github.com/user-attachments/assets/6544fb7a-7623-4fc3-b799-89ef8fe897d6" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: assessin. |
||
|
|
c8c97abe75 |
Fix transportship target tile on construction (#2896)
## Description: The recent pathfinding rework broke the naval invasion target. This change reverts the code to the previous working one. <img width="232" height="273" alt="image" src="https://github.com/user-attachments/assets/0cc3ed80-bd77-4e7b-886b-0ce5012840ac" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom |
||
|
|
a28a7ef6fd |
Fix: Bots NEVER attacked someone if they had water access (#2894)
## Description: Bots always attacked Terra Nullius if they shared a border with Terra Nullius. But water is Terra Nullius... So I changed that condition to `this.bot.neighbors().some((n) => !n.isPlayer())`. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
42c944c9cc |
Create ranked type enum, last person not afk wins in 1v1 (#2892)
## Description: * Add RankedType enum, for now it's just 1v1 * Add new method to MapPlaylist to create 1v1 game config * Update WinCheck so the last player is declared a winner on 1v1. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan |
||
|
|
35b7213c5c |
Enhance nuke alliance breaking logic to account for allied structures in blast radius 💣 (#2887)
## Description: Doesn't need a description :D https://github.com/user-attachments/assets/8de576fd-050b-4b35-8526-e4c88d1a9f25 https://github.com/user-attachments/assets/c99147a1-efdf-426b-96d1-e996e01f89aa ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
0e3ced3bfa |
Pathfinding Refactor pt. 2 (#2866)
## Playtest https://pf-pt-2.openfront.dev/ ## Pathfinding Refactor pt. 2 <img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/9477958e-54b7-4c83-b317-ba789e809e9e" /> This is a follow-up to a previous PR introducing pathfinding changes. This time, it introduces a complete refactor of `pathfinding` directory and breakdown into composable pieces. ### Unified PathFinder interface `PathFinder<T>` and `SteppingPathFinder<T>` are introduced to unify **all** pathfinding across the application. First one exposes complete path, while stepping variant allows the callee to iterate over the path by calling `.next`. All pathfinders share this one common interface, which makes them easy to use in any scenario - `PathFinding.Water(game).search(from, to)`. `SteppingPathFinder<T>` extends `PathFinder<T>` with an ability to iterate over the path. It handles caching, storing current index and invalidation. This allows the units to not care about the inner workings of the pathfinder and just call `pf.next(current, target)` and receive instructions on what to do next. ### Common entry point All pathfinders are now exposed from common `PathFinding` entrypoint: - `PathFinding.Water` - `PathFinding.Rail` - `PathFinding.Stations` - `PathFinding.Rail` Additional entry point is introduced for pathfinders which need to work both in the worker, but also on the frontend, which lacks `Game` interface. Currently only `UniversalPathFinding.Parabola` is available. ### Spatial Query New module has been introduced close to `pathfinding` - `SpatialQuery`. It aims to resolve any questions game may have about finding tiles meeting criteria. Currently `SpatialQuery.closestShore(player, target)` and `SpatialQuery.closestShoreByWater(player, target)` are available - they help answering questions about naval invasion: "What is the best landing location from user's click?" and "Which our tile should be used to launch the transport ship?". Under the hood they use very similar mechanics to pathfinding, so it felt right to put them close by. ### Modular architecture Pathfinders now support transformers: `MiniMapTransformer`, `ShoreCoercingTransformer`, `ComponentCheckTransformer`, `SmoothingTransformer`. Transformers functions like a middleware in the pathfinding chain. They wrap around the pathfinder and provide additional functionality. This allows the pathfinder to focus on actually finding the path instead of doing unrelated things. Example chain for simple (A*) water pathfinding: ```ts static WaterSimple(game: Game): SteppingPathFinder<TileRef> { const miniMap = game.miniMap(); const pf = new AStarWater(miniMap); return PathFinderBuilder.create(pf) .wrap((pf) => new ShoreCoercingTransformer(pf, miniMap)) .wrap((pf) => new MiniMapTransformer(pf, game.map(), miniMap)) .buildWithStepper(tileStepperConfig(game)); } ``` The Pathfinder - here `AStarWater` - does not care about the conversion between minimap and main map tiles. It also does not care if the source or destination is a land tile. The transformers take care of that. The pathfinder gets a set of valid coordinates and produces the path - that's it. Modular approach makes working on a particular set of utilities much easier - for example map upscaling is handled consistently across all pathfinders. Additionally, the pathfinders are not tied to the particular map resolution used. Pass them a different map and they will work the same. ### Algorithms Algorithms used are neatly organized inside `src/core/pathfinding/algorithms`. They are prefixed with the algorithm name and suffixed with the use case. File without suffix exposes generic version ready to traverse any graph with adapters. Specialized versions either use an adapter or inline logic when performance is critical - using adapters leads to 20-30% performance loss. The directory includes `A*` and `BFS` but also other useful utils, such as `AbstractGraph` used to generate... an abstract graph on top of the tile map and `ConnectedComponents` helping to identify whether two tiles are connected by a path without actually computing the path. ### Playground The playground have been updated with new algorithms, including tweaked very greedy `A*`. <img width="2175" height="1424" alt="image" src="https://github.com/user-attachments/assets/1f833651-0024-4299-bf86-882f5368358c" /> ### Tests Yeah, there are some, a little too many if I say so myself. But there are no useless tests. I had to ensure refactored code works somehow reliably. This PR comes with trust me bro guarantee, but I would appreciate someone confirming **naval invasions, nukes (esp. MIRV) and warships**. ### Discord `moleole` GL & HF |
||
|
|
8235da9335 |
Translate displayMessage events via events_display keys (#2847)
If this PR fixes an issue, link it below. If not, delete these two lines. Resolves #1225 ## Description: Replace all raw displayMessage strings with events_display.* keys + params and add the new English translations in resources/ lang/en.json so EventsDisplay can translate them. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: aotumuri --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
240690c574 |
Yet another nation improvement PR 🤖 (#2841)
## Description: ### `NationAllianceBehavior` - `isAlliancePartnerSimilarlyStrong()` now also checks for `numTilesOwned`, should make it a bit easier to get alliances. The troop calculation now also uses the players `outgoingAttacks` to make it feel less random. OF-Discord-Humans complained. - Rebalanced `checkAlreadyEnoughAlliances()` a bit ### `NationNukeBehavior` - Don't save up for MIRV if they are disabled - Don't try to throw atom bombs / hydros if they are disabled - Hydro-Nations are allowed to throw atom bombs if they are under heavy attack - Rebalance `isUnderHeavyAttack()` a bit - Increased perceived cost ### `NationEmojiBehavior` - Fix multiple nations congratulated the winner instead of one - Don't brag with our crown if the game is already over ### `DonateGoldExecution` & `DonateTroopExecution` - Added `canSendEmoji` checks ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
5955f89fe8 |
Nation build order improvements 🤖 (#2833)
## Description: My first PR about the nation build order. This one is a bit important for HumansVsNations. - Nations build more SAMs in team games - Nations build less factories if they have access to the ocean (instead of focusing ports and factories with the same priority) - Nations no longer place defense posts "without reason" - only when they share a border with someone they haven't allied I'm planning to make the build order a bit different based on difficulty. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
d8762b1317 |
Fix transport ship src and dst to always be water (#2832)
## Description: Issue discovered by @DevelopingTom, posted on Discord. After merging pathfinding PR, transport ships lost the ability to navigate between land tiles. For now, the quick fix is to select adjacent water tile and select it for pathing. Conquer logic still applies to original destination on the shore. ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: moleole |
||
|
|
96aa39a415 |
Improve nations 🤖 (#2817)
## Description: ### Refactor - Moved `maybeSpawnWarship()` from `NationExecution` to `NationWarshipBehavior` - Moved `maybeAttack()` (and sub-methods) from `NationExecution` to `AiAttackBehavior` ### Betrayal - Added nice betrayal logic in `maybeBetray()`. Previously that method was basically just a placeholder for a future implementation. ### Attacking - Added `veryWeak()` attack strategy for hard and impossible difficulty nations attack orders to target MIRVed players with higher priority - Optimized the `weakest()` attack strategy so that nations don't attack stronger players. This should make nation-attacks feel less random (humans complained in discord) - `findNearestIslandEnemy()` and `randomBoatTarget()` also no longer returns stronger players - `afk()` and `hated()` attack strategies no longer return MUCH stronger players - Several tiny refactorings, fixes and balance optimizations in `AiAttackBehavior` ### Emojis - Added some `canSendEmoji()` because I saw some "cannot send emoji" warnings in the console ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
971e7f4a45 |
Move UI elements from the FX layer to a new UI layer (#2827)
## Description: Some FX animations were previously used as UI elements (e.g. nuke area, naval invasion target, gold text). This PR moves those animations to a dedicated UI layer. Those UI elements handle correctly the current zoom level and remain sharply rendered at all zoom levels. The new UI layer can be disabled using the same setting that disables the FX layer. Performance-wise, this layer is equivalent to the FX layer, as it reuses the same animations. ### Naval target Don't scale with the zoom level, but has a minimum zoom level so the targeted tile can still be easily highlighted by zooming  ### Nukes Has to scale because the size is set, but the border radius is not so the area is more visible from afar.  ### Popup text Scale with zoom level, and stop showing when zoomed-out:  ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom |
||
|
|
b090f2f624 |
HPA* Pathfinding (#2815)
## Pathfinding with HPA*
Hi! The primary objective of this PR is to replace per-tile A* with
hierarchical pathfinding - HPA*. In practice, this means we create an
abstract graph on top of the actual map with far fewer points and use it
to decide on general path structure. Only then we go back to tile-level
and build path between selected waypoints. This speeds up long distance
pathfinding by over 1000x in some cases. To make the review easier, it
comes with a benchmark and visual playground.
## PREPROCESSING
H part of HPA* means "hierarchical" and requires preprocessing.
This PR includes pre-processing as part inside `new Game()` constructor.
It takes about 135ms for `giantworldmap` on my machine, which increases
the effective initialization from ~95ms to ~230ms. This time could be
reduced in different ways, which are **out of scope** for this PR.
After confirming the initialization time is bearable on low-end devices,
I argue merging this PR as-is is acceptable tradeoff. It creates small
lag at the beginning of a round but pays for itself in the first minute
of the match.
## Nerdy details
**Architecture**
- HPA*-style hierarchical pathfinding
- 32×32 sectors on minimap with gateway nodes on borders
- Gateway graph built via BFS during preprocessing
- Water component optimization skips unreachable gateway pairs
- A* on gateway graph → local A* within sectors → Bresenham path
smoothing
- Minimap upscaling identical to currently used in MiniAStar
**Key Optimizations**
- Typed arrays instead of high-level primitives
- Stamp-based visited tracking (no need to recreate buffers, O(1)
clearing)
- Optional - enabled by default - caching of tile paths between gateways
- Line of sight smoothing for the final path
## Review Focus
Play with included tools, benchmark and visualization. Pathfinding
should be safe to merge as a black box - you do not need to understand
the details. Outcomes can be tested empirically in-game. Visualize (and
share!) edge cases with included playground. Confirm the 100x speedup is
real with benchmark.
If you plan to dive into the code, I suggest the following order:
- Pathfinding abstraction in `src/core/pathfinding/`
- Pathfinding tests in `tests/core/pathfinding/`
- NavMesh in `src/core/pathfinding/navmesh/` + integration with
`Game.ts`
- Benchmark in `tests/pathfinding/benchmark/`
Do not look at playground's code, it has been created with a clanker.
The design is 100% mine and I spent way too long polishing it, but I
haven't even once edited the code manually. There is probably no
abstraction whatsoever, just do not look at the code, let it play.
## Core Changes
#### Pathfinding (`src/core/pathfinding/navmesh/`)
- HPA* + refinement -> three phased pathfinding: A* over the graph ->
naive path -> refinement
- comes with A* and BFS optimized for for specific needs
#### Pre-Processing (`src/core/pathfinding/navmesh/`)
- identify water bodies to avoid pathfinding between disconnected nodes
- create high-level graph of gateways on top of tile map
#### Abstraction (`src/core/pathfinding/`)
- common `PathFinder` interface that can return full path and also act
as state machine (`.next()`)
- adapters for both new and legacy algorithm with fallback to legacy if
navigation mesh not available
#### Benchmark (`tests/pathfinding/benchmark/`)
- `npx tsx tests/pathfinding/benchmark/run.ts` - no guesswork, numbers
- `npx tsx tests/pathfinding/benchmark/run.ts --synthetic` - 1000s of
synthetic paths
- `npx tsc tests/pathfinding/benchmark/generate.ts` - generate more as
needed, test new maps
- includes ONE synthetic scenario to avoid PR bloat, generate more
locally / later
#### Playground (`tests/pathfinding/playground/`)
- `npx tsx tests/pathfinding/playground/server.ts` - visualize paths
with both new and legacy algorithm
## Benchmarks
### Compared with legacy in default - hand picked - scenario:
```
Initialization: 95.95ms -> 227.29ms
Pathfinding: 3038.43ms -> 6.45ms
Distance: 26972 -> 26810 tiles
```
### 42,000 synthetic routes across all maps
```
Running 42 synthetic scenarios with hpa.cached adapter...
✅ synthetic/achiran | Init: 93.42ms | Path: 139.07ms | Dist: 1481630 tiles | Routes: 1000/1000
✅ synthetic/africa | Init: 87.14ms | Path: 155.08ms | Dist: 1829414 tiles | Routes: 1000/1000
✅ synthetic/asia | Init: 57.60ms | Path: 112.55ms | Dist:
|
||
|
|
9512e480d2 |
Fix: Players don't auto-send emoji replies when donated to, unlike nations (#2808)
## Description: The new (awesome) nation emoji updates had a small bug in them when I was playtesting with a friend where donating troops to them (a human player) would result in the player automatically sending an emoji reply. Sometimes these replies were negative-connotations like ❓ and 🥱, which could impact how other players perceive their donation attempt. This PR fixes that issue. ### Example of player nation sending emojis automatically https://github.com/user-attachments/assets/99689966-b784-4c3f-b43b-953a4a102e2d ### Donating to player after fix https://github.com/user-attachments/assets/ace0c1ee-3eb8-4240-9c78-167dd773cfb2 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
f73953c240 |
☢️ Nations send much better nukes now (Part 2) ☢️ (#2779)
## Description: ### Refactor - Moved `findBestNukeTarget()` (and child methods) from `AiAttackBehavior` to `NationNukeBehavior` because it makes more sense. - `NationNukeBehavior`: Renamed `mg` to `game` (So its similar to the other nation .ts files) - Moved the `removeOldNukeEvents()` method a bit ### New features - Impossible difficulty nations are now optimized for SAM outranging! - 1 of 3 nations is now a hydro-nation. They cannot send atom bombs. They save up for hydros. This is to reduce atom-bomb-spam on the map. - On impossible difficulty, the crown nukes the second place now (in FFAs) - On hard and impossible difficulty, nations now ignore the perceived cost for nukes if they get heavily attacked. Reasoning: They should stop saving for MIRV, they should defend with all their gold! - On medium, hard and impossible difficulty, nations no longer throw nukes at places where another team member already has a nuke "in the flying process" - Optimized `lastNukeSent` a bit (to respect nuke radius) - Adjusted `maybeSendNuke()` to use 30 instead of 10 randomTiles on impossible difficulty to improve chances of finding a perfect SAM outranging spot - On impossible difficulty, nations now ignore their "most hated enemy" if the crown has 50%+ of the map (Very big danger). Instead they are nuking the crown. - Added `isFriendly` check to `findStrongestTeamTarget()` ### Media SAM outranging: https://github.com/user-attachments/assets/d1e88bb6-0060-400b-9c16-24d7399f5949 Team game nukes not hitting the same spot: <img width="1160" height="708" alt="Screenshot 2026-01-03 042236" src="https://github.com/user-attachments/assets/c017fb3c-3e3f-45fb-9d45-dd4caba7a59f" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --------- Co-authored-by: iamlewis <lewismmmm@gmail.com> |
||
|
|
af0b8a8d50 |
Configurable immunity timer (#2763)
## Description: Resolve discussions about stalled PR https://github.com/openfrontio/OpenFrontIO/pull/2460 <img width="724" height="348" alt="image" src="https://github.com/user-attachments/assets/c2c9fa79-cace-431a-9ca4-b3656612fa9d" /> Changes: - Added a `Player::canAttackPlayer(other)` function to determine whether a player can be attacked. - This function is now used in most places where a fight can occur: - AttackExecution (land attacks) - Naval invasion - Warship fight - Nukes can't be thrown during the truce - Immunity only affect human players. Nations and bot will fight as usual, and can be fought against. - The immunity timer uses minutes in the modal window. UI: - The immunity phase is displayed with a timer bar at the top. This is from the original PR, to be discussed if it's not deemed visible enough: <img width="632" height="215" alt="image" src="https://github.com/user-attachments/assets/f5ab9aa0-bd4f-4503-b8d6-b40b121fba65" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: newyearnewphil <git@nynp.dev> |
||
|
|
ab5b044362 |
Fix train was destroyed message spam (#2774)
## Description: Trains are made of a primary unit (`TrainExecution.train`) followed by 6 cars (`TrainExecution.cars`). Currently when any of the units is destroyed, a message "Your Train was destroyed" is shown. In worst case scenario, when all cars are blasted by a nuke, 7 messages are displayed to the user, but the train continues. Since the actual logic is unaffected as long as the primary unit stays alive, displaying messages is confusing. This PR fixes it. The message is only displayed when the primary unit is destroyed. The following cars are only there for fancy visuals. ## Screencast ##### Current - multiple messages for single train https://github.com/user-attachments/assets/3df04c71-d899-4f68-af83-36c9d0ffa730 First nuke was a test. Second nuke destroyed the entire train, prompting 7 messages. Third nuke destroyed one full train and then some. Prompting many too many messages. ##### Fixed - one message, only if train was fully destroyed https://github.com/user-attachments/assets/1f3840a7-6c62-487d-af3a-82de39dad9e8 First train was only partially destroyed, no message was shown, delivery mission completed A+. Following 2 trains had the front engine destroyed and therefore were promptly decommissioned. ## Please complete the following: - [x] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: moleole |
||
|
|
4877e202f6 |
Update MIRV target selection algorithm (#2765)
## Description:
`MIRVExecution.separate` is consuming more resources than it needs to.
This PR introduces a series of minor performance changes which do not
alter the behavior of the selection algorithm. Assessing the code
initially, I was convinced there are multiple easy wins - starting with
the proximity check. However, after many hours of mostly math, no
alternative solution came close to the speed of current implementation.
Therefore this PR consists of only a few minor tweaks:
#### Removed `console.log`
This is by far the worst offender, in my test removing the three lines
of console log improved execution time from ~30ms down to ~10ms. The
logs are not very useful. I do not see a clear pattern in the logs
produced by the application therefore they were completely removed for
now. If there is a need for the log in production build, I suggest
adding single line with the number of destinations selected.
```diff
- console.log(`dsts: ${dsts.length}`);
- console.log(`got ${dsts.length} dsts!!`);
- console.log("couldn't find place, giving up");
```
#### Replaced multiple calls to `random.nextInt` with single call to
`random.next`
The flamechart shows calling pseudo random number generator is
expensive. Therefore instead of calling it twice, the code now generates
a single random number and derives further calculations from it. It
remains 100% deterministic and there should not be any noticeable change
to the enthrophy. This saves about ~1.5ms in my tests.
```diff
- const x = this.random.nextInt(
- this.mg.x(ref) - this.mirvRange,
- this.mg.x(ref) + this.mirvRange,
- );
- const y = this.random.nextInt(
- this.mg.y(ref) - this.mirvRange,
- this.mg.y(ref) + this.mirvRange,
- );
+ const r1 = this.random.next();
+ const r2 = (r1 * 15485863) % 1;
+ const x = Math.round(r1 * this.range * 2 - this.range + this.baseX);
+ const y = Math.round(r2 * this.range * 2 - this.range + this.baseY);
```
#### Caching of destination coordinates
Since the target tile coordinates are used a lot, instead of retrieving
them every time with `this.mg.x` and `this.mg.y`, they get cached as
`baseX` and `baseY`. To reduce usage further, I also exposed `x` and `y`
to `isOverlapping` / `proximityCheck` directly instead of passing the
tile. Since available methods operate on `TileRef`, this change requires
the calculations - manhattan and euclidean distance - to be inlined. I
do not think this is a big issue, considering this code is responsible
for very specific task. This saves another ~1.5ms in my tests.
```diff
- if (this.mg.euclideanDistSquared(tile, ref) > mirvRange2) {
+ if ((x - this.baseX) ** 2 + (y - this.baseY) ** 2 > this.rangeSquared) {
```
## Benchmark:
**Before**
```
=== MIRV Performance Benchmark Results ===
MIRV target selection - sparse territory x 53.53 ops/sec ±0.48% (71 runs sampled)
MIRV target selection - dense territory x 53.39 ops/sec ±0.57% (70 runs sampled)
MIRV target selection - giant world map (350 targets) x 1,129 ops/sec ±0.98% (90 runs sampled)
```
**After**
```
=== MIRV Performance Benchmark Results ===
MIRV target selection - sparse territory x 198 ops/sec ±0.39% (85 runs sampled)
MIRV target selection - dense territory x 200 ops/sec ±0.28% (86 runs sampled)
MIRV target selection - giant world map (350 targets) x 1,409 ops/sec ±0.89% (92 runs sampled)
```
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory*
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
\* Tests in separate PR, implemented against master:
https://github.com/openfrontio/OpenFrontIO/pull/2767
## Please put your Discord username so you can be contacted if a bug or
regression is found:
moleole
|
||
|
|
2f7c9eb930 | Merge branch 'v28' |