Commit Graph

145 Commits

Author SHA1 Message Date
Zixer1 78ef7b56fd feat(doomsday-clock): battle-royale style zone gamemode (#4469)
Resolves Issue #4463

## Description:

An optional game mode that (almost) guarantees a finish instead of
letting late-game
stalemates drag on.
Originally called sudden death, renamed to Doomsday clock

Once enabled, every side (each player in FFA, each whole team in team
modes)
must hold a rising share of the map. A side below the bar is skulled;
after a
short warn its troops bleed to zero, forcing consolidation to a winner.

### How it works
- **Rising zone:** a grace period, then the required share ramps up
linearly to
each level with 30s pauses between (a battle-royale "zone"). Levels
track the
  ofstats FFA territory median (3/5/10/20/30%).
- **Four speed presets** (slow / normal / fast / very fast) change only
the pace:
  normal ends ~30 min, very fast ~15.
- **Troop decay:** a linear ramp as a % of max capacity, ~50s from
caught to zero
  (10s warn + ~50s ≈ 1 min total).
- **UI:** a HUD panel (live share vs target, wave/decay countdowns,
red/orange
cues) and an on-map skull above flagged players (blinks in danger,
steady while
  draining).

### Notes for review
- Off by default; no effect on existing games. However, as discussed we
can add it to the modifier pool for public games to see how popular the
gamemode is vs normal play.
- Sim is deterministic (integer-only, in `src/core`), covered by unit +
  integration tests.
- One-line addition to `GameServer.updateGameConfig` so the setting
survives the
  host → server → client round-trip.
- Status is packed into the existing name-pass data slot (`pd4.w`: 0/1/2
=
none/danger/draining); the skull is composited into the icon atlas at
load.

### Testing
`npm test`, `npm run lint`, `npx prettier --check .`, `npm run
build-prod` all pass.

### UI:
<img width="243" height="100" alt="Image"
src="https://github.com/user-attachments/assets/c4c9eeb0-4feb-437d-9aac-b2786a841b74"
/>

Dropdown between slow, normal, fast, very fast

Before zone:
<img width="302" height="175" alt="Image"
src="https://github.com/user-attachments/assets/7359a1ea-4951-446d-a23c-0711fe06cc5d"
/>

Zone started, player not affected the pannel also blinks orange for 10s:
<img width="297" height="175" alt="Image"
src="https://github.com/user-attachments/assets/fcc565a5-d5d0-47a7-97ea-d0ba9d9ad899"
/>

Player affected, grace period (Danger):
<img width="314" height="170" alt="Image"
src="https://github.com/user-attachments/assets/ff96d21e-96f3-4ef9-8190-48eecc7aac0f"
/>

Skull icon blinking over player (everyone sees it) - older screenshot,
the clipping has been fixed
<img width="462" height="145" alt="Image"
src="https://github.com/user-attachments/assets/53899211-33b1-40e1-83f2-77f2096f0cad"
/>

Player affected, grace period ended (Draining):
<img width="360" height="159" alt="Image"
src="https://github.com/user-attachments/assets/4b226d57-da4d-4866-ab5f-db48e4ed1ea2"
/>

Skull icon no longer blinking, everyone can see you are in a state of
decay, and troops are draining:
<img width="732" height="146" alt="image"
src="https://github.com/user-attachments/assets/cd10fedb-6e87-4dfc-9fbf-55d3945a7901"
/>


Skull is visible like alliances icon also on player tab
<img width="558" height="81" alt="Image"
src="https://github.com/user-attachments/assets/6acdbe91-bdd0-40c7-942b-3990d4dae87f"
/>

(just UI example, best way to see it is to hop on a solo game and play
against AI)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

zixer._
2026-07-02 18:42:03 -07:00
Ryan dae129c6a3 replace leave lobby popup with custom popup (#4449)
## Description:
old:
<img width="1009" height="491" alt="image"
src="https://github.com/user-attachments/assets/0b95877c-dac7-4025-bdfa-62ab6879d208"
/>

new:
<img width="1017" height="561" alt="image"
src="https://github.com/user-attachments/assets/cfb49b31-eb46-4d64-bd9e-3f25bb7cd0fb"
/>




## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
2026-06-29 16:16:46 -07:00
Zixer1 3b84a6f569 Feat/anonymize names (#4318)
**Add approved & assigned issue number here:**

Resolves #4296

## Description:

Adds an "Anonymous players" option to private lobbies (host toggle, off
by default).

When it is on, the server sends each client anonymized usernames for
everyone except themselves. The lobby creator and admins still see real
names so they can moderate. Names are hidden on every player-facing
surface: the game start message, lobby info, /api/game/:id, and the link
preview. It is enforced server-side, so a client extension cannot read
real names off the wire. Initially added as part of our overhaul of
OpenFront masters, but this feature can very well be useful for content
creators, and other tournament hosts.

Anonymized names reuse the existing tribe word lists (no emoji), so they
pass UsernameSchema, and they are seeded per user, so a player looks
different to different users but stays consistent from the lobby into
the game.

The saved game record keeps real names (anonymization is a per-send
transform, gameStartInfo is never mutated), so replays and stats are
unaffected. Nothing changes for normal games.

New option selection:
<img width="990" height="918" alt="image"
src="https://github.com/user-attachments/assets/31df0b0b-7757-4b2b-9bff-84310faee8d9"
/>

The host, when enabling the option, gets a little eye icon next to the
players(including himself to enable/disable the anon names for himself,
and/or other player)

By default(the names everyone will see are random and unique):
<img width="979" height="188" alt="image"
src="https://github.com/user-attachments/assets/f0caa4a4-9f14-41d3-89c6-9a38e8c2e6f0"
/>

Toggling the eye ON for yourself (the host, or any given player, will
allow them to see the real names of everyone, in the lobby and in game):
<img width="969" height="138" alt="image"
src="https://github.com/user-attachments/assets/89abf0e0-1433-43ea-9870-49d96ca46d30"
/>


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

zixer._
2026-06-24 07:54:44 -07:00
Evan c55ea6bb5a Mint game ids on the server, randomly route create-game across workers (#4393)
## What

Game creation no longer requires the caller to pick the `gameID` or
compute its owning worker. The client POSTs to a prefix-less
`/api/create_game`; **nginx (prod) and the vite dev proxy randomly route
it to a worker**, which **mints an id that hashes back to itself** and
returns it along with its `workerIndex`.

## Why it stays correct

The minted id still hashes to the creating worker (via the existing
`generateGameIdForWorker`), so everything downstream that derives the
worker from the gameID — websocket connect, share URL, join flow — keeps
working unchanged. The only thing that moved is *who picks the id and
worker*.

## Changes

- **`src/server/Worker.ts`** — factor create into a shared
`createGameForId`; add `POST /api/create_game` (no id) that mints a
self-owned id and returns `gameInfo` + `workerIndex`/`workerPath`. The
existing `POST /api/create_game/:id` stays.
- **`nginx.conf`** — `location = /api/create_game` proxies to a `random`
worker upstream.
- **`generate-nginx-upstream.sh` + `Dockerfile`** — the entrypoint
generates that upstream from `NUM_WORKERS` at container **start** time.
`NUM_WORKERS` isn't known at image build time (the image is built once
and deployed with different env), so it can't be baked into `nginx.conf`
— hence runtime generation of exactly the live worker ports (no
dead-server padding).
- **`vite.config.ts`** — dev-only middleware forwards `POST
/api/create_game` to a random worker. Vite's `http-proxy` can't pick a
per-request random target, so this is a small middleware plugin (same
pattern as the existing `serveProprietaryDir`), registered before the
`/api` proxy.
- **`src/client/HostLobbyModal.ts`** — stop generating the id
client-side; use the server's.

## Behavior change to note

The host's share link used to be copied **instantly** from a
client-generated id. Now the id comes from the server, so the copy waits
one create round-trip — I moved the URL build/copy into the create
`.then` (and kept the failure path that clears the clipboard). Brief
empty-link state in the modal until create resolves.

## Verification

- tsc + eslint clean; full suite green (1543 tests).
- nginx additions validated with `nginx -t` in isolation (the full file
references container-only paths like `/etc/nginx/mime.types`); upstream
+ `proxy_pass` resolve.
- `generate-nginx-upstream.sh` tested with `NUM_WORKERS` set and unset
(defaults to 1).

Not yet exercised live end-to-end (needs a dev-server restart —
`vite.config.ts` + `Worker.ts` changes aren't hot-reloaded).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:15:09 -07:00
evanpelle cbf1e5c513 Make host lobby start button yellow during countdown
Add a `warning` variant to o-button that reuses the existing
`--color-cyber-yellow` brand token, and apply it to the host lobby's
start button while the "Starting in Xs" countdown is active. The button
stays primary blue in the Waiting/Start states.
2026-06-22 12:46:18 -07:00
Zixer1 758063651d Add allowlist for private lobbies (OFM) (#4351)
**Add approved & assigned issue number here:**

Resolves #4349

## Description:

1. **Private-lobby allowlist.** `create_game` accepts an optional
`allowedPublicIds`. It's set by whoever creates the lobby (admin-token
gated, no client UI), the game server pulls it out of the config so it's
never broadcast to clients or written to the game record, and it rejects
any joiner whose OF publicId isn't on the list before they take a slot
(stickily, so they can't retry on reconnect). Lobbies created without it
behave exactly as before.

It is off by default
Previews:
<img width="241" height="140" alt="image"
src="https://github.com/user-attachments/assets/30c4e47b-399d-4720-b25b-a04c63668577"
/>
<img width="982" height="456" alt="image"
src="https://github.com/user-attachments/assets/1b5c68b7-9b99-4ccc-b987-e70c8ec25dce"
/>
<img width="547" height="369" alt="image"
src="https://github.com/user-attachments/assets/1623090b-ea2b-4657-9cd8-903fbabca51b"
/>


I am not able to manually test all of it since it needs to also run the
auth API (infra) and actually be connected to disc and whatnot (but
still tested the refused flow)..
Also, we would need to place some guards and visual error feedback, but
since this only would affect casual of players and is more of a
improvement to the feature, I will consider it out of scope for now.

## Please complete the following:

- [x] I have added screenshots for all UI updates (no UI changes in this
PR)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (no new user-facing text)
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

zixer._
2026-06-21 15:10:48 -07:00
FrederikJA 19db66f424 Delayed lobby start (#4184)
Resolves #4169

## Description:

Adds a delayed lobby start option.
Utilizes the same system as for public lobbies.
The default for the option is for lobbies to take 3 seconds to start,
however this can easily be changed.

The current setting is controlled through an enable-disable slider,
however there are multiple other options for how to control this.
For example we could do a slider, an input field, a dropdown etc. And i
dont necessarily know if the currently implemented option is the best.

Furhtermore im not sure if i have used the language file completely
correctly. There is now a duplicate field for both private and public
lobby. However there is not category shared between the two. So i
decided to reuse the field from public for private games, as this
simplified the code a bit.

**Host video**

https://github.com/user-attachments/assets/6f3db6e4-7323-4fad-8544-efb8cef4d969

**Non-host video**

https://github.com/user-attachments/assets/ee02a072-1f42-4dde-a5d9-120fda862eb7

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory


## Please put your Discord username so you can be contacted if a bug or
regression is found:
FrederikJA
2026-06-12 12:22:03 -07:00
evanpelle 96c032850d Fix start game button regression in lobby modals
The Start Game button in SinglePlayerModal and HostLobbyModal was
scrolling away with the body instead of staying pinned at the bottom.

HostLobbyModal's body lacked overflow-y-auto/min-h-0, so the body
couldn't scroll independently and the footer scrolled off-screen.
SinglePlayerModal was missing the flex flex-col h-full wrapper entirely
(and had an unmatched closing div), so the footer never had a sticky
column to anchor to.

Restore the v31 layout: outer flex-col h-full, body is the scroll
container (flex-1 min-h-0 overflow-y-auto custom-scrollbar), footer is
shrink-0.
2026-06-03 20:51:28 -07:00
Ryan 15ac42b4c1 streamer mode bugfix (#3953)
## Description:

fixes 
https://github.com/openfrontio/OpenFrontIO/issues/3572

streamer mode bufix


## 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
2026-05-18 19:20:29 -07:00
Evan bbe727cc84 Refactor modal system: BaseModal renders shell, unified open(args) API (#3923)
## Description

Refactors the modal system so that `BaseModal` owns the `<o-modal>`
shell rendering, tab state, and lifecycle. Modal subclasses now provide
content via small hook methods (`renderHeaderSlot()`, `renderBody(tab)`,
`modalConfig()`) instead of each rebuilding the `<o-modal>` template and
inline-mode branching.

This sets up the foundation for a future modal URL router (e.g.
`#modal=store&tab=flags`), which will be a follow-up PR.

### What changed

**`BaseModal`** — `src/client/components/BaseModal.ts`
- Now renders the `<o-modal>` shell itself; subclasses no longer
duplicate it
- Owns `activeTab` state and dispatches per-tab rendering via
`renderBody(tab)`
- Single `modalConfig()` method returns `{ title?, tabs?, hideHeader?,
hideCloseButton?, alwaysMaximized?, maxWidth? }`
- Uniform `open(args?)` / `close(args?)` interface; subclasses interpret
args in `onOpen(args)` / `onClose(args)`
- Tabbed modals can lazy-load via `onTabEnter(tab)` lifecycle hook
- Re-entrancy guard on `open()` so `showPage()` re-invocations don't
clobber state set by the outer call
- Initial tab defaults to first entry in `modalConfig().tabs` so the
active tab is highlighted on first open

**17 modals migrated** to the new shape:
- Tabbed: Store, UserSetting, Leaderboard, Clan
- Non-tabbed: FlagInput, Account, TokenLogin, News, TerritoryPatterns,
Troubleshooting, SinglePlayer, Matchmaking, RankedModal, Help, Language
- Lobby: JoinLobbyModal, HostLobbyModal (kept their `confirmBeforeClose`
/ `closeAndLeave` / `closeWithoutLeaving` methods)

Per-modal diffs are mostly mechanical:
- Drop the `<o-modal>` wrapper template and the `if (this.inline) return
content` branch
- Drop the inner `<div class="${this.modalContainerClass}">` wrapper
(shell styling now lives on `<o-modal>`)
- Move header content into `renderHeaderSlot()` so it lives in the
sticky header area
- Convert `super.open()`/`super.close()` overrides into
`onOpen(args)`/`onClose(args)` hooks
- For tabbed modals: drop subclass `@state activeTab`, manual
`handleTabChange`, and the `render()` switch — all owned by BaseModal
now

**Other changes:**
- `Store`: in affiliate mode (`#affiliate=X`), tabs are hidden and a
single combined grid of purchasable affiliate items is shown
- `Main.ts`: `joinModal.open(lobbyId, lobbyInfo)` callsites converted to
the new `open({ lobbyId, lobbyInfo })` shape

### Follow-up

Modal URL router (`#modal=X&tab=Y&...`) is a separate PR on top of this
foundation.

## Please complete the following:

- [x] I have added screenshots for all UI updates _(no visual changes;
smoke-tested in dev)_
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file _(no new user-visible strings)_
- [ ] I have added relevant tests to the test directory _(no test
coverage; tested in browser)_
- [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
2026-05-14 15:33:41 -07:00
Evan 275fd0dccc refactor: collapse per-env Configs into ClientEnv + ServerEnv (#3906)
## Description:

This is a refactor to simplify config handling.

Replaces the per-environment DevConfig/PreprodConfig/ProdConfig class
hierarchy with two static classes: ClientEnv (browser main thread, reads
from window.BOOTSTRAP_CONFIG) and ServerEnv (Node server, reads from
process.env). The four config classes are deleted, the abstract
DefaultServerConfig is gone, and DefaultConfig is renamed to Config.

The values that flow server → client (gameEnv, numWorkers,
turnstileSiteKey, jwtAudience, instanceId) used to be baked into the
hardcoded per-env classes. They're now real env vars on the server,
embedded into a single window.BOOTSTRAP_CONFIG object in index.html at
request time (alongside the existing gitCommit/assetManifest/cdnBase
globals, which moved into the same object), and read back by ClientEnv
on the client. The dev defaults previously hidden inside DevServerConfig
are now explicit in start:server-dev (NUM_WORKERS=2,
TURNSTILE_SITE_KEY=1x..., JWT_AUDIENCE=localhost, etc.) and in
vite.config.ts's html plugin inject.data. Production deploys plumb
NUM_WORKERS and TURNSTILE_SITE_KEY through deploy.yml (GitHub vars) into
the remote env file; JWT_AUDIENCE is derived from DOMAIN in deploy.sh.
The dynamic /api/instance endpoint is gone — INSTANCE_ID rides along in
BOOTSTRAP_CONFIG now.

ServerEnv is the only thing server code touches; ClientEnv is
browser-only. The two classes have intentional overlap (env, numWorkers,
jwtIssuer, gameCreationRate, workerIndex, etc.) since they derive
identical logic from different sources — there's a TODO in each to
consolidate via a shared helper later. The game-logic Config no longer
stores a ServerConfig/ClientEnv reference and its serverConfig() getter
is gone; the one caller (MultiTabModal) now reads ClientEnv.env()
directly. Worker init no longer carries server-config values since
nothing in the worker actually reads them.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
2026-05-11 19:24:01 -07:00
Evan 2994a5f848 Start game via WebSocket intent (#3794)
## Description:

Replaces the HTTP POST /api/start_game/:id endpoint with a WebSocket
intent, making private game start consistent with how kick_player and
update_game_config already work. Also verifies that only the lobby
creator can start 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
2026-04-29 13:26:14 -06:00
Evan 62299c9714 standardize UI colors to fit brand guidelines (#3754)
## Description:

We have brand colors:

<img width="738" height="900" alt="Screenshot 2026-04-25 at 12 52 29 PM"
src="https://github.com/user-attachments/assets/aac69e87-91f2-4c3f-9f1e-f69f48f5943e"
/>

So update the homepage & in-game UI to use those colors:

<img width="1185" height="946" alt="Screenshot 2026-04-25 at 12 51
06 PM"
src="https://github.com/user-attachments/assets/89a0b12c-2db1-43d4-9500-fcf405c0f7ff"
/>

Also updated buttons to use the o-button element

## 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
2026-04-25 13:53:21 -06:00
Zixer1 9ae6f8a378 Feat/auto copy lobby code (#3758)
Resolves #3757

## Description:

Simple patch that would remove an extra click that users have to do each
time they create a private lobby. On top of the existing button, the
game link will automatically be copied to the clipboard when clicking
"Create Lobby".


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

zixer._
2026-04-25 11:54:11 -06:00
FloPinguin 9821e8e041 Add host cheats for streamers (Specifically Enzo) (#3671)
## Description:

- Adds a "Host Cheats" toggle in the private lobby options section that
reveals a dedicated section with four host-only cheats: infinite gold,
infinite troops, gold multiplier, and starting gold
- Only the lobby creator receives the cheat effects in-game (checked via
`isLobbyCreator` in DefaultConfig)
- Joining players see active host cheats displayed as yellow badges in
the lobby UI
- Adds `hostCheats` optional object to `GameConfigSchema` and wires it
through the server config update whitelist
- Raises the intent size limit for `update_game_config` messages
(lobby-only, not stored in turn history) to prevent rate-limiter kicks
(I always got too-much-data-kicked after selecting "host cheats" lol)

<img width="861" height="525" alt="image"
src="https://github.com/user-attachments/assets/51e51ec4-c2e8-46ca-b258-11a93487964f"
/>


<img width="933" height="825" alt="image"
src="https://github.com/user-attachments/assets/5acbd38d-2097-42e1-ba78-0fb17d6afe82"
/>

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
2026-04-15 15:20:08 -07:00
iamlewis 41c72a0f9e UI Updates (#3616)
## Description:

Updates Favicon and other key UI elements



## 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:

iamlewis

---------

Co-authored-by: iamharry <harrylong0905@gmail.com>
Co-authored-by: FloPinguin <25036848+FloPinguin@users.noreply.github.com>
Co-authored-by: evanpelle <evanpelle@gmail.com>
2026-04-13 19:51:08 -07:00
FloPinguin 7f7cbba12f Water-Nukes 💧 (#3604)
## Description:

Adds a new `waterNukes` game config option that causes nuclear
detonations to convert land tiles into water instead of just leaving
fallout. When enabled, nuked land tiles are batched and converted to
water each tick, with full terrain metadata updates including:

- Ocean bit propagation from adjacent ocean tiles (BFS flood fill)
- Magnitude recomputation via BFS from remaining coastlines
- Shoreline bit fix-up in a 2-ring neighborhood around converted tiles
- Minimap terrain sync (majority-rule downsampling)
- Throttled water navigation graph rebuild (every 20 ticks) for ship
pathfinding
- Ship executions detect graph rebuilds and refresh their pathfinders
- TransportShips auto-retreat if their destination becomes water
- Water nuke craters use a smoothed angular noise ring with a
bounding-box scan instead of the regular per-tile random coin flip with
BFS, producing clean blob-shaped craters without scattered land pixels
that players would have to boat to individually

The `TerrainLayer` now incrementally repaints tiles that changed terrain
type, and tile update packets encode the terrain byte alongside tile
state so clients can reflect water conversions in real time.

When `waterNukes` is disabled, behavior is unchanged (fallout only).

Includes a new test suite (WaterNukes.test.ts) covering the conversion
pipeline, ocean propagation, magnitude recalculation, shoreline updates,
and minimap sync.

Also adds a new public game modifier for the special rotation.

### The only problem
A bit of lag on impact. But otherwise it works great and is fun. Maybe
needs some followup improvements if it gets merged.
I think its very cool in baikal / four islands team games. Chip away the
territory of your opponents.
Its also fun to turn The Box / Alps into a water map (its actually
possible to boat-trade then)

### Media

Video does not show the updated craters


https://github.com/user-attachments/assets/aed8bf08-0e94-4484-b997-4de11ae313d9

Updated craters (no tiny islands after impact):

<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/e896870b-bc9d-493d-8bc8-b3a5427d69d3"
/>

<img width="1472" height="920" alt="image"
src="https://github.com/user-attachments/assets/677065aa-0159-48cd-af44-a91b0f57adfc"
/>

<img width="1296" height="892" alt="image"
src="https://github.com/user-attachments/assets/886ffaba-541f-4e46-97c6-ce963f632fe0"
/>

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
2026-04-08 20:56:02 -07:00
FloPinguin 50bd075b1c Fix deselected host lobby settings persisting for joiners 🐛 (#3607)
## Description:

### Problem

When a host toggled off certain settings (game length, PVP immunity,
starting gold, gold multiplier, disable alliances) in the host lobby
modal, joiners still saw the old values. The settings appeared "stuck"
once enabled.

### Root Cause

`putGameConfig()` sent `undefined` for disabled settings, but
`JSON.stringify` strips `undefined` properties entirely. The server's
`!== undefined` guard never fired, so the old value was never cleared.

### Fix

- **HostLobbyModal**: Send `null` instead of `undefined` when these
settings are toggled off (`null` survives JSON serialization)
- **Schemas**: Add `.nullable()` to the five affected fields
(`maxTimerValue`, `spawnImmunityDuration`, `goldMultiplier`,
`startingGold`, `disableAlliances`)
- **GameServer**: Use `?? undefined` (nullish coalescing) to convert
incoming `null` back to `undefined` when storing on the config

Other settings are unaffected. Booleans like `infiniteGold` always send
`true`/`false`, and fields like `bots`/`gameMap` always have a concrete
value..

## 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>
2026-04-07 11:51:42 -07:00
scamiv 2be858869c Split runtime and game logic env loading (#3505)
## Description:


This refactors client configuration loading to make the environment
split explicit.

The app currently has two different env concerns:
- the browser main thread needs the live runtime env to select API /
Turnstile / JWT settings
- the worker and game-logic path need a build-time env to select game
config behavior

Before this change, both responsibilities were hidden behind the same
loader, which made the intent unclear and caused confusion around the
worker fallback behavior.

This PR separates those paths explicitly:
- main-thread browser code now uses `getRuntimeClientServerConfig()`
- game creation and worker/game-logic code now uses
`getGameLogicConfig()`
- the build-time game-logic env is represented explicitly as
`GameLogicEnv`

## What Changed

- Added `GameLogicEnv` to model the build-time game config choice
explicitly.
- Added `getRuntimeClientServerConfig()` for live runtime browser config
from `window.BOOTSTRAP_CONFIG`.
- Added `getBuildTimeGameLogicEnv()` and
`getServerConfigForGameLogicEnv()` for build-time worker/game-logic
config.
- Renamed game config loading from `getConfig()` to
`getGameLogicConfig()` to reflect what it actually does.
- Updated browser call sites to use the runtime client config loader.
- Updated worker/game creation paths to use the game-logic config
loader.
- Updated config loader tests to cover both paths.

## Behavior

This keeps the current intended behavior, but makes it explicit:

- Runtime client env:
  - comes from `window.BOOTSTRAP_CONFIG`
- controls live browser integration settings such as API origin,
Turnstile, and JWT audience/issuer

- Build-time game-logic env:
  - comes from bundled `process.env.GAME_ENV`
  - maps:
    - `dev` -> dev game config
    - `staging` -> default/prod game config
    - `prod` -> default/prod game config

That means preprod/staging deployments can continue using prod game
logic while still using staging API/auth settings on the main thread.

## Why

The previous setup worked, but the naming and loader boundaries were
misleading:
- the same function was used for both runtime browser config and
worker/game config
- the worker fallback looked like an implementation detail instead of an
intentional architectural split

This change makes that intent visible in code without changing the
desired deployment behavior.



## Please complete the following:

- [ ] I have added screenshots for all UI updates
- [ ] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [ ] I have added relevant tests to the test directory
- [ ] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

DISCORD_USERNAME
2026-03-24 10:54:39 -07:00
FloPinguin 3838de1d30 Option to disable alliances + 2 new modifiers for variety 😄 (#3392)
## Description:

Rex had this idea: "It would be funny to have an option in private
lobbies to disable alliances."
I added it as an option.
Now people can choose to live in constant fear of their neighbors 😆 

Also added two new public game modifiers for variety (only for the
special rotation):
- Alliances disabled (low probability)
- x2 gold multiplier (low probability)

Would be nice to squeeze this into v30, last minute?

## 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
2026-03-09 21:13:13 -07:00
Evan c63b304a97 various homepage improvements (#3387)
## Description:

Various changes, applied more styling from the homewrecker branch

* dimmed background
* Content width: expands to 24cm on 2xl screens
* game card ocean color: French blue → sky-950
* Action buttons (Create/Ranked/Join): French blue → slate-700
* Modifier badges: teal → sky blue, to keep in color scheme
* CTA buttons (Start Game, Join Lobby): blue-600 → sky-600 across all
modals and <o-button>
* Nav font: font-bold tracking-widest → font-medium tracking-wider
* Username/flag inputs: font weight lightened to font-medium
tracking-wider
* Language flag: blue color filter applied


BEFORE:


<img width="1446" height="978" alt="Screenshot 2026-03-08 at 6 48 57 PM"
src="https://github.com/user-attachments/assets/ff748e1c-6cb5-4a66-ac27-9538e935b325"
/>

AFTER:

<img width="1629" height="988" alt="Screenshot 2026-03-08 at 6 46 53 PM"
src="https://github.com/user-attachments/assets/364bb57a-65ff-40cf-931b-067ed36e3c5b"
/>


## 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
2026-03-08 19:00:24 -07:00
FloPinguin c594487f5e Starting gold input in millions with decimal support (#3349)
## Description:

**Starting gold input: use millions**

Changes the starting gold input in singleplayer and host lobby modals to
accept values in millions (e.g. enter `5` for 5M gold). Supports
decimals like `6.6` for 6.6M. The value is multiplied by 1,000,000
before being sent to the game config.

- Label updated to "Starting Gold (Millions)"
- Input uses float parsing with min 0.1, matching gold multiplier
behavior
- JoinLobbyModal shows clean values without unnecessary decimals (e.g.
"5M" not "5.00M")

Previous

<img width="215" height="139" alt="image"
src="https://github.com/user-attachments/assets/00ce5b6d-f74d-4aee-92f5-c9be1a0a6d3d"
/>
<img width="292" height="74" alt="image"
src="https://github.com/user-attachments/assets/4de936a3-22bd-4ffc-8dbe-0d5066f28186"
/>

Now

<img width="216" height="151" alt="image"
src="https://github.com/user-attachments/assets/489de13e-65b5-4b02-a654-5f6f74b165d1"
/>
<img width="292" height="72" alt="image"
src="https://github.com/user-attachments/assets/51723d5a-55ab-4b7b-bbce-011a586eeb44"
/>

## 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
2026-03-05 20:48:03 -08:00
FloPinguin 0dc520b1c8 Add confirmation dialog before closing host lobby modal 🔧 (#3364)
## Description:

Show a confirm prompt when the user tries to close the host lobby via
click-outside or Escape key, preventing accidental lobby exits. Happened
to many people and also DougDoug...

Does not show the prompt when the user clicks the arrow button because
it's probably intentional.

## 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
2026-03-05 20:39:22 -08:00
FloPinguin 0b9d43cb46 Configurable nation count 🤖 (#3338)
## Description:

I hope we can get this into v30?
The nation count is configurable now, just like the bot count.
Replaced the "Disable Nations" toggle with a nations slider (0–400) in
SinglePlayer and Host Lobby modals.

<img width="710" height="121" alt="Screenshot 2026-03-03 021952"
src="https://github.com/user-attachments/assets/c8d0f0c3-db51-4303-95fa-dbc770460ec2"
/>


Public games are staying exactly the same, this is just for singleplayer
and private lobby fun.
Youtubers could play HvN against 400 nations, for example.
Singleplayer enjoyers no longer have to play against 1 nation in HvN,
they can freely choose.

`GameConfig.disableNations: boolean` got replaced by `nations: number
(0-400, optional)`
`undefined` = map default, 
`0` = disabled, 
number = custom count

Nations slider defaults to the map's nation count, shows "(MAP DEFAULT)"
label when unchanged
Compact map toggle reduces nations to 25% when at default, restores when
toggled off (just like we already do with bots)
The nation count for HvN no longer automatically matches the human count
in singleplayer and private games, only in public games.

**What if there aren't enough nations configured for the map?**
We just use the HvN logic (Generate random nations)

### Warning

**This infra PR also needs to get merged:
https://github.com/openfrontio/infra/pull/263
Otherwise players can set 0 nations and get achievements.**

## 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
2026-03-03 14:07:06 -08:00
Ryan 70f2abb181 Homepage update & add 3 public lobbies (#3191)
## Description:

Update UI 
check https://homepageupdate.openfront.dev/ 

Improved mobile UI (now fills whole screen for all modals) e.g.:
<img width="432" height="852" alt="image"
src="https://github.com/user-attachments/assets/56de40af-4137-4c57-96b7-3910c9a665b8"
/>

Converted PublicLobby to be "GameModeSelector" to get a nicer 4x4 grid
div, where <GameModeSelector> now handles all the username validation
now (removed redundant code from modals such as matchmaking) also fixed
a bug where someone could have "[XX] X" as thier username (when the
minimum should be 3 chars for their name)

Now visually displays the 3 lobbies ffa/team/special (which is a
continuation from the work done in: #3196 )
<img width="818" height="563" alt="image"
src="https://github.com/user-attachments/assets/a15cd31b-6061-4fb8-83ee-ffde6225cfa7"
/>

updated the background:
<img width="1919" height="807" alt="image"
src="https://github.com/user-attachments/assets/358a7434-51b8-4540-baf2-d1be05053c44"
/>



slightly updated the glassy-look to be less glassy:
<img width="825" height="729" alt="image"
src="https://github.com/user-attachments/assets/1801871b-bbf8-43db-ac53-489337ae80a5"
/>



## 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
2026-02-18 23:11:01 -06:00
Ryan f8c14398c8 UI Extraction Host/Solo Modal (#3181)
## Description:

UI Extraction Host/Solo Modal
- Made all buttons do the same "press" feel (options/settings/random
map) are now the same as the map button.
- Also fixed a bug where you could "drag" the map image off the button.

## 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
2026-02-12 10:41:14 -08:00
Ryan 8dcc7cfb9a Fix client reconnection after page refresh (#3117)
## Description:

- Removed all code related to generating a client ID on the client. The
server now assigns the client ID and sends it to the client in lobby
messages.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
2026-02-09 01:10:11 +00:00
Ryan 41a9bb80c0 Added source for join context (#3116)
## Description:

Added source for join context

## 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
2026-02-04 10:01:05 -08:00
Ryan c9d8ed767c Add getClientIDForGame for consistent client IDs per game session (#3108)
## Description:

- Add getClientIDForGame function to Auth.ts that generates and stores a
consistent clientID per gameID using sessionStorage
- Update HostLobbyModal to use getClientIDForGame for lobby creation
- Update Matchmaking to use getClientIDForGame when joining games
- Update PublicLobby to use getClientIDForGame when joining lobbies


This enables reconnection support by ensuring the same clientID is used
when rejoining a game session.

## 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>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-03 15:07:12 -08:00
evanpelle c54c73d157 Merge branch 'v29' 2026-01-28 16:06:32 -08:00
Evan cb3128f390 Better CrazyGames integration (#3055)
## Description:

Better integration with CrazyGames:

* Don't show login because accounts have not been integrated with
CrazyGames yet
* Integrate CG invite links & usernames
* Refactor match making logic to Matchmaking.ts
* Allow periods to support crazy game usernames
* Create a no-crazygames class that disabled elements when on crazygames

## 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
2026-01-28 11:29:27 -08:00
Aotumuri 1314115d3f Add map picker with Featured/All tabs (#3005)
Resolves #2996

## Description:

Replace map selection UI in src/client/SinglePlayerModal.ts and
src/client/HostLobbyModal.ts with the picker (Featured/All tabs + random
map card).

Also, since the html was getting quite long, I extracted the shared
parts into a separate component.

<img width="575" height="592" alt="スクリーンショット 2026-01-23 21 57 03"
src="https://github.com/user-attachments/assets/fc6bfbc3-cb66-452a-b971-436940b0fb99"
/>

<img width="633" height="648" alt="スクリーンショット 2026-01-23 21 57 12"
src="https://github.com/user-attachments/assets/1aa409a1-b801-4a60-8b26-ba20e343d66e"
/>

I separated Map.ts because the display logic looked reusable in other
places, but I’m also open to merging it back if that makes more sense.
If the review prefers it integrated, I can combine them again.

## 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
2026-01-27 15:52:48 -08:00
Aotumuri 6cca96b545 Anonymized/hidden names on lobby preview (#2965)
Resolves #2962

## Description:

Anonymize lobby preview player names when “Hidden names” is enabled,
using the same deterministic mapping as in-game.

<img width="864" height="618" alt="スクリーンショット 2026-01-20 21 13 19"
src="https://github.com/user-attachments/assets/30ebe155-c66e-49a5-8957-f8ec0a1ccd76"
/>

<img width="668" height="341" alt="スクリーンショット 2026-01-20 21 13 27"
src="https://github.com/user-attachments/assets/6ef74d98-ea2f-4156-a321-306acf012672"
/>


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

aotumuri

Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2026-01-27 15:49:06 -08:00
FloPinguin 4a0ce7128e Fix for v29: Add nation count loading for JoinPrivateLobbyModal; change HvN difficulty (#2941)
## Description:

1. In JoinPrivateLobbyModal the nation count loading was missing. That
caused the team preview UI to show different player counts compared to
the HostLobbyModal. For example it showed 0/0 nations for the
HumansVsNations team mode (instead of 2/2):

<img width="726" height="217" alt="Screenshot 2026-01-16 211337"
src="https://github.com/user-attachments/assets/8b4219de-e2b2-46ff-a600-c86915e5bdb3"
/>

2. Turn down HvN difficulty from Impossible to Hard. 
We steamrolled over Hard nations in the playtest (at least in two of the
three games) because we donated lots of troops to each other.
But after some API data research I noticed that only 33% of players in
public team games ever use the donate functionality.
And we probably have less skilled players in public games than in the
playtest.
So its probably better to use the Hard difficulty to ensure balanced
gameplay.
I know, I'm overthinking this 😂

## 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
2026-01-17 21:32:21 -08:00
Ryan c8e0838b15 CopyButton, extract into component (#2934)
## Description:

Extracted the CopyButton into its own component, and now reusing it in
"Account" too.

## 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
2026-01-17 21:31:45 -08:00
Ryan 8bc037e098 Host/Solo modal code cleanup (#2939)
## Description:

Refactor / Clean-up code inside Host/Solo modals.

## 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
2026-01-18 02:08:23 +00:00
FloPinguin 2383e057bc Fix for v29: Add nation count loading for JoinPrivateLobbyModal; change HvN difficulty (#2941)
## Description:

1. In JoinPrivateLobbyModal the nation count loading was missing. That
caused the team preview UI to show different player counts compared to
the HostLobbyModal. For example it showed 0/0 nations for the
HumansVsNations team mode (instead of 2/2):

<img width="726" height="217" alt="Screenshot 2026-01-16 211337"
src="https://github.com/user-attachments/assets/8b4219de-e2b2-46ff-a600-c86915e5bdb3"
/>

2. Turn down HvN difficulty from Impossible to Hard. 
We steamrolled over Hard nations in the playtest (at least in two of the
three games) because we donated lots of troops to each other.
But after some API data research I noticed that only 33% of players in
public team games ever use the donate functionality.
And we probably have less skilled players in public games than in the
playtest.
So its probably better to use the Hard difficulty to ensure balanced
gameplay.
I know, I'm overthinking this 😂

## 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
2026-01-17 15:02:32 +00:00
Ryan 0d81626760 Revert "Fix for v29: Add nation count loading for JoinPrivateLobbyModal; change HvN difficulty" (#2940)
Reverts openfrontio/OpenFrontIO#2933
2026-01-17 15:46:45 +01:00
FloPinguin dba04027df Merge pull request #2933 from FloPinguin/fix-nation-loading
Fix for v29: Add nation count loading for JoinPrivateLobbyModal; change HvN difficulty
2026-01-17 14:35:26 +00:00
Ryan 513be864c9 CopyButton, extract into component (#2934)
## Description:

Extracted the CopyButton into its own component, and now reusing it in
"Account" too.

## 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
2026-01-16 17:08:13 -08:00
FloPinguin 4e8454f3cc Lobby Gold Options (Starting Gold, Gold Multiplier) 💰 (#2915)
## Description:

We might want to add this to v29 to have a third possible public game
modifier from the beginning on 😄 Would be fun

- Add starting gold option (0 to 1_000_000_000 allowed, also applies to
nations)
- Add gold multiplier option (0.1 to 1000 allowed, also applies to
nations and bots)
- Add third public game modifier (3% chance of starting with 5M gold)
- Why 5M? It's enough gold to massively change the game start but not
enough to insta-hydro someone (launcher + hydro is 6M)

<img width="357" height="140" alt="image"
src="https://github.com/user-attachments/assets/72acc15c-e788-4e04-8590-ac72dd9657c7"
/>


## 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
2026-01-16 10:20:35 -08:00
FloPinguin fb910cbff5 Lobby Gold Options (Starting Gold, Gold Multiplier) 💰 (#2915)
## Description:

We might want to add this to v29 to have a third possible public game
modifier from the beginning on 😄 Would be fun

- Add starting gold option (0 to 1_000_000_000 allowed, also applies to
nations)
- Add gold multiplier option (0.1 to 1000 allowed, also applies to
nations and bots)
- Add third public game modifier (3% chance of starting with 5M gold)
- Why 5M? It's enough gold to massively change the game start but not
enough to insta-hydro someone (launcher + hydro is 6M)

<img width="357" height="140" alt="image"
src="https://github.com/user-attachments/assets/72acc15c-e788-4e04-8590-ac72dd9657c7"
/>


## 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
2026-01-16 10:19:41 -08:00
FloPinguin 0421c4e958 Refreshed images for the help modal and other little optimizations (#2897)
## Description:

1. Changed default difficulty in singleplayer / host lobby to Easy (to
synchronize the settings with the public lobby settings)
2. Switch bot count in singleplayer / host lobby to 100 after selecting
"compact map" (to synchronize the settings with the public lobby
settings) (and back to 400 after deselcting)
3. Some little padding optimizations, for example for the modal title:

<img width="961" height="190" alt="Screenshot 2026-01-14 163837"
src="https://github.com/user-attachments/assets/1ecca3e9-8daf-4bed-a75a-c8e840051601"
/>

4. Refreshed images for the help page:


![infoMenu2](https://github.com/user-attachments/assets/dc0c49c1-b970-47e5-a188-56fefc2e1c90)

![infoMenu2Ally](https://github.com/user-attachments/assets/c6c49a2c-eec6-44ae-877e-b8bdd2ab8caf)

![playerInfoOverlay](https://github.com/user-attachments/assets/1c6c2fc0-ecc5-4946-a7a7-35b90c13790a)

![controlPanel](https://github.com/user-attachments/assets/3d10fbf7-fbff-46af-b02a-9bb390dd9955)

![eventsPanelAttack](https://github.com/user-attachments/assets/04af2c91-6be1-458f-bf13-f4ddaf247d8a)

![eventsPanel](https://github.com/user-attachments/assets/517ad982-b001-4a36-9dfd-84a7ca1e0162)

![leaderboard2](https://github.com/user-attachments/assets/8956d053-682f-4055-9fe9-a36b066b1ce3)


## 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
2026-01-14 09:47:44 -08:00
Ryan 247c78151c Discord(et al.) embedded URLs (#2740)
## Description:

Changes URL embeds within other platforms, e.g. Discord, WhatsApp & X.

Updates game URLs to `/game/<code>` instead of `/#join=<code>` (required
for embedded URLs). An added benefit of this is that you would be able
to change a url from `openfront.io/game/RQDUy8nP?replay` to
`api.openfront.io/game/RQDUy8nP?replay` (add api. In front) and be in
the right place for the API data.

Updates URLs when joining/leaving private lobbies

Appends a random string to the end of the URL when inside a private
lobby and options change - this is to force discord to update the
embedded details.

Updates URL in different game states to ?lobby / ?live and ?replay.
These do nothing other than being used as a _cache-busting_ solution.


-----------------------------------------------
### **Lobby Info**

Discord:
<img width="556" height="487" alt="image"
src="https://github.com/user-attachments/assets/efd4a06d-506c-4036-9403-ee7c9a669e21"
/>

WhatsApp:
<img width="353" height="339" alt="image"
src="https://github.com/user-attachments/assets/3b2d0c69-988c-424f-9dee-f4e6a6868f6b"
/>


x.com:
<img width="588" height="325" alt="image"
src="https://github.com/user-attachments/assets/d9e78169-20be-4a3e-8df4-8ad41d08a750"
/>


-------------------------
### **Game Win Details**
Discord:
<img width="506" height="468" alt="image"
src="https://github.com/user-attachments/assets/69947774-c943-4a50-b470-5634ed3bf3d7"
/>

WhatsApp:
<img width="770" height="132" alt="image"
src="https://github.com/user-attachments/assets/eec28bf8-bf64-4ab8-954e-03dfdd1aae40"
/>

x.com
<img width="584" height="350" alt="image"
src="https://github.com/user-attachments/assets/168063e2-b707-422b-b7a1-0025f3ebeb92"
/>


## 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
2026-01-13 19:48:00 -08:00
Ryan 7353d785fb Created ModalHeader and moved/unified all modal headers (#2882)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

Moved the Modal Headers into its own class

## 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
2026-01-12 21:03:02 -08:00
Ryan 3e661752af UI refinements (#2859)
## Description:

UI Refinements requested by @evanpelle  check https://ui.openfront.dev

## 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
2026-01-11 14:52:03 -08:00
Ryan 5e6c90d9bb Main Menu UI Overhaul (#2829)
## Description:

Overhauls the Main Menu UI, visit https://menu.openfront.dev to see
everything.

## 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
2026-01-09 20:26:34 -08:00
FloPinguin ebcb654825 Added a public game modifier system 😮 For more variety (#2801)
## Description:

Added a public game modifier system. It causes that

5% of public games are played on the compact version of the map
10% of public games have "Random Spawn" activated

Percentages can easily get changed via `DefaultConfig`.
We can also easily add more modifiers.
Modifiers can stack, so in rare cases you will play on a compact map
with random spawn 😄
More variety!

### "Compact Map" modifier implementation

- With the "Compact Map" modifier the lobby max player count gets
reduced to 25% and only 25% of the regular bots and only 25% of the
regular nations will spawn (because the map has only 25% of its regular
size)
- In private lobbies and singleplayer the nation reduction happens too
(When "Compact Map" is enabled).

### Restrictions

- Duos/Trios/Quads team modes do not get Random Spawn (defeats the
purpose)
- Maps with smallest player count < 50 do not get Compact Map in team
games (not enough players after the reduction to 25%). I have calculated
all the possible max player counts.

### How it looks like

Random Spawn modifier:

<img width="528" height="183" alt="Screenshot 2026-01-06 194959"
src="https://github.com/user-attachments/assets/2f729da9-80c3-4548-8205-71129da2a76a"
/>

Very rare case: Two modifiers at the same time and only 10 max players
have been chosen from `[GameMapType.FaroeIslands]: [20, 15, 10]`.
Because of the 75% reduction in player count only 3 players are allowed
(3 is the minimum). I think its funny that you can play a 1v1v1 in rare
occasions 😄

<img width="526" height="184" alt="Screenshot 2026-01-06 194938"
src="https://github.com/user-attachments/assets/834326eb-df03-41b7-b1db-1efa3f1013b5"
/>

### Funny side-effect

Team games with random spawn. That will be interesting. No more "Who is
better in donating troops to the frontline". Instead you have to heavily
coordinate with your teammates.

## 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
2026-01-06 19:37:58 -08:00
Wraith e79c805804 refactor(ui): migrate tailwindcss v3 to v4 (#2735)
## Description:

migrate tailwindcss v3 to v4

## 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:

wraith4081

---------

Co-authored-by: iamlewis <lewismmmm@gmail.com>
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2026-01-06 14:21:52 -08:00
DevelopingTom af0b8a8d50 Configurable immunity timer (#2763)
## Description:

Resolve discussions about stalled PR
https://github.com/openfrontio/OpenFrontIO/pull/2460

<img width="724" height="348" alt="image"
src="https://github.com/user-attachments/assets/c2c9fa79-cace-431a-9ca4-b3656612fa9d"
/>

Changes:
- Added a `Player::canAttackPlayer(other)` function to determine whether
a player can be attacked.
- This function is now used in most places where a fight can occur:
    - AttackExecution (land attacks)
    - Naval invasion
    - Warship fight
- Nukes can't be thrown during the truce
- Immunity only affect human players. Nations and bot will fight as
usual, and can be fought against.
- The immunity timer uses minutes in the modal window.

UI:

- The immunity phase is displayed with a timer bar at the top. This is
from the original PR, to be discussed if it's not deemed visible enough:

<img width="632" height="215" alt="image"
src="https://github.com/user-attachments/assets/f5ab9aa0-bd4f-4503-b8d6-b40b121fba65"
/>


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

IngloriousTom

---------

Co-authored-by: newyearnewphil <git@nynp.dev>
2026-01-03 20:04:48 -08:00