mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 01:14:36 +00:00
0801798fbdb75612bf950f88486fb51f13d60ec0
952 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0801798fbd |
Feat: Alliance and betrayal hotkeys (#3110)
Original Feature request by @FloPinguin Resolves #3077 ## Description: Adds hotkeys for Requesting alliances and breaking alliances. This allows for players to send or break alliances whose tile is under the cursor, without opening the radial menu. Keybinds: New "Ally Keybinds" section in Settings -> Keybinds Request alliance: Default: K - sends an alliance request to the player/bot/nation under the cursor Break alliance: Default: L - breaks the alliance with the player at the cursor Behavior: - Cursor must be over a tile owned by the target player. The action runs only when the game allows it, following the same logic as the radial menu. (canSendAllianceRequest and canBreakAlliance) - When an alliance request is sent, the events log shows: "Alliance request sent to [target]" for confirmation. No extra message for breaking an alliance (betrayal/debuff message already exists and is sent upon breaking an alliance) ## Screenshots: Keybind menu: <img width="739" height="595" alt="image" src="https://github.com/user-attachments/assets/ee958eab-fd50-4971-85c5-dfd49c6f0bdc" /> In game logs: <img width="373" height="232" alt="image" src="https://github.com/user-attachments/assets/2cf6bb07-5f0d-425a-82d3-65a44fef99c5" /> ## 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 Discord username: _Dave9595_ |
||
|
|
eedb90ffb5 |
Fix game ID display for team games 🪪 (#3734)
## Description: Game ID display was looking weird in team games... Before: <img width="389" height="321" alt="image" src="https://github.com/user-attachments/assets/cf39b490-cfba-4c3a-86af-8f9498380eae" /> After: <img width="394" height="323" alt="image" src="https://github.com/user-attachments/assets/9e828169-b267-4627-85eb-548dca224a8a" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
29a1e8dfda |
feat: multi-warship selection with Shift+drag box (#3677)
Resolves #3666 ## Description: Adds RTS-style box selection for warships. Hold Shift and drag (desktop) or long-press and drag (touch/mobile) to draw a selection rectangle — all player-owned warships inside get selected at once. A subsequent click/tap on water sends them all to that location. - `SelectionBoxLayer` — pixel-dashed rectangle in world-space, player territory color; shared between desktop and touch - `UILayer` — same pulsing selection outline on each box-selected warship; clears correctly when switching between single/multi selection - `UnitLayer` — finds warships in screen rect, filters inactive ships before sending; touch support included - `InputHandler` — Shift+drag and touch long-press+drag both emit selection box events; cursor becomes crosshair on Shift; discards active ghost structure on Shift press; configurable via `shiftKey` keybind - `Transport` — single atomic `move_multiple_warships` intent (no split on socket drop) - `Schemas` + `ExecutionManager` + `MoveMultipleWarshipsExecution` — server fans out atomic intent into individual `MoveWarshipExecution` per ship - `DynamicUILayer` — `MoveIndicatorUI` chevron animation on target tile for both single and multi move - `UnitDisplay` — warship tooltip Shift hint via `translateText` - `HelpModal` — new hotkey row: Shift + drag → select multiple warships ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## UI update ### Mouse + Keyboard https://github.com/user-attachments/assets/3f35ab5e-1f3c-4c5d-bc4f-aabccf64dc60 ### Touch https://github.com/user-attachments/assets/0d6aec3f-44fa-4fee-b5c6-b267b9b14d79 ## ## Please put your Discord username so you can be contacted if a bug or regression is found: fghjk_60845 |
||
|
|
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 |
||
|
|
4e04bed44c |
Show game ID ingame 🪪 (#3674)
## Description: Instead of begging youtubers to share their game id to be able to debug: Display the current game ID in the top-right corner of the in-game leaderboard panel (there was unused space) <img width="391" height="326" alt="image" src="https://github.com/user-attachments/assets/8b0aa7c2-fc8c-48e5-ae11-edd60fd40de9" /> ## 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 |
||
|
|
565060f346 | Merge branch 'v30' | ||
|
|
adeb6a306a |
Update homepage & in-game promos (#3602)
## Description: * Replace the static footer ad (HomeFooterAd component) with a Playwire bottom_rail ad that loads on lobby join and persists into the spawn phase * Expand in-game ad slots from 1 to 3 (standard_iab_left1, left3, left4) with a timer-based visibility check to show a background container when ads render * Remove the resize-based footer ad height logic and gutter ad vertical offset adjustments that depended on it <img width="1828" height="961" alt="Screenshot 2026-04-16 at 12 14 00 PM" src="https://github.com/user-attachments/assets/50bfd0de-dd54-4f8b-b75e-04b720a1841b" /> <img width="1286" height="939" alt="Screenshot 2026-04-16 at 11 59 18 AM" src="https://github.com/user-attachments/assets/e0fb0762-82e7-444f-8706-5908aad0f094" /> ## 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 |
||
|
|
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> |
||
|
|
318d1e2c44 |
Refactor/Fix(UserSettings): last localstorage calls now use UserSettings, and Keybinds fixes (#3619)
## Description: 1) Have last localstorage calls for keybinds and attack ratio also use UserSettings cache instead, after #3481. Remaining calls to localstorage are for different things than user settings, so they are left as is. 2) Consolidate and centralize keybinds logic. And three fixes for it. - **UnitDisplay** and **UserSettingsModal**: _parsedUserKeybinds_ is introduced in **UserSettings** to centralize their logic. It is also used by _normalizedUserKeybinds_, see point below. - **UserSettingsModal** -- replaced unwanted cast `as SettingKeybind` by a typed QuerySelector. -- renamed this.keybinds to this.userKeybinds for more clarity, and distinction from defaultKeybinds. -- state private _userKeybinds_: remove type string[] since loadKeybindsFromStorage replaces a value array by its first string element, so it can not contain string[] anymore. -- _handleKeybindChange_ and _getKeyValue_: no need to check for Array.isArray anymore, see above reason. -- **Fix**: checks after calling _parsedUserKeybinds_ are improved a bit: don't delete all keybinds and print a console warning when finding just one invalid keybind and (i think i have seen people complaining about things being removed). Instead it now migrates or throws out the invalid ones but keeps the valid ones. Also works with the "Null" value expected and removed within **UserSettingsModal**._handleyKeybindChange_() and in **HelpModal**. When legacy value is an array and key is empty, don't put value as key but get first array element or empty string as key name. So that check on line 68 is true. - **HelpModal** and **InputHandler**: Also centralize/consolidate their logic more, by having __keybinds()_ from **UserSettings** perform fetching _getDefaultKeybinds_ and _normalizedUserKeybinds_. -- Functionality in _normalizedUserKeybinds_ is the same: Where HelpModal did return [k, v.value] if typeof (v as any).value === "string", this is now handled by lines 309-310 of normalizedKeybinds still the same but with less lines. Same for old HelplModal if (typeof v === "string") return [k, v], this is stil returned by line 112 of normalizedKeybinds. And return [k, undefined] when (typeof val !== "string") as was done in InputHandler, isn't needed as values that weren't strings were already filtered out right after which we still do on line 314 of normalizedKeybinds. -- **Fix** in _normalizedUserKeybinds_: added one extra thing that was a discrepancy between **HelpModal**/**InputHandler** and **UserSettingsModal** before: **UserSettingsModal** would handle array values, and normalize them by picking only the first value if it is a string. Now have _normalizedKeybinds_ do the same. Otherwise it would have thrown those values out while **UserSettingsModal** would have kept the first value. This may still help a returning player who hasn't played in the last version (i think i have seen people complaining about things being removed, but that may not have been about this). And makes the logic more consistent between **UserSettingsModal** and **HelpModal**/**InputHandler**. - **UserSettings**: -- _getDefaultKeybinds_: centralized/consolidated logic, accepts Platform.isMac parameter. In **HelpModal**, **InputHandler** and **UserSettingsModal** the same list with default keybinds was hardcoded. Now they all read from _getDefaultKeybinds_. The list of default keybinds in **HelpModal** was a little shorter, but that doesn't matter since its _render_() function has hardcoded which of the hotkeys **HelpModal** shows. Have thought about putting default keybinds in **DefaultConfig** but with all the logic handled through **UserSettings**, this seemed the better place in the current refactor. -- _removeCached_: make public, now that **InputHandler.test.ts** needs to be able to call it. We could instead make a public function like removeKeybinds() and keep removeCached() private, but went with this for now. -- _parsedUserKeybinds_: centralized/consolidated logic for **UserSettingsModal**/**UserDisplay**. Always returns an object, even an empty one if the JSON wasn't parsable. -- _normalizedKeybinds_: centralized/consolidated logic. Used by _keybinds_() which is now called by **HelpModal**/**InputHandler**. -- _keybinds_: now uses getDefaultKeybinds() and normalizedKeybinds() to get the default and user changed keybinds. -- **Fix** in _keybinds_: it now removes a key if it is Unbound by the user in **UserSettingsModal**. Instead of first loading the parsedUserKeybinds, removing "Null" keys from it, and then merging that with defaultKeybinds (so default key would overwrite an unbound key), we now merge parsedUserKeybinds with defaultKeybinds and after that remove "Null" keys from it (so that unbound key stays removed). For example if Boat Attack Up is set to "None" ("Null") by clicking Unbind, there is now no hotkey working for it anymore. Even when the default is "B". Why? This prevents the user from being confused, they have deliberately Unbound it, they don't understand why it still works (have seen bug reports and game feedback about this)? Also more importantly: they used to now be able to bind "B" to another action. Effectively making key "B" bound to two actions: the user choosen one and Boat Attack. This also makes the logic more consistent. Because building hotkeys in **UnitDisplay** already didn't work when unbound, eg. when Build Missile Silo was Unbound, the "5" key did not do anything anymore (there is a fallback in **UnitDisplay** in case the key is actually null, but it does respect "Null" as it should). -- _setKeybinds_: have it accept an object, it stringifies it itself. Callers UserSettingsModal and InputHandler.test.ts now just send either a string or an object. - **InputHandler.test.ts**: -- use **UserSettings** functions instead of localStorage for more real-world testing. -- change test "ignores non-string values and preserves defaults, removes 'Null' for unbound keys". As explained above, as a fix we no longer preserve unbound ("Null") keys within InputHandler. UserSettings.keybinds() now removes "Null" keys as explained above. - ControlPanel: use UserSettings to fetch initial attack ratio. ## 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 |
||
|
|
616ba1c794 |
Add support to purchase cosmetics with in-game currency (#3648)
## Description: Caps & Plutonium can be used to purchase different cosmetics. * The cosmetic button can display pluto/caps/dollars * Create a "purchaseCosmetic" helper function that handles purchase logic ## 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 |
||
|
|
17c1a6300f |
Trading in lakes 🚤 (#3653)
## Description: - Widened port placement and warship spawn/patrol checks from `isOcean`/`isOceanShore` to `isWater`/`isShore`, so ports can be built on lake shores and ships can operate on lakes, we discussed it here: <img width="996" height="423" alt="image" src="https://github.com/user-attachments/assets/acf1e970-9631-4848-a0ed-6d0470616e1d" /> - Filtered `tradingPorts()` by water component so ports only attempt trades with reachable ports - prevents silent path-not-found failures across disconnected water bodies - Applied the same water component filter when a captured trade ship reroutes to its new owner's nearest port - Removed the `WaterManager` fallback that force-marked isolated water-nuked-tiles as ocean (no longer needed since lakes are now navigable) - Added a check to prevent nations from building ports on water bodies that aren't accessible to other players ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --------- Co-authored-by: Evan <evanpelle@gmail.com> |
||
|
|
d5a2cc0fca |
cosmetic refactor (#3628)
## Description: The motivation is to have a single "cosmetic-button" element, so we can abstract out the cosmetic types. This will make it much easier to add new cosmetic types in the future. Unifies PatternButton and FlagButton into a single CosmeticButton component. Extracts a resolveCosmetics() function that flattens patterns × color palettes + flags into a ResolvedCosmetic[] with relationship status pre-computed, replacing duplicated resolution logic across four callers. * New CosmeticButton — renders patterns or flags based on ResolvedCosmetic.type * New resolveCosmetics() — centralizes ownership/purchase/blocked resolution * Extracted PatternPreview — canvas rendering split into its own module * Added type: "pattern" | "flag" discriminator to Zod cosmetic schemas * Deleted FlagButton.ts and PatternButton.ts * Added 320-line test suite for resolveCosmetics ## 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 |
||
|
|
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 |
||
|
|
646d7ecaf6 |
Disable Radial Menu during spawn phase, just left click or tap to pick spawn point (#3611)
## Description: During spawn phase, disable Radial Menu further. Its options where already greyed out or non-responding on purpose, except for the attack button (middle button) which could only be used to select a spawn point but two clicks for that is convoluted. It was mostly a nuisance, especially on mobile where you where forced to go through the Radial Menu, so tap and then tap again to pick a spawn point. - Now, left click directly places a spawn point. Even if "Left click opens menu" is enabled. - And right click does not open Radial menu anymore. Which had no use anyway. And also makes touch screen and mouse players more alike in that they now both have no access to the Radial Menu (which didn't have a purpose anyway except picking spawn point in a convoluted way with two clicks). - On touch screen during spawn phase, the Radial Menu also doesn't open anymore. Instead of two taps (open Radial Menu > tap attack button), now one tap suffices to pick a spawn point just like one left mouse click now does. Fixes https://github.com/openfrontio/OpenFrontIO/issues/3609 Also from UnitLayer > onTouch: - remove redundant isValidRef check. Since isValidCoord check was added in PR 3226 above it, we know it is a correct coord and with that correct ref, already. - remove redundant comment about isValidCoord/Ref not being checked there yet intentionally, because it is actually being checked there since PR 3226. ## 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: iamlewis <lewismmmm@gmail.com> |
||
|
|
105404ca50 |
Fix: Player Panel shown on top of Win Modal (#3618)
## Description: Put Win Modal on top of other modals, namely the Player Panel (z-index 10001) and EmojiTable (z-index 10002). Because currently if Player Panel is open when the Win Modal (z-index 9999) appears on death/win, the Panel is incorrectly shown on top of the Win Modal. Fix is to up Win Modal z-index to 10010, which also leaves room for other (newly added) panels/modals below it still. Fixes: https://discord.com/channels/1284581928254701718/1284581928833388619/1491504813521895534  ## 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 |
||
|
|
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> |
||
|
|
1cbee79cc7 |
Reduce Attacking Troops Overlay Reflows (#3608)
## Description: Vimacs on Discord pointed out a heavier than needed DOM load from the [AttackingTroopsOverlay PR](https://github.com/openfrontio/OpenFrontIO/pull/3427) - Caches a single `labelTemplate` in `AttackingTroopsOverlay`, built once on init and cloned per label instead of recreating it each time - Removes redundant inline style assignments that were repeated on every label creation - Simplifies `updateLabelContent` by accessing template-guaranteed children directly by index ## 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: Radyus |
||
|
|
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 |
||
|
|
341f344ce5 |
Perf/Refactor(UserSettings): caching makes it 10-20x faster (#3481)
## Description: Skip slow and blocking LocalStorage reads, replace by a Map. Also some refactoring. ### Contains - No out-of-sync issue between main and worker thread: Earlier PRs got a comment from evan about main & worker.worker thread having their own version of usersettings and possibly getting out-of-sync (see https://github.com/openfrontio/OpenFrontIO/pull/760#pullrequestreview-2845155737, https://github.com/openfrontio/OpenFrontIO/pull/896#pullrequestreview-2871836979 and https://github.com/openfrontio/OpenFrontIO/pull/1266. But userSettings is not used in files ran by worker.worker, not even 10 months after evan's first comment about it. In GameRunner, createGameRunner sends NULL to getConfig as argument for userSettings. And DefaultConfig guards against userSettings being null by throwing an error, but it has never been thrown which points to worker.worker thread not using userSettings. So we do not need to worry about syncing between the threads currently. (If needed in the future after all, we could quite easily sync it, by loading the userSettings cache on worker.worker and listening to the "user-settings-changed" event @scamiv to keep it synced (changes in WorkerMessages and WorkerClient etc would be needed to handle this). - Went with cache in UserSettings, not with listening to "user-settings-changed" event: "user-settings-changed" was added by @scamiv and is used in PerformanceOverlay. Which is great for single files that need the very best performance. But having to add that same system to any file reading settings, scales poorly and would lead to messy code. Also, a developer could make the mistake of not listening to the event and it would end up just reading LocalStorage again just like now. Also a developer might forget removing the listener or so etc. The cache is a central solution and fast, without changes to other files needed and future-proof. - Make sure each setting is cached: UserSettingsModal was using LocalStorage directly by itself for some things. Made it use the central UserSettings methods instead so we avoid LocalStorage reads as much as possible. For this, changed get() and set() in UserSettings to getBool() and setBool(), to introduce a getString() and setString() for use in UserSettingsModal while keeping getCached() and setCached() private within UserSettings. - Remove unused 'focusLocked' and 'toggleFocusLocked' from UserSettings: was last changed 11 months ago to just return false. Since then we've moved to different ways of highlighting and this setting isn't used anymore. No existing references or callers are left. - Other files: -- Have callers call the renamed functions (see point above) -- Remove userSettings from UILayer and Territorylayer: the variable is unused in those files. Also remove from GameRenderer when it calls TerritoryLayer. -- Cache calls to defaultconfig Theme (which in turn calls dark mode setting)/Config better in: GameView and Terrainlayer. ### Update on Contents later on It wasn't really in scope of this PR but further consolidation was called for. These changes could also pave the way for UserSettingsModal (main menu) perhaps being partly mergable with SettingsModal (in-game) one day as it begins to look more like it. Even though UserSettingsModal still does things its own way, and does console.log where SettingsModal doesn't, etc. They both have partially different content and settings but also have a large overlap. - UserSettings: Removed localStorage call from clearFlag() and setFlag() which were added after creation of this PR, and were neatly merged in silence without merge conflicts so i wasn't aware of them yet until now. - UserSettings: added key constants, exported to use both inside UserSettings and in files that listen to its events. - UserSettings 'emitChange': now done from setCached, removed from setBool, setFlag etc. Also removed from the new setFlag. And from setPattern even though it emitted "pattern" instead of key name "territoryPattern"; now it emits the default "territoryPattern" from PATTERN_KEY which is re-used in Store, TerritoryPatternsModal and PatternInput. - UserSettingsModal: made UserSettingsModal call existing toggle functions in UserSettings, or new or existing getter or setter. We do not need CustomEvent: checked anymore. In UserSettingsModal, its toggle functions did not all actually toggle, some like toggleLeftClickOpensMenu actually just set a value. Based on the 'checked' value of the CustomEvent. But we don't need that 'checked' value anymore and none of the checks for it inside the toggle functions in UserSettingsModal, now that we just directly call toggleLeftClickOpensMenu and others in UserSettings. - SettingToggle: continuing about not needing CustomEvent anymore: the old way actually fired two events. The native change event from <input> and our own CustomEvent from handleChange in SettingToggle. It prevented handling both events by checking e.detail?.checked === undefined. But now, the native <input> event is all we need to show the visual toggle change and trigger @changed in UserSettingsModal which calls the toggle function. - Use the toggle functions too from CopyButton and PerformanceOverlay.ts. In PerformanceOverlay, change in onUserSettingsChanged was needed because of how setBool works. - UserSettingsModal 'toggleDarkMode': in UserSettingsModal, removed the event from toggleDarkMode in UserSettingsModal; nothing is listening to this event anymore after DarkModeButton.ts was removed some time ago. Also both UserSettingsModal an UserSettings added/removed "dark" from the document element. Now that UserSettingsModal calls toggleDarkMode in UserSettings, we could centralize that. But UserSettings is in core, not in client like UserSettingsModal. But now that we emit "user-settings-changed", we could handle it even more centralized and not have UserSettingsModal or UserSettings touch the element directly. Instead have Main.ts listen to the event and change it dark mode from there. - UserSettings: added claryfing comment to attackRatioIncrement and the new attackRatio setters/getters, to explain their difference. Noticed a small omitment in its description and fixed that right away in en.json: you can change attack ratio increment by shift+mouse wheel scroll or by hotkey. So made "How much the attack ratio keybinds change per press" also mention "/scroll." **BEFORE** (with getDisplayName added back to NameLayer as a fix i will do soon) get > getItem in UserSettings  renderLayer in NameLayer (with getDisplayName added back to NameLayer as a fix i will do soon)  **AFTER** (with getDisplayName added back to NameLayer as a fix i will do soon) getCached in UserSettings  renderLayer in NameLayer (with getDisplayName added back to NameLayer as a fix i will do soon)  ## 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 |
||
|
|
b5ca0f9d8f |
Perf/refactor/fix(NameLayer): about 10% extra improvement (#3540)
## Description: NameLayer perf part 2 after https://github.com/openfrontio/OpenFrontIO/pull/3475 with thanks to @scamiv. Shaves off another 10% or thereabouts, even doing something extra for a fix (see below). Also refactor/fixes around NameLayer and PlayerIcons, which is used by both NameLayer and PlayerInfoOverlay, and underlying function in GameView. This would go well with other PR https://github.com/openfrontio/OpenFrontIO/pull/3481, since this layer reads multiple settings. Reasoning to not use events and instead rely on fast caching is explained in that PR. ### Contents - Fixes: -- Fixes bug on .dev introduced by wrong assumption by me in previous PR https://github.com/openfrontio/OpenFrontIO/pull/3475. displayName CAN change during game, when Hidden Names is toggled from settings, so needs to be put back in renderPlayerInfo. -- Fixes longer existing bug: it was assumed Dark Mode didn't change after creation of icon element. Now it also sets Dark Mode attribute when updating icons elements. -- Fixes target mark icons not being shown to team members, while the icons were shown to normal allies. And EventsDisplay displayed message "XX requests you attack XX" to both team members and allies already. So why is the icon not shown to both if the message already is. While we improve performance of GameView > PlayerView > transitiveTargets (which is only used by NameLayer/PlayerIcons so only in this context). We can add team members' targets to it in one go. So previously transitiveTargets returned: your own targets and allies' targets. Now transitiveTargets is faster and returns: your own targets and allies' targets and team members' targets. - NameLayer: -- renderLayer: for target icons, getPlayerIcons used to fetch myPlayer.transitiveTargets each time. While that doesn't change per player we're rendering for. So now, we fetch myPlayer.transitiveTargets once per call to renderPlayerInfo, which passes it on to getPlayerIcons. So now we check it 1x each 100ms (renderCheckRate) inside of renderLayer. Instead of up to 100s of times each 500ms (renderRefreshRate) inside of getPlayerIcons inside of renderPlayerInfo loop. -- createBasePlayerElement and renderPlayerInfo: use cloneNode where possible with templates -- createPlayerElement: only find the elements and set font and flag. Leave the rest to renderLayer > renderPlayerInfo which fills displayName and troops and font color very soon after anyway. I haven't noticed a difference in testing. -- cache game.config() and others -- renderPlayerInfo: remove check if render.flagDiv exists, we know it exists. Check if fontColor changed before assigning it (it never changes, currently, be it dark mode or light mode). Don't check if troops or size changes, that happens so often that the overhead for checking would be smaller than the win, probably. -- We don't require nameLocation to be changed to change scale (see previous Namelayer perf PR for the reason). But it seems good to check if the transform changed before 'overwriting' it, so do that now instead. -- Remove Alliance icon DOM traversals. Only do it once, for each time an alliance icon is displayed. To this end, also made NameLayer more agnostic on Alliance icon stuff. By moving more code to PlayerIcons. See below. -- Use cached allianceDuration instead of fetching this static value every time -- Re-use from PlayerIcons: ALLIANCE_ICON_ID, TRAITOR_ICON_ID etc -- create more sub-functions to make the icons loop in renderLayer more readable: handleEmojiIcon, handleAllianceIcons, createOrUpdateIconElement (createIconElement already existed, now combined), handleTraitorIcon. For Alliance icons, this was already done in PlayerIcons.ts through createAllianceProgressIcon (now createAllianceProgressIconRefs), and more now to skip some DOM traversals. But most of this belongs in NameLayer itself when it comes to seperation of concern. - cache dark mode (as boolean and as string) - use dark mode to update (alliance) icons too, not only on create, since the setting can change after icon element creation and before it is removed -- for getPlayerIcons, add this.alliancesDisabled. If disabled, getPlayerIcons won't fetch Alliance icon and Alliance Request icon. - PlayerIcons: -- use cloneNode where possible -- added check for alliances disabled: then skip alliance (request) icon checks -- See point under NameLayer about the move of Alliance icon code to PlayerIcons. To make NameLayer even more agnostic on it and keep it in one place. -- getPlayerIcons: skip creating a new Set from myPlayer.transitiveTargets() each time getPlayerIcons is called. One allocation less. Just do .includes on the returned array. Probably just as fast in this case, also because not many Targets are present many times anyway. -- getPlayerIcons: on outgoingEmojis(), use .find() instead of .filter() since we only use the first result anyway and it saves us another allocation. -- getPlayerIcons: for nukes, only fetch the ones from the player we're rendering for, not all game nukes. Also don't use .filter() and just a normal loop to skip an allocation. Logic outcome is the same. -- getPlayerIcons: for target icons, it used to fetch myPlayer.transitiveTargets each time. While that doesn't change per player we're rendering for. So now, NameLayer fetches myPlayer.transitiveTargets once per call to renderPlayerInfo, which passes it on to getPlayerIcons. -- Remove the need for querySelector and getElementsByTagName("img") alltogether. Since this would be done for every time an alliance was (re-)created, here in createAllianceProgressIconRefs in PlayerIcons it makes more sense to not do DOM traversal than in createPlayerElement in NameLayer where we only do it once per player per game anyway. We assume updateAllianceProgressIconRefs just knows of all image names in createAllianceProgressIconRefs. This is a bit less dynamic and maintainable maybe, but i think worth the win. And the functions are all one-purpose and not meant to be used dynamically by another caller anyway. -- So instead of updateAllianceProgressIconRefs looping through refs.images, now just update the different images each. See point above. - PlayerInfoOverlay: also re-use the new exported consts from PlayerIcons. Since we put those in PlayerIcons anyway, need to be consistent. Even though PlayerInfoOverlay is outside of the scope of this PR otherwise. -- for getPlayerIcons, add this.alliancesDisabled here too. If disabled, getPlayerIcons won't fetch Alliance icon and Alliance Request icon. We also send includeAllianceIcon = false, which means Alliance icon will already be excluded but Alliance Request icon is normally still fetched and shown. - GameView > PlayerView: for transitiveTargets (only used in NameLayer/PlayerIcons so only in this context), improve performance. It did several allocations. Now it loops directly over the arrays we need. Also (as mentioned under Fixes) previously transitiveTargets returned: your own targets and allies' targets. Now transitiveTargets is faster and returns: your own targets and allies' targets and team members' targets. **BEFORE**  **AFTER** (including now getting team members' targets for myPlayer.transitiveTargets)  ## 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 |
||
|
|
494b409f55 |
Cap StructureLayer Maximum Texture Size (#3574)
Resolves #3569 ## Description: While browsers like Firefox will report their maximum texture size of 16384, going over 8192 causes extreme VRAM usage and massive FPS drops, even when no structures are actually being rendered (I experienced ~60ms rendering time on this layer with no structures present). This PR sets the StructureLayer texture size to cap at 8192, while keeping near-exact scales. The result is increased performance, reduced VRAM Usage, (especially in larger maps), and the resolution of the unplayable performance issues when StructureLayer is present, with zero noticeable degradation. VRAM Usage also no longer rises when zoomed in (Sitting at around a constant 40MB no matter zoom level, previously it would rise to over 160MB when StructureLayer was present). All tested on Giant World, where the issues were first spotted, but applies to all maps. Discord: @enderboy9217 |
||
|
|
21c286189e |
Perf/fix(NukeExecution/NukeTrajectoryPreviewLayer): unnecessary fetch and pass to listNukeBreakAlliance (#3546)
## Description: Perf/fix: listNukeBreakAlliance doesn't ask for allySmallIds and doesn't do anything with it. But both NukeExecution and NukeTrajectoryPreviewLayer do fetch and pass allySmallIds to it. Make allySmallIds optional, have wouldNukeBreakAlliance (which does use allySmallIds) handle it potentially being undefined, and remove fetch and pass of allySmallIds to listNukeBreakAlliance. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
b88c3a3bdd | Merge branch 'v30' | ||
|
|
54e29c5b4e |
Revert "Add new bouncing trajectory for april fools (#3534)"
This reverts commit
|
||
|
|
c92895620f |
Add new bouncing trajectory for april fools (#3534)
## Description: For April Fools day, the nukes can randomly bounce (1/10 chance). Changes: - New BouncingParabola trajectory - Generic "Text Event" to display a `Boing!` where the bounce happens - The complete trajectory, with or without bounce, is used by the SAM interceptor so there should be no defense gap.  ## 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 |
||
|
|
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 |
||
|
|
130315cba1 | Merge branch 'v30' | ||
|
|
73016bb56b |
Add bottom left ad in crazygames (#3526)
## Description: If on crazy games, shows an in-game ad on the bottom left corner ## 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 |
||
|
|
7fdda33fb9 | Merge branch 'v30' | ||
|
|
39ad547c04 |
support for unlockable flags (#3479)
## Description: Add support for purchasable/gated flags. * Create a new "Store" modal that renders both skins & flags * move all store related logic out of TerritoryPatternsModal * use nation:code for existing nation flags & flag:key for gated flags * check if user has the appropriate flags before purchasing ## 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 |
||
|
|
496f1008bb |
Fix: icons structure icons and others at wrong location (#3453)
## Description: Fix for all structure icons suddenly being displaced from the actual structure location. And in some cases, structure itself created at wrong location, or coordinate grid, nuke trajectory preview target, nuke circles, naval landing target, or pop-up text of gold earned from train or tradeship. - Was triggered on iOS almost exclusively, but was also possible on other devices when a top/left margin was present. Like when an ad was shown. Why noticed almost only on iOS? Because of different behaviors where it shifts the DOM elements around relative to the screen temporarily, so we get a top/left offset on getBoundingClientRect for the canvas. Possible culprit is overscroll which lets you scroll outside of the viewport for several hundred pixels before snapping back. Which was triggered by mistake by dragging instead of tapping somewhere, or so. - Some of the bug reports: https://discord.com/channels/1284581928254701718/1451393982159523982, https://discord.com/channels/1284581928254701718/1463548362526822649, https://discord.com/channels/1284581928254701718/1378672255336189964, fixes https://github.com/openfrontio/OpenFrontIO/issues/3406 - The fix brings a little performance win as well because we need to be doing less calculations. It is basically "if drawing on a canvas, work with canvas coordinates and not with screen coordinates". Was stress tested by two players and me, see below for reproduction. - (BTW. When researching if the current logic was intended in any way, I found CodeRabbit had already noticed part of the bug twice. One of them was [resolved](https://github.com/openfrontio/OpenFrontIO/pull/2059#discussion_r2396277710), the other [left open](https://github.com/openfrontio/OpenFrontIO/pull/2059#discussion_r2370413213). Another reminder that we need to heed Rabbits calls!) **CONTAINS** - StructureIconsLayer > computeNewLocation and StructureDrawingUtils > createUnitContainer. In renderLayer, when TransformHandler.hasChanged (after onZoom, goTo, onMove), computeNewLocation recalculates the position of all structure icons. Sometimes all icons would suddenly be displaced, not above their map position but somewhere else. New single icons/levels/sprites would be placed wrongly too by computeNewLocation and createUnitContainer. -- Fix: don't use TransformHandler > worldToScreenCoordinates but the new worldToCanvasCoordinates. Because worldToScreenCoordinates adds the canvas boundingRect top/left offset. When the main canvas is already shifted down with a margin, the icons also get shifted within the canvas. So they're moved twice as much as they should be. We only need to know at what canvas location to put the icons, the main GameRender canvas already handles the overall displacement. - TransformHandler > worldToCanvasCoordinates -- Use the new worldToCanvasCoordinates too instead of worldToScreenCoordinates in CoordinateGridLayer, MoveIncicatorUI, NavalTarget, NukeTeleGraph, TextIndicator. They were affected by the same bug as the shifting Structure icons because the boundingRect top/left offset was applied twice, but it was noticed less. I have seen reports of NavalTarget or MobveIndicatorUI (for warships) not being in the correct spot though iirc. And just like for StructureIconsLayer/StructureDrawingUtils, it's only logical. Since we're drawing on the canvas, we only need to know where to place things within that canvas. - TransformHandler > worldToScreenCoordinates -- Split in two sub-functions. New seperate function worldToCanvasCoordinates was needed for the above fix. For canvasToScreenCoordinates the reason is explained below. - TransformHandler > screenToWorldCoordinates: this function already subtracts the canvas boundingRect top/left offset. Some callers were themselves getting the boundaryRect and subtracting top/left, before calling screenToWorldCoordinates. Not only unnecessary. But also, when there was more than 0 top/left offset, it would be subtracted twice from the mouse/tap position. Meaning a (ghost) structure or nuke trajectory preview target would not be put where the mouse/tap was. Same bug as above but reversed. -- Checked all callers. Most did it right. Fixes where needed in StructureIconsLayer > createStructure and renderGhost, and in NukeTrajectoryPreviewLayer > updateTrajectoryPreview and updateTrajectoryPath. -- Removed comment in screenToWorldCoordinates that talked about zoom. It doesn't belong there because we do more than zooming there, it was probably copied once from onZoom() which has the exact same comment and it belongs in that function. -- Small fix in caller BuildMenu when checking all callers of screenToWorldCoordinates: it checked if clickedCell was null, but screenToWorldCoordinates never returns null. - TransformHandler > added public helper functions screenToCanvasCoordinates and canvasToScreenCoordinates to make code re-usable -- screenToCanvasCoordinates is used in screenToWorldCoordinates and onZoom in TransformHandler itself -- screenToCanvasCoordinates is now also used also in moveGhost and createGhostStructure in StructureIconsLayer. No bugs there, but the same calculation was done (getting boundingRect, subtracting left/top from mouse/tap position). So they now use the centralized function which also adds to their readability. -- canvasToScreenCoordinates is (for now) only used in worldToScreenCoordinates in TransformHandler. It makes the function more readable imo. And, since it has such a similar calculation to screenToWorldCoordinates, it only seemed neat to have them both have their own function. **BEFORE** (only showing "all structure icons get displaced", but the cause and fix is basically the same for all) https://youtu.be/CfDdUwIRQeE **AFTER** https://youtu.be/w5w_wvh5V0g ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
eb51853b05 |
Perf/Fix: spawn and other functions that need closest by unit (#3243)
## Description: Performance improvements. - **PlayerImpl**: for _nukeSpawn_, cache config to const. - **Other files**: for nukeSpawn and other functions doing the same, introduce findClosestBy function. - for **TradeShipExecution**, with the move from _distSortUnit_ to _findClosestBy_, also add check if port isActive, !_isMarkedForDeletion_ and !_isUnderConstruction_. These checks should have been there already, so now do it in one go to make use of the predicate isCandidate in findClosestBy. - for **TradeShipExectution.test.ts**, add mock functions for _isMarkedForDeletion_ and _isUnderConstruction_ because of the above. Also, set Unit tiles and Pathfinding node to actual valid TileRefs for the testing map. This prevents NaN as return value from manhattanDist. This problem was already present with the use of distSortUnit, but that function just did NaN - NaN, returned the first and only port unit in the array and called it a day. For findClosestBy we have to make sure the predicate manhattanDist actually returns a number instead of NaN so we need actually valid tiles. We now have a working test instead of a test that actually silently failed like before. - **PlayerImpl**: _warshipSpawn_ and _nukeSpawn_: Make use of the isCandidate predicate of findClosestBy to have warshipSpawn not return ports under construction or (smaller change) inactive. This fixes a bug i have seen right away (where Warship spawns from under construction Port). Same for _nukeSpawn_ silos, don't return inactive silo just to be sure now that we can easily add it to isCandidate predicate anyway. This costs no performance in the _nukeSpawn_ benchmarks actually. This should as a by-effecft fix an edge case bug i have seen, where a nuke is sent from a phantom silo. Some of this goes along with PR #3220 since playerImpl buildableUnits makes use of the underlying spawn functions via canBuild. Just like ConstructionExecution does. But i didn't want to add more to PR 3220 since there's already a lot in there. The new function _findClosestBy_ could also be applied to some other parts of code to benefit of it being faster, so i did that. _findClosestBy_ uses _findMinimumBy_, which is a little more generic in name. I think _findMinimumBy_ could be used by other parts of code, while _findClosestBy_ is more clear naming for what it does now. But we could ditch _findMinimumBy_ and just leave findClosestBy? Examples of synthetic benchmarks (not included in this PR): **BEFORE CHANGES (before Scamiv's PR #3241)** <img width="705" height="91" alt="image" src="https://github.com/user-attachments/assets/d6d91c08-39f1-4387-9ccc-e51951caa539" /> <img width="751" height="101" alt="image" src="https://github.com/user-attachments/assets/80d400ac-3408-4107-aa58-6d2a847311e9" /> **AFTER CHANGES (before Scamiv's PR #3241)**   **BEFORE CHANGES (after Scamiv's PR #3241)**   **AFTER CHANGES (after Scamiv's PR #3241)** <img width="717" height="96" alt="image" src="https://github.com/user-attachments/assets/5b106843-bf6e-4448-a8e8-94448fb30ced" /> <img width="767" height="92" alt="image" src="https://github.com/user-attachments/assets/e6714c7b-26c1-455b-adae-f0060f1cbc7b" /> _Also see more **BEFORE** and **AFTER** in this comment:_ https://github.com/openfrontio/OpenFrontIO/pull/3243#issuecomment-3949060395 _And here a comparison in the flame charts:_ - based on the same replay and tried to get the performance recording going at the same speed and length but always end up with small differences - because of a bug in replays currently, it puts you in with the same clientID/persistantID currently. This means we can also record part of what is normally only recordable with live human input (the playerActions/playerBuildables). **BEFORE** flame chart with nukeSpawn (human player) and maybeSendNuke (Nation players, uses nukeSpawn via canBuild):    **AFTER** flame chart with nukeSpawn (human player) and maybeSendNuke (Nation players, uses nukeSpawn via canBuild):      ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 |
||
|
|
05e2bc9f0a |
Improve cacheability with content-hashed public assets and a cacheable app shell (#3494)
## Description: This reworks asset delivery and cacheability across the app and moves non-bundled public resources onto immutable, content-hashed URLs. Vite bundle outputs continue to live under `/assets/**` and remain content-hashed by Vite. Public resources that were previously fetched from stable paths in `resources/` now go through a custom hashed namespace under `/_assets/**`, backed by a generated asset manifest that is available to the server, browser, and worker runtime. In parallel, the root app shell is now cacheable shared HTML instead of request-time `no-store` HTML. Dynamic and live routes remain explicitly uncached. ## Why - Improve browser and Cloudflare cacheability for static assets. - Remove query-string and release-version cache busting for runtime-fetched assets. - Allow unchanged public assets to keep the same URL across releases. - Reduce avoidable work on `/` by serving a shared app shell instead of rendering HTML on every request. - Make cache behavior explicit instead of relying on mixed framework defaults and file-extension heuristics. ## What Changed ### 1. Content-hashed public asset pipeline - Added a build-time public asset manifest and hashing pipeline for non-Vite resources. - Production now emits hashed public assets under `/_assets/**`. - Added runtime manifest loading for Node so server-rendered paths resolve against built hashed files instead of rebuilding from source at runtime. - Emitted the runtime asset manifest as an ESM module for server consumption. Result: - `/assets/**` = Vite-managed hashed bundle outputs - `/_assets/**` = custom content-hashed public resources ### 2. Runtime asset URL migration - Added a shared `assetUrl(...)` resolution path. - Migrated runtime references away from query-string versioning and stable source paths. - Updated browser, worker, and server-side rendering paths to resolve through the asset manifest. - Moved map manifests, map binaries, thumbnails, sprites, sounds, fonts, flags, icons, screenshots, and other runtime-fetched resources onto hashed URLs. ### 3. Map and preview fixes - Fixed directory and per-file map asset resolution so map manifest and binary fetches resolve to the correct hashed URLs. - Updated preview metadata and map thumbnail paths to use the hashed asset namespace. - Fixed runtime manifest loading in prod after deployment. ### 4. Explicit cache policies - Added explicit immutable cache headers for: - `/assets/**` - `/_assets/**` - worker-prefixed equivalents under `/wN/...` - Added explicit `no-store` headers for live and dynamic APIs. - Removed the old `/api/env` bootstrap request and baked `gameEnv` into the HTML bootstrap instead. ### 5. Cacheable root app shell - Refactored the root HTML path to serve a shared app shell with: - `Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=86400` - `/` and the SPA fallback now serve shared cacheable HTML instead of request-time `no-store` rendering. - `/game/:id` remains dynamic and `no-store`, but now reuses the shared shell before injecting preview tags. ### 6. Matchmaking instance handling - Because the app shell is now cacheable, `INSTANCE_ID` was removed from shared HTML. - Added `/api/instance` as a temporary `no-store` runtime lookup used only by matchmaking. - This preserves correctness with the current random-per-boot `INSTANCE_ID` model while keeping `/` cacheable, but it is not the intended long-term design. ## Behavior Changes ### Asset URL contract Production URLs for non-Vite public resources now change from stable paths such as: - `/maps/...` - `/images/...` - `/manifest.json` to content-hashed paths under: - `/_assets/...` Examples: - `/_assets/maps/<map>/manifest.<hash>.json` - `/_assets/images/Favicon.<hash>.svg` ### Bootstrap/config - `/api/env` is removed. - `gameEnv` is now bootstrapped from HTML. ### HTML caching - `/` and the SPA fallback are now cacheable shared HTML. - `/game/:id` remains dynamic. ## Cache Matrix After This Branch - `/_assets/**`: `public, max-age=31536000, immutable` - `/assets/**`: `public, max-age=31536000, immutable` - live `/api/**`: explicit `no-store` - `/api/health`: explicit `no-store` - `/api/instance`: explicit `no-store` - `/game/:id`: explicit `no-store` - `/` and SPA fallback: `public, max-age=0, s-maxage=300, stale-while-revalidate=86400` ## Notes / Tradeoffs - `/api/instance` is a temporary compromise. It exists because `INSTANCE_ID` is currently random per boot, which is not safe to embed into cacheable shared HTML. - The current matchmaking flow still asks the client to provide `instance_id` during `matchmaking/join`. That is functional, but it is the wrong ownership boundary: instance selection should be handled by the matchmaking service, not by the browser. - The cleaner end-state would be: - make `matchmaking/join` stop requiring `instance_id` from the client, and let the matchmaking service select a healthy instance from worker check-ins - This branch makes the origin behavior edge-cache-friendly, but Cloudflare still needs matching cache rules if HTML itself should be cached at the edge. ## Validation Verified during development with: - `npx tsc --noEmit` - `node node_modules\\vite\\bin\\vite.js build` - `node node_modules\\vitest\\vitest.mjs run tests/server/RenderHtml.test.ts tests/server/NoStoreHeaders.test.ts tests/server/StaticAssetCache.test.ts tests/core/configuration/ConfigLoader.test.ts` Additional targeted tests added: - `tests/AssetUrls.test.ts` - `tests/core/game/FetchGameMapLoader.test.ts` - `tests/core/configuration/ConfigLoader.test.ts` - `tests/server/NoStoreHeaders.test.ts` - `tests/server/StaticAssetCache.test.ts` - `tests/server/RenderHtml.test.ts` ## Known Existing Warnings The production build still reports pre-existing warnings that are not addressed by this branch: - inconsistent JSON import attributes for `resources/countries.json` - inconsistent JSON import attributes for `resources/QuickChat.json` - large chunk warnings from Vite ## Rollout Notes - Cache rules should treat `/_assets/**` and `/assets/**` as immutable. - Cloudflare will still classify HTML as dynamic after deploy unless matching edge cache rules are configured for it. ## Follow-ups - Remove `/api/instance` by changing `matchmaking/join` so the server selects the target instance, or by making `INSTANCE_ID` deploy-stable if the current contract must remain. ## 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 |
||
|
|
13df5cf324 |
Perf/Cleanup/Fix(NameLayer): 40% better performance (#3475)
## Description: TL;DR: NameLayer cleanup+ fix + about 40% faster. The potential move of NameLayer to canvas is stalled so this is a welcome improvement until then imo. - It was previously attempted to move NameLayer from HTML to canvas. But currently that work is stalled so it might take awhile. Therefore optimizations to NameLayer are useful to merge in the meantime. Also there's a fix in this PR (see point below) and some cleanup. Overall it would probably be better to base future changes on this better version of NameLayer. Messages about attempt on Feb 6 and reference to having done that attempt on March 3: https://discord.com/channels/1359946986937258015/1381293863712591872/1469117172767784981 https://discord.com/channels/1359946986937258015/1381293863712591872/1469401090385641573 https://discord.com/channels/1359946986937258015/1381293863712591872/1469435973522686127 https://discord.com/channels/1359946986937258015/1359946989046989063/1478213329079242752 - Fix: TL;DR: Remove redundant comparison that unintentionally didn't work and always resolved to true. Leading to scale always being recalculated. It is now still always recalculated because otherwise name may be too big for the territory for several seconds, which looks buggy. (More on this: In renderPlayerInfo(), it cached render.location in oldLocation. Then put new Cell() in render.location. Later on it would strictly compare render.location against oldLocation, to decide if scale should be changed. Which would always be true because render.location would have a new Object (long ago they were compared non-strictly with ==, later on strictly when those checks were updated in the entire repo to ===). With this comparison always returning true (even if render.location x and y did not actually change), the scale would always be updated by updating render.element.style.transform. After the fix (removing the non-working comparison which always resolved to true), scale updates happen at same frequency as before. I have not kept a similar check like "positionChanged". Because in testing, player/tribe name would be scaled as too big for their territory size for several seconds. This felt buggy. Cause for this is two delays sometimes overlap resulting in several seconds of delay before scale is recalculated after name position changed: time in Namelayer per render refresh inside renderLayer (renderRefreshRate 500ms) plus the waiting time in PlayerExecution per recalculation of largestClusterBoundingBox (every 20 ticks). I ultimately decided that it should not wait for "positionChanged" and just be updated every 500ms (renderRefreshRate) just like unintentionally happened before.) - Remove redundantly re-adding the name, when a player name doesn't change anyway. Only adding it when creating the element is enough - Remove dead code for Shield - Cache DOM lookups - Use textContext instead of innerHTML for nameSpan - Only transform container if it has updates - Remove currently unused Canvas. Also from public renderLayer(). Layer.ts expects renderLayer(context: CanvasRenderingContext2) so i could put it back, but it isn't needed per se and i think it makes more clear that NameLayer doesn't use Canvas currently. - Remove two unneeded/outdated comments, update others - Move setting render.fontSize and render.fontColor after early return - Pass baseSize to updateElementVisibility so as to not calculate it twice - Cache render.player.nameLocation() to re-use BEFORE:  AFTER:  ## 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 |
||
|
|
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.  A recent change updates the layer to just the # of attackers and a symbol for attack/defence:  Left: Perspective of Anon 667 (Blue) | Right: Perspective of Anon332 (Red)  **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 |
||
|
|
6b9603d445 | Merge branch 'v30' | ||
|
|
cb7164a52f |
Place footer ad during spawn phase (#3458)
## Description: Show the "footer_ad" ad type during the spawn phase. <img width="1855" height="914" alt="Screenshot 2026-03-17 at 7 01 37 PM" src="https://github.com/user-attachments/assets/9c4c1730-95a0-4fc7-a983-4fe625d38c80" /> ## 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 |
||
|
|
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 |
||
|
|
79c3deabd8 | remove spawn video ad | ||
|
|
51db6a2772 |
Add in game add to bottom left corner (#3446)
## Description: Remove the header ad because that's where the player info overlay is, and instead put it on the bottom left Ad is not displayed on small screens. <img width="1916" height="924" alt="Screenshot 2026-03-16 at 7 27 58 PM" src="https://github.com/user-attachments/assets/e39c4be6-5270-4a77-bfbd-9521d15b9211" /> ## 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 |
||
|
|
f356f09f81 |
Feat: Game Speed + Pause keybinds (#3397)
## Description: Can't tell you how many times I've been playing solo, I try to go change the speed from `Max` to `x1` and before I've opened the speed controls and clicked on one the AI completely wipes me. But not to worry, we now have a pause and speed toggle up/down keybinds! https://github.com/user-attachments/assets/48692c27-888f-40fb-837a-45e26f262441 Keybinds were added to "Menu Shortcuts" submenu: <img width="1750" height="1099" alt="image" src="https://github.com/user-attachments/assets/8c4500d5-f43e-4a1c-9940-04db75bf18cf" /> Tested on Solo, custom match, and public lobbies. Pause intent works correctly on solo and private match (only if you are host), and neither feature works in public matches, as expected. ## 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: bijx |
||
|
|
b045608c89 |
ui: reduce HUD transparency for control, attacks, events, and hover panel (#3429)
## Description: Reduce HUD transparency for better readability by making the Control Panel, Attacks panel, Events/Chat panel, and Hover panel more opaque while keeping a subtle blur effect. ## Please complete the following: - [ x] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [x ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: HulKiora Before : <img width="518" height="134" alt="image" src="https://github.com/user-attachments/assets/dd714a8e-5b8c-4754-b68d-a86a0f82afcf" /> After : <img width="519" height="138" alt="image" src="https://github.com/user-attachments/assets/828b88be-a071-474c-a4a2-80d1c679b3df" /> |
||
|
|
681aa98fb1 | bugfix: don't remove player name renderer if name location is not set | ||
|
|
28b2372ef5 | shorten the control panel vertically a bit to increase center space for gameplay | ||
|
|
37f4f606f6 |
reduce margin for player info & control panel (#3414)
## Description: To give more space in the center of the screen remove the top & bottom margin. Also make the control panel & player info panel slightly shorter & longer. <img width="1167" height="961" alt="Screenshot 2026-03-12 at 7 20 40 PM" src="https://github.com/user-attachments/assets/15baf640-daf0-4394-9107-d175130e7754" /> ## 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 |
||
|
|
36572b69b5 | radial menu: make elements brighter on hover | ||
|
|
e922d336a1 |
update radial menu styling (#3404)
## Description: Before: <img width="215" height="239" alt="Screenshot 2026-03-10 at 10 29 12 PM" src="https://github.com/user-attachments/assets/bb044425-eb2f-427c-afd6-6c9dd5d075aa" /> After: <img width="240" height="207" alt="Screenshot 2026-03-10 at 10 27 33 PM" src="https://github.com/user-attachments/assets/21ce4c3b-ab24-4af0-b608-6be5603320fb" /> ## 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 |
||
|
|
8329dd479e |
Mobile UI polish ✨ (#3401)
## Description: ### Fix attack percentage label causing slider width jitter on mobile by setting a fixed width on the container https://github.com/user-attachments/assets/41ea3b98-48fa-49f3-b22e-2a567f208dfb ### Reduce event panel max-height from 30vh to 15vh on mobile Previous: <img width="406" height="781" alt="Screenshot 2026-03-10 231050" src="https://github.com/user-attachments/assets/ea83a75d-a0cb-4ce2-8714-99e822b028e9" /> After: <img width="397" height="781" alt="Screenshot 2026-03-10 231414" src="https://github.com/user-attachments/assets/153a4291-a3dc-4255-8a19-725bfdf6ff8b" /> ### Change lobby buttons (Create/Ranked/Join) from `slate-700` to `slate-600` for better contrast against the background Previous: <img width="411" height="387" alt="Screenshot 2026-03-10 232720" src="https://github.com/user-attachments/assets/1fd8bc7a-dbaa-414a-8e74-a070c66c40c6" /> After: <img width="408" height="495" alt="Screenshot 2026-03-10 232704" src="https://github.com/user-attachments/assets/e7693a28-6560-4440-8581-91b1fd7a5cf5" /> ## 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 |
||
|
|
3a195005d8 |
outgoing attacks (#3398)
## Description: Show outgoing attacks on the PlayerInfoPanel. Also update the AttacksDisplay so it uses the same icon for incoming & outgoing attacks <img width="502" height="97" alt="Screenshot 2026-03-10 at 2 02 49 PM" src="https://github.com/user-attachments/assets/de83bf4d-b1b9-4fec-a8c6-e25bbf82c942" /> <img width="483" height="167" alt="Screenshot 2026-03-10 at 2 03 02 PM" src="https://github.com/user-attachments/assets/cf43106f-5f3a-4a78-abae-dff019de4fb4" /> <img width="509" height="203" alt="Screenshot 2026-03-10 at 2 03 28 PM" src="https://github.com/user-attachments/assets/79db2081-dd93-4ff4-aaa7-75281c16c037" /> ## 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 |
||
|
|
8eb879aff4 |
Fix: Coordinate Grid text is now white when dark mode is enabled (#3396)
## Description: When the game was on dark mode, seeing the coordinate text on oceans became impossible. Now we check for dark mode in the layer to change the text color accordingly. Light Mode: <img width="3430" height="1669" alt="image" src="https://github.com/user-attachments/assets/181d53f5-b218-4f85-9d06-7e80f5d20004" /> Dark Mode: <img width="3488" height="1706" alt="image" src="https://github.com/user-attachments/assets/8a8e3951-31b4-448d-bc87-7fabd105e5fb" /> ## 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: bijx |