mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 11:23:25 +00:00
661d96ba280f094e83a412fa825f20bc9c3e0b7b
737 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8a8079b979 |
Fix parallel bot boat attacks of nations 🐛 (#4297)
## Description: When a nation attacked multiple bots via boat attacks in parallel, each boat attack computed its troop allocation independently using `player.troops() / 5` without subtracting `botAttackTroopsSent`. The cumulative troop commitment could exceed the nation's actual troop count, and when the queued `AttackExecution`s ran `init()`, they drained the nation to zero. Planetary Realignment found this bug by accident, here Russia has only 39 troops: <img width="1189" height="654" alt="image" src="https://github.com/user-attachments/assets/07b85e00-6734-4ddd-a16e-fe53309e0ef8" /> The land attack path already handled this correctly. The bug in `sendBoatAttack` was introduced by #3786, which made nations see and attack enemies across rivers via boats, and changed `attackBots()` from `.neighbors()` to `.nearby()`. So the bug was on prod for the entirety of v31. This fix extracts the shared attack troop calculation (reserve, bot-aware allocation, troopSendCap, isAttackTooWeak, emoji) into a new `calculateAttackTroops` method, with a callback for the non-bot troop default (land: `player.troops() - targetTroops`, boat: `player.troops() / 5`). Bot targets in both paths now go through the same reserve-aware calculation. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
094aa766ce |
Improve "Better troop management for nations 🤖" (#4278)
## Description: **Allow Hard/Impossible nations to retaliate and expand freely** Previously, nations on Hard/Impossible difficulty could be stuck unable to fight back if their `troopSendCap` or `isAttackTooWeak` checks blocked them from sending enough troops. **@legan320** on the main discord noticed it. Now: - `troopSendCap` raises the cap to at least the total incoming attack troops, so nations can match the force being used against them - `isAttackTooWeak` bypasses the 20% minimum check entirely when under attack - `troopSendCap` no longer applies when attacking Terra Nullius, so nations can always expand into unowned land with full troops All checks still apply normally for unprovoked attacks against other players. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
f4db4a33c8 |
Send nukes as motion plans and render them smoothly per frame (#4255)
## Summary Follow-up to #4244's payload work: nukes were the last per-tick movers flooding the worker → main update stream. - **Core**: nuke trajectories are fully determined at launch (precomputed parabola), so `NukeExecution` now records a `GridPathPlan` when the nuke is built — same mechanism trade ships use — and the client derives the position each tick. Per-tick `UnitUpdate`s for nukes in flight are suppressed; only targetable flips and deletion (interception/detonation) still emit. This covers atom bombs, hydrogen bombs, and MIRV warheads (dozens of per-tick movers per MIRV separation). - The plan path replays a separate pathfinder rather than reusing the stored trajectory array: the curve's cached points don't advance exactly one index per tick, and the plan must match the movement pathfinder's exact per-tick tile sequence. - `startTick` accounts for MIRV warheads' staggered `waitTicks`. - **Render**: `UnitPass.drawMissiles` now lerps each nuke's instance position `lastPos→pos` by wall-clock progress through the current tick, so nukes glide along their arc at render framerate instead of jumping once per 100ms tick. Both endpoints are real simulated positions — the rendered nuke trails the sim by at most one tick and settles exactly on it when ticks stop. Plan-driven units sync `lastPos` on path-stall ticks so the lerp never replays a segment. Shells keep their existing two-instance trail; SAM missiles are unchanged. ## Test plan - New `tests/nukes/NukeMotionPlan.test.ts`: tick-exact alignment between the recorded plan and core nuke position over the whole flight (mirroring `GameView.advanceMotionPlannedUnits` math), `waitTicks` offset, and that no per-tick unit updates are emitted in flight except targetable flips and deletion. - Full suite passes (1452 + 65), tsc/eslint/prettier clean. - Verified in-game (headless Chromium, real WebGL): atom bomb arcs from silo to target with the client position driven by the plan, missile sprite renders intact while the smoothing rewrites the instance buffer every frame, detonation FX land at the target. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
82b68d16a1 |
Fix "Better troop management for nations 🤖" (#4265)
## Description: There was a check missing... The troop management stuff should be disabled for team games because nations can expect donations in that case, and its mainly relevant for FFAs. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
aa4b490e68 |
Simplify WebGL renderer integration: remove dead extension code, untangle GameView naming (#4240)
## Summary The WebGL renderer was adapted from an external extension and carried a lot of machinery this integration never uses (replay playback, its own input/event system, a GL radial menu). This PR is two mechanical cleanup passes with **no behavior change**: delete the dead code, then untangle the `GameView` naming collision. **78 files, +142 / −2,197.** ### Pass 1 — remove dead extension baggage - **Replay/copy mode**: `FrameData.tileMode` was hard-coded `"live"`; the copy branches in `frame/Upload.ts`, `UploadOptions` (never passed), `applyFullFrame`/`applyFullTiles`/`applyDelta` on the facade and `GPURenderer`, `HeatManager.resetForSeek`, and the seek-upload methods on `TerritoryPass`/`TrailPass` were all unreachable. Also deletes `types/Replay.ts`, `types/FrameSource.ts`, `types/GameUpdates.ts`, `types/Game.ts` (imported only by the types barrel). - **FrameEvents**: trimmed from 14 fields to the 3 actually populated and read (`deadUnits`, `conquestEvents`, `bonusEvents`). The other 11 fed the extension's stats system and were never written or read here. - **GL radial menu**: `RadialMenuPass`, its 4 shaders, and ~10 API methods on facade + renderer had zero callers — the game uses the DOM/d3 radial menu in `hud/layers/RadialMenu.ts`. The pass was constructed and drawn every frame for nothing. - **Facade event system**: `GameViewEventMap` defined 10 event types (`click`, `hover`, `scroll`, …) but only `contextrestored` was ever emitted — input actually flows through `InputHandler` → EventBus → controllers. Replaced the listener map with a single `onContextRestored` callback and deleted `Events.ts`. Also fixed the stale header comment claiming the facade handles user interaction. - **Unused API surface**: removed ~20 facade/renderer methods with zero callers (camera passthroughs like `panTo`/`zoomTo`/`fitMap`/`screenToWorld`, hit-testing queries, SAM replay setters, `setSelectedUnit`, `clearFx`/`setFxTimeFn`, `onFrame`/`afterRender`/fps tracking). Deliberately left alone: `Camera`'s pan/zoom primitives (building blocks for a possible future camera unification) and the `timeFn` plumbing inside the FX passes (deeply embedded as defaults; only the dead renderer-level wrappers were removed). ### Pass 2 — untangle the three GameViews - `render/gl/GameView.ts` → **`MapRenderer.ts`** (class `MapRenderer`). Every importer was already aliasing it as `WebGLGameView` to dodge the collision with the simulation-mirror `GameView` in `client/view/`, so this removes aliasing rather than adding churn. `render/CLAUDE.md` updated. - Deleted the `src/core/game/GameView.ts` back-compat shim (its own TODO asked for this). All 51 importers now import from `src/client/view/` directly via a new 3-line barrel `view/index.ts`. ## Test plan - `tsc --noEmit` clean, `eslint` clean - Full test suite passes (1,385 + 65 server tests) - Manual verification via headless Chromium: started a singleplayer game and confirmed the renderer works end-to-end — terrain draws, spawn-phase overlay shows, territories fill with borders after spawning, player names/flags render, no renderer console errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d96c055df1 |
Better troop management for nations 🤖 (#4239)
## Description:
When human pro players have non-allied players with similar troops next
to them, they wouldn't send out a big attack.
But nations are doing exactly that.
With this PR, they no longer do. On hard and impossible.
On easy and medium they are stupid 😀
```
1. Troop send cap: the nation must retain a minimum fraction of its
strongest non-allied neighbor's troop count (Hard: 75%, Impossible:
90%). Attacks that would drop below this floor are scaled down or
skipped entirely. Allied and same-team neighbors are ignored since
they pose no threat. The cap applies to land attacks, boat attacks,
and random boat attacks.
2. Minimum attack strength: if the capped troop count is less than 20%
of the target's troop count, the attack is skipped as too weak to be
worthwhile. Only applies on Hard and Impossible.
```
_Coded by MiMo 2.5 Pro, reviewed by MiniMax M3_
## 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
## Please put your Discord username so you can be contacted if a bug or
regression is found:
FloPinguin
|
||
|
|
2789db8b96 |
Optimize core simulation hot paths (no behavior change) (#4230)
## Summary Pure performance optimizations to the attack/conquer/cluster hot paths in `src/core`, driven by the full-game perf harness from #4228. **No behavior change**: the final game-state hash is identical before/after on every config tested — world quick run (2 different seeds), giantworldmap, and the default 1800-tick run. ### Changes - **Flat-arithmetic neighbor iteration**: `forEachNeighbor` / `forEachNeighborWithDiag` / `isBorder` / `isOceanShore` are now implemented inside `GameMapImpl` using raw `ref±1` / `ref±width` index math, skipping the per-neighbor `ref()` coordinate validation (`Number.isInteger` etc.). `GameImpl` and `GameView` delegate. - **New `neighbors4(ref, out)`**: zero-allocation, callback-free neighbor query for hot loops (W, E, N, S — same order as `forEachNeighbor`). - **`AttackExecution`**: the per-tile closures in `tick()` / `addNeighbors()` are replaced with reusable neighbor buffers, a cached `GameMap` reference, and integer `smallID()` owner comparisons instead of owner-object lookups. - **`GameImpl`**: the per-conquer `updateBorders` closure is hoisted to a method with a reusable buffer; `removeInactiveExecutions` compacts the executions array in place instead of allocating a new ~4200-element array every tick. - **`PlayerExecution`**: `surroundedBySamePlayer` / `isSurrounded` / `getCapturingPlayer` de-closured (`neighbors4` + integer compares; neighbor visit order preserved, so `getCapturingPlayer`'s Map-insertion-order tie-breaking is unchanged); flood-fill visit closure hoisted out of the while loop. - **`FlatBinaryHeap.dequeue`**: returns the tile directly instead of allocating a `[tile, priority]` tuple per dequeued tile (AttackExecution is the only caller). ### Performance (`npm run perf:game`, same machine, before → after) | run | mean tick | ticks/sec | max tick | |---|---|---|---| | default (world, 400 bots, 1800 ticks) | 9.04 → **7.98 ms** | 111 → **125** | 31.7 → 35.7 ms | | giantworldmap, 600 ticks | 22.5 → **17.4 ms** | 44 → **58** | 52.8 → **36.2 ms** | The giantworldmap tail improvement (max tick −31%) is the most relevant for the 100 ms tick budget. ### Determinism verification Identical `Final hash` before and after on all configs: | config | hash | |---|---| | `--map world --ticks 200 --bots 100` | `5455008589403520` | | same + `--seed second-seed-check` | `5580840142777488` | | `--map giantworldmap --ticks 600` | `37373734953428430` | | default run | `26773450321979388` | ### Tests - New `tests/NeighborIteration.test.ts` pins the exact neighbor iteration orders (W,E,N,S cardinal; dx-major diagonal — conquest order and RNG consumption depend on them) and conquer/border-tile invariants checked mid-battle. - New `tests/FlatBinaryHeap.test.ts` covers heap ordering, clear, and growth. - Full suite passes (122 files / 1386 tests + server tests); lint and prettier clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c6296c0bb1 |
Fix/warship freezing no path (#4151)
**Add approved & assigned issue number here:** Resolves #4113 ## Description: Warships now reject the PatrolTile change when the new one is a different water component. Adds a test ensuring this behavior. ## Please complete the following: - [x] I have added screenshots for all UI updates There are none - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file No texts - [x] I have added relevant tests to the test directory I also have tested in game and tested that the test does indeed fail if my fix is not present. ## Please put your Discord username so you can be contacted if a bug or regression is found: Katokoda |
||
|
|
775ae77e0a |
Fix nations not spawning when random spawn is enabled 🤖 (#4117)
## Description: When random spawn is active, human SpawnExecutions are pre-created in GameRunner.init() and fire on the same tick as NationExecution. Because humans were added first, their SpawnExecution ticked first, called endSpawnPhase() (in singleplayer), and NationExecution then saw inSpawnPhase()=false, found the nation not alive, and deactivated it before ever queuing a SpawnExecution. Two changes fix this: 1. GameRunner.init(): Move nationExecutions() before spawnPlayers() so NationExecution ticks first and queues its SpawnExecution before the human SpawnExecution can end the spawn phase. 2. NationExecution.tick(): After the spawn-phase block, add a guard that waits when spawnExecAdded is true but the nation hasn't actually spawned yet. This prevents NationExecution from deactivating on the very next tick (via !isAlive()) before its queued SpawnExecution has had a chance to fire and give the nation territory. I tested it in singleplaye with and without random spawn and also in public lobbies. Nations now always spawn. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
f3ba95574c |
fix(core): prevent bots from invading/attacking themselves (#3865) (#4014)
Resolves #4094 ## Description: In Free-For-All (FFA) mode where teams default to 0, player isOnSameTeam checks returned false for oneself, allowing players to attack themselves. Consequently, if a bot conquered the targeted tile between queueing a transport ship action and its actual initialization, the target became itself, causing the bot to execute a self-invasion. This fix adds a reflexive check in PlayerImpl.ts's isFriendly method to always treat oneself as friendly. It also adds a safety guard in TransportShipExecution.ts's init method to abort ship execution if the target has shifted to the attacker. ## 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: barfires |
||
|
|
413efed895 |
Add per-recipient cooldown to QuickChatExecution (#4012)
`QuickChatExecution` had no cooldown, allowing a player to spam quick-chat intents and flood a recipient's chat UI. This could bury incoming alliance request notifications, preventing them from being seen or accepted. This fix mirrors the existing emoji cooldown pattern: - Added `quickChatCooldown()` to `Config` (default: 30 ticks / 3 seconds) - Added `canSendQuickChat(recipient)` and `recordQuickChat(recipient)` to `Player` / `PlayerImpl`, tracking outgoing chats per recipient - `QuickChatExecution.tick()` now checks `canSendQuickChat` before displaying and records before the display calls (so the cooldown is always written even if display throws) |
||
|
|
38f0709e53 |
fix(core): destroy defense posts on tile capture instead of downgrading and transferring ownership (#1563) (#4016)
## Description: Capturing defense posts previously demoted their level by 1, and transferred ownership to the invading player if their level was still above 0. The expected strategic behavior is that defense posts should always be destroyed (deleted) upon capture. This fix updates PlayerExecution.ts's structure tick loop to immediately destroy the Defense Post unit via u.delete(true, captor) instead of transferring ownership. It also rewrites the corresponding unit tests in PlayerExecution.test.ts to verify the complete destruction of Defense Posts of all levels (including level 2+) when the tile owner changes. ## 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: barfires |
||
|
|
cbf38ffb81 |
Fix destroyed cooldown structures reappearing in game (#3997)
## Description: Stop deleted missile silos from reappearing after cooldown expiry before https://github.com/user-attachments/assets/714d3580-b60d-479e-8e45-6ee065b79d54 ## 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 |
||
|
|
41ef675e98 |
Improve Notification Panel (#3913)
Resolves #3910 ## Description: - Split the events HUD into two components: a new **`<actionable-events>`** that owns alliance prompts (request / renew) and a slimmed-down **`<events-display>`** for everything else. - Reworked `<events-display>` into two visual tiers: dim/scrolling tier 2 on top (trade results, unit losses, donations, alliance status), prominent tier 1 anchored at the bottom (inbound nukes, naval invasion, attack requests, alliance broken, conquered player, chat). Tier 2 caps at the 4 newest entries; events expire after 8s. - Added a transient **+gold pip** above the gold pill in `<control-panel>`, animated with a small fade-in. Fires for trade ships, trains, donations, and conquest. Trade-ship and train arrivals are removed from the events scroll since they're surfaced here instead. - New `MessageType.NUKE_DETONATED` and a server-side emission in `NukeExecution.detonate` — once an inbound nuke lands or gets intercepted, the inbound warning vanishes and a "detonated" entry takes its place. - `displayMessage` gained optional `unitID` and `focusPlayerID` params so events can link to a unit or a player. Unit captures and destructions now navigate to the unit's last tile when clicked; donations navigate to the other player. - ActionableEvents card width matches `<events-display>`; cards persist until the user clicks Accept/Reject/Renew/Ignore or the server-side request timeout expires. - Removed the in-events category filter UI and the gold-amount banner — `<events-display>` is now a lightweight log that hides entirely when empty. <img width="570" height="444" alt="Screenshot 2026-05-21 at 1 42 30 PM" src="https://github.com/user-attachments/assets/f103efb3-0e11-4b72-a11b-91ff6896177c" /> <img width="430" height="296" alt="Screenshot 2026-05-21 at 1 41 34 PM" src="https://github.com/user-attachments/assets/ae58475a-b252-4aa6-9ce5-99dea7575ce3" /> ## 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 |
||
|
|
0eb8578996 |
Fix nations not spawning in singleplayer when player picks fast
NationExecution gated its first SpawnExecution by the same attackRate/attackTick throttle used for AI actions, so a nation could wait up to ~100 ticks before scheduling its spawn. In singleplayer the human's spawn ends the spawn phase immediately, stranding any nation that hadn't yet reached its attackTick — on the next tick its NationExecution sees inSpawnPhase()=false and isAlive()=false (no tiles), and deactivates itself. First spawn now fires on tick 1, gated by a one-shot flag to avoid queuing duplicates. The attackRate cadence is preserved for subsequent re-spawns so nations still hop locations during the spawn phase. |
||
|
|
2e17fb5184 |
fix: remove double x() dereference in MIRV separation point calc (#3940)
mg.x() takes a TileRef and returns a number (x coordinate). The code was calling mg.x(mg.x(tile)), feeding the numeric result back into x() which expects a TileRef. This produces an incorrect midpoint for MIRV warhead separation, causing warheads to spread from a wrong position on the map. If this PR fixes an issue, link it below. If not, delete these two lines. Resolves #(issue number) ## Description: Describe the PR. ## 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 |
||
|
|
f1d162825e |
feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 ## Description: Remove the singleplayer spawn countdown so the game starts when the player spawns, spawn nations immediately after player spawn, and align game timer/max-timer timing with the new start point. Added a singleplayer regression test for spawn-immunity timing (GameImpl.test.ts) and updated spawn-phase loop tests to use gameType: GameType.Public where singleplayer behavior is not under test (e.g. MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase() timeout hangs after the new singleplayer start logic. https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756 ## 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 |
||
|
|
23b8eaa04f |
perf(AttackExecution): migrate hot loops to forEachNeighbor (#3820)
Resolves [#3815 ](https://github.com/openfrontio/OpenFrontIO/issues/3815) ## Description: Migrates the five `mg.neighbors(tile)` call sites in `src/core/execution/AttackExecution.ts` to the existing zero-allocation `forEachNeighbor(tile, cb)` helper (`src/core/game/GameImpl.ts:1107`). `neighbors()` allocates a fresh `TileRef[]` of length up to 4 on every call; `forEachNeighbor` invokes a callback with no allocation. The helper is already used in `PlayerExecution`, `GameImpl.updateBorders`, and similar hot paths — this PR finishes that rollout in the hottest attack loop. Affected sites in `src/core/execution/AttackExecution.ts`: | Original line | Context | | --- | --- | | 277 | `tick()` border check around `tileToConquer` | | 326 | `addNeighbors()` outer neighbor loop | | 335 | `addNeighbors()` nested `numOwnedByMe` count | | 370 | `handleDeadDefender()` border test | | 375 | `handleDeadDefender()` neighbor scan | Notes: - `handleDeadDefender` previously narrowed `this.target` from `Player | TerraNullius` to `Player` via the early-return check. Inside an arrow callback that narrowing isn't preserved by TypeScript, so the function now captures `target: Player` once after the check. Same pattern as `tick()`'s `targetPlayer` cache. - For loops that previously used `break` or `.some()`, I used a small flag variable to short-circuit the callback body. With at most 4 neighbors per tile the extra callback invocations are negligible compared to the eliminated array allocation. Behavioral guarantees: - Iteration order is identical (`forEachNeighbor` enumerates the same tiles in the same order as `neighbors()`). - No RNG, no float math, no wire-format changes. - Determinism preserved. Verification: - `npm test` — all 994 tests pass across 104 files. The 22 attack-related tests in `Attack.test.ts`, `AttackStats.test.ts`, and `AiAttackBehavior.test.ts` exercise the migrated code paths end-to-end. - `npx tsc --noEmit` — clean for the changed file. - `npx prettier --check` — clean. Briefly flagged in `#development` per the contribution guidelines before opening. ## 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: sxndrexe |
||
|
|
eca5794ebb |
Chore(deps): Update and remove dependencies (#3819)
## Description:
Only mentioning removals/major updates/notable changes below, not all
minor upgrades.
### Removed:
- "@aws-sdk/client-s3": not used anywhere (was used in Archive.ts
previously)
- chai, "@types/chai", sinon-chai: not used anywhere, probably leftover.
Vitest uses a bundled version of Chai for its expect asserations under
the hood too.
- protobufjs, "@types/google-protobuf": not used anywhere, probably left
from evan's experiment with it? Removed from vite.config.ts too.
- "@types/jquery": not used anywhere, probably leftover
- sinon, "@types/sinon": not used anywhere just like chai, probably
leftover. And Vitest provides us with the same functionality.
- "@types/systeminformation": dependency systeminformation was removed
last year, this is an unneeded, deprecated and unmaintained remainder.
- vite-tsconfig-paths: removed, and removed the import and usage in
vite.config.ts and replaced it by adding `tsconfigPaths: true` to the
`resolve` block. Because of this message displayed on running the tests:
"The plugin "vite-tsconfig-paths" is detected. Vite now supports
tsconfig paths resolution natively via the resolve.tsconfigPaths option.
You can remove the plugin and set resolve.tsconfigPaths: true in your
Vite config instead."
- vite-plugin-static-copy: removed, we don't use it anymore (was used in
our vite.config.ts once,, probably before Vite natively supported
copying static assets via its publicDir configuration)
### Updated:
- color.js: v0.5 > v0.6, no breaking change affecting us
- cross-env: v7 > v10. It's a publicly archived repo since Nov 2025. But
before that he got it up-to-date from June 2025, porting to TS, dropping
old Node versions, dependencies etc. Seems still good to use for some
amount of time to come.
- dotenv: v16 > v17, now logs an informational message by default when
it loads an environment file. Can be disabled by using
dotenv.config({quite: true}) if needed.
- ejs: v3 > v5: security patches mostly. Vite still uses v3 btw.
- eslint: v9 > v10. Newly enabled rules by default:
'no-unassigned-vars', 'no-useless-assignment' and
'preserve-caught-error'. Mostly faster and minimum support moved to
higher node versions, which shouldn't be a problem.
- "@eslint/compat": v1 > v2. Minimum supported Node versions, which
should not be a problem.
- intl-messageformat: v10 > v11 no breaking changes that affect us
- jdom: v27 > v29. Faster. Most notably minimum support moved to higher
node v22 version, which should not be a problem. Also, see types/node,
kind of expecting v24 to be installed now.
- nanoid: from v3 to v5, no breaking changes that affect us
- "@opentelemetry/sdk-logs": now that addLogRecordProcessor is removed,
changed Logger.ts to pass an (empty) provider array directly to the
LoggerProvider constructor. Follows the changes in
https://github.com/open-telemetry/opentelemetry-js/pull/5588
- "@tailwindcss/vite": supports vite v8 from 4.2.2, and a fix for it in
4.2.4
- tailwindcss: supports vite v8 from 4.2.2
-- in 4.1.15 (we were already above this version) break-words was
deprecated in favor of wrap-break-word. But break-words, which we use in
15 places, will still work as expected
(https://github.com/tailwindlabs/tailwindcss/pull/19157). Same goes for
also deprecated "order-none".
- "@types/node": from v22 to v24, assuming most now use node 24
- vite v7 > v8:
-- is now on 8.0.10 so first bugs are out of it, while v8 itself also
fixed a big number of bugs.
-- in vite.config.ts, fixed Ts error/compilation issue by changing the
manualChunks option in build.rollupOptions.output to use the function
syntax, which is required by the updated types instead of the object
syntax.
- zod: no changes that affect us
### Prettier:
Updated only because of (new because of update?) Prettier errors for
files untouched in this PR originally:
- PathFinder.Parabola.ts
- WorkerMessages.ts
- ClanModal.handlers.test.ts
- ClanModal.rendering.test.ts
- CONTRIBUTING.md
- README.md
### ESLint:
Fixes needed to silence errors coming from newly enabled recommended
rules 'no-useless-assignment' and 'preserve-caught-error':
For 'no-useless-assignment' (default assignment never used because of
unreachable code or they are guaranteed to get a value, so they can be
undefinedat the start. Exception was AttackExecution, so made the
default value of 0 the default case in the switch statement):
- ClientGameRunner
- GameModeSelector
- NameBoxCalculator
- StructureDrawingUtils
- TerritoryLayer
- Diagnostics
- GameRunner
- ColorAllocator
- DefaultConfig
- AttackExecution
- AiAttackBehavior
- Worker.worker
- GamePreviewBuilder
For 'preserve-caught-error', disabled the rule here because the possible
fix `{cause: error}` was introduced in ES2022 while we're still on
target ES2020 currently:
- GameServer
- Privilege
_Error: The value assigned to 'gameMap' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'timeDisplay' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'scalingFactor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'radius' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'teamColor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'gl' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'power' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'tickExecutionDuration' is not used in
subsequent statements. (no-useless-assignment)
Error: The value assigned to 'selectedIndex' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'mag' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'speed' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'matchesCriteria' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'shouldContinue' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'description' is not used in subsequent
statements. (no-useless-assignment)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)_
All tests pass. TypeScript and ESLint errors resolved.
## 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
---------
Co-authored-by: Copilot <copilot@github.com>
|
||
|
|
58ec8b280f |
Nations: Fix city farming + reactive defense posts + fix nuked territory capture 🛡️ (#3814)
## Description: Would be very good to get these fixes last minute into v31. - **Farming nations for cities is fixed**: Here you can see city farming in action, vari farms Finland: https://www.youtube.com/watch?v=J4J0ajlnSHs&t=137s - Lots of 125k gold cities... Now nations will build defense posts instead of cities: - **Nations now build defense posts reactively** instead of through the normal structure build order. When a nation is under significant land attack (incoming/own troops >= 35%), it places a defense post near the attack front -- skipped on Easy, capped at 1 on Medium, and scaled by the incoming troop ratio on Hard/Impossible. Posts spread along the front. The old `defensePostValue()` placement path is removed. - **Nations now capture nuked territory.** `hasLandBorderWithTerraNullius()` was incorrectly filtering out tiles with fallout, causing the `nuked` attack strategy to never dispatch a attack. Big bug . - [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 |
||
|
|
bcdc2126c6 |
bugfix: SAM was only reloading when not in cooldown (#3817)
## Description: reloadMissile() was inside the isInCooldown() block. For level-2+ SAMs, isInCooldown() returns queue.length === level, so after firing one of two missiles (queue.length = 1 < level = 2) the SAM is not in cooldown — meaning expired timers were never cleaned up. Stale queue entries caused subsequent shots to be treated as still-cooling even after the cooldown elapsed. SAM execution now mirrors the missile silo execution ## 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 |
||
|
|
02353cf77d |
Fix nuke cancellation on alliance to use blast radius
cancelNukesBetweenAlliedPlayers previously only cancelled a nuke if its exact target tile was owned by the new ally. This meant nukes aimed at neutral or own tiles near allied territory would survive alliance formation and still land (and break the alliance). Now uses wouldNukeBreakAlliance — the same blast-radius logic used by maybeBreakAlliances on impact — so a nuke is cancelled if its blast would have meaningfully hit the ally's tiles or structures. Also switches from the exhaustive listNukeBreakAlliance (scans all players) to wouldNukeBreakAlliance with a single-player allySmallIds set for early-exit performance. |
||
|
|
ccb80f4245 |
Fix warship diagonal chase and improve trade ship capture reliability (#3807)
## Description: The warship pathfinder operates on a 2x downscaled mini-map, and upscaling mini-map paths back to full coordinates produces diagonal interpolated steps. At close range (< 20 tiles), the entire path consists of these diagonal moves, causing the warship to approach the trade ship at an awkward angle and never converge cleanly. ## 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 |
||
|
|
742a544a69 |
2661 PR 3/3 Warship Manual Override, Aggro Override, and Heal-at-Port Command (#3501)
Part of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661) (split into 3 PRs so they are not too large..) ## Description: Part 3/3 of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661). This PR adds the retreat control and override behavior for warships: - Manual override: moving a warship manually cancels retreat and suppresses auto-retreat for 5 seconds - Aggro override: a retreating warship will aggro a nearby enemy transport or warship before continuing retreat - Heal-at-port command for sending a warship to a friendly port manually - Friendly-port validation for HealAtPortExecution - Regression tests for manual override, aggro override, and heal-at-port behavior ## 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: zixer._ --------- Co-authored-by: iamlewis <lewismmmm@gmail.com> Co-authored-by: evanpelle <evanpelle@gmail.com> |
||
|
|
bd6c63b6ea |
Nation ship improvements (#3724)
## Description: Nations preform poorly on large water maps, these changes aims to improve how they handle and react to water combat. Adding the ability for hard and impossible nations to retaliate against incoming transport ships. Adding the ability for ships to be moved if the nation is not able to build a new ship. ## 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: babyboucher --------- Co-authored-by: iamlewis <lewismmmm@gmail.com> Co-authored-by: FloPinguin <25036848+FloPinguin@users.noreply.github.com> |
||
|
|
7654537a00 |
Much better river handling for nations and tribes! 🏞️ (#3786)
## Description: In this example, the two nations DONT see each other as neighbors, but as ISLANDERS. Because they dont have a direct border connection, there is water in between. <img width="526" height="329" alt="image" src="https://github.com/user-attachments/assets/cf2c15b5-7793-4445-afd2-920d6cd50a2a" /> This is a big problem, because most of the logic in AiAttackBehavior gets ignored. Only the "islander" strategy runs (late, because its a not-important strat). ### Summary - `PlayerImpl.neighbors()` now includes cross-water neighbors: a new `shoreReachableNeighbors()` helper samples every 10th shore border tile and looks up to 5 tiles in each cardinal direction across water, finding land owners on the other side (covers rivers up to 4 tiles wide). - `AiAttackBehavior.maybeAttack()` extends the `hasNonNukedTerraNullius` check to also trigger on TN detected via `player.neighbors()`, so nations notice and pursue TN that is only reachable across a river. - `sendAttack()` uses a new `hasLandBorderWithTerraNullius()` land-only adjacency check to decide between a land attack and a boat attack for TN, rather than `sharesBorderWith()` which includes water tiles. - Added `sendBoatAttackToNearbyTerraNullius()`: when no TN land is directly adjacent, the AI scans its shore border tiles for unowned land across water and dispatches a transport ship. ### Also works for Tribes! Tribes can boat rivers now, really cool. https://github.com/user-attachments/assets/382e85aa-c437-4e0c-afc2-0c381432da3d ## 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 |
||
|
|
692028d033 |
Make nations more forgiving toward humans on Easy difficulty 🕊️ (#3791)
## Description: - Nations on Easy no longer betray human allies - Nations on Easy now attack humans only 25% of the time (down from 50%) - Increased Easy reaction tick range (65-100, up from 65-80) so nations respond more slowly - Raised MIRV steamroll-stop thresholds on Easy (city gap multiplier 1.5 -> 2, min leader cities 15 -> 20) and slightly tuned Medium/Hard thresholds ## 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 |
||
|
|
408d0e4862 |
Improve TribeExecution deleteAllStructures 🏛️ (#3777)
## Description: `deleteAllStructures` did not check for `isMarkedForDeletion`, not very clean. Now its clean. Also rename to `deleteNextStructure` because its not possible to delete more than 1 structure at a time. ## 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 |
||
|
|
8099b9fad7 |
Massive nation improvement 🤖 (#3761)
## Description: - Hard / Impossible nations in team games auto-stop trading with all enemies - If there are a LOT of nations on the map (Enzo stream with 400 nation HvN private games) they no longer start with a city, they start with eco (port / factory) because they cannot gain much gold from bot-killing - Impossible nations built way too many missile silos sometimes, caused by the SAM overwhelming logic. Fixed now. - In public HvN games with 5M starting gold, nations placed their structures way too fast, which slowed down their expansion. And humans could easily cause a lot of damage with one atom bomb. Now their first structure is a SAM (on hard / impossible) and they wait between their earlygame structure placements. - Nations now spread out their port placements more evenly - Nations are now able to attack much stronger enemies in team games (They can expect donations) - Improve performance a bit by adding more early-returns (Dont run any nuking logic if nukes are disabled, no alliance logic if alliances are disabled, no boating logic if transport boats are disabled, ...) - Fix some of the "cannot send troops" messages in the console (DonateTroopExecution) - Nations build their first missile silo sooner, they should also build more SAMs - Nations spend their gold better after reaching the save-up-target (previously they stopped nuking) - Optimized save-up-targets for team games - The richest impossible nation is nuking very dense players now (lot of structure levels on a small island) ### How does a 5M gold HvN start look like now? https://github.com/user-attachments/assets/e9da89c3-c0d4-4144-a741-3101746b16da ## 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 |
||
|
|
c0febacb8e |
2661 PR 2/3 Warship Port Healing, Docking Capacity, and Waiting Behavior (#3499)
Part of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661) (split into 3 PRs so they are not too large..) ## Description: Part 2/3 of [#2661](https://github.com/openfrontio/OpenFrontIO/issues/2661). This PR adds port-based healing and docking behavior: - Passive healing near friendly ports - Active docked healing pool scaled by port level and shared across docked ships - Docking radius and capacity-by-port-level behavior - Waiting behavior near full ports until a slot opens - Auto-undock once fully healed For the active healing, it works like `ActiveHeal = (PortLevel * 5) / DockedShipsAtThatPort` Ex: 1 ship at level 1 port -> +5 HP/tick 1 ship at level 2 port → +10 HP/tick 2 ships at level 3 port → +7.5 HP/tick each Includes regression tests covering healing math and docking/waiting behavior. ## 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: zixer._ |
||
|
|
37079e6a05 |
2661 PR 1/3 Warship Retreat Core, Blue UI Signal, and Transport-First Target Priority (#3498)
Part of #2661 (split into 3 PRs so they are not too large..) ## Description: Part 1/3 of #2661. This PR adds warship retreat basics, a blue retreating UI state, and updates target priority. Added: - Retreat state handling - Blue visual for retreating warships - Target priority: transport > warship > trade - Tests for retreat and target priority Example video: https://youtu.be/2hE2qeOeY48 Ship retreating: <img width="630" height="488" alt="image" src="https://github.com/user-attachments/assets/56d3e6d5-08af-453d-afe5-ee21dd6f3414" /> Ship healing: <img width="483" height="311" alt="image" src="https://github.com/user-attachments/assets/aeaf2239-bb81-444f-84ef-62dbcb48fddf" /> Back to being deployed: <img width="585" height="358" alt="image" src="https://github.com/user-attachments/assets/875828a2-8a24-4593-ac76-26426bb81057" /> ## 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: zixer._ |
||
|
|
f7716c7d42 |
Little Console Cleanup 🧹 (#3741)
## Description: Fixes these console warnings from bots: <img width="591" height="94" alt="Screenshot 2026-04-19 033624" src="https://github.com/user-attachments/assets/6ee79302-e2a7-4195-94e5-c1f455eb1799" /> Removes some spammy logs, they dont seem to be helpful? <img width="271" height="174" alt="Screenshot 2026-04-19 033739" src="https://github.com/user-attachments/assets/70122506-e8fb-4a72-b73e-08e72fe222bd" /> <img width="284" height="656" alt="Screenshot 2026-04-19 033646" src="https://github.com/user-attachments/assets/4b4ebef2-e191-4947-9615-0e26cd9bf075" /> ## 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 |
||
|
|
29a1e8dfda |
feat: multi-warship selection with Shift+drag box (#3677)
Resolves #3666 ## Description: Adds RTS-style box selection for warships. Hold Shift and drag (desktop) or long-press and drag (touch/mobile) to draw a selection rectangle — all player-owned warships inside get selected at once. A subsequent click/tap on water sends them all to that location. - `SelectionBoxLayer` — pixel-dashed rectangle in world-space, player territory color; shared between desktop and touch - `UILayer` — same pulsing selection outline on each box-selected warship; clears correctly when switching between single/multi selection - `UnitLayer` — finds warships in screen rect, filters inactive ships before sending; touch support included - `InputHandler` — Shift+drag and touch long-press+drag both emit selection box events; cursor becomes crosshair on Shift; discards active ghost structure on Shift press; configurable via `shiftKey` keybind - `Transport` — single atomic `move_multiple_warships` intent (no split on socket drop) - `Schemas` + `ExecutionManager` + `MoveMultipleWarshipsExecution` — server fans out atomic intent into individual `MoveWarshipExecution` per ship - `DynamicUILayer` — `MoveIndicatorUI` chevron animation on target tile for both single and multi move - `UnitDisplay` — warship tooltip Shift hint via `translateText` - `HelpModal` — new hotkey row: Shift + drag → select multiple warships ## 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 ## UI update ### Mouse + Keyboard https://github.com/user-attachments/assets/3f35ab5e-1f3c-4c5d-bc4f-aabccf64dc60 ### Touch https://github.com/user-attachments/assets/0d6aec3f-44fa-4fee-b5c6-b267b9b14d79 ## ## Please put your Discord username so you can be contacted if a bug or regression is found: fghjk_60845 |
||
|
|
12b06fa0b2 |
Pathfinding Fixes (Water Nukes / Lakes) 💧 (#3714)
## Description: Fixes water-pathfinding errors that started appearing after the first water nuke and persisted across the rest of the match. Users reported warships "getting stuck" (stopped moving). <img width="374" height="281" alt="image" src="https://github.com/user-attachments/assets/de38b8f1-c4d8-469e-b3a7-d0cef4dfb772" /> ### Summary - The new `AbstractGraphBuilder.buildClusterConnectionsFromCache` was buggy _(The cached edge costs reused by "clean" clusters were keyed by tile pair without their original `(clusterX, clusterY)`, so a boundary edge could be re-stamped with the wrong cluster and become untraversable by the query-time single-cluster bounded A*. The cache now stores `{ cost, clusterX, clusterY }` and `buildClusterConnectionsFromCache` preserves the original attribution when re-adding the edge.)_ - Warships: `findTargetUnit` now skips trade ships that are not in the warship's water component, avoiding pathfinding to provably unreachable targets. - Warships: On `patrol` `NOT_FOUND`, clear `targetTile` so the warship picks a new target. This is a defensive guard for the rare case where a water nuke splits the component between target selection and pathfinding - without it, the warship retries the same now-unreachable target every tick and spams the log forever. ### Test - Added a Warship test verifying that trade ships in a different water component are not targeted. ## 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 |
||
|
|
bcd9cd6af5 |
Cache shared-water computation for nation port placement 💧 (#3696)
## Description: - Cache `sharedWaterComponents` globally with a 30-tick (~3s) TTL so all nations share one `O(total_border_tiles)` pass instead of each nation re-scanning every other player's border on every call. - Treat ocean as always-shared: any ocean neighbor short-circuits as a valid port site, skipping the `getWaterComponent` lookup in both the build pass and the per-tile port check. - Exclude bots and mutually-embargoed players from the trade-partner candidate set, so nations no longer avoid port sites that only "share" water with a player they can never trade with. Port placement is not time-critical, so the 3-second staleness is acceptable and lets the expensive build amortize across many attack cycles. ### Performance Benchmarked on World map (2000×1000, 61 nations) with the realistic call pattern of ~3 nations invoking `sharedWaterComponents` per tick: - **Before (main):** ~414 μs per tick - **After:** ~8 μs per tick amortized (29/30 ticks hit the warm cache; 1/30 rebuilds) - **~50× faster** on this AI hot path ## 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 |
||
|
|
0801cad0b5 |
Fix NaN coordinates in Warship patrol logic 🚢 (#3697)
## Description: This PR fixes the `Invalid coordinates: NaN,NaN` crash during Warship patrol execution. ### Root cause `WarshipExecution.randomTile` picks a patrol destination inside `warshipPatrolRange / 2` of the current patrol tile. When a search fails to find a valid tile, the range expands by 50% per retry (`100 → 150 → 225 → 337`) and becomes odd. Once odd, `warshipPatrolRange / 2` is a float (e.g. `112.5`), which is handed straight to `PseudoRandom.nextInt`: ```ts Math.floor(this.rng() * (max - min)) + min; ``` With a float `min`, this returns `integer + float` - a float. Despite its name, `nextInt` was silently returning a non-integer. From there: - `x = mg.x(patrolTile) + floatOffset` → float - `mg.isValidCoord(floatX, floatY)` → `true` (only bounds were checked) - `mg.ref(floatX, floatY)` → `yToRef[floatY] + floatX` → `undefined + float` → `NaN` - `hasWaterComponent(NaN, …)` → `miniMap.ref(NaN, NaN)` → **throw** ### Why this only started crashing recently The float‑leaking `nextInt` bug has been latent since at least the pathfinding refactor (#2866, January), which introduced the `hasWaterComponent` check. It was invisible because the guard directly above it short‑circuited on `NaN`: ```ts if (!this.mg.isOcean(tile) || (!allowShoreline && this.mg.isShoreline(tile))) continue; ``` For a `NaN` tile ref, `terrain[NaN]` is `undefined`, so: - `isOcean(NaN)` → `Boolean(undefined & OCEAN_BIT)` → **`false`** - `isLand(NaN)` → **`false`** - `isWater(NaN)` → `!isLand(NaN)` → **`true`** Before: `!isOcean(NaN)` was `true`, execution hit `continue`, and the poisoned ref never reached `hasWaterComponent`. The "Trading in lakes" PR (#3653) relaxed that single line to allow patrol on lakes: ```diff - if (!this.mg.isOcean(tile) || ...) continue; + if (!this.mg.isWater(tile) || ...) continue; ``` Because `isWater(NaN)` is `true`, `!isWater(NaN)` is now `false` - execution falls through to `hasWaterComponent(NaN, …)` and crashes. #3653 didn't introduce the bug; it just happened to remove the accidental NaN filter that was hiding it. ### Changes - **`PseudoRandom.nextInt`** - root‑cause fix. Floors both `min` and `max` so `nextInt` always returns an integer regardless of what callers pass. Future callers can't re‑trip this trap. - **`WarshipExecution.randomTile`** - replaced the unsafe `this.warship.patrolTile()!` non‑null assertion with a proper `undefined` guard that returns early. - **`GameMap.isValidCoord`** - defense in depth: also requires `Number.isInteger(x)` and `Number.isInteger(y)`. Non‑integer coords can still be produced outside `nextInt` (trig, arithmetic); this makes `ref()` fail loudly at the boundary instead of silently producing `NaN` refs. ### Original stacktrace Please paste the following in your bug report in Discord: Game crashed! game id: gGicMpDh client id: wXE5SpT2 Error: Invalid coordinates: NaN,NaN Message: Error: Invalid coordinates: NaN,NaN at at.ref (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:64853) at r_.hasWaterComponent (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:243326) at l_.hasWaterComponent (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:260740) at b1.randomTile (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:92634) at b1.patrol (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:91728) at b1.tick (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:89996) at https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:251463 at Array.forEach (<anonymous>) at l_.executeNextTick (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:251383) at p_.executeNextTick (https://nightly.openfront.dev/assets/Worker.worker-DL_guV2P.js:31:271256) Discord: https://discord.com/channels/1284581928254701718/1494336024740888667 ## 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 |
||
|
|
ae96eb7e98 |
Fix nation nuke crash when attacker has no remaining tiles 🛡️ (#3703)
## Description: Fixes a game crash (`Error: array must not be empty`) thrown from `PseudoRandom.randElement` when a nation tries to pick a nuke target whose territory no longer exists. ## Root cause `NationNukeBehavior.findBestNukeTarget` calls `findIncomingAttackPlayer`, which iterates the player's `_incomingAttacks`. `AttackImpl` instances can linger in this array past the point where the attacker has lost all tiles — the attack is only removed on explicit `delete()` and `removeOnDeath` cleanup isn't guaranteed to run before other executions tick within the same turn. A dead attacker gets returned as the nuke target, `randTerritoryTileArray` samples their (empty) territory, and `randElement` throws on the empty array. ## Fix - `Player.incomingAttacks()` now filters out attacks whose attacker is no longer alive, so consumers can't observe stale references from mid-tick deaths. - `randTerritoryTile` guards against `numTilesOwned() === 0` before falling back to `randElement(p.tiles())` as a defense-in-depth safeguard at the util level. - `PlayerImpl.toUpdate()` now uses the `incomingAttacks()` / `outgoingAttacks()` accessors (rather than the raw `_` arrays) so serialized client state stays consistent with the server-side view. `outgoingAttacks()` is intentionally left unfiltered, the engine relies on seeing in-flight attacks during their retreat phase after a target is conquered (attack merging/cancellation in `AttackExecution.init`). ### Bug report from discord Please paste the following in your bug report in Discord: Game crashed! game id: LQDSWbh6 client id: JjwysSLN Error: array must not be empty Message: Error: array must not be empty at sa.randElement (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:28:39166) at H1 (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116429) at Jc (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:116135) at G1.maybeSendNuke (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:117597) at n5.tick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:171764) at https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251463 at Array.forEach (<anonymous>) at c.executeNextTick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:251383) at b.executeNextTick (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:271256) at S_ (https://main.openfront.dev/assets/Worker.worker-DoPM94lr.js:31:366356) ## 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 |
||
|
|
9821e8e041 |
Add host cheats for streamers (Specifically Enzo) ⭐ (#3671)
## Description: - Adds a "Host Cheats" toggle in the private lobby options section that reveals a dedicated section with four host-only cheats: infinite gold, infinite troops, gold multiplier, and starting gold - Only the lobby creator receives the cheat effects in-game (checked via `isLobbyCreator` in DefaultConfig) - Joining players see active host cheats displayed as yellow badges in the lobby UI - Adds `hostCheats` optional object to `GameConfigSchema` and wires it through the server config update whitelist - Raises the intent size limit for `update_game_config` messages (lobby-only, not stored in turn history) to prevent rate-limiter kicks (I always got too-much-data-kicked after selecting "host cheats" lol) <img width="861" height="525" alt="image" src="https://github.com/user-attachments/assets/51e51ec4-c2e8-46ca-b258-11a93487964f" /> <img width="933" height="825" alt="image" src="https://github.com/user-attachments/assets/5acbd38d-2097-42e1-ba78-0fb17d6afe82" /> ## 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 |
||
|
|
35a64fa0d9 |
Big Water-Nukes Performance Improvements 💧 (#3668)
## Description: ### 1. Water Magnitude Calculation Optimization (WaterManager.ts) * Boxed BFS Approach: Refactored the water magnitude recomputation to use "Dirty" and "Seed" boxes. Instead of a global update, the system now only recalculates magnitudes within a specific radius of the affected area, significantly reducing CPU load after water-nuke-explosions * Shoreline Bit Optimization: Narrowed the scope for updating shoreline bits to a 2-ring neighborhood around converted tiles, avoiding unnecessary checks across the entire map. Performance test on the world map: - AtomBomb (r=30): 24ms (was 344ms with global BFS), 2,993 changed tiles (was 630k) - Massive (r=200): 178ms (was 378ms), 130k changed tiles (was 654k) ### 2. Pathfinding Rebuild Staggering (PathFinder.ts, TradeShipExecution.ts, TransportShipExecution.ts) * Distributed Rebuilds: Introduced a staggering mechanism in WaterPathFinder. Ship pathfinders now wait a randomized/distributed number of ticks (0 - 5 seconds) before rebuilding after a water graph change. * CPU Spike Mitigation: By spreading out these expensive A* rebuilds over time, we prevent lag when hundreds of ships attempt to re-path simultaneously * Like Mole said it: "Pretty realistic I;d say the capitan needs a second to realize the big nuke on the left opened a new path" From a performance test on the big new Luna map: Graph rebuild: 256.4ms Pathfinder-Rebuild of 329 ships (Including other Executions): 1564.4ms (No longer noticeable, spread over 5s) ### 3. Performance Refinements * Simplified deep ocean magnitude logic within the optimized BFS flow. * Improved memory efficiency by utilizing clipped BFS wavefronts. ## 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 |
||
|
|
17c1a6300f |
Trading in lakes 🚤 (#3653)
## Description: - Widened port placement and warship spawn/patrol checks from `isOcean`/`isOceanShore` to `isWater`/`isShore`, so ports can be built on lake shores and ships can operate on lakes, we discussed it here: <img width="996" height="423" alt="image" src="https://github.com/user-attachments/assets/acf1e970-9631-4848-a0ed-6d0470616e1d" /> - Filtered `tradingPorts()` by water component so ports only attempt trades with reachable ports - prevents silent path-not-found failures across disconnected water bodies - Applied the same water component filter when a captured trade ship reroutes to its new owner's nearest port - Removed the `WaterManager` fallback that force-marked isolated water-nuked-tiles as ocean (no longer needed since lakes are now navigable) - Added a check to prevent nations from building ports on water bodies that aren't accessible to other players ## 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: Evan <evanpelle@gmail.com> |
||
|
|
7f7cbba12f |
Water-Nukes 💧 (#3604)
## Description: Adds a new `waterNukes` game config option that causes nuclear detonations to convert land tiles into water instead of just leaving fallout. When enabled, nuked land tiles are batched and converted to water each tick, with full terrain metadata updates including: - Ocean bit propagation from adjacent ocean tiles (BFS flood fill) - Magnitude recomputation via BFS from remaining coastlines - Shoreline bit fix-up in a 2-ring neighborhood around converted tiles - Minimap terrain sync (majority-rule downsampling) - Throttled water navigation graph rebuild (every 20 ticks) for ship pathfinding - Ship executions detect graph rebuilds and refresh their pathfinders - TransportShips auto-retreat if their destination becomes water - Water nuke craters use a smoothed angular noise ring with a bounding-box scan instead of the regular per-tile random coin flip with BFS, producing clean blob-shaped craters without scattered land pixels that players would have to boat to individually The `TerrainLayer` now incrementally repaints tiles that changed terrain type, and tile update packets encode the terrain byte alongside tile state so clients can reflect water conversions in real time. When `waterNukes` is disabled, behavior is unchanged (fallout only). Includes a new test suite (WaterNukes.test.ts) covering the conversion pipeline, ocean propagation, magnitude recalculation, shoreline updates, and minimap sync. Also adds a new public game modifier for the special rotation. ### The only problem A bit of lag on impact. But otherwise it works great and is fun. Maybe needs some followup improvements if it gets merged. I think its very cool in baikal / four islands team games. Chip away the territory of your opponents. Its also fun to turn The Box / Alps into a water map (its actually possible to boat-trade then) ### Media Video does not show the updated craters https://github.com/user-attachments/assets/aed8bf08-0e94-4484-b997-4de11ae313d9 Updated craters (no tiny islands after impact): <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e896870b-bc9d-493d-8bc8-b3a5427d69d3" /> <img width="1472" height="920" alt="image" src="https://github.com/user-attachments/assets/677065aa-0159-48cd-af44-a91b0f57adfc" /> <img width="1296" height="892" alt="image" src="https://github.com/user-attachments/assets/886ffaba-541f-4e46-97c6-ce963f632fe0" /> ## 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 |
||
|
|
21c286189e |
Perf/fix(NukeExecution/NukeTrajectoryPreviewLayer): unnecessary fetch and pass to listNukeBreakAlliance (#3546)
## Description: Perf/fix: listNukeBreakAlliance doesn't ask for allySmallIds and doesn't do anything with it. But both NukeExecution and NukeTrajectoryPreviewLayer do fetch and pass allySmallIds to it. Make allySmallIds optional, have wouldNukeBreakAlliance (which does use allySmallIds) handle it potentially being undefined, and remove fetch and pass of allySmallIds to listNukeBreakAlliance. ## 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 |
||
|
|
eb51853b05 |
Perf/Fix: spawn and other functions that need closest by unit (#3243)
## Description: Performance improvements. - **PlayerImpl**: for _nukeSpawn_, cache config to const. - **Other files**: for nukeSpawn and other functions doing the same, introduce findClosestBy function. - for **TradeShipExecution**, with the move from _distSortUnit_ to _findClosestBy_, also add check if port isActive, !_isMarkedForDeletion_ and !_isUnderConstruction_. These checks should have been there already, so now do it in one go to make use of the predicate isCandidate in findClosestBy. - for **TradeShipExectution.test.ts**, add mock functions for _isMarkedForDeletion_ and _isUnderConstruction_ because of the above. Also, set Unit tiles and Pathfinding node to actual valid TileRefs for the testing map. This prevents NaN as return value from manhattanDist. This problem was already present with the use of distSortUnit, but that function just did NaN - NaN, returned the first and only port unit in the array and called it a day. For findClosestBy we have to make sure the predicate manhattanDist actually returns a number instead of NaN so we need actually valid tiles. We now have a working test instead of a test that actually silently failed like before. - **PlayerImpl**: _warshipSpawn_ and _nukeSpawn_: Make use of the isCandidate predicate of findClosestBy to have warshipSpawn not return ports under construction or (smaller change) inactive. This fixes a bug i have seen right away (where Warship spawns from under construction Port). Same for _nukeSpawn_ silos, don't return inactive silo just to be sure now that we can easily add it to isCandidate predicate anyway. This costs no performance in the _nukeSpawn_ benchmarks actually. This should as a by-effecft fix an edge case bug i have seen, where a nuke is sent from a phantom silo. Some of this goes along with PR #3220 since playerImpl buildableUnits makes use of the underlying spawn functions via canBuild. Just like ConstructionExecution does. But i didn't want to add more to PR 3220 since there's already a lot in there. The new function _findClosestBy_ could also be applied to some other parts of code to benefit of it being faster, so i did that. _findClosestBy_ uses _findMinimumBy_, which is a little more generic in name. I think _findMinimumBy_ could be used by other parts of code, while _findClosestBy_ is more clear naming for what it does now. But we could ditch _findMinimumBy_ and just leave findClosestBy? Examples of synthetic benchmarks (not included in this PR): **BEFORE CHANGES (before Scamiv's PR #3241)** <img width="705" height="91" alt="image" src="https://github.com/user-attachments/assets/d6d91c08-39f1-4387-9ccc-e51951caa539" /> <img width="751" height="101" alt="image" src="https://github.com/user-attachments/assets/80d400ac-3408-4107-aa58-6d2a847311e9" /> **AFTER CHANGES (before Scamiv's PR #3241)**   **BEFORE CHANGES (after Scamiv's PR #3241)**   **AFTER CHANGES (after Scamiv's PR #3241)** <img width="717" height="96" alt="image" src="https://github.com/user-attachments/assets/5b106843-bf6e-4448-a8e8-94448fb30ced" /> <img width="767" height="92" alt="image" src="https://github.com/user-attachments/assets/e6714c7b-26c1-455b-adae-f0060f1cbc7b" /> _Also see more **BEFORE** and **AFTER** in this comment:_ https://github.com/openfrontio/OpenFrontIO/pull/3243#issuecomment-3949060395 _And here a comparison in the flame charts:_ - based on the same replay and tried to get the performance recording going at the same speed and length but always end up with small differences - because of a bug in replays currently, it puts you in with the same clientID/persistantID currently. This means we can also record part of what is normally only recordable with live human input (the playerActions/playerBuildables). **BEFORE** flame chart with nukeSpawn (human player) and maybeSendNuke (Nation players, uses nukeSpawn via canBuild):    **AFTER** flame chart with nukeSpawn (human player) and maybeSendNuke (Nation players, uses nukeSpawn via canBuild):      ## 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 |
||
|
|
2ec12f0a3a |
Auto-reject alliance request when transport ship is sent to target player (#3477)
## Description: When a player sends a transport ship toward another player's territory, any pending alliance request from the target is now automatically rejected. This mirrors the behavior already in place for direct attacks, preventing a player from exploiting a pending alliance request while launching a naval invasion. ## 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 |
||
|
|
1049b7e7dc |
Clan System Part 1 (#3276)
## Description: Properly split out clantags and usernames, a clantag should not be part of a username. <img width="285" height="286" alt="image" src="https://github.com/user-attachments/assets/8ac56e82-b12c-4fc0-9774-e445252a6e61" /> https://api.openfront.dev/game/ojkqZFb2 <img width="296" height="596" alt="image" src="https://github.com/user-attachments/assets/85152f80-c111-4f87-b85b-8516c9c6137b" /> https://api.openfront.dev/game/MF32BkVc requires; https://github.com/openfrontio/infra/pull/264 ## 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 |
||
|
|
6952550014 |
Fix: player name and location on wrong spot on the map (#3455)
## Description: Fixes https://github.com/openfrontio/OpenFrontIO/issues/1021 Fixes issue that has been there since the beginning. Player name and location and conquest FX (swords) not being in the right place. It can happen at any time during a game and can be game-breaking in that regard. This makes it hard to find players, especially when trying to eliminate their last few tiles on some island. So when clicking name in leaderboard > wrong tiles. And when seeing name > above wrong tiles. Bug report: https://discord.com/channels/1284581928254701718/1444669324571967680 Also, when removing those last tiles, the wait time between updates of player location can make it frustrating to find and eliminate them fast. You need 2-3 clicks on their name in leaderboard, before finally being moved to their current location. **Cause:** largestClusterBoundingBox not being changed when last attack happened in same tick removeClusters last ran. **Fix:** Also call removeClusters, and therefore update largestClusterBoundingBox , when LastTileChange was AT lastCalc tick. **Also:** Run removeClusters if player owns less than 100 tiles, don't wait for ticksPerClusterCalc in that case. This way, sniping off the last couple of island tiles of the player is easier. So it doesn't take 2-3 clicks bbut just 1 click on the player name in the Leaderboard before the camera moves to the next little island they are on. Also their last clusters are annexed faster, only helping with the faster cleanup. I think this is an optional to the fix in this PR, but still an important QoL fix for sniping those last tiles quickly. **BEFORE:** https://github.com/user-attachments/assets/0960a4d3-7f8b-4368-9531-8244356bff17 **AFTER:** (also notice how it now just takes 1 click in the leaderboard to immediately go to their next location, not 2-3 clicks) https://youtu.be/qXJPekjsrP4 ## 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 |
||
|
|
0dc8fbf129 |
Fix inverse annexation (#3448)
## Description: An inverse annexation could happen where the small player (even with 0,01% tiles owned) could fully annex the large player. **TL;DR:** basically wrong use of calculateBoundingBox in surroundedBySamePlayer, feeding it all bordertiles, making enemyBox far bigger than it actually was in some cases. Which resulted in enemyBox of small player with two small clusters at some distance from each other, being seen as inscribing the largest cluster of the bigger player. While that largest cluster is actually the border tiles of the bigger player surrounding the main cluster of the small player. Instead of an annexation of small by bigger, small would incorrectly annex bigger completely. **Situation:** bigger player fully surrounds main cluster of smaller player. Those border tiles are also the largest cluster of the bigger player, for which surroundedBySamePlayer is called. SurroundedBySamePlayer finds the small player as the only bordering enemy of this cluster. Then it needs to check which of the two players is surrounded by the other one. EnemyBox uses calculateBoundingBox with all border tiles of the small player as argument. The small player also has at least one seperate cluster elsewhere, could be on another island, which count as border tiles too. The enemyBox from the main cluster of the small player to the seperate cluster elsewhere, can be huge. Now inscribed() is called and it determines that largest cluster box of the bigger player (which was in fact calculated correctly, also making use of calculateBoundingBox) is surrounded by the bigger enemyBox. And so the small surrounded player fully annexes the bigger player. **Fix:** instead of a global enemyBox, we only need the localEnemyBox that touches the largest cluster of the bigger player. With that, inscribed() can correctly conclude that largest cluster box surrounds the localEnemyBox. As a matter of fact isSurrounded() already used the same method to calculate its enemyBox as introduced by @scamiv for v30: https://github.com/openfrontio/OpenFrontIO/pull/3127/changes#diff-fb1101a2b50dd7c353d082ff7a3351cff5469b8249b3ebca91c10573a3dfaaf1 - Change in PlayerExecution - Added test NoInverseAnnexation.test.ts, which fails before and passes after the fix The bug was introduced in this commit 10 months ago: https://github.com/openfrontio/OpenFrontIO/commit/c4381a9ad3828b06764ab1a21fc1514e37aacfd7 It has probably led to some weird annexations happening since then. The bug could seemingly happen on any map. But was noted recently a few times on square islands (Sierpinski) or maps (The Box/The Alps), where the circumstances probably highten the chances of the bug occuring. **Bug reports:** https://discord.com/channels/1359946986937258015/1481916231689703477/1481916231689703477 https://discord.com/channels/1359946986937258015/1481916231689703477/1481963273367851030 https://discord.com/channels/1284581928254701718/1479993924432171008/1479995658302652496 https://discord.com/channels/1284581928254701718/1479993924432171008/1481865495492956182 https://discord.com/channels/1284581928254701718/1483047153571201034 **BEFORE:** https://github.com/user-attachments/assets/4440182b-f696-45cf-bb01-b10159df8763 **AFTER**, on the same replay but with the bugfix: https://github.com/user-attachments/assets/5f461ab2-eb62-4cc3-ae07-e2224adbbc6a ## 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 |
||
|
|
aa59dd23be |
Rebalance HvN (#3433)
## Description: For the next v30 fix version <img width="868" height="364" alt="imaege" src="https://github.com/user-attachments/assets/520a999c-67e7-4c57-8651-895ad9eeb73a" /> HvN balancing for the revamped difficulty steps of v30 sadly doesn't really work out... In medium difficulty games humans nearly always win (boring) In hard difficulty games humans usually lose It was intended differently... So lets get rid of medium difficulty HvN, always use hard difficulty and disable the donation-capability for public game nations. That will tune the human winrate towards a middle ground at about 65% I think. Which should be nice. Easier than in v29 (was frustrating sometimes) but not as easy as it's now. We can only test this in prod lol ## 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 |
||
|
|
3013133d08 |
Embrace the aftergame! 😄 (#3410)
## Description: In v30 we have the following change to prevent teammates from destroying your structures: **Block nuking teammate structures** - Nukes blocked if they'd hit a teammate's structure (that was possible by nuking oceans / rivers) (by @FloPinguin) Original idea was from Wonder. I think it makes sense, but it has a side effect: The aftergame, which many players love, will be dead because of this change. <img width="835" height="103" alt="image" src="https://github.com/user-attachments/assets/521b7915-be28-4d83-8d45-65835e7385ab" /> <img width="1101" height="105" alt="image" src="https://github.com/user-attachments/assets/db74a9c6-da12-44a2-aa06-f042b8e58b8a" /> I think a lot of complaints will follow after v30 is live. So why not add a little bit of logic for the aftergame? After a team wins/loses, players can nuke their teammates. No longer need to aim for water. SAMs also intercept teammate nukes in this phase. ## 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 |
||
|
|
3e65d08942 |
reduce train gold after each city (#3400)
## Description: Now that cities snap to existing rails, it's possible to line up dozens of cities in a row, producing way too much gold. This PR reduces the gold after each stop to prevent that. Gold only starts decreasing after the 3rd city. ## 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 |