## Description:
This PR centralizes all username validation using UsernameSchema with a
set maximum, minimum, and a regex pattern,
It also removes sanitization, as all places where the username would be
sanitized on the server have been gatekept, so no unvalidated usernames
can get onto the server past the ClientMessageSchema safeParse in
GameServer's on message func.
Here is how the errors look if that happens, Note that if the client is
funtioning correctly and the user doesn't manually send a WS message,
they should never see this. The screenshots are from a debug build where
client uname validation was disabled.
<img height="300" alt="error message too short"
src="https://github.com/user-attachments/assets/1b7ac32c-2f03-40fb-8ce9-1f4ab66100bd"
/>
<img height="300" alt="error message bad regex"
src="https://github.com/user-attachments/assets/c78b4114-7e4b-4d39-a135-4cab3ad52c0b"
/>
Profanity sanitization was not changed.
Additionally, the censor tests were updated to reflect the new
expectations.
Jose was added to the jest config as an allowed transform pattern, as it
didn't make sense to me to mock a zod schema.
The UsernameSchema pattern was set to `^[a-zA-Z0-9_ \[\]üÜ]+$`, I
personally think either we should allow all latin characters (regex has
a pattern for this, `\p{L}` or `\p{sc=Latin}`) and then we'd use some
kind of library to normalize all latin characters into regular ascii for
name filtering, or we should only keep ascii letters.
## 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
(I just realized sanitization isn't a word, it's supposed to be
sanitation, sorry.)
## Description:
To prevent MAD stalemates, have the price of MIRVs increase after each
launch. This will encourage players to launch a MIRV once they have
enough money for it. Also reduce the price of the first MIRV to 25
million to reduce snowballing, each subsequent MIRV cost an extra 15
million:
1. 25 million
2. 40 million
3. 60 million
4. etc
## 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:
**Fix:**
Error box like "Username must be at least 3 characters long" sometimes
still remains after the game starts. See bug report with screenshot:
https://discord.com/channels/1284581928254701718/1449169462426079343/1449169462426079343
This is while in main.ts, element username-validation-error is already
set to hidden. Most of the time this works but apparently sometimes
there's a re-render which removes the 'hidden' tag again. Most probable
solution for this edge case: clear validationError first. Then to make
sure, still hide element username-validation-error.
**Cleanup username.ts:**
- Remove cat and clover emojis from validPattern. For single player
games, the only other validity checks are done from GameRunner calling
sanitize() from Util.ts. And from GameImpl where from addPlayer the
PlayerImpl are created, which uses sanitizeUserName also from
username.ts, which uses validPattern again. Both GameRunner and
PlayerImpl therefor allow emoji. But for muliplayer there's an extra
step where ClientJoinMessageSchema enforces UserNameSchema = SafeString.
Which does NOT allow cat or clover emoji. So for multiplayer you get an
error and can't join a game with cat or clover emoji. To align their
behavior more, just disallow the emojis from validPattern from the
start. Almost no-one ever used them anyway, they were once put in
because a developer liked to use them before the existance of
ClientJoinMessageSchema. There's more consolidation/refactoring possible
but this is an important first step.
- Remove sending arg max: MAX_USERNAME_LENGTH for translation key
invalid_chars, since the translation string doesn't contain params
**Cleanup UserNameInput.ts:**
- Remove userSettings: isn't used anywhere in the code
- Remove dispatchUsernameEvent: nowhere in the repo is event
'username-change' listened to. Also, dispatchUsernameEvent is only
called from connectedCallBack but not anywhere else. It seems like
handleChange was made for this. Also main.ts calls
usernameInput.getCurrentUsername() and doesn't listen for the event
either.
## 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
## Response to alliance requests
Previously the way nations responded to alliance requests was quite
simple / boring / exploitable. Basically you couldn't ally them if you
had a bad relation with them, or if you had too many alliances.
Otherwise they would just take it.
Now there is a **complete decision tree which is based on the
difficulty**. The nations should also feel more human now.
For example, just like humans, nations will now consider to take an
alliance even if you have a bad relation with them (If you are a
threat).
Also, nations no longer check if YOU have too many alliances. Now they
do what humans do: Check if THEY have too many alliances (they want to
be able to attack somebody).
Another big change is the default case: Previously it was just `return
true`. Now it's `return isAlliancePartnerSimilarlyStrong`. So they do
what humans do: Take a quick look at their troop count before allying
them.
## Sending alliance requests
Previously alliance requests were sent randomly. Quite boring.
Now we use the same decision tree as for responding.
## Alliance extension requests
They also use the same decision tree.
## Tests
Tested it a lot in singleplayer.
I have planned to add unit tests for all the nation/bot stuff in the
upcoming cleanup 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
When user tries to join either a public or private multiplayer game, the
turnstile callback is triggered, and the turnstile token is passed to
the server when joining a game.
## 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:
Fixes the failing nationNameLength.test by usinbg the proper absolute
path glob syntax
Also switches to fast-glob because I don't see a reason to not use it.
## 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
Resolves#2448
Hi team,
I've implemented and locally tested the alliance-related changes
(including unit tests and some manual simulation with multiple browser
profiles).
Unfortunately I wasn't able to perform full end-to-end testing on the
live game server with two separate machines/accounts.
If someone on the team (or another contributor) can verify the alliance
flow with two real players, that would be greatly appreciated before
merging. Happy to hop on a call or provide any clarification needed.
Thanks!
## Description:
Fixed a race condition bug where donations (troops/gold) between human
players failed after forming an alliance. The issue was caused by a
one-tick delay in `AllianceRequestReplyExecution`: the alliance
acceptance logic ran in `tick()` instead of `init()`, meaning the
alliance wasn't created until the tick after the execution was added. If
a donation execution was added in the same turn as the alliance
acceptance, it would fail the `isFriendly()` check because the alliance
didn't exist yet.
**Root cause:** When human players formed alliances via reply, the
execution model delayed alliance creation by one tick, while bots called
`accept()` directly without this delay.
**Solution:** Moved alliance acceptance logic from `tick()` to `init()`
in `AllianceRequestReplyExecution.ts`, ensuring immediate alliance
creation and eliminating race conditions with donations.
**Changes:**
- Modified
`src/core/execution/alliance/AllianceRequestReplyExecution.ts` to
process alliance replies in `init()` instead of `tick()`
- Added comprehensive test suite `tests/AllianceDonation.test.ts` with 5
test cases covering donation scenarios after alliance formation (reply
and mutual request flows)
- All existing tests pass (323 total)
## Please complete the following:
- [x] I have added screenshots for all UI updates (N/A - backend logic
fix only)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (N/A - no user-facing text
changes)
- [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: loacky
GitHub: @LoackyBit
---------
Co-authored-by: Evan <evanpelle@gmail.com>
Description:
There is a boating exploit where players could repeatedly send and
retreat boats to effectively increase troop regeneration and maintain
almost double the max troop cap. This PR fixes#2388.
Summary
This pr adds a troop penalty to the boat retreats in
src/core/execution/TransportShipExecution.ts of 25 percent (currently
the same as land attacks, but may require fine tuning). this prevents,
to some degree, the ability to stockpile large amounts of troops above
your max cap.
Testing
I tested the change locally and confirmed that the troop penalty is
applied at the correct times and under the correct conditions, i.e. it
is only applied on successful retreat (applying on reteat initiation may
be confusing if you are told you have lost troops if the transport ship
never arrives anyway). The boating exploit is far less effective with
this fix in place.
Notes
- This is a exploit and does not introduce any new features.
- Existing game behavior outside the retreat penalty remains unchanged.
- The attack message is the same as the land attack, so the translation
should already be present.
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
---------
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:
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>
## Description:
Describe the PR.
Added a new chat message from the server once player wants to renew the
alliance, to the other player.
## Please complete the following:
- [x] I have added screenshots for all UI updates
<img width="572" height="256" alt="image"
src="https://github.com/user-attachments/assets/7feec21f-fff5-4544-8992-caf99c45913d"
/>
- [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
notifxy (1379678982676676639)
## Description:
Some stats are missing from the recorded game stats:
- Unit upgrade
- Gold from trade and from steal
The gold from trade/steal was introduced with [PR
784](https://github.com/openfrontio/OpenFrontIO/pull/784) but was
quickly reverted with [PR
927](https://github.com/openfrontio/OpenFrontIO/pull/927), probably
involuntarily.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
IngloriousTom
## Description:
Implements SAM range extension for stacked SAMs to cover hydros as
requested in #2347 and many times from users in discord.
This implementation is as simple as possible: At level 5 and higher,
SAMs extend their range to the range of a hydrogen bomb + 10 for a small
safety margin. Levels 2-4 are interpolated between.
Screenshot to show the sizes compared to a hydro:
<img width="400" alt="image"
src="https://github.com/user-attachments/assets/a857d66c-e3d4-467f-855f-3539cc90b719"
/>
Everything works together with the new range UI, although I might need
to unify with / rebase on #2350. Not yet tested with #2348, but
shouldn't be an issue.
## Input needed:
- Should I add tests for this?
- This is in effect a massive buff to SAMs, might be too strong. Popular
ideas / suggestions from Discord to balance things:
- Cap the SAM upgrade level at the maximum range (easy to do)
- Alternative, instead of capping the level, decrease the range when
missiles are reloading
- Increase the cost scaling for SAMs per stack, and scale way higher
(e.g. 1.5M > 3M > 4.5M > 6M or something like that) (UI integration
unclear, breaks with existing cost logic)
- Decrease SAM hit probability for Hydros
I'm happy to implement any of these paths, or just roll with the simple
way it's set up now, just let me know.
## 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:
newyearnewphil
---------
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
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:
- Move the Delete button to where the Boat button is otherwise. The Boat
and Delete button already mutually exclude eachother anyway; boat button
is only visible on other's tiles, delete button is only visible on your
own tiles. Evan agreed to this new position:
https://discord.com/channels/1359946986937258015/1381293863712591872/1429147325049077860
- Increase the cooldown between deletions from 5 to 15 seconds. PR #2216
introduced a destruction time (deletionMarkDuration) making it take 15s
to delete a building. With the cooldown of 15s between clicking the
Delete button (deleteUnitCooldown) on top of that, you can actually only
delete a building every 15 seconds while it also takes that same time to
destruct it. Players have voiced between 10s to 30s or more so 15s is
still a reasonable time, keeping deletion of mistakenly placed buildings
still possible, while also keeping a small 'scorched earth' option
during an attack but probably only being able to delete 1-2 units in an
attack. Evan and Vivacious Box agreed with the mentioned 10-15s cooldown
too:
https://discord.com/channels/1359946986937258015/1381293863712591872/1429103999088459897
**Video: Delete button new location and 15s cooldown:**
https://github.com/user-attachments/assets/b0b13fc1-1e50-4a7a-8f32-55f7891f9945
**Delete button new location disabled:**
<img width="310" height="316" alt="Delete button disabled new location
radial menu"
src="https://github.com/user-attachments/assets/f65b88ad-5859-4982-be53-8f2f693f5767"
/>
**Delete button new location enabled:**
<img width="332" height="305" alt="Delete button enabled new location
radial menu"
src="https://github.com/user-attachments/assets/037f07c5-622a-4857-9ab8-fc20981de816"
/>
**Radial menu unchanged on others' tiles:**
<img width="346" height="307" alt="Radial menu unchanged on other
territory"
src="https://github.com/user-attachments/assets/085b2043-096f-4c44-8917-467adb8a7213"
/>
## 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: Vivacious Box <jon@rouillard.org>
## 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 PR expands clan name possibilities available to players.**
**Suggested Label:** Feature
**Suggested Milestone:** v26 or v27
The current clan name logic does not allow for all alphanumeric
characters (e.g. 0-9). Instead it only allows for uppercase and
lowercase letters (e.g. a-z and A-Z). This PR updates the logic to
include 0-9 in the allowable character set. This is in line with how
many other games utilize clan names.
Secondarily, the requirement for the clan name to occur at the start of
the player name has been relaxed. Now, the requirement is that the clan
name is matched with `\[([a-zA-Z0-9]{2,5})\]`. The pre-requisites for
clan regex matching have been updated so that both `[` and `]` must be
*included* in the player name (whereas previously the `[` was required
to appear at the start of the player name). This allows the clan name to
occur anywhere in the player name.
Finally, clan names (once matched by RegEx) are converted to Uppercase
so that clan names such as `[un]`, `[UN]`, `[Un]`, and `[uN]` are all
recognized as the *same* clan.
As a result, all existing clan names remain valid, but new clan names
are now possible. For example `[3M]MeanMrMustard` now has `3M`
recognized as the clan name and `T[UN]able` now has `UN` recognized as
the clan name. Test cases within the `tests/PlayerInfo.tests.ts` file
have been updated accordingly to accurately represent the full
alphanumeric character set.
This addresses issue #2267.
## 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 (GlacialDrift_)
## Description:
**This PR expands clan name possibilities available to players.**
**Suggested Label:** Feature
**Suggested Milestone:** v26 or v27
The current clan name logic does not allow for all alphanumeric
characters (e.g. 0-9). Instead it only allows for uppercase and
lowercase letters (e.g. a-z and A-Z). This PR updates the logic to
include 0-9 in the allowable character set. This is in line with how
many other games utilize clan names.
Secondarily, the requirement for the clan name to occur at the start of
the player name has been relaxed. Now, the requirement is that the clan
name is matched with `\[([a-zA-Z0-9]{2,5})\]`. The pre-requisites for
clan regex matching have been updated so that both `[` and `]` must be
*included* in the player name (whereas previously the `[` was required
to appear at the start of the player name). This allows the clan name to
occur anywhere in the player name.
Finally, clan names (once matched by RegEx) are converted to Uppercase
so that clan names such as `[un]`, `[UN]`, `[Un]`, and `[uN]` are all
recognized as the *same* clan.
As a result, all existing clan names remain valid, but new clan names
are now possible. For example `[3M]MeanMrMustard` now has `3M`
recognized as the clan name and `T[UN]able` now has `UN` recognized as
the clan name. Test cases within the `tests/PlayerInfo.tests.ts` file
have been updated accordingly to accurately represent the full
alphanumeric character set.
This addresses issue #2267.
## 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 (GlacialDrift_)
## Description:
Implement a basic matchmaking modal that connects to the api service and
waits for a game id. It then waits until the game starts and connects to
it.
Workers use long polling to check in with the matchmaking server and
receive player assignments.
## 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
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Description:
Adds a timer before self deleting units
Adds a loading bar under deleting units
Adds a timer in radial menu for clarity purposes

## 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:
Mr.Box
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
Adds a max timer setting
The timer starts at max timer and goes down, becoming red if reaching <
1 min
The player with the biggest territory wins at the end of the timer

## 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
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors
## Please put your Discord username so you can be contacted if a bug or
regression is found:
Vivacious Box
---------
Co-authored-by: Loymdayddaud <145969603+TheGiraffe3@users.noreply.github.com>
## Description:
Added a colors tab in territory patterns modal so players can select
their color.
Refactored the PrivilegeChecker, removed custom flag checks since we no
longer support custom flags.
<img width="479" height="345" alt="Screenshot 2025-09-27 at 5 01 17 PM"
src="https://github.com/user-attachments/assets/ad96da65-f0eb-4731-a861-e6e5fcb4566a"
/>
## 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:
Closes https://github.com/openfrontio/OpenFrontIO/issues/1619.
On capture, defense posts will be downgraded.
On the live version this means defense posts will be destroyed, as
defense posts can only be level 1.
Misc. changes:
- added `decreaserLevel` helper
- cleaned up if/else in tick unit loop for clarity to avoid yet another
nested layer
Continuation of the stale PR,
https://github.com/openfrontio/OpenFrontIO/pull/1622.
## 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: `seekerreturns`
## Description:
### ✅ Summary of Changes
This PR enables the ESLint rule **`@typescript-eslint/no-unused-vars`**
as requested in the issue and applies the necessary code adjustments
across the project.
#### 🔧 What was done:
- Activated the rule `@typescript-eslint/no-unused-vars` in the ESLint
config.
- Updated ~70 files to comply with the rule:
- Replaced unused variables with a `_` prefix where appropriate.
- Added inline ESLint disable comments (`eslint-disable-next-line`) for
specific cases where the variable or code block seemed important for
context, readability, or future use.
- Ensured no linting errors remain related to this rule.
---
### ❓ Clarification
Some cases were handled with inline disable comments instead of removing
the variable entirely, to avoid accidental breaking changes or loss of
intent.
If a different approach is preferred (e.g., stricter removal or
alternative handling), I’m happy to adjust the implementation
accordingly — just let me know!
---
### 🙌 Next Steps
Please review and let me know if:
- Any file should be handled differently.
- You prefer removal instead of disabling in certain areas.
- Additional rules should be enforced or reverted.
I’m available to make any follow-up improvements needed.
---
### 🎃 Hacktoberfest Note
I'm participating in **Hacktoberfest**, so if this PR is accepted,
please add the label:
`hacktoberfest-accepted`
Thank you!
#1784
## 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:
Added tests/NationNameLength.test.ts to enforce nation name length ≤27.
Updated manifest.json files in resources/maps with shorter names to pass
the test.
This fix ensures that names will no longer be truncated
Examples of country names that are too long and have their endings cut
off
<img width="231" height="331" alt="スクリーンショット 2025-10-01 10 51 40"
src="https://github.com/user-attachments/assets/8c4611ab-f97b-4606-9834-7816dbd1ee8d"
/>
### Changes
- Replaced overly long country names with shorter common forms:
- "The Democratic Republic of the Congo" -> "DR Congo"
- "Democratic Republic of the Congo" -> "DR Congo"
- "Lao People's Democratic Republic" -> "Laos"
- "Federated States of Micronesia" -> "Micronesia"
- "People's Democratic Republic of Algeria" -> "Algeria"
- "People's Republic of Algeria'" -> "Algeria"
## 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
## Description:
Make the unit display bar a proper unit build bar
Add shortcuts for all structures and units
Add ranges for ranged structures and units
Changed the shortcuts to use the key instead of the code for
internationalization purposes

<img width="285" height="517" alt="image"
src="https://github.com/user-attachments/assets/91bb01e6-e48c-4255-ace1-306af9cdc25b"
/>
## 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:
Mr.Box
---------
Co-authored-by: evanpelle <evanpelle@gmail.com>
Co-authored-by: icslucas <carolinacarazolli@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Closes#2071
## Description:
- Created Functional Test for scenario
- Added an alliance revoker function to 'NukeExection'
## Please complete the following:
- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
dpop
## Description:
Record player death ticks and "conquests" (when a player has < 100
tiles).
Tournaments would be easier to manage with those information.
Related infra PR: https://github.com/openfrontio/infra/pull/196
Tested locally with docker/infra repo
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
IngloriousTom
## Description:
Create mini map option
<img width="741" height="234" alt="Screenshot 2025-09-25 at 4 47 47 PM"
src="https://github.com/user-attachments/assets/6c442698-8e3b-44d5-b07e-c4f0a916c3bc"
/>
## 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:
This PR fixes a critical race condition bug where players could
unintentionally receive the traitor debuff when alliance requests were
accepted mid-attack.
Critical Bug Fixes#1866
**Root Cause:**
Players could bypass UI alliance checks ( isFriendly() ) by accepting
alliances and immediately attacking after that, causing the server to
treat the attack as betrayal
Solution: Added server-side alliance validation in
AttackExecution.init()
This ensures attacks on allies are blocked at the server level.
- Once Bots and Nations decide to attack, they breaks the alliance. I
added maybeConsiderBetrayal(), which currently always returns true. I’ll
add proper logic for alliance-breaking soon on another PR; this didn’t
exist in the code before.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
abodcraft1
---------
Co-authored-by: evanpelle <evanpelle@gmail.com>
## Description:
This removes the dependencies on R2, and allows contributors to replay
games without R2 access.
## 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:
Move the MapGenerator repo into OpenFrontIO so we don't need to copy
generated map files over.
## 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