51 Commits

Author SHA1 Message Date
Vansh 9e39a7f5a1 fix(client): block Safari page-level pinch-zoom (#3901)
iOS Safari has ignored the `user-scalable=no` viewport hint since iOS

10, so two-finger pinch still zooms the whole page and can softlock the

in-game HUD. Intercept WebKit's non-standard `gesturestart`,

`gesturechange` and `gestureend` events at `document` and call

`preventDefault()` so the page stays put. The game's own pinch-to-zoom

on the map canvas is driven by pointer events (InputHandler) and is

unaffected; browsers that do not fire GestureEvent treat the listeners

as a no-op.

Resolves #2330

If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

Describe the PR.

## Please complete the following:

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

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

DISCORD_USERNAME
2026-05-12 16:31:17 -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
evanpelle 4d0324c5cc run formatter 2026-05-06 21:43:03 -06:00
evanpelle e960b5130a run formatter 2026-05-06 21:08:42 -06:00
Ryan 005e1b6044 clan stats breakdown (#3869)
## Description:

improvements to clan ui. 
<img width="788" height="290" alt="image"
src="https://github.com/user-attachments/assets/736ca147-bff4-44d8-8180-7b80a85556fe"
/>
added "expand all" and new collapsible sections.


<img width="787" height="550" alt="image"
src="https://github.com/user-attachments/assets/deb2f813-854b-46a9-a767-52c4f749f30f"
/>

which changes to collapse all when expanded

also adds more info about team (d,t,q,2,3,4,5,6,7 team)

## 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-06 16:09:53 -06:00
evanpelle 879d502eb7 Merge branch 'v31' 2026-05-06 13:09:58 -06:00
Evan df84ee023e Refactor & standardize modal tabs (#3864)
## Description:

Refactors tab handling out of the individual modal components and into
the base o-modal component. Tabs are now declared by passing tabs,
activeTab, and onTabChange props, and a new named header slot pins
consumer-supplied content above the tabs. This standardizes the modal
tab look.

<img width="1089" height="290" alt="Screenshot 2026-05-06 at 12 17
33 PM"
src="https://github.com/user-attachments/assets/08d5a039-0aef-4aa7-b972-1e43b8723685"
/>

## 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-06 12:47:11 -06:00
VariableVince eca5794ebb Chore(deps): Update and remove dependencies (#3819)
## Description:

Only mentioning removals/major updates/notable changes below, not all
minor upgrades.

### Removed:
- "@aws-sdk/client-s3": not used anywhere (was used in Archive.ts
previously)
- chai, "@types/chai", sinon-chai: not used anywhere, probably leftover.
Vitest uses a bundled version of Chai for its expect asserations under
the hood too.
- protobufjs, "@types/google-protobuf": not used anywhere, probably left
from evan's experiment with it? Removed from vite.config.ts too.
- "@types/jquery": not used anywhere, probably leftover
- sinon, "@types/sinon": not used anywhere just like chai, probably
leftover. And Vitest provides us with the same functionality.
- "@types/systeminformation": dependency systeminformation was removed
last year, this is an unneeded, deprecated and unmaintained remainder.
- vite-tsconfig-paths: removed, and removed the import and usage in
vite.config.ts and replaced it by adding `tsconfigPaths: true` to the
`resolve` block. Because of this message displayed on running the tests:
"The plugin "vite-tsconfig-paths" is detected. Vite now supports
tsconfig paths resolution natively via the resolve.tsconfigPaths option.
You can remove the plugin and set resolve.tsconfigPaths: true in your
Vite config instead."
- vite-plugin-static-copy: removed, we don't use it anymore (was used in
our vite.config.ts once,, probably before Vite natively supported
copying static assets via its publicDir configuration)

### Updated:
- color.js: v0.5 > v0.6, no breaking change affecting us
- cross-env: v7 > v10. It's a publicly archived repo since Nov 2025. But
before that he got it up-to-date from June 2025, porting to TS, dropping
old Node versions, dependencies etc. Seems still good to use for some
amount of time to come.
- dotenv: v16 > v17, now logs an informational message by default when
it loads an environment file. Can be disabled by using
dotenv.config({quite: true}) if needed.
- ejs: v3 > v5: security patches mostly. Vite still uses v3 btw.
- eslint: v9 > v10. Newly enabled rules by default:
'no-unassigned-vars', 'no-useless-assignment' and
'preserve-caught-error'. Mostly faster and minimum support moved to
higher node versions, which shouldn't be a problem.
- "@eslint/compat": v1 > v2. Minimum supported Node versions, which
should not be a problem.
- intl-messageformat: v10 > v11 no breaking changes that affect us
- jdom: v27 > v29. Faster. Most notably minimum support moved to higher
node v22 version, which should not be a problem. Also, see types/node,
kind of expecting v24 to be installed now.
- nanoid: from v3 to v5, no breaking changes that affect us
- "@opentelemetry/sdk-logs": now that addLogRecordProcessor is removed,
changed Logger.ts to pass an (empty) provider array directly to the
LoggerProvider constructor. Follows the changes in
https://github.com/open-telemetry/opentelemetry-js/pull/5588
- "@tailwindcss/vite": supports vite v8 from 4.2.2, and a fix for it in
4.2.4
- tailwindcss: supports vite v8 from 4.2.2
-- in 4.1.15 (we were already above this version) break-words was
deprecated in favor of wrap-break-word. But break-words, which we use in
15 places, will still work as expected
(https://github.com/tailwindlabs/tailwindcss/pull/19157). Same goes for
also deprecated "order-none".
- "@types/node": from v22 to v24, assuming most now use node 24
- vite v7 > v8: 
-- is now on 8.0.10 so first bugs are out of it, while v8 itself also
fixed a big number of bugs.
-- in vite.config.ts, fixed Ts error/compilation issue by changing the
manualChunks option in build.rollupOptions.output to use the function
syntax, which is required by the updated types instead of the object
syntax.
- zod: no changes that affect us

### Prettier:
Updated only because of (new because of update?) Prettier errors for
files untouched in this PR originally:
- PathFinder.Parabola.ts
- WorkerMessages.ts
- ClanModal.handlers.test.ts
- ClanModal.rendering.test.ts‎
- CONTRIBUTING.md
- README.md

### ESLint:
Fixes needed to silence errors coming from newly enabled recommended
rules 'no-useless-assignment' and 'preserve-caught-error':

For 'no-useless-assignment' (default assignment never used because of
unreachable code or they are guaranteed to get a value, so they can be
undefinedat the start. Exception was AttackExecution, so made the
default value of 0 the default case in the switch statement):
- ClientGameRunner
- GameModeSelector
- NameBoxCalculator
- StructureDrawingUtils
- TerritoryLayer
- Diagnostics
- GameRunner
- ColorAllocator
- DefaultConfig
- AttackExecution
- AiAttackBehavior
- Worker.worker
- GamePreviewBuilder

For 'preserve-caught-error', disabled the rule here because the possible
fix `{cause: error}` was introduced in ES2022 while we're still on
target ES2020 currently:
- GameServer
- Privilege

_Error: The value assigned to 'gameMap' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'timeDisplay' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'scalingFactor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'radius' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'teamColor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'gl' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'power' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'tickExecutionDuration' is not used in
subsequent statements. (no-useless-assignment)
Error: The value assigned to 'selectedIndex' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'mag' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'speed' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'matchesCriteria' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'shouldContinue' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'description' is not used in subsequent
statements. (no-useless-assignment)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)_

All tests pass. TypeScript and ESLint errors resolved.

## Please complete the following:

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

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

tryout33

---------

Co-authored-by: Copilot <copilot@github.com>
2026-05-06 09:12:27 -06:00
babyboucher 94bab78d24 Fix off by one error (#3827)
## Description:

Currently it is impossible to search for 2 letter clan tags (UN, FR,
EU), this is because of an off by one error present in the API

## Please complete the following:

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

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

Babyboucher
2026-05-05 22:15:47 -06:00
babyboucher b8a544a7d5 Fix off by one error (#3827)
## Description:

Currently it is impossible to search for 2 letter clan tags (UN, FR,
EU), this is because of an off by one error present in the API

## Please complete the following:

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

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

Babyboucher
2026-05-05 22:15:28 -06:00
Evan 08b9fd96e6 simplify attack overlay to reduce visual clutter (#3848)
## Description:

Simplifies the attacking-troops overlay: removes the soldier icon and
strength bar, dropping each label down to just the troop number in cyan
(outgoing) or red (incoming) with a soft dark text-shadow halo and no
background fill so territory borders show through cleanly. Also splits
the label into outer (transitioned position) and inner (instant scale)
divs so zoom changes no longer get smeared by the 0.25s cluster-move
transition, retunes the zoom→size curve, and skips incoming labels from
bot tribes to cut clutter.

<img width="374" height="307" alt="Screenshot 2026-05-04 at 5 53 17 PM"
src="https://github.com/user-attachments/assets/a7044221-06cc-4027-b19a-6ff4ca8f542a"
/>

## 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-04 18:10:06 -06:00
Ryan df05d21fc2 Clan System Part 2 - UI (#3625)
## Description:

Continuation from #3276 

Adds the complete client-side clan UI as a Lit web component
(`<clan-modal>`), a typed API client with Zod-validated responses,
shared response schemas, and a reusable `<confirm-dialog>` component.


### New: `ClanModal.ts`

| View | What it does |
|------|-------------|
| **My Clans** | Lists joined clans + pending join requests (built from
`/users/@me`, no extra fetches) |
| **Browse** | Search by tag (min 3 chars), paginated results,
configurable per-page (10/25/50) |
| **Clan Detail** | Stats, paginated + searchable member list, role
badges, join/leave/request actions |
| **Manage** | Edit name (max 35 chars) + description, toggle
open/invite-only, disband |
| **Transfer** | Leadership transfer with member selector + confirmation
|
| **Requests** | Approve/deny join requests (leader/officer) |
| **Bans** | View and unban (leader/officer) |
| **My Requests** | View and withdraw outgoing requests |

### New: `ConfirmDialog.ts`

Reusable `<confirm-dialog>` Lit component — replaces native
`confirm()`/`prompt()` which are blocked or broken on mobile and
CrazyGames iframes. Supports danger/warning variants and an optional
textarea (used for ban reasons). Fires `confirm`/`cancel` events.

### New: `ClanApi.ts`

Typed API client covering all clan endpoints. Every response is
Zod-validated. Auth header is always last in the spread (can't be
overridden by callers). Unknown server error messages always fall back
to a generic client-side string — never displayed verbatim.

### New: `ClanApiSchemas.ts` (in `src/core/`)

Shared Zod schemas for clan API responses with max-length constraints on
`name` (35) and `description` (200). Lives in `core/` so it can be
consumed by both client code and the leaderboard table.

### Modified: `ApiSchemas.ts`

- Added `clans` and `clanRequests` arrays to `UserMeResponseSchema`
- Moved clan leaderboard schemas out to `ClanApiSchemas.ts`
- Renamed `LeaderboardClanTagSchema` → `RequiredClanTagSchema`

### Modified: `Api.ts`

- Added `invalidateUserMe()` to bust the cached `/users/me` response
after mutations
- Removed `fetchClanLeaderboard` (moved to `ClanApi.ts`)

### Tests

- `ClanModal.test.ts` — rendering, view navigation, user actions
- `ClanApiQueries.test.ts` — fetch functions, error handling, pagination
- `ClanApiMutations.test.ts` — join, leave, kick, ban, promote,
transfer, etc.
- `ClanApiBans.test.ts` — ban/unban calls and error paths
- `ClanApiSchemas.test.ts` — Zod schema validation edge cases
- `LeaderboardModal.test.ts` — updated imports

## Notable design decisions

- **Not-logged-in state** — shows "Sign in to join clans" instead of
false "no clans" empty state
- **Rate limit feedback** — reads `Retry-After` header and surfaces wait
time to the user

## 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: evanpelle <evanpelle@gmail.com>
2026-04-30 21:27:35 -06:00
Evan 0c0f9c2a81 Update attack labels (#3784)
## Description:

The motivation behind this PR is to standardize colors & icons for
incoming and outgoing attacks. Outgoing attacks are always aquarious and
incoming are red. This also makes it much easier to see which attacks
are incoming vs outgoing at a glance, as previously the color changed
depending on attack effictiveness. Instead, show a small bar on the left
side that displays attack effectiveness.

<img width="498" height="456" alt="Screenshot 2026-04-27 at 12 58 53 PM"
src="https://github.com/user-attachments/assets/ea6928b3-5dfa-47fa-84d2-63e1e81ef6a4"
/>


Updates the in-game attack labels to match AttacksDisplay: a single
soldier icon recolored via CSS filters, aquarius for outgoing and
red-400 for incoming. Color is now purely directional — the previous
attacker-vs-defender comparison (and the troopAttackColor /
troopDefenceColor helpers that drove it) is gone, along with the
defenderTroops plumbing.

Also adds zoom-aware sizing via a new computeLabelScale(zoom) (full
screen size when zoomed in, linear shrink with a floor so labels never
disappear), bumps font/padding/snap-jump threshold for readability, and
moves immutable per-label DOM writes (icon src/filter, color) into
element creation so the per-tick path only updates the troop count.

Also fixes a bug where the labels kept swapping when 2 clusters where
similar size

## 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-27 20:53:09 -06:00
Ryan c3d7d0373e Improve ingame moderation for admins (#3678)
## Description:

Players with the `admin` flare can now kick players from any game
(including public lobbies), not just the lobby creator in private
lobbies.

## 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-04-20 11:09:04 -07:00
Ivan Batsulin 76f8441b45 feat: add warning news type and Firefox performance notice (#3680)
## Description:
Adds a new `warning` news type to the news banner system and uses it to
display a Firefox performance notice.

Changes:
- Added `warning` type with red styling to `NewsBox.ts`
- Added `news_box.warning` key (`"WARNING"`) to `en.json`
- Added Firefox performance notice to `resources/news.json` using the
new `warning` type
- Added `news_box.*` dynamic key pattern to `TranslationSystem.test.ts`
to fix unused key detection

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

## UI change:
<img width="2101" height="1770" alt="CleanShot 2026-04-16 at 15 04
35@2x"
src="https://github.com/user-attachments/assets/7a8b9290-4216-4799-b271-606afd9b8723"
/>



## Please put your Discord username so you can be contacted if a bug or
regression is found:
fghjk_60845
2026-04-16 16:53:38 -07:00
Evan 1ebac8e854 Move brand images to proprietary/ and support multi-dir asset pipeline (#3662)
## Description:

* Move proprietary brand images (logos, favicon) from resources/images/
to proprietary/images/ to separate open-source assets from proprietary
ones
* Extend the asset pipeline (PublicAssetManifest, vite.config.ts) to
support multiple source directories (resources/ + proprietary/), so
buildAssetUrl resolves assets from either location transparently
* In dev, serve proprietary/ as a fallback middleware (registered after
Vite's publicDir handler) so resources/ takes precedence when files
exist in both. The idea is we could have placeholder assets placeholders
that can be used by forks, and only the production build uses
proprietary assets.

## 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-16 08:42:11 -07:00
Alex Besios 55e8a4edb7 feat: add NewsBox component and integrate news items into PlayPage (#3545)
Resolves #2998 

## Description:

Adds a news box to the lobby homepage that advertises upcoming clan
tournaments, weekly tournaments, and new player tutorials. The component
sits above the username input and cycles through items automatically.

<img width="1138" height="591" alt="screenshot-2026-03-31_00-48-33"
src="https://github.com/user-attachments/assets/4b79287d-6aca-4c81-9bfe-36aad043f381"
/>

<img width="1107" height="595" alt="screenshot-2026-03-31_00-48-24"
src="https://github.com/user-attachments/assets/598e6b8b-e0f2-4864-a5fb-a00c0cc98f37"
/>

<img width="1367" height="599" alt="screenshot-2026-03-31_00-48-04"
src="https://github.com/user-attachments/assets/14f74e70-9dc0-4d67-af6e-c4708e539490"
/>


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

deathllotus

---------

Co-authored-by: Evan <evanpelle@gmail.com>
2026-04-08 10:53:02 -07:00
Cameron Clark 18da7134c8 Implement FX sound effects (#3394)
## Description:
Adds sound effects for approved events from the [sound asset
pack](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing).
15 new sound effects triggered from `FxLayer`, `EventsDisplay`, and
`RadialMenu`. Sounds play even when visual FX are off, so disabling
explosions doesn't kill audio. Unapproved sounds are included as assets
but not wired up yet.

### SoundManager architecture

Reworked `SoundManager` per [maintainer
feedback](https://github.com/openfrontio/OpenFrontIO/issues/1893#issuecomment-4184649434)
and [follow-up
review](https://github.com/openfrontio/OpenFrontIO/pull/3394):

- No more singleton. `SoundManager` is instantiated in
`createClientGame()` with `EventBus` and `UserSettings`
- Layers emit events (`PlaySoundEffectEvent`,
`SetBackgroundMusicVolumeEvent`, `SetSoundEffectsVolumeEvent`) via
EventBus instead of holding a `SoundManager` reference
- `SoundManager` subscribes to these events in its constructor
- `SoundEffect` is a type union (not an enum), per project convention
- All sound configuration (type, URL mapping, events) lives in
`Sounds.ts`
- Sound effects are lazy-loaded on first play
- Channel limit of 8 concurrent sounds. New sounds always play; when at
the limit, the oldest active sound gets stopped
- `SoundManager` bootstraps volume from `UserSettings` in its
constructor
- All Howler calls are wrapped in try/catch with error logging, so sound
failures never crash the game
- `dispose()` method unsubscribes from EventBus and unloads all Howl
instances on game shutdown
- Sound code stays entirely in `src/client/`, nothing in `core/` touches
it

## Sound approval status (per
[spreadsheet](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing))

### Approved, wired up in this PR

| Event | Sound file | Trigger location |
|-------|-----------|-----------------|
| Message sent/received | `message.mp3` | EventsDisplay |
| Menu open/select | `click.mp3` | RadialMenu |
| Atom bomb launch | `atom-launch.mp3` | FxLayer (unit created) |
| Atom bomb / MIRV hit | `atom-hit.mp3` | FxLayer (reached target) |
| Hydrogen launch | `hydrogen-launch.mp3` | FxLayer (unit created) |
| Hydrogen hit | `hydrogen-hit.mp3` | FxLayer (reached target) |
| MIRV launch | `mirv-launch.mp3` | FxLayer (unit created) |
| Alliance suggested | `alliance-suggested.mp3` | EventsDisplay |
| Alliance broken | `alliance-broken.mp3` | EventsDisplay |
| Port built | `build-port.mp3` | FxLayer (construction complete) |
| City built | `build-city.mp3` | FxLayer (construction complete) |
| Defense post built | `build-defense-post.mp3` | FxLayer (construction
complete) |
| Warship built | `build-warship.mp3` | FxLayer (unit created) |
| SAM built | `sam-built.mp3` | FxLayer (construction complete) |

### Waiting for approval, sound files included but NOT wired up

| Event | Sound file | Notes |
|-------|-----------|-------|
| Missile Silo built | `silo-built.mp3` | Waiting for Approval |
| SAM shoot | `sam-shoot.mp3` | Waiting for Approval |
| SAM hit | - | Waiting for Approval, no sound file assigned |
| Warship sunk | - | Waiting for Approval, no sound file assigned |
| Warship shoot | - | Waiting for Approval, no sound file assigned |

### Not done, no sound files exist yet

| Event | Notes |
|-------|-------|
| Looted player | "Not sure if needed" |
| Invaded | - |
| Ship invasion incoming | - |
| Ship sent | - |
| Menu theme song | - |
| Ambience | "Not sure if needed" |

## Test plan
- [x] Start a private game and launch atom/hydrogen/MIRV nukes, verify
launch and detonation sounds
- [x] Build structures (city, port, defense post, SAM), verify build
completion sounds
- [x] Build a warship, verify warship built sound
- [x] Receive an alliance request, verify alliance suggested sound
- [x] Break an alliance, verify alliance broken sound
- [ ] Receive a chat message, verify message sound
- [x] Open the radial menu and click items, verify click sound
- [x] Disable visual FX in settings, verify sounds still play
- [x] Adjust SFX volume slider, verify it affects all new sounds
- [x] Verify no audio issues with rapid/overlapping events
- [x] Verify SoundManager responds to EventBus events and unsubscribes
cleanly on dispose
- [x] Verify SoundManager swallows Howler errors without crashing the
game
- [x] Verify channel limit of 8, oldest sound stopped when at cap

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

Resolves #1893

## Please put your Discord username so you can be contacted if a bug or
regression is found:
cool_clarky
2026-04-06 21:01:23 -07:00
evanpelle b88c3a3bdd Merge branch 'v30' 2026-04-01 20:03:39 -07:00
Evan e7b4317718 fix: radial build sub-menu items stay grayed out after gaining enough gold (#3415)
## Description:

canBuildOrUpgrade was captured once at sub-menu open time, so
disabled/color
never updated while the menu was open. Evaluate canBuildOrUpgrade
dynamically
inside the disabled and color callbacks so the menu reflects current
gold on
each refresh tick.


## 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-30 16:44:44 -07:00
Ralfi Salhon 015e3c7d19 feat: Attacking Troops Overlay (#3427)
## Description:

https://troop-advantage-layer.openfront.dev/

Hey OpenFront dev team, I've been really enjoying the game, and the
v0.30 changes have felt great so far. Happy to start contributing!

This PR introduces `AttackingTroopsOverlay`, a layer that renders live
attacker vs. defender troop counts directly on active front lines.
Players can immediately gauge combat strength without leaving the map
view.


![troop-advantage-layer](https://github.com/user-attachments/assets/9e862812-84b4-46cb-a0c6-65fa50320198)

A recent change updates the layer to just the # of attackers and a
symbol for attack/defence:

![visual-front-line](https://github.com/user-attachments/assets/46bc7117-2314-44c9-96fc-8a7e9c6ab5cd)

Left: Perspective of Anon 667 (Blue) | Right: Perspective of Anon332
(Red)

![ezgif-6261e6669d6b972b](https://github.com/user-attachments/assets/734d90c1-8f22-44dc-8f2f-b22e46676f46)

**How it works:**
- Attacker count shown for ground invasions. When attacking, your troop
count will display amber for disadvantageous, and green for advantageous
battles. When defending, the enemy troop count will switch to red if you
are at a severe disadvantage.
- Label position recalculates every tick at 200ms, tracking the front
line as it moves.
- Automatically hidden during Terrain view (spacebar)
- Labels clean up when an attack ends or its target becomes invalid

**Settings:** An "Attacking Troops Overlay" toggle is added to Settings,
enabled by default.
--> the screenshot is old, but the text has been updated
<img width="448" height="410" alt="Settings toggle"
src="https://github.com/user-attachments/assets/2df8ec7a-3f77-48b7-a9b5-ee4a6eed0412"
/>

## Checklist

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

## Discord
Radyus
2026-03-19 15:04:33 -07:00
Ryan 1049b7e7dc Clan System Part 1 (#3276)
## Description:

Properly split out clantags and usernames, a clantag should not be part
of a username.

<img width="285" height="286" alt="image"
src="https://github.com/user-attachments/assets/8ac56e82-b12c-4fc0-9774-e445252a6e61"
/>

https://api.openfront.dev/game/ojkqZFb2


<img width="296" height="596" alt="image"
src="https://github.com/user-attachments/assets/85152f80-c111-4f87-b85b-8516c9c6137b"
/>


https://api.openfront.dev/game/MF32BkVc


requires;
https://github.com/openfrontio/infra/pull/264

## Please complete the following:

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

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

w.o.n
2026-03-17 15:55:47 -07:00
VariableVince 9f8a2d2d84 Fix "you didn't enter the lobby in time" when device clock isn't synced (#3451)
## Description:

If the time on the local device differs from the server time, users may
see the message “You did not join the lobby on time.”

Resolve this by accounting for the time difference, reusing the logic in
`JoinLobbyModal` that was previously in `GameModeSelector`, and
centralizing it into `ServerTime.ts`.

Bug reports:
https://github.com/openfrontio/OpenFrontIO/issues/3428

https://discord.com/channels/1284581928254701718/1482511096597315815

https://discord.com/channels/1284581928254701718/1482382264011591781

Resolves #3428 

## 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
2026-03-17 15:21:38 -07:00
VariableVince 82d0fb385d Fix "you didn't enter the lobby in time" when device clock isn't synced (#3451)
## Description:

If the time on the local device differs from the server time, users may
see the message “You did not join the lobby on time.”

Resolve this by accounting for the time difference, reusing the logic in
`JoinLobbyModal` that was previously in `GameModeSelector`, and
centralizing it into `ServerTime.ts`.

Bug reports:
https://github.com/openfrontio/OpenFrontIO/issues/3428

https://discord.com/channels/1284581928254701718/1482511096597315815

https://discord.com/channels/1284581928254701718/1482382264011591781

Resolves #3428 

## 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
2026-03-17 13:52:35 -07:00
Bartosz Woźniak 936928fed9 Enhance InputHandler to allow using NumPad (#3317)
## Description:

Adds **Enter** and **Numpad Enter** as confirmation for placing a ghost
structure after selecting a building with hotkeys (1–0 or numpad).
Players can cancel with Esc but previously had to click to confirm; they
can now confirm with Enter or Numpad Enter at the current cursor
position. This supports keyboard-only or mouse + numpad workflows (e.g.
one hand on numpad for select + confirm, one on mouse for aiming).

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

.wozniakpl
2026-03-07 15:27:25 -08:00
Skigim f7598369ed refactor: consolidate platform detection across client components (#3325)
## Description:

This PR consolidates ad hoc platform/environment/viewport detection into
a single shared utility. It is scoped to this refactor only, and serves
as groundwork for the mobile-focused feature work planned for the v31
milestone.

### What changed
- Introduced a shared `Platform` utility centralising:
  - OS detection (with `userAgentData` + UA fallback)
  - Electron environment detection
- Viewport breakpoint helpers (`isMobileWidth`, `isTabletWidth`,
`isDesktopWidth`)
- Replaced duplicated inline checks across client files with the shared
API.
- Normalised Mac detection to derive from the consolidated OS logic
rather than a separate regex.

### Why
- Multiple client files each independently ran `navigator.userAgent`
regexes or copy-pasted `isElectron` logic — this unifies all of that.
- Puts a stable, tested abstraction in place before v31 mobile work
lands, so mobile feature branches have a consistent surface to build
against.

## Please complete the following:

- [x] I have added screenshots for all UI updates (N/A: refactor only,
no visible UI changes)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (N/A: no new user-facing strings)
- [x] I have added relevant tests to the test directory (N/A: refactor
only)
- [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:

skigim
2026-03-02 10:12:48 -08:00
Mattia Migliorini 7b785ea79a Fix alliance renewal prompt incorrectly dismissed for both players (#3297)
## Description:

NOTE: Applies to current main / beta version. Needs to be included in
v30.

When a player clicked "Renew Alliance", the `AllianceExtensionUpdate`
broadcast caused both players' renewal prompts to be removed, even the
one who hadn't yet acted. This happened because
`onAllianceExtensionEvent` called `removeAllianceRenewalEvents`
unconditionally on every client.

This PR fixes the behavior by calling `removeAllianceRenewalEvents` only
for the player that executed the action.

## Please complete the following:

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

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

deshack_82603
2026-02-25 21:12:58 -06:00
Mattia Migliorini 90204f6628 Add alliance renewal action to Radial Menu (#3148)
## Description:

The following PR replaces the (disabled) alliance request button with an
alliance extension/renewal button when the alliance with the target
player is expiring.

Agreeing to renewal via radial menu also hides the message in the
EventsDisplay.

<img width="369" height="364" alt="image"
src="https://github.com/user-attachments/assets/d8040f5c-ad7b-47d0-852f-925ecbf273a8"
/>


https://github.com/user-attachments/assets/aa589edf-6505-46bf-88a3-aa4c2df9137f

Icon size adjusted:

<img width="294" height="252" alt="image"
src="https://github.com/user-attachments/assets/7ca63500-b1fb-427b-965c-cf121a5213da"
/>

## Please complete the following:

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

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

deshack_82603

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-02-19 19:47:57 -06:00
Ryan 4bc168dffb make usernames linkable in news (#3200)
## Description:

make usernames linkable in news

now:
<img width="399" height="153" alt="image"
src="https://github.com/user-attachments/assets/39644fe2-9af1-4765-b839-9f8b5f9d0418"
/>


before:
<img width="409" height="82" alt="image"
src="https://github.com/user-attachments/assets/d7a1c37e-63cf-4417-ac61-c6db39a33851"
/>



## 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: iamlewis <lewismmmm@gmail.com>
2026-02-16 11:11:10 -08:00
Wawa cb6e97ed11 Add Leaderboard refresh time (#3190)
## Description:

I added a small refresh time text (see screenshots below).

> I play ranked a lot since it's been added and I just reached the top
100 (yay !!), I was wondering what was the refresh time so after I found
it in the code, I wanted to add a small text for easier understanding :)





<details>
  <summary><h2>Open Screenshots "players" here</h2></summary>

Before "players" :
<img width="622" height="645" alt="image"
src="https://github.com/user-attachments/assets/d3335954-8e16-4465-b09f-89d03defe643"
/>

After "players" :

<img width="628" height="637" alt="image"
src="https://github.com/user-attachments/assets/fd89df53-0942-4869-bfb5-9c7e7497af38"
/>

</details>


This can be edited as you want but I did not added the text in the
"clans" section.

I did not added any test in the tests files since this is a minor UI
improvement, but I can if needed, And I do tested everything locally
myself to take the screenshots :)

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

@noleet
2026-02-12 14:58:17 -08:00
FloPinguin c212735f09 Orange betrayal button for no-debuff-betrayals 🖌️ (#3161)
Resolves #1276

## Description:

Orange betrayal button if the player is a traitor or disconnected.
So people can easier tell that this is a betrayal without consequences.
The color changes back to red without reopening the menu (live) when the
traitor debuff ends or the player reconnects.

<img width="268" height="257" alt="image"
src="https://github.com/user-attachments/assets/276e91ce-e49d-474c-afaa-ffa18d45a2c7"
/>

## Please complete the following:

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

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

FloPinguin

---------

Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2026-02-09 23:23:20 +00:00
Skigim 32adfa2f79 Add requeue button to Ranked victory/defeat modal (#3121)
## Description:

Adds a "Play Again" requeue button to the victory/defeat modal for
Ranked 1v1 games. When clicked, it navigates the player back to the
homepage and automatically opens the matchmaking modal to queue for
another ranked match.

Changes:

- WinModal.ts: Added isRankedGame state, purple "Play Again" button
(only shown for ranked 1v1), and _handleRequeue() method
- Main.ts: Added ?requeue URL parameter handling to trigger matchmaking
modal on page load
- en.json: Added "requeue": "Play Again" translation string
- added tests to WinModal.test.ts

Note: temporarily set isRanked flag to true to get the modal to pop in a
solo match on dev server and confirmed that ?requeue URL parameter
called _handleRequeue() correctly, which opened the sign in process
since actually signing in and queuing for a ranked match isn't possible
on dev server.

<img width="771" height="364" alt="play-again"
src="https://github.com/user-attachments/assets/6e3f5a02-f1ae-465a-9b28-656126c11d3d"
/>


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

skigim
2026-02-07 12:51:02 -08:00
Evan 294a1b4784 move lobby websockets to worker (#2974)
## Description:

Currently only the master process sends public lobby updates to clients.
This is not scalable since it could overload the master process.

In this PR, the master uses IPC to send public lobby info to all
workers. Then clients connect to a random worker to get public lobby
updates via websocket. This way clients never connect directly to the
master websocket.

The flow looks like this:

Every 100ms:
1. Master schedules a public game on a random worker if new games are
needed
2. Master broadcasts public lobby info to all workers (all public games
& num clients connected to each game)
3. Each worker responds to that update with the number of clients
connected to its own public games
4. Master then updates its public lobby state so it knows how many
clients are connected to each public 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-02-03 18:26:38 -08:00
Ryan e4280c28e1 Add Ranked 1v1 Leaderboard (#3008)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)

## Description:

@wraith4081 's pr

updates the stats modal to show both 1v1 and clan stats

## 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: Wraith <54374743+wraith4081@users.noreply.github.com>
Co-authored-by: iamlewis <lewismmmm@gmail.com>
2026-02-01 14:58:54 -08:00
FloPinguin 0286224299 Move betrayal button, remove betrayal confirmation 🔧 (#3076)
## Description:

- Move betrayal button to the boat-sending-button-location (you can't
send boats to allies) to prevent missclicks
- Remove betrayal confirmation

<img width="260" height="248" alt="image"
src="https://github.com/user-attachments/assets/0a25fc9c-c8a0-4ba9-a8c8-971d6a7a0511"
/>

## 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-31 13:52:58 -08:00
Mitchell Zinck de3794313d feat: Kick player in game (#2969)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #2686 

## Description:
- Implemented feature for lobby creator to kick players in game.
- Added new moderation option for lobby creator, with a kick player
option if they aren't the creator, a bot, and exist in game.
- Includes a confirm kick option, and keeps track of kicked players so
that the kick option changes to "Already Kicked" if the kicked player
panel is opened again on the kicked player.

Screenshot order:
1) Open player panel
2) Click on moderation
3) Click on kick player and confirm kick
4) Player is kicked, open same player panel again and observe change in
kick status
5) Receiving player kick message

<img width="1470" height="776" alt="Screenshot 2026-01-20 at 12 33
55 PM"
src="https://github.com/user-attachments/assets/7c47b5a2-a0f8-4e92-833c-7b9732f751a8"
/>
<img width="1470" height="776" alt="Screenshot 2026-01-20 at 11 58
58 AM"
src="https://github.com/user-attachments/assets/3aa026af-9a42-4512-91b8-916f146849a6"
/>
<img width="1470" height="776" alt="Screenshot 2026-01-20 at 12 31
46 PM"
src="https://github.com/user-attachments/assets/5e1d271b-bf32-4335-8eb1-bcdf84aba8ce"
/>
<img width="1470" height="776" alt="Screenshot 2026-01-20 at 11 57
58 AM"
src="https://github.com/user-attachments/assets/7cbd5ea6-bcb6-4a35-a003-ea0add936925"
/>
<img width="1470" height="776" alt="Screenshot 2026-01-20 at 11 57
39 AM"
src="https://github.com/user-attachments/assets/4309b3e3-2fe6-48dd-8e0c-55036e567461"
/>



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

mitchfz
2026-01-24 20:55:58 -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
Vahant Sharma 96622779d1 Add tooltips to Win/Loss Score columns in clan stats (#2752)
Resolves #2508

## Description:

Adds hover tooltips to the "Win Score" and "Loss Score" column headers
in the clan stats table to help players understand what these weighted
scores represent.

### Changes Made
- Added tooltip to **Win Score** column: "Weighted wins based on clan
participation and match difficulty"
- Added tooltip to **Loss Score** column: "Weighted losses based on clan
participation and match difficulty"
- Uses native HTML `title` attribute (follows existing codebase
patterns)
- Fully i18n-ready via `translateText()` - other languages will be
translated via Crowdin

### Implementation Details
- **Files Modified**: 2 files, 4 lines total
  - `resources/lang/en.json`: Added 2 tooltip translation keys
- `src/client/StatsModal.ts`: Added `title` attributes to table headers
- No breaking changes

### Expected Behavior
When hovering over "Win Score" or "Loss Score" column headers, users see
a tooltip explaining the weighted scoring system based on clan
participation and match difficulty.

## Please complete the following:

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

![correctimplementation](https://github.com/user-attachments/assets/bbbad9ab-36cf-4364-96ff-feff6ee966cd)
2026-01-01 10:26:34 -08:00
Ryan 3dcd38a58d lobby websocket instead of polling (#2727)
## Description:
Changes game lobbies into websockets instead of polling

## 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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: iamlewis <lewismmmm@gmail.com>
2026-01-01 09:38:33 -08:00
Wraith 26f5d40819 build: migrate build system to Vite and test runner to Vitest & Remove depracated husky usage (#2703)
- Replace Webpack with Vite for faster client bundling and HMR.
- Migrate tests from Jest to Vitest and update configuration.
- Update Web Worker instantiation to standard ESM syntax.
- Implement Env utility in `src/core` for safe, hybrid environment
variable access (Vite vs Node).
- Refactor configuration loaders to remove direct `process.env`
dependencies in shared code.
- Update TypeScript environment definitions and project scripts for the
new toolchain.
- Remove the [depracated usage of the
husky](https://github.com/typicode/husky/releases/tag/v9.0.1).

## Description:

migrate build system to Vite and test runner to Vitest & Remove
depracated husky usage

## 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
- [ ] 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: evanpelle <evanpelle@gmail.com>
2025-12-28 22:10:26 -08:00
Achim Marius 5a065d71c5 Fix alliance renewal popup not being removed when alliance is broken (#2722)
Resolves #2464

## Description

This PR fixes a bug where the alliance renewal popup remained visible
after an alliance was broken or betrayed.
The issue occurred because renewal UI events were tied to player
identifiers instead of the unique allianceId.
When a player had multiple alliances, breaking one alliance did not
correctly remove the associated renewal popup.
This change ensures that renewal popups are correctly removed only for
the specific alliance that was broken.


### What was wrong
Alliance renewal UI events were previously associated implicitly with
players.
This caused incorrect behavior when a player had **multiple alliances**,
because **players are not unique identifiers of an alliance**.

As a result, breaking one alliance could leave stale renewal popups
visible.

### What was changed
- Alliance break logic now relies on **allianceId**, not player IDs
- Renewal popups are removed **only for the specific broken alliance**
- Alliances involving the same player but different allianceIds are
unaffected
- Added tests to ensure this bug cannot reappear

### Result
- Renewal popup disappears immediately when an alliance is
betrayed/broken
- No unintended removal of other alliance renewal popups
- Correct behavior even when a player is involved in multiple alliances

## Please complete the following:

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

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

assessin.
2025-12-28 13:35:05 -08:00
VectorSophie 00babf4289 Rework fluentslider component and write tests (#2682)
## Description:

After 2 months of vacancy(my bad sorry), i have returned to end this
mess of a PR stain that i left to the codebase.
The issue is fixed, my written tests are passing, and i hand-tested and
it worked out.
Also I had to transforms Lit's ES modules into commonJS format so jest
can execute for my test,
which will indirectly enable other Lit components to be able for
testing(only my test for now)
refer to #2148 for UI stuff, nothing changed there.

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

jackochess
2025-12-24 06:24:37 -08:00
Ahmet Dedeler 327d425fd5 Fix obvious typos (#2585)
## Summary
- fix obvious spelling typos flagged by codespell across docs, tests,
comments
- no functional changes

## Testing
- pre-commit hooks (eslint/prettier) ran during commit
2025-12-09 16:12:00 -08:00
CrackeRR11 8f53785a80 BUG FIX: Gold double deduction + Rmoval of UnitType.Construction (#2378)
## 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>
2025-11-26 14:45:14 -08:00
VariableVince 64e8733132 Delete unit: 5s > 15s cooldown and new location in Radial Menu (#2345)
## 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>
2025-11-01 10:48:40 -07:00
Cameron Clark 1ecd6c4ee1 Private lobby toggle donation (#1752)
Resolve #1652

1. Add the ability to toggle **gold donations** and **troop donations**
for private lobbies
~2. Add relevant translations.~
3. Refactor `canDonate` to be specific to gold and troop donations
4. Add placeholders for singleplayer mode if this is to be extended to
support that too.
5. Add Tests for Donate logic
<img width="1643" height="1788" alt="image"
src="https://github.com/user-attachments/assets/82b93400-a1f0-45f0-8b2b-a7f78dc0c3e9"
/>

_Private Lobby_

![donatetroopsprivatelobby](https://github.com/user-attachments/assets/c6690bbc-958e-48a1-9cf1-e2b361dfb1b2)
_Testing Troop Send In Private Lobby_

![donatetroopsprivatelobby2](https://github.com/user-attachments/assets/698c7603-6b4b-4da7-91ab-7bdc38bb49a5)

_Troop Send Complete In Private Lobby_

![testtradepublicteams](https://github.com/user-attachments/assets/1010332c-3f38-4644-9218-46aa7141f578)
Confirming that public teams still works

- [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 have read and accepted the CLA agreement (only required once).
regression is found:

DISCORD_USERNAME: cool_clarky

---------

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
Co-authored-by: Drills Kibo <59177241+drillskibo@users.noreply.github.com>
2025-09-03 16:19:13 -07:00
Scott Anderson 3a703b0b4b Fix broken test (#1688)
## Description:

Due to a bad merge resolution, a test is failing.

## 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
- [ ] I have read and accepted the CLA agreement (only required once).
2025-08-02 14:58:35 -04:00
Kipstz Avenger cf662bc1bc Add Split radial menu into separate attack and build buttons (#1598)
## Description:

This PR implements a new radial menu system that separates attack and
construction functionalities into distinct buttons. Previously, all
units (both attack and construction) were grouped together in a single
orange button, making the interface confusing and inefficient.

## 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 have read and accepted the CLA agreement (only required once).

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

Kipstzz

<img width="420" height="345" alt="image"
src="https://github.com/user-attachments/assets/73d67fe1-d5d2-4c7e-8894-360877fa7004"
/>
<img width="422" height="345" alt="image"
src="https://github.com/user-attachments/assets/e576d543-4156-48f4-81ac-e7a06d26b25b"
/>
2025-08-01 18:38:16 -04:00
evanpelle 488ffc5c9e Improve unit updates & reloading (#1394)
## Description:

Previously upgrading a unit would have it reload immediately, eg
upgrading missile silo 1=>2 gives it 2 missiles immediately. With this
change it must reload the missile. Same with SAMs. This prevents users
from spamming upgrades as missiles are coming in.

Fix the progress reloading bar. Previously it only showed the SAM/silo
as reloading when it was completely out of missiles. Now it shows
roughly how many missiles are available (eg level 5 fires 3 missiles,
now progress bar is at 40%). It also shows progress of missiles
reloading. If no missiles are available, progress bar is empty

There was a bug where if a silo of SAM was in cooldown it would be
updated each tick, causing the sprite/icon to be rerendered each tick.

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

evan
2025-07-10 10:06:05 -07:00
Vivacious Box 9b0e1b04a4 fix healthbars not being removed (#1329)
## Description:

Warships being nuked doesnt remove the healthbar


![image](https://github.com/user-attachments/assets/67d40a5b-255b-45ea-9b5d-4f5441ff271a)


## 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: evanpelle <evanpelle@gmail.com>
2025-07-02 16:12:54 -07:00