- Consolidated defended state logic for tiles into dedicated methods in GameImpl to improve clarity and maintainability.
- Updated CanvasTerritoryRenderer to utilize the new isDefended method for determining tile defense status.
- Removed redundant checks and streamlined the painting logic for territory tiles.
- Extend SharedTileRing to include a shared dirtyFlags buffer alongside header and data
- Pass shared dirty buffer through WorkerClient/WorkerMessages and initialize views in Worker.worker
- In SAB mode, mark tiles dirty via Atomics.compareExchange before enqueuing to ensure each tile is queued at most once until processed
- On the main thread, clear dirty flags when draining the ring and build packedTileUpdates from distinct tile refs
- Keep non-SAB behaviour unchanged while reducing ring pressure and making overflows reflect true backlog, not duplicate updates
- Share GameMapImpl tile state between worker and main via SharedArrayBuffer
- Add SAB-backed tile update ring buffer to stream tile changes instead of postMessage payloads
- Wire shared state/ring through WorkerClient, Worker.worker, GameRunner, and ClientGameRunner
- Update GameView to skip updateTile when shared state is enabled and consume tile refs from the ring
## Description:
Fix for v28.
After PR #2378 changes, unit type Construction doesn't exist anymore.
Resulting in totalUnitLevels counting in construction buildings towards
total units in TeamStats, old and new Build Menu and Player Info
Overlay. To fix this we now need to filter out the construction phase.
totalUnitLevels is only used for displaying total count purposes, it
doesn't affect other logic.
Before PR 2378 changes:
https://github.com/user-attachments/assets/7748092a-8b7b-41bb-bc68-439ba922b479
After PR 2378 changes:
https://github.com/user-attachments/assets/bc77d7c2-be02-487a-b46a-c02367ae5ad8
After fix in this PR it is back to what is was before:
https://github.com/user-attachments/assets/8d7d14b6-7b6b-4ddb-96d1-17a0a45727f3
## 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
## Description:
Overhaul to nuke previews:
- Nuke previews now show when they become invulnerable (if
invulnerability setting enabled).
- Nuke previews now show if a SAM defense could intercept it, and where
the intercept occurs.
- SAMs range is now color-coded and outlined to be able to know if
missile will be intercepted by it.
<img width="1326" height="862" alt="image"
src="https://github.com/user-attachments/assets/4cc43a0b-ebac-4707-85bb-4d422f23da28"
/>
Colors/outlines aren't meant to be final, and there are constants to
play around with at the start of both files.
I also kept the naive implementation since the performance cost isn't
too high from my testing and any improvements I can think of are small
and require a bigger rewrite.
## 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
## Description:
Fixes for v28. In https://github.com/openfrontio/OpenFrontIO/pull/2444,
lobby team preview was added. But for players with clan tags, this
doesn't work correctly. They don't get assigned to the same team in the
preview, while they do get assigned to the same team in the actual game.
Also added some small fixes and QoL improvements like showing the number
of Nations next to the number of Players. Since we needed that info
anyway. Did not choose to show and assign Nations to teams (just the
numbers), see why under CONSIDERED OPTIONS THAT I DIDN'T WENT WITH.
**BEFORE:**
https://youtu.be/AV_aDJ4PgOk
<img width="767" height="117" alt="Malformed argument because of double
accolades for remove_player"
src="https://github.com/user-attachments/assets/7de1114e-7ce1-4a8f-95cc-6b0528a61e3b"
/>
**AFTER:**
https://youtu.be/aDCKkwedqes
Cause of bug:
maxTeamSize is a number in assignTeams, only used to assign clan
players. It uses the length of the players array as input. At actual
game start, the Nations are also in the players array. But at lobby
preview the Nations aren't yet fetched. So when 2 players want to do a 3
Teams private lobby with them using clan tags to be in the same team.
maxTeamCount would do Math.ceil(2/3)=1. So only 1 clan player per team
as a result. While actually there could be 10 Nations which would result
in maxTeamCount Math.ceil(12/3)=4 so in the game they would actually be
assigned to the same team.
Fix for bug:
fetch Nations count in HostLobbyModal and pass on to LobbyTeamView. Add
it to the number of players for maxTeamCount that
assignTeamsLobbyPreview uses for its calculation. Also added nation
count to the similar teamMaxSize in LobbyTeamView itself, to display the
same and correct number of max players. For random maps, we now need to
know the random map before the game starts to get its Nation count. So
made some changes for that too in HostLobbyModal.
Also fixed:
- willUpdate ran comptuteTeamPreview every second, now checks if
properties like 'client' actually changed. PollPlayers in HostLobbyModal
'changes' the clients property every second even if there are no actual
changes. Checks if the other properties are actually changed too, to
make it more future proof.
- cache teamsList so it is only fetched once instead of first in
computeTeamPreview and then again for showTeamColors.
- don't show the "Empty Teams" header if there are no empty teams.
- console error ICU format error SyntaxError: MALFORMED ARGUMENT.
Because of double accolades around remove_player in translation value.
- remove fallback for comparing clients on clientID, which used client
name. Players may have the same names so it's not safe to compare based
on name.
Also show number of Nations next to number of Players:
now we now the nationCount since it is needed for the fix, show number
of Nations next to number of Players. It's handy and it prevents
confusion as to why it says 2/32 for two teams if there are only 2
players; it is because there are 61 Nations as well on the World map for
example.
Also determine number of teams based on Players + Nations:
now we now the nationCount since it is needed for the fix, use it to
determine the number of teams. Just like populateTeams in GameImpl does.
This means for Duos on the World map, a minimum of 31 teams will be
shown since there are 61 Nations. This is better than just show two
teams based on 1 or 2 humans in the lobby. Also it makes more clear how
many teams there'll be the game and how the players and nations are
divided over the teams. Choose to not show the Nations' team assignments
though. That could be for another PR. See explanation under CONSIDERED
OPTIONS THAT I DIDN'T WENT WITH.
Also show Nations team as pre-filled for HumansVSNations:
now we now the nationCount since it is needed for the fix, for
HumansVSNations, show the Nations team as fully assigned and non-empty.
For example for World map it shows Nations 61/61. Don't show them as
Empty Team but as Assigned Team. Although i choose not to show the
actual Nations (see CONSIDERED OPTIONS THAT I DIDN'T WENT WITH), this
makes it clear their team is pre-filled and how many Nations you're
actually up against. Whereas for other Team game types like 7 Teams or
Duos, it will display the teams that the Nations will fill up as empty.
CONSIDERED OPTIONS THAT I DIDN'T WENT WITH
- Use an optional param 'nationsCount' to assignTeams with default = 0.
And simply add nationsCount to the players.length count for maxTeamSize.
This would be error prone; 'nationsCount' should then never be assigned
a value except when called from LobbyTeamView. But in the future someone
might assign it a value even when called from somewhere else. Then you
could say, check if there are Nations in the players array and if so, do
not use Nationscount because we know they are already counted from
players.length. But if Disable Nations is enabled, there would be no
Nations in the players array but if nationsCount was somehow given a
value we still should not use it. So again, too developer error prone.
- Not only fetch the number of Nations, but actually get the Nations
themselves to show them in the team assign preview as well. They are
shuffled on team assignment but of course deterministicly (nation player
ID assigned based on Pseudorandom seeded with GameID in GameRunner, then
shuffled in TeamAssignment with Pseudorandom seeded with map's first
Nation's playerID), so we could replicate it. But then, how to
distinguish humans and Nations in the UI? This feels like something for
a follow-up PR.
FOR A FUTURE PR
- change the way Clan team overflow is handled. Now they're "kicked" as
in not assigned to a team without their knowing, are loaded into the
game but cannot spawn. This UX could need some improvement. And the
logic can be improved too, ie. don't "kick" too soon, check the number
of Clans in the lobby and the number of teams to decide if we can assign
the 'overflowing' Clan player to another team that doesn't have
rivalling Clan players. Far out of scope for this PR.
## 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
## Description:
Adds a map based on Lisbon and the surrounding area. Also credits the
ESA's Copernicus Digital Elevation Model, which was used to create this
map and the Gulf of St. Lawrence map.
<img width="1257" height="1257" alt="screenshot of the new Lisbon map"
src="https://github.com/user-attachments/assets/39fa73da-c77d-4d5c-8d00-7ee2794d0298"
/>
## Checklist:
- [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
## Discord Username:
sehentsin
## Description:
Adds the Gulf of St. Lawrence map and the flags of New Brunswick
(ca_nb), Nova Scotia (ca_ns), and Prince Edward Island (ca_pe).
## 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
<img width="1380" height="1150" alt="Screenshot 2025-11-26 201008"
src="https://github.com/user-attachments/assets/77531058-b429-4c68-b645-a3e59033458b"
/>
<img width="1434" height="1195" alt="Screenshot 2025-11-26 202128"
src="https://github.com/user-attachments/assets/9c3e2bc2-882f-4662-a32b-16e17db852f2"
/>
## Discord username
sehentsin
Resolves#2517
## Description:
Fixes territory skin option not doing anything.
Also changes some instances of directly accessing
"this.cosmetics.pattern" to use the precalculated "pattern" variable.
## 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:
Lavodan
## Description:
- Removed the temporary UnitType.Construction and embedded construction
state into real units via isUnderConstruction().
- Centralized non-structure spawning to perform a single validation
right before unit creation/launch.
- Updated UI layers to render construction state without relying on the
removed enum.
- Adjusted and created tests to match the new flow and to cover the
no-refundscenarios.
# Tests updated
- tests/economy/ConstructionGold.test.ts: covers structure cost
deduction and income, tolerant of passive income; ensures no refunds
during construction.
- tests/nukes/HydrogenAndMirv.test.ts: accounts for single-check launch
flow; MIRV test targets a player-owned tile; ensures launch after
payment.
- tests/client/graphics/UILayer.test.ts: mocks now provide
isUnderConstruction and real type strings;
## 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:
CrackeRR1
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
In commit
https://github.com/openfrontio/OpenFrontIO/commit/bbf72bd14f7f31146c687523aea8fc0aff31bbe1#diff-ee2fcbca50d87cc09d2c7d2667210defe2e3e111239820c89c40283be5385b64
it was added that startManpower in DefaultConfig used
playerInfo.nation.strength to set the starting troops for a Nation. In
ExecutionManager, param nation of PlayerInfo was set during the
instantiation of a new FakeHumanExecution.
However in commit
https://github.com/openfrontio/OpenFrontIO/commit/d6a412aa50dd86d474d80c216fd9ba36e7426ef9#diff-2d0a5d8b171d8b504f934891025e42742e142ef0964d6e17712bfdcd30bf050c
the changes made it so that **param nation of PlayerInfo was never
set**. While startManpower in DefaultConfig still checked for
playerinfo.nation.strength. Since it was always undefined, it would use
a multiplier of 1 instead of the actual nation strength.
This PR fixes it by passing the nation strength to param nationStrength
in PlayerInfo. Removing param strength from class Nation. Strength isn't
used anywhere else so this isn't a problem and it also consolidates
human player info and nation player info even more. We could have also
used the Nation.strength directly, but that would have required more
code in addPlayers and addPlayer in GameImpl, especially for Teams
games. So this PR has the simplest solution.
- I did add a config setting useNationStrengthForStartManpower with a
comment that explains its reason for being. Namely that in the months
that startManpower didn't get to use nation strength because of the bug,
FakeHumans have become much harder to fight. Re-enabling higher starting
troops from this fix would make them even harder to fight, and i think
rebalancing is needed before that.
- Or we could decide to scrap Nation strength altogether, as it is only
ever used to set starting troops anyway. This would make map making a
little easier as a bycatch.
## 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
## Description:
This PR optimizes how the rail network looks up railroads connecting two
stations by introducing an O(1) neighbor→railroad map on `TrainStation`.
It also updates `getOrientedRailroad` and railroad deletion to use this
new API, avoiding repeated linear scans over all railroads attached to a
station.
### What changed
- **TrainStation neighbor→railroad index**
- Added `railroadByNeighbor: Map<TrainStation, Railroad>` to
`TrainStation` for quick edge lookup.
- Kept `railroads: Set<Railroad>` for iteration and existing APIs.
- Updated lifecycle methods to keep both data structures in sync:
- `addRailroad(railRoad: Railroad)` now:
- Adds to `railroads`.
- Computes the neighbor station (`railRoad.from === this ? railRoad.to :
railRoad.from`).
- Stores the mapping in `railroadByNeighbor`.
- `removeRailroad(railRoad: Railroad)` now:
- Removes from `railroads`.
- Removes the corresponding entry from `railroadByNeighbor`.
- `clearRailroads()` now clears both `railroads` and
`railroadByNeighbor`.
- Added `getRailroadTo(station: TrainStation): Railroad | null` to
retrieve the connecting railroad in O(1).
- **Use the new API in `TrainStation` and `Railroad`**
- `TrainStation.removeNeighboringRails(station)` now calls
`removeRailroad(toRemove)` instead of manually deleting from the set,
ensuring the map stays in sync.
- `Railroad.delete(game)` now calls `from.removeRailroad(this)` and
`to.removeRailroad(this)` instead of mutating the sets directly.
- **Refactor `getOrientedRailroad` to use O(1) lookup**
- Replaced a linear scan over `from.getRailroads()` with a direct
lookup:
```ts
export function getOrientedRailroad(
from: TrainStation,
to: TrainStation,
): OrientedRailroad | null {
const railroad = from.getRailroadTo(to);
if (!railroad) return null;
// If tiles are stored from -> to, we go forward when railroad.to === to
const forward = railroad.to === to;
return new OrientedRailroad(railroad, forward);
}
```
- Behavior is preserved:
- `getRailroadTo` returns the same `Railroad` instance that was
previously found by scanning `getRailroads()`.
- Direction (`forward` vs reversed) is still derived from the
`Railroad.from` / `.to` fields in the same way as before.
### Motivation
- `getOrientedRailroad` and upcoming logic both need to resolve “the
railroad between station A and station B” frequently.
- The old pattern (`for (const railroad of from.getRailroads()) { ...
}`) was:
- O(degree) per lookup,
- Repeated in multiple places,
- Harder to maintain as more features (like fare-based costs) touch this
code.
- Centralizing edge lookup in a dedicated `railroadByNeighbor` map makes
this:
- **O(1)** per lookup,
- Less error-prone (one source of truth),
- Easier to reuse from new systems (e.g. train pathfinding, fare-aware
logic).
### Impact / Risk
- **Public behavior:** No functional change in how railroads are
created, deleted, or oriented; only the lookup mechanism changed.
- **Internal invariants:** Correctness relies on:
- All railroad creations using `addRailroad` on both endpoints (already
true via `RailNetworkImpl.connect`).
- All removals (`Railroad.delete`,
`TrainStation.removeNeighboringRails`, `disconnectFromNetwork`) using
`removeRailroad` / `clearRailroads`, which this PR updates.
- **Tests:** Existing `TrainStation` tests still pass; they exercise
`addRailroad`, `removeNeighboringRails`, and `getRailroads()`, which
continue to behave the same from the outside.
## 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:
DISCORD_USERNAME
## Description:
Made a type of map we don't already have: Small square map, one Island
in each corner. Could be great for boat gameplay or maybe even nuke
wars.
The special thing is: **All islands are exactly 25% of the territory!**
Was a lot of work drawing that in GIMP...
<img width="1164" height="1168" alt="Screenshot 2025-11-20 000839"
src="https://github.com/user-attachments/assets/ad8345c7-562d-49e8-b367-12be9274f3e4"
/>
<img width="1227" height="1222" alt="Screenshot 2025-11-20 000633"
src="https://github.com/user-attachments/assets/f7e2c58a-fcb3-4e07-91f2-aead5f497fad"
/>
<img width="361" height="288" alt="Screenshot 2025-11-20 000655"
src="https://github.com/user-attachments/assets/120f82ef-2d19-497b-8a31-819e30013c89"
/>
<img width="756" height="282" alt="Screenshot 2025-11-20 005949"
src="https://github.com/user-attachments/assets/8ee04da3-d5fa-4ec9-9e99-9e30ebda2b78"
/>
## 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>
## Description:
Add dynamic alliance icon with time-based fill and extension request
indicator
- Implement bottom-up green fill on alliance icon proportional to
remaining time
- Use AllianceIconFaded.svg as base layer with green overlay clipped
from top
- Add 20-82.40% clip range to account for icon vertical offset
## 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
<img width="1132" height="631" alt="Screenshot 2025-11-18 205205"
src="https://github.com/user-attachments/assets/4af71ddc-f847-4460-9046-167275efc773"
/>
<img width="1387" height="792" alt="Screenshot 2025-11-18 205532"
src="https://github.com/user-attachments/assets/9dd0e018-323f-4de1-bae8-2633c09fe867"
/>
## Please put your Discord username so you can be contacted if a bug or
regression is found:
hauke4707
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
After the v27 playtest, some players experienced instant death on spawn.
The issue was that the human random spawn occasionally coincided with a
bot’s spawn. Previously, bot spawns didn’t account for human spawn
locations and could appear on the same tile, now they don’t.
## Please complete the following:
- [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: Evan <evanpelle@gmail.com>
## Description:
See PR https://github.com/openfrontio/OpenFrontIO/pull/2203
It was reverted. This unreverts it, with an added fix for boat troops
not getting returned to owner. And small comment updates. And a const
for boatOwner to re-use.
The added bugfix is check for this.sourceTile === null in the retreat()
function in AttackExecution. A boat attack always sets removeTroops to
false because the troops were already removed from owner troops when the
boat departed. They don't have to be removed again in AttackExecution
init, when the boat lands and the attack starts. But at the end of the
attack, in retreat() in AttackExecution, the starting/boat troops still
need to be returned to the owner. That's why even if removeTroops is
false, when sourceTile is not null (only when it's a boat attack) we add
back the troops to the owner.
## 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: Fx Morin <28154542+FxMorin@users.noreply.github.com>
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves#2447
## Description:
This PR updates the logic used to generate structure fill and border
colors. Currently, (v0.26.16 and earlier), some light territory colors
have structures that are difficult to see and identify. This PR ensures
that all territory colors have structures that are easily visible.
Instead of using `Colord.lighten()` and `Colord.darken()` to generate
structure colors, the logic now:
- queries the territory color and border color of the structure owner
- Converts these colors to the [LAB color
space](https://en.wikipedia.org/wiki/CIELAB_color_space) (which is a
human-perception-uniform color space).
- Darkens the border color (by decreasing LAB luminance) and sometimes
lightens the territory color (by increasing LAB luminance) until a
specific `Color Delta` is achieved (currently `delta > 0.5`)
- This ensures contrast between the structure and the territory
background.
Additionally, this PR re-organizes colors in the `Colors.ts` file for
better visibility and removes redundant colors from the `nationColors`
list.
This PR is an implementation of the proposed mock-up posted on imgur in
issue #2447. Screenshots of the original, final, and side-by-side
comparison of structure colors (for all available player colors) are in
the [imgur
album](https://imgur.com/a/openfront-color-playground-4cxSbbj).
I'd recommend inclusion as a feature/fix for v27.
## 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:
GlacialDrift
## Description:
This PR is intended to increase singleplayer fun.
This is a follow-up to my previous PR #2161, which made the
"Hiding-Strategy on small islands" harder.
But its still possible. You can easily warship-infest the map and get
rich from the trade, even though you are playing on impossible
difficulty.
Also you can easily hinder the nations to, for example, get from the
left to the right side of the world map. So you are always in full
control of the game.
This PR makes nations send a warship if their troop transport boat got
destroyed. The warship will travel to the location where the boat got
destroyed.
Because the nations send more boats now (previous PR), this actual has
an impact and there is more action on the map now :)
The chance of retaliation is based on the game difficulty.
### COMPARISON
[Youtube Video](https://www.youtube.com/watch?v=F4_iP54LGNU) of me
playing the infestation-strat without this PR.
[Youtube Video](https://www.youtube.com/watch?v=VHesXJwPtcA) of me
playing the infestation-strat with this PR (Its sill possible but not
that easy, I gave up 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
## Description:
Finishes & closes#1969 and closes#1806.
## 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
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
Enhanced the performance overlay to display additional tick-related
performance metrics. The overlay now shows:
1. **Tick Execution Duration** - Average and maximum time (in
milliseconds) it takes to execute a game tick
2. **Tick Delay** - Average and maximum time (in milliseconds) between
receiving tick updates from the server
The server sends 10 updates per second (100ms interval), so these
metrics help identify:
- Client-side performance bottlenecks (tick execution duration)
- Network latency issues (tick delay)
**Additional improvements:**
- Renamed `FPSDisplay` component to `PerformanceOverlay` to better
reflect its expanded purpose
- Updated method names (`updateFPS` → `updateFrameMetrics`) and CSS
classes for consistency
All metrics are tracked over the last 60 ticks, providing rolling
averages and maximum values for performance analysis.
## Please complete the following:
- [x] I have added screenshots for all UI updates:
- <img width="495" height="227" alt="image"
src="https://github.com/user-attachments/assets/142b0313-61bf-46cc-b595-61fe73f6b54c"
/>
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- Translation keys already exist in en.json for
"performance_overlay_label" and "performance_overlay_desc"
- [x] I have added relevant tests to the test directory
- All existing tests pass (309/310 tests passed)
- No new tests added as this is primarily a display enhancement
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- Tested locally with npm test
- Verified performance overlay displays all metrics correctly
- Confirmed tick metrics are calculated and displayed accurately
## Please put your Discord username so you can be contacted if a bug or
regression is found:
Discord: kerverse
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
The **proximityBonusPortsNb** function increased the likelihood a
tradeship would go to a nearby port. But now that trade gold is nerfed
from nearby ports, we shouldn't encourage trading with ports that are
too close. So now add another factor **tradeShipShortRangeDebuff** That
cancels out the proximity bonus if the port is too close.
Now tradeships are encouraged to go to ports that are close, but not too
close.
Also move tradingPorts method to the PortExecution class because that's
the only place it's used.
## 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
## Description:
Enhanced the performance overlay to display additional tick-related
performance metrics. The overlay now shows:
1. **Tick Execution Duration** - Average and maximum time (in
milliseconds) it takes to execute a game tick
2. **Tick Delay** - Average and maximum time (in milliseconds) between
receiving tick updates from the server
The server sends 10 updates per second (100ms interval), so these
metrics help identify:
- Client-side performance bottlenecks (tick execution duration)
- Network latency issues (tick delay)
**Additional improvements:**
- Renamed `FPSDisplay` component to `PerformanceOverlay` to better
reflect its expanded purpose
- Updated method names (`updateFPS` → `updateFrameMetrics`) and CSS
classes for consistency
All metrics are tracked over the last 60 ticks, providing rolling
averages and maximum values for performance analysis.
## Please complete the following:
- [x] I have added screenshots for all UI updates:
- <img width="495" height="227" alt="image"
src="https://github.com/user-attachments/assets/142b0313-61bf-46cc-b595-61fe73f6b54c"
/>
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- Translation keys already exist in en.json for
"performance_overlay_label" and "performance_overlay_desc"
- [x] I have added relevant tests to the test directory
- All existing tests pass (309/310 tests passed)
- No new tests added as this is primarily a display enhancement
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- Tested locally with npm test
- Verified performance overlay displays all metrics correctly
- Confirmed tick metrics are calculated and displayed accurately
## Please put your Discord username so you can be contacted if a bug or
regression is found:
Discord: kerverse
---------
Co-authored-by: Evan <evanpelle@gmail.com>
Adds a timer before self deleting units
Adds a loading bar under deleting units
Adds a timer in radial menu for clarity purposes

- [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
regression is found:
Mr.Box
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
Have AFK player's Warships not attack team members ships, like Transport
Ships boating in. If team mate conquers the AFK player, transfer over
Warships and Transport Ships to conqueror. The transfered Transport
ships attack in the name of the new owner when landing, and when they
are retreated they move back to a new owner shore tile if they have any.
Added tests. Expectation is this PR will be merged in v26 as the real
solution for the temporary workaround of deleting warships.
**Currently:**
- An AFK player can be attacked without troop loss by their team
members. For this purpose, isFriendly now returns false if the other
player isDisconnected
- But that meant Warships would get False from isFriendly too, and
attack the ships and boats of their team members.
- [Temporary workaround was to delete
warships](https://github.com/openfrontio/OpenFrontIO/commit/eea8db7a06aed50c005db35ad55ece026f7a3643)
as soon as the player was deemed AFK. But this is a disadvantage to the
team. For example the AFK player could have 6 warships in the waters,
either defending team land or helping the team cross over to the enemy
team.
- Transport Ships that were on the way to attack, were still deleted
after the AFK player was conquered. But this is also a disadvantage, if
say a transport ship has just managed to breach through to the enemy
lands despite warships all around. That could have made the win for the
team.
(Left to think about: do we want to transfer part of the defender troops
to the isOnSameTeam attacker? Defender looses less troops in the attack
from their team mate. You'd expect troops to lay down their arms mostly,
if the attacker is on the same team and doesn't loose troops themselves.
Those troops that they loose less than normal, are then added to the
attacker once they've been deemed conqueror. The enemy team can still
attack and do normal damage, and can still also be the conqueror so the
team members have to be fast.)
**Changes in this PR:**
- GameImpl > conquerPlayer: Transfer ownership of the warships to the
conquerer of their lands. If the conqueror is not a team member (other
team can still attack, in their case with troop loss), they won't get
the warships and the ships will be deleted like normal. If the conqueror
is a team member, have them capture the warships.
(Captures need to happen in conquerPlayer since this is right before the
last tiles are conquered and PlayerExecution finds out the player is
dead and deletes its units. Captures will be recorded in the stats just
like normal. Things like this add an extra incentive to be the fastest
to conquer the AFK player, next to getting their gold which is also
recorded in stats. The normal Event Box messages are also displayed.)
- GameImpl > conquerPlayer: Also transfer Transport Ships. As a note:
the limit of 3 transport ships concurrently out on water for one player,
can be exceeded in this specific case (the boatMaxNumber is only checked
for canBuild via TransportShipUtils, and in the init of a
TransportShipExecution, not for an existing TransportShipExecution with
a changing owner). This keeps the situation even for the team in terms
of ships that are already out to attack, which is fair.
captureUnit/setOwner won't do the full job here though, so changes in
TransportShipExecution are needed.
- TransportShipExecution: Added originalOwner. So we can check within
the execution itself if the Original owner disconnected, and if so if
the new owner is on the same team. Only in that case change private
this.attacker. This.attacker can still not be changed from the outside
in this way. Find new src of new owner to retreat boat to if needed, and
if new owner has no shore set it to null so the boat will be deleted
upon retreat. To find new src tile of new owner, use
bestTransportShipSpawn instead of canBuild because canBuild checks for
max boats = 3 etcera but the boat is already on its way so those checks
don't apply (we could get false back from canBuild because there's 3
ships out, while we only need to find the source tile so use
bestTransportShipSpawn).
- TransportShipUtils: to use bestTransportShipSpawn to find new owner
source tile to retreat to, we need to make sure it can handle a new
owner without shore tiles. When the new owner has no shore tiles
(candidates.length === 0), return false. This way it won't go on to call
MiniAStar which would have SerialAStar error on an empty this.sources
array.
- WarshipExecution: Changed isFriendly. This makes sure we have the
wanted behavior: allied/team ships should not attack each other once one
of their owners goes AFK.
- AttackExecution: added one more test specifically to check if attack
on AFK teammate is still witthout troop loss "Player can attack
disconnected team mate without troop loss". Also a bugfix that I left in
after removing a related change from this PR: Add a check for
removeTroops === false in the retreat() function, so at the end of the
attack we don't add troops back to owner troops if they were never
removed in the init. This check in retreat() is actually a bug fix
because removeTroops is in the constructor and can be set to True, but
in retreat() troops would then have been given back after not being
removed at init.
- DefaultConfig: small addition to comment.
- Disconnected.test.ts: added tests. Added useRealAttackLogic because at
least "Player can attack disconnected team mate without troop loss"
needs to use the real attackLogic.
- TestConfig: new class useRealAttackLogic extends TestConfig class, so
a test setup can use the real attackLogic from DefaultConfig instead of
the mock function in TestConfig.
- Setup.ts: for the test setup, add parameter to accept
useRealAttackLogic extension class. Defaults to TestConfig.
## 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: Evan <evanpelle@gmail.com>
## Description:
Betrayal count in PlayerUpdates came from stats. But stats are only kept
for players with ClientID aka real humans. So betrayals stayed 0 for
Nations even after betraying others. This PR fixes it by keeping a
seperate betrayal count for PlayerUpdates while stats are still being
kept to go in the database.
See bug report
https://discord.com/channels/1284581928254701718/1432759837560799403
After:
<img width="642" height="337" alt="image"
src="https://github.com/user-attachments/assets/1b8bcfa1-aadd-4bea-8a5f-7fa9f2c9111f"
/>
## 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
## Description:
This PR adds the Nuke Wars Baikal map (more balanced out than the
original). This is made for the upcoming Nuke Wars gamemode.
## 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:
DISCORD_USERNAME
Nikola123
## Description:
> [!IMPORTANT]
> Try here: https://mirv-test.openfront.dev/
> [!NOTE]
> Blocks PRs:
> - #2244
> - #2263
### Summary
Implements intelligent MIRV usage for fakehuman players, enabling them
to make strategic nuclear strikes based on game state analysis.
### Changes
#### Core FakeHuman Strategy (`FakeHumanExecution.ts`)
- **MIRV Decision System**: Added `considerMIRV()` method that evaluates
game state and determines optimal MIRV usage
- **Three Strategic Targeting Modes**:
1. **Counter-MIRV**: Retaliatory strikes against players actively
launching MIRVs at the fakehuman
2. **Victory Denial**: Preemptive strikes against players approaching
win conditions
- Team threshold: n% of total land (configurable)
- Individual threshold: n% of total land (configurable)
3. **Steamroll Prevention**: Strikes against players with dominant city
counts (n% ahead of next competitor)
#### FakeHuman Behavior Tuning
- **Cooldown System**: n-minute cooldown between MIRV attempts
(configurable)
- **Failure Rate**: ~n% chance of cooldown trigger without launch
(simulates human hesitation/resource management; configurable)
- **Territory Targeting**: Centers MIRV strikes on enemy territory
center-of-mass for maximum impact
#### Technical Improvements
- **Type Safety**: Updated `UnitParamsMap` to include `targetTile`
parameter for MIRV units
- **Execution Flow**: Integrated MIRV consideration into fakehuman tick
cycle outside of standard attack logic, due to its holistic strategic
nature
### Game Balance Impact
- **FakeHuman Threat Level**: Increases late-game fakehuman
competitiveness
- **Endgame Dynamics**: Prevents runaway victories, extends game tension
### Breaking Changes
None - purely additive feature
### Related GitHub Issues:
- #2205
------
## Other
- [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
Discord Username: samsammiliah
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
Fixes: #676
This PR adds Players Vs Nations as a game mode in the menu.
For this change I have added two mutually exclusive option for this
mode:
1. Match number of nations to number of players who have joined
2. Set the number of nations to a fixed value
### Screenshots:
#### Options in Single player mode
<img width="1025" height="790" alt="image"
src="https://github.com/user-attachments/assets/c0685ea5-94f5-43c7-a9e5-390835fc94e9"
/>
<img width="1005" height="795" alt="image"
src="https://github.com/user-attachments/assets/dddba015-a424-40dd-a0fe-2571fd7b0fba"
/>
#### Options in lobby mode
<img width="1015" height="888" alt="image"
src="https://github.com/user-attachments/assets/45bc865b-c6a8-4b6a-9062-4eb499c1ea36"
/>
#### Example gameplay (1 Human Vs 90 Nations)
<img width="1888" height="912" alt="image"
src="https://github.com/user-attachments/assets/38faec75-171f-4358-a3be-93630cca1587"
/>
## 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:
saphereye
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
This will be used to determine clan winners in the api layer.
## 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