mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 22:38:59 +00:00
4fef27d014907c8233e6500d543fa46419f2b741
343 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4fef27d014 |
Fix WorldRotated enum value to match lang key 🌐 (#3553)
Fixes this display bug, says "WORLD ROTATED" now <img width="235" height="140" alt="image" src="https://github.com/user-attachments/assets/dc8bb97d-350a-4ced-a63d-f3564479ba41" /> - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced regression is found: FloPinguin |
||
|
|
bfb8a9447c |
Add multiple funny maps april fools day (#3538)
Requesting that these 4 maps be added temporarily for the April Fools holiday. Included are: - A wider version of the Amazon River to allow for larger ships. - An edit of The Box with more varied terrain. - A modification to The World which optimizes play for mobile users. - A custom remix to Antarctica which re-adds ice to the map. All maps are edits of existing work. All maps have been added as their own individual map files rather than edits to existing files for ease of management. <img width="912" height="1067" alt="temp" src="https://github.com/user-attachments/assets/089ef59f-8bdf-4c86-b479-620b8c220c3a" /> https://discord.com/channels/1284581928254701718/1483786939332169830 - [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 PlaysBadly |
||
|
|
be9ea14fe9 |
refactor: rename Bot to Tribe in internal execution code (#3372)
## Description Follows up on #3290 which renamed the user-facing "Bots" to "Tribes". This renames the internal implementation to match: - `BotExecution` → `TribeExecution` - `BotSpawner` → `TribeSpawner` - `BotNames` → `TribeNames` (`BOT_NAME_*` → `TRIBE_NAME_*`) All callers updated accordingly. `PlayerType.Bot` and `ColoredTeams.Bot` are intentionally left unchanged as they are serialised wire-format values. Closes #3335 ## Please complete the following: - [x] My changes do not break existing functionality - [x] I have tested my changes ## Please put your Discord username so you can be contacted if a bug or regression is found: Just reply here --------- Co-authored-by: PGray <PGrayCS@users.noreply.github.com> |
||
|
|
3838de1d30 |
Option to disable alliances + 2 new modifiers for variety 😄 (#3392)
## Description:
Rex had this idea: "It would be funny to have an option in private
lobbies to disable alliances."
I added it as an option.
Now people can choose to live in constant fear of their neighbors 😆
Also added two new public game modifiers for variety (only for the
special rotation):
- Alliances disabled (low probability)
- x2 gold multiplier (low probability)
Would be nice to squeeze this into v30, last minute?
## Please complete the following:
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
FloPinguin
|
||
|
|
84ae5fc879 |
Adds Aegean map (#3384)
## Description: Adds Aegean map, a map centered in the Aegean Sea and the aegean islands between Greece and Turkey. Map has approx. 1M land tiles, size of 1600x2000. Nations are named after Ancient Greece era city-states and tribes. Elevation source from OpenTopography, already credited <img width="1600" height="2000" alt="image" src="https://github.com/user-attachments/assets/e4f52d6e-bba4-4641-9eac-31e3ddee354e" /> <img width="453" height="445" alt="Captura de pantalla 2026-03-08 141256" src="https://github.com/user-attachments/assets/5d2bc8cf-1e54-461a-ae0f-9bb2dafc2db6" /> This has been a heavily requested map in the community, having suggestions in the Discord, Subreddit and even in some videos (for example i saw Ultimus Rex suggest an aegean map when asked what maps he would like in his last stream) The map is designed so that the greek and turkish main landmasses are around 400,000 pixels in area, which combined account for around less or equal to 80% of the map. This means players will have to cross the sea to win, and to do so they will have to hop across the islands, which means there will be heavy warship action, instead of having stalemates like in most island maps where the gap between islands is too large and players would rather bomb each other. ## 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: tri.star1011 |
||
|
|
df62251d3a |
Add San Francisco map (#3373)
## Description: Adds San Francisco bay map. 21 nations based on cities and towns of the area. 1.8M land pixels , size of 2000x1700 Elevation data from Opentopography, already credited. Map frequency of 3, as to mirror New York map. <img width="2000" height="1700" alt="image" src="https://github.com/user-attachments/assets/dc80a2db-6233-4b50-8f07-bd21c23c8b53" /> ## 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: tri.star1011 |
||
|
|
6254cc0598 |
Add Arctic map (#3341)
## Description: Adds Arctic map, a map centered about the Geographical North Pole using an azimuthal equidistant projection. Features Cold War themed countries and subdivisions as nations. Square map with 1.6M land tiles. Terrain data from Opentopography and Arctic SDI real relief data <img width="1830" height="1830" alt="image" src="https://github.com/user-attachments/assets/0b8b1e42-f477-4ebf-a256-c07536db87d9" /> ## 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: tri.star1011 |
||
|
|
0733c680b9 |
homepage UI improvements (#3352)
## Description: A bunch of small UI improvements: * Make the content width a bit smaller so gutter ads fit * remove the "duos" "trios" "quads" description on the game card since it's redundant * update UI in game card * minor footer layout changes * update z-index to ensure content appears above ads * removed hasUnusualThumbnailSize, instead just check the map ratio * Use "object cover" for non-irregular maps to the entire game card is filed * remove white ouline from the version * changed solo button to sky blue * make timer "s" lowercase I think we may need to change the openfront logo color a bit too to match the color palette, but we can do that in a follow up. <img width="1591" height="969" alt="Screenshot 2026-03-05 at 2 04 48 PM" src="https://github.com/user-attachments/assets/7bb9ea4c-5a17-47e1-bdad-9d6437b363b3" /> ## 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 |
||
|
|
e137fcaa6c |
Fix/Perf/Refactor: playerActions and buildableUnits, their callers and related types (#3220)
## Description: TL;DR: it's faster. buildableUnits is called via PlayerView.actions from UnitDisplay (each tick without TileRef), BuildMenu (each tick when open), MainRadialMenu (each tick when open), PlayerPanel (each tick when open), StructureIconsLayer (when placing a building from build bar), NukeTrajectoryPreviewLayer (when placing nuke, on tick when tile changes), ClientGameRunner (on click to attack/auto-boat or hotkey B or G). After https://github.com/openfrontio/OpenFrontIO/pull/3213 got merged, the change with largest impact in https://github.com/openfrontio/OpenFrontIO/pull/3193 was done in such a different way that a new PR was needed The idea in 3193 was to not always ask for Transport Ship from buildableUnits. In such a way that very little extra data was send to the worker. This had the biggest impact on performance (the idea was months older btw, see https://github.com/openfrontio/OpenFrontIO/pull/2295). Now, we do it the other way around, by telling buildableUnits all unit types we want. Or we want them all (undefined). The downside is more data is send in the worker message. The upside is we have more options and can add more in this PR. This PR implements some of the leftovers in 3193 on top of 3213 and adds further improvements. (Some unrelated refactor/perf changes where moved out of this PR and into already merged https://github.com/openfrontio/OpenFrontIO/pull/3233, https://github.com/openfrontio/OpenFrontIO/pull/3234, https://github.com/openfrontio/OpenFrontIO/pull/3235, https://github.com/openfrontio/OpenFrontIO/pull/3236, https://github.com/openfrontio/OpenFrontIO/pull/3237, https://github.com/openfrontio/OpenFrontIO/pull/3238, https://github.com/openfrontio/OpenFrontIO/pull/3239) - **GameRunner**, **WorkerMessages**: _playerActions_ and _PlayerActionsMessage ._ Option to ask for no buildable units (null). It now has 3 modes: get all actions and all buildings (units undefined), get all actions and no buildings (units null), or get all actions and specific building (units contains Unit Types). - **GameRunner**: _playerActions_. fixes wrong assumption in PR 3213: that only if units was undefined, we have to know canAttack. ClientGameRunner wants to know both, in case of a click on non-bordering land, to decide if it should auto-boat using a Transport Ship. So units is not undefined (we only ask for Transport Ship now which has a positive effect on performance for each click/tap) but we need canAttack still. Solved by removing the unit === undefined check before _canAttack_ in _playerActions_. - **GameRunner**, **GameView**, **WorkerClient**, **WorkerMessages**, **Worker.worker**: added _playerBuildables_ / _buildables_ next to existing _playerActions_ / _actions_. With above solved, there was still no option to only get buildable units when the actions are not needed. While **StructureIconsLayer**, **NukeTrajectoryPreviewLayer**, **BuildMenu** and **UnitDisplay** need only that. To not make playerActions more convoluted with more params or so, i've added a new function _playerBuildables_ in **GameView** to only get buildable units (**GameRunner** _playerBuildables_). _playerBuildables_ has 2 modes: get all buildings (units undefined) or get specific buildings (units contains Unit Types). Also update some comments that mentioned .actions in **NukeTrajectoryPreviewLayer**. - **ClientGameRunner**, **PlayerPanel**, **BuildMenu**, **UnitDisplay**, **StructureIconsLayer** and **NukeTrajectoryPreviewLayer**: Since PR 3213, **StructureIconsLayer** and **NukeTrajectoryPreviewLayer** ask for specific types of units from **GameView** _actions_ (**GameRunner** playerActions). Now have the other files do the same. For example **BuildMenu** asks for the new _BuildMenuTypes_ when it calls ._buildables_ and **ClientGameRunner** asks for UnitType.TransportShip when sending a boat - **ClientGameRunner**: canBoatAttack now accepts BuildableUnit[] instead of PlayerActions so we can send it either actions.buildableUnits or just buildables. Have functions call myPlayer.buildables(tileRef, [UnitType.TransportShip]) when we only need a buildable unit and no actions. Or myPlayer.actions(tileRef, null) when we need actions but no buildable units. Or myPlayer.actions(tileRef, [UnitType.TransportShip]) when we need both actions, like canAttack, and a buildable unit. Then if needed send either actions.buildableUnits or buildables to to _canAutoBoat_ / _canBoatAttack_. - **MainRadialMenu**: needs all player buildable unit types including Transport Ship, so the _actions_ call argument for unit types can stay undefined (unchanged) there. - **MainRadialMenu**: now that **BuildMenu** uses _playerBuildables_ instead of _playerActions_, we must put data in _this.buildMenu.playerBuildables_. And since we're not putting the (unneeded) full _actions_ in there anymore, we can now put only the needed and expected _actions._buildableUnits_ in it. - **Game**, **PlayerImpl**, **StructureIconsLayer**: Typesafety and some added perf: new type _PlayerBuildableUnitType_ (see also the below point for how it is formed). So callers of _buildableUnits_ can never ask for the wrong type like e.g. UnitType.Train because it doesn't return data for that type. This type is now used in **PlayerImpl**, **BuildMenu**, **RadialMenuElements**, **StructureDrawingUtils** and **UnitDisplay** for that reason. And **InputHandler**, **StructureIconsLayer** and **UIState** (little more on that in point below). - **InputHandler**, **StructureIconsLayer**, **UIState**: In order to make type safety work for GhostUnit.buildableUnit.type too (line ~217 of StructureIconsLayer), changed type of interface _BuildableUnit_ to _PlayerBuildableType_. Which is only more accurate. Same for and this.structures and uiState.ghostStructure and with the latter, _renderUnitItem_ in **UnitDisplay** and _setGhostStructure_ in **InputHandler**. All Structures are of PlayerBuildableType (there are even some in PlayerBuildables that aren't Structures, but it is much more confined than UnitType). - **Game**: Typesafety and some added perf: added _BuildMenus_ and _BuildableAttacks_ in the same fashion that the existing StructureTypes was already used (simplified it a bit too, with it renamed _StructureTypes_ to _Structures_ and removed _isStructureType_). They can be used with .types or .has(). _BuildableAttacks_.has() is used in **RadialMenuElements**. _BuildableAttacks_ and existing _Structures_ now make up _BuildMenus_. Which is used in **BuildMenu**, **StructureIconsLayer** and **UnitDisplay**. Then _BuildMenus_ together with UnitType.TransportShip make up the _PlayerBuildables_. Which is used in **PlayerImpl** _buildableUnits_ (see point below). And with _PlayerBuildableUnits_ we get the new _PlayerBuildableUnitType_ (see above point on Game / PlayerImpl). - **RadialMenuElements**: replace non-central ATTACK_UNIT_TYPES in **RadialMenuElements** with centralized _BuildableAttackTypes_ too. Use _PlayerBuildableUnitType_ for more type safety (can't by mistake add UnitType.Train to its build menu). Make use of _BuildableAttackTypes_ instead of adding items hardcoded line by line in _getAllEnabledUnits_, just like we already did since PR 3239 with _StructureTypes_. And use _BuildableAttacks.types_ in the same fashion that existing _isStructureTypes_ (now Structures.types) was already used elsewhere. - **PlayerImpl**: _buildableUnits_ -- would do Object.values(UnitTypes) on every call. Now for better perf directly loop over player buildable units by using _PlayerBuildables_ (see above point). In this way we also exclude MIRVWarhead, TradeShip, Train, SamMissile and Shell so there are less unit types to loop through by default. Since a player doesn't build those by themselves, they are only build by Executions which use _canBuild_ directly and not _buildableUnits_. -- for more performance, do for loop instead of using .map and .filter, no intermediate array needed nor callback overhead. We just loop over the given units (which if undefined will contain _PlayerBuildables_). Also pre-allocate the results array to get the most out of it, even if V8 might already be very good at this. -- cache config, railNetwork and inSpawnPhase so they can be re-used inside the for loop. -- cache cost inside the loop -- it would check twice for tile!==null to decide to call findUnitToUpgrade and canBuild. Now once. -- eliminated double/triple checks for the same thing. It called _findUnitToUpgrade_ (and with that _canUpgradeUnit_) and then _canBuild_ which both check if player has enough gold for the cost of the unit type. And they both check if the unit type is disabled. Now we call private functions _canBuildUnitType_, _canUpgradeUnitType_ to first do checks on unit type level for early returns, and _findExistingUnitToUpgrade_ to find existing unit without doing anything extra. in a specific order to check everything only once. The public functions _findUnitToUpgrade_ and _canBuild_ have an unchanged functionality and we don't call them from _buildableUnits_ anymore. -- would get _overlappingRailRoads_ and _computeGhostRailPaths_ when canBuild was true. But this data is only meant for **StructureIconsLayer** and it logically only uses it when placing a new unit, not when upgrading one. Which is also commented on line 351 of **StructureIconsLayer**. So, we now only get overlapping railroads and ghost rails if we're not hovering to upgrade an existing unit. - **PlayerImpl**: _findUnitToUpgrade_: unchanged functionality, but have it call new private function _findExistingUnitToUpgrade_ to find existing unit. - **PlayerImpl**: _canBuild_: unchanged functionality, but have it call new private function _canBuildUnitType_ to do the checks it first did itself. And then new private function _canSpawnUnitType_ for the rest of the checks. This way we can call _canBuildUnitType_ and _canSpawnUnitType_ from _buildableUnits_ in a specific order to prevent double/triple checks. - **PlayerImpl**: _canBuildUnitType_: new private function to be shared by _buildableUnits_, _canBuild_ and _canUpgradeUnit_ to be able do unit type level checks in a specific order to prevent double/triple checks. Via parameter knownCost, _buildableUnits_ can send it the cost it already fetched so that it doesn't have to be fetched again. For caller _canUpgradeUnit_, the isAlive() check (which was previously only done in canBuild) is new but harmless, maybe even better to have also check isAlive() on upgrade now that Nations are also upgrading which might prevent some edge case bugs. - **PlayerImpl**: _canUpgradeUnitType_: new private function to be shared by _buildableUnits_ and _canUpgradeUnit_ to be able do unit type level checks in a specific order to prevent double/triple checks. - **PlayerImpl**: _canSpawnUnitType_: new private function to be shared by _buildableUnits_ and _canBuildUnit_ to be able do unit type level checks in a specific order to prevent double/triple checks. - **PlayerImpl**: _findExistingUnitToUpgrade_: new private function to be shared by _buildableUnits_ and _findUnitToUpgrade_ to be able do unit level checks in a specific order to prevent double/triple checks. - **PlayerImpl**: _isUnitValidToUpgrade_: new private function to be shared by _buildableUnits_ and _canUpgradeUnit_ to be able do unit level checks in a specific order to prevent double/triple checks. - **PlayerImpl.test.ts**: because of the isAlive() check in which is new for _canUpgradeUnit_ (see above at _canBuildUnitType_), the tests needed to have the players be alive at the start, in order to pass. - **BuildMenu**: use .find instead of .filter in canBuildOrUpgrade, a function we already needed to change. This is faster and prevents an allocation. **PERFORMANCE** As for calling ._buildables_ instead of unnecessarily getting ._actions_, there is an obvious win because there's less to send calculate and recieve. Also asking for only the needed buildings helps a lot (especially if TradeShip isn't needed, see the difference in benchmark in original #3193). But the real-world impact is hard to measure. gave it a try in #3193 and those results should be even better now. Now testing only _buildableUnits_ performance in a synthetic benchmark, we get these results. This is after other performance improvments so the base is already better than it was in original #3193: **BEFORE** (only buildableUnits itself) <img width="602" height="96" alt="image" src="https://github.com/user-attachments/assets/7770c0fa-a35e-42fc-90de-1de83242ec23" /> **AFTER** (only buildableUnits itself) <img width="603" height="91" alt="image" src="https://github.com/user-attachments/assets/a1578382-7010-4160-937c-7117bad18beb" /> ## 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
c238af389a |
Feat: Passage Map 🚢 (#3304)
## Description: Introduces Passage, an island filled thin map (like Amazon River but inverted) which is a long, thin stretch of islands that makes for some really fun gameplay. I playtested with 3 friends a number of times and it's great in pretty much all the modes. Naval gameplay on this map is particularly fun since some islands in the center basically hold the choke points on trade ships passing from one side to another, making them hotly contested territory. <img width="6000" height="400" alt="long map" src="https://github.com/user-attachments/assets/7904d6f8-e7b8-437d-852e-68a2f006d200" /> Describe the PR. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx |
||
|
|
9ca342e510 |
Add Nile Delta map (#3306)
## Description: Add Nile Delta as a new regional map. Features 11 nations across the delta region (Alexandria, Damietta, Faraskur, Sheremsah, El Senbellawein, Aga, Mit Ghamr, Cairo, Heliopolis, Memphis, El Mansoura). 1.36M land tiles at 1556x1280, terrain generated from real relief data. Includes the Suez Canal. Playlist frequency: 4.  ## 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 Source: https://commons.wikimedia.org/wiki/File:Niledelta_33.svg Discord: gabigabogabu |
||
|
|
417fa0fe09 |
For v30: Add new modifiers (Hard nations and 25M Starting Gold) 🙂 (#3316)
## Description: Adds two new public game modifiers for variety and improves compact map eligibility for team games. ### New Modifiers **Hard Nations (`isHardNations`)** - We need this modifier for HvN, because medium nations are easier now (will result in a much higher human winrate) - In a discord discussion we concluded that HvN should generally be easier (higher winrate than 50%, so players are less frustated) - Thats why only 20% of HvN games have the hard nations modifier (for now) - For PvPvE enjoyers, the modifier is also active in FFA games => (Only 2.5% chance, and 1 ticket in `SPECIAL_MODIFIER_POOL`) **25M Starting Gold (`startingGoldHigh`)** - Some people in the main discord wanted this modifier, and it will result in crazy games - Rare special-only modifier (1 ticket in pool); mutually exclusive with 5M starting gold via `MUTUALLY_EXCLUSIVE_MODIFIERS` - Disables nations (they lack PVP immunity, so 25M gold doesn't work well with them) - Excluded from HumansVsNations games (since it disables nations) - Spawn immunity set to **2 minutes 30 seconds** (vs 30s for 5M gold), so people can spend the gold and prepare ### Other Changes - **Improved `supportsCompactMapForTeams`**: Replaced the hard `smallest >= 50` land-tile cutoff with a per-team-config calculation that simulates worst-case compact player count and checks every team gets at least 2 players. - **HvN spawn immunity**: Always 5 seconds in both regular and special lobbies (to get rid of a confusing PVP immunity HeadsUpMessage in 5M starting gold games) - **Regular public lobby random spawn modifier probabilty**: Reduced from 10% to 5% (Because of the new modifier, so there aren't too many modifiers in non-special-lobbies, should only occur sometimes there) - Rebalanced `SPECIAL_MODIFIER_POOL` a bit ## 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 |
||
|
|
5542ac12e1 |
Add new map: Bosphorus Straits (#2927)
## Description: Adds a new Bosphorus Map (Turkey). One of the key strategic locations in the world and control access to the Black Sea. Smaller map that most to facilitate smaller FFA and Team games. Added are a selection of nations that correspond to the location. <img width="1000" height="612" alt="image" src="https://github.com/user-attachments/assets/27a6debc-a33b-4b54-b522-69ab814c39f0" />   ## 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: TSProphet --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Co-authored-by: iamlewis <lewismmmm@gmail.com> |
||
|
|
11d3228608 |
Adds Bering Strait map. (#2924)
## Description: PR to add Bering Strait map. Produced using TOPO data from real world location, rivers and lakes correct to real world location. The map is slightly smaller than most so facilitates smaller FFA games or 2 team game modes. The centre island has been increased in size to enable better warfare to capture the strategic location. <img width="1500" height="918" alt="image" src="https://github.com/user-attachments/assets/bc9b2e69-cef1-4f21-92b5-4ffdce5812e1" />  ## 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: TSProphet --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> |
||
|
|
c911bfb2d8 |
Packed unit updates / MotionPlans (#3292)
## Description: Reduce per-step `Unit` update traffic by shipping packed motion plans and letting the client advance plan-driven units locally. Changes: - Add packed motion plan records (`packedMotionPlans?: Uint32Array`) to game updates and transfer the buffer worker -> main. - Introduce `src/core/game/MotionPlans.ts` (schema + pack/unpack) for grid + train motion plans. - Extend `Game` with `recordMotionPlan(...)` and `drainPackedMotionPlans()`, and implement buffering/packing in `GameImpl`. - Treat units with motion plans as “plan-driven”: suppress per-tile `Unit` updates on `move()` and advance positions client-side. - Emit motion plans from executions: - `TradeShipExecution`: record/update grid motion plans and `touch()` when changing target after capture. - `TransportShipExecution`: record initial plan and update it when destination changes. - `TrainExecution`: record a train plan on init (engine + cars). - Client: apply motion plans in `GameView` and ensure `UnitLayer` updates sprites for motion-planned units even when no `Unit` updates arrived. ## 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 |
||
|
|
339ace0bd6 |
v30 nuke wars preparation: Disable boats & Team spawn zones (#3263)
## Description: Preparation for nuke wars, for v30. Next PR will be adding the nuke wars modifier for public games, but Wonders https://github.com/openfrontio/OpenFrontIO/pull/3224 needs to be merged first to avoid merge conflicts. ### 1. Disable boats setting It's possible to disable `UnitType.TransportShip` now. Because they are not needed in nuke wars and can even be annoying. <img width="720" height="320" alt="image" src="https://github.com/user-attachments/assets/661bc10d-b204-4b4f-b876-ee7c9b92de8c" /> ### 2. Team spawn zones for random spawn Maps can have `teamGameSpawnAreas` in their json file now. Spawn areas are currently active if - a supported map is chosen (Baikal Nuke Wars or Four Islands) - a supported team size is chosen (2 teams on Baikal Nuke Wars or 2/4 teams on Four Islands) - random spawn is enabled ## 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 |
||
|
|
ea2a76609f |
perf(core): speed up packedTileUpdates (Uint32 pairs, no tile wrappers) (#3255)
## Description Reduces CPU + GC pressure from tile update serialization. **What changed** - Switched `packedTileUpdates` from `BigUint64Array` (BigInt packing) to `Uint32Array` `[tileRef, state]` pairs, updating `GameView` ingestion. - Updated tile state to use `GameMap.tileState(tile)` and `GameMap.updateTile(tile, state)`. - Removed per-tile `GameUpdateType.Tile` wrapper allocations by recording raw `(tile, state)` pairs in `GameImpl` and draining them via `drainPackedTileUpdates()` in `GameRunner`. **Why it’s faster** - Avoids BigInt and pack/unpack. - Avoids per-tile object allocations. **Compatibility** - Wire format change: `packedTileUpdates` is now `Uint32Array` pairs instead of `BigUint64Array`. ## 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 |
||
|
|
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> |
||
|
|
8b66c8bd53 |
Perf/refactor: use StructureTypes, remove territoryBound (#3238)
## Description: PR 6/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already merged https://github.com/openfrontio/OpenFrontIO/pull/3237. Please see if these can be merged for v30. - **PlayerImpl**: validStructureSpawnTiles did a filter on unit types to get isTerroritoryBound units, on every call again. It read this from unit info in DefaultConfig. While having it centrally in DefaultConfig unitInfo is good for maintainability, other code uses hardcoded StructureTypes and isisStructureType from Game.ts. Which has the same purpose and thus contains the same unit types. StructureTypes and isisStructureType do need manual maintainance outside of DefaultConfig. And are more bug prone/less type safe. But, using them gives more speed compared to getting these unit types out of DefaultConfig unitInfo centrally with some cached function in GameImpl for example (tested with buildableUnits and MIRVPerf.ts). So I went with StructureTypes in validStructureSpawnTiles too. - **PlayerExecution**: now validStructureSpawnTiles no longer needs isTerritoryBound (see the point above), PlayerExecution is the last place where it was used. Replaced it for isStructureType here too (since it has the same meaning and outcome). - **Game.ts** and **DefaultConfig** unitInfo: remove the now unused _territoryBound_. As it was only used in validStructureSpawnTiles and PlayerExecution and has been replaced in both (see the two points above). ## 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 |
||
|
|
4f08e57304 |
Small refactor: nearbyUnits readonly UnitType[] (#3236)
## Description: PR 4/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Follows on already merged https://github.com/openfrontio/OpenFrontIO/pull/3235. Please see if these can be merged for v30. - **Game**/**GameImpl**/**GameView**: nearbyUnits required "UnitType | UnitType[]" for tiles, but calls UnitGrid nearbyUnits which requires "UnitType | readonly UnitType[]". Made the requirement the same for Game/GameImpl/GameView nearbyUnits. This way, we don't have make a shallow copy of the StructureTypes array everytime we want to send it as an argument. Other callers than listNukeBreakAlliance in Util.ts are unaffected. - **Util.ts**: listNukeBreakAlliance needs no shallow copy of StructureTypes anymore as argument for NearbyUnits ## 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 |
||
|
|
ba2a947061 |
Feat: Display ghost railways when building cities and ports (#3202)
## Description: Based on [this suggestion on Discord](https://discord.com/channels/1284581928254701718/1447110257196138577) and feedback gathered in [this thread](https://discord.com/channels/1359946986937258015/1469598906173227184). Supersedes #3143 This PR introduces "ghost railways": when you are going to place a city or port, previews railway connections that will be made when actually building the structure. Ghost railways are skipped if the structure is going to be snapped to existing railways (as in railway snapping functionality introduced in #3156 ). ### Video https://github.com/user-attachments/assets/ff8cf325-6501-4df8-801d-c8ae3ced3d0e ### Ghost rails color revisited black with 40% opacity <img width="695" height="430" alt="image" src="https://github.com/user-attachments/assets/272efbcc-4185-426a-921c-7fae61f6c462" /> ## 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 |
||
|
|
52036cc20c |
Cleanup: remove unused code from four files (#3234)
## Description: PR 2/x in effort to break up PR https://github.com/openfrontio/OpenFrontIO/pull/3220. Removes unused code and properties. - **Game.ts** and **DefaultConfig** unitInfo: removed _canBuildTrainStation_ and _expirimental_ properties, as they weren't used anywhere anymore. - **PlayerActionHandler**: remove unused getPlayerActions, the only potential caller MainRadialMenu already just calls myPlayer.actions via GameView directly. - **StructureIconsLayer**: remove unused PlayerActions ## 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 |
||
|
|
0b46f90d81 |
Feature add alps clean (#3229)
## Description: Fixes closed PR and adds the new “Alps” map and register it in the game/map playlist. With 7 geographically accurate nations. Pure large land map for land warfare only. <img width="2564" height="1400" alt="Untitled" src="https://github.com/user-attachments/assets/2c51fad3-7345-46a8-9256-2874d1c4211b" /> <img width="2000" height="1837" alt="551068953-fe4445cd-c0b2-4a5a-b652-1e480e38cdb6" src="https://github.com/user-attachments/assets/1c466262-e20b-46bc-b782-944cbf6ee62a" />  ## 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: TsProphet |
||
|
|
86e51ab790 |
Fix nation spawnkilling 🔧 (#3222)
## Description: As far as I can remember, in v28 the spawn immunity applied to both humans and nations. With the configurable spawn immunity (added for v29) the spawn immunity no longer applies to nations... Because its called PVP immunity now. So right now it's possible to spawnkill nations. This is a big problem for the 5M gold modifier games... And you can "cheat" in singleplayer. This PR changes two things: - Nations always have 5 seconds spawn immunity now, no matter whats configured for the PVP immunity - Nations attack TerraNullius earlier (Otherwise the easy nations would sometimes do their first attack after the 5 seconds are over, spawnkills would still be possible) ## 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> |
||
|
|
040766d417 |
Add units filter on playeractions for performance (#3213)
## Description: The ghost structure calls player actions each frame, which is costly since it's checking for all possible actions. This add a unit list filter in actions so if there are units it only checks for buildability of those units. Before:  Player actions takes 20-30% of the worker After: <img width="825" height="342" alt="image" src="https://github.com/user-attachments/assets/36e47547-5028-4dc9-bc42-e17df4a87200" /> Player actions takes 1-3% of the worker Both performances are relevant only when a ghost structure is selected ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: Mr. Box |
||
|
|
6e557c52db |
Add Hawaii map (#3187)
## Description: Add Hawaii as a new regional map. Features 9 nations across the Hawaiian island chain (Niihau, Kauai, Oahu, Molokai, Lanai, Kahoolawe, Maui, Kona, Hilo). ~~612K land tiles at 3920x2544~~ 408K land tiles at 3200x2076, terrain generated from real relief data with accurate volcanic peaks and coastal lowlands. Playlist frequency: 4.  ## 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 Source: https://commons.wikimedia.org/wiki/File:USA_Hawaii_relief_location_map.svg Discord: gabigabogabu |
||
|
|
07e13b3479 |
Fix: remove alliances on death (#3168)
## Description: - Remove alliances on death: after death, alliances would stay active including countdown timers and (when dead player kept spectating) icons. Now remove them when player becomes inActive. - Moved code to private method within PlayerExecution + added comments in NationExecution and BotExecution for more clarity as to where removals are performed from at death - Remove renewal request from Events Display when Alliance doesn't exist anymore (after death or otherwise). - Also cleanup this.alliancesCheckedAt when alliance doesn't exist anymore. Before, old/broken alliance id's would accumulate in it during a game. - Removed now-redundant isAlive check in EventsDisplay. Both the alliances array as the isAlive are updated in the same tick from PlayerUpdates so now alliance is removed from alliances array on player death, the other.isAlive() check is no longer needed. Of course we could keep it in just to be very safe, so just let me know when you're doubtful about this. - Attack.test.ts: fix failing test. Player B dies because of the attack, meaning the alliance now gets removed. Prevent this by gving both a different, adjecent, starting tile. And to be more clear about what is needed for the test to pass, add isAlive check for both of them after the attacks. ## 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 |
||
|
|
c44130cf32 |
New map! "Traders Dream" 🏝️ (#3177)
## Description: A redditor posted this, got quite a few upvotes <img width="753" height="667" alt="image" src="https://github.com/user-attachments/assets/9cd6664f-6afa-428a-b85e-4c335dbe1699" /> https://www.reddit.com/r/Openfront/comments/1qlrqro/these_maps_are_so_fun_i_want_more/ So I thought why not make another one, this time a bit focused on traders (tiny islands to trademaxx) And two big islands (about 50% of the map land tiles). So its big islands vs small islands. <img width="1098" height="959" alt="Screenshot 2026-02-10 231024" src="https://github.com/user-attachments/assets/32368223-bef4-4ba1-a203-29d9afd5b762" /> 13 nations :) 2200 × 1920 ## 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 |
||
|
|
c6c793f6b3 |
Highlight hovering railroad (#3156)
## Description:  The `RailroadLayer` simply displays tiles as instructed by the core worker. While it's practical for the layer to only care about the tiles, it also means it has no understanding of railroads as entities (their paths, connections, or identities). It also means that the core worker is responsible for rendering tasks such as tile orientation and construction animation, which is not expected. To support ID-based events and better separation of concerns, the rendering layer needs to be aware of complete railroads. With this change, the core worker can send the tiles once and subsequently reference railroads only by ID for all other events. #### Changes: - `RailroadLayer` now stores full railroad data instead of only individual tiles - `RailroadLayer` is responsible for animating newly built railroads - Add a new `RailroadSnapUpdate` sent when a new structure is built over an existing railroad. This event is used by `RailroadLayer` to keep railroad ID in sync. - When hovering over a railroad, the render worker is querying the core worker about overlapping railroads. Alternatively, RailroadLayer could compute overlaps itself now that it has full railroad knowledge, but this logic would need to be duplicated and kept in sync across workers. Keeping a single source of truth in the core worker is preferred. #### Edgecases: - When a structure snaps over a railroad, the original railroad is split into two new railroads. If the construction animation is still in progress, instead of resuming the animation at the correct point on the new railroads, all remaining tiles are rendered immediately - Previously, `RailroadUpdate` handled both construction and destruction. This no longer works with `RailroadSnapUpdate`, as event ordering is now pretty important and IDs may be lost before they are consumed. To address this, RailroadUpdate is split in two: `RailroadConstructionUpdate` and `RailroadDestructionUpdate`. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: jrouillard <jon@rouillard.org> |
||
|
|
e97f4650b7 |
Readd Yenisei (#3145)
## Description: This PR readds the Yenisei map, with it being about 3 times smaller than before. I haven't updated the en.json file, as it still has the string for the map, which was probably forgotten when the map got removed from the repo. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: DISCORD_USERNAME Nikola123 |
||
|
|
13117251af |
Add new map “The Box” (#3054)
<img width="1670" height="200" alt="banner" src="https://github.com/user-attachments/assets/b6561231-71dc-46dd-a521-453d4e97931f" /> ## Description: This proposal adds a new, completely flat map called "The Box," where players will find themselves on a completely flat landmass and will only be able to trade by land using trains and factories. - Map size: 2048×2048 pixels - Binaries generated with MapGenerator (map.bin, map4x.bin, map16x.bin) - Integrated into arcade category and multiplayer playlist - English and Spanish translations added - 13 nations/bots with unique and thematic names: > - **Middle Defender (located in the center)** > - **King of the Corner (located in the top-left corner)** > - Suspicious Ally > - Punch Merchant > - Nuke Thrower > - Fullsender > - Factory Builder > - Front Manager > - Box Fighter > - Cage Liberator > - Train Trader > - Non-peaceful Bot > - **Evan The Dev** (special mention) All other bots are distributed to cover the map evenly. <img width="838" height="835" alt="Screenshot 2026-01-28 at 17 39 45" src="https://github.com/user-attachments/assets/4665088d-cb94-4db3-8602-8a22b63bfee2" /> ## 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: sardidefcon |
||
|
|
7942990037 |
Crowded modifier 😄 (#3023)
## Description: To increase variety a bit more I present: The "crowded" public game modifier :) It basically simulates a crazy youtuber lobby. Cramp a lot of players on a small map 😄 I think its fun, exciting and you actually need skill to manage the chaos. 5% of public games get this modifier, but because we remove the modifier for big maps its more like 2.5% (should be something special) | <img width="321" height="269" alt="Screenshot 2026-01-25 200427" src="https://github.com/user-attachments/assets/7d2e90c1-e6bc-40a8-a19e-a0849612f472" /> | <img width="317" height="264" alt="Screenshot 2026-01-25 200554" src="https://github.com/user-attachments/assets/8b4bd5da-bed1-4743-a107-9ce07fce3040" /> | <img width="317" height="244" alt="Screenshot 2026-01-25 200521" src="https://github.com/user-attachments/assets/16293de3-0fc4-431f-8151-31b4e11040fe" /> | |---|---|---| ## 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 |
||
|
|
f7d3c2e0bc |
Nations donate troops now 💀 (In team games) (#2984)
## Description: For v29, balances the HvN winrate. In team games, nations now donate troops to their weakest team members (if they have no attack options available). How often they donate depends on the difficulty. This PR also has some other little fixes: - For HvN games, always return true in `shouldAttack()` (make nations a bit more aggressive). - Early exit in `attackWithRandomBoat()` for performance - Early exit in `findNearestIslandEnemy()` for performance AND to make sure nations which are encircled by friends don't run into this method (=> no donation happening!) ## 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 |
||
|
|
a4a41ac9f4 |
Fix map name formatting for Baikal Nuke Wars 🔧 (#2922)
## Description: Fixes this little i18n problem: <img width="732" height="172" alt="Screenshot 2026-01-16 050833" src="https://github.com/user-attachments/assets/65fe27a6-f77a-49d9-94a9-145b4e719a88" /> ## 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> |
||
|
|
fb910cbff5 |
Lobby Gold Options (Starting Gold, Gold Multiplier) 💰 (#2915)
## Description: We might want to add this to v29 to have a third possible public game modifier from the beginning on 😄 Would be fun - Add starting gold option (0 to 1_000_000_000 allowed, also applies to nations) - Add gold multiplier option (0.1 to 1000 allowed, also applies to nations and bots) - Add third public game modifier (3% chance of starting with 5M gold) - Why 5M? It's enough gold to massively change the game start but not enough to insta-hydro someone (launcher + hydro is 6M) <img width="357" height="140" alt="image" src="https://github.com/user-attachments/assets/72acc15c-e788-4e04-8590-ac72dd9657c7" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
42c944c9cc |
Create ranked type enum, last person not afk wins in 1v1 (#2892)
## Description: * Add RankedType enum, for now it's just 1v1 * Add new method to MapPlaylist to create 1v1 game config * Update WinCheck so the last player is declared a winner on 1v1. ## 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 |
||
|
|
35b7213c5c |
Enhance nuke alliance breaking logic to account for allied structures in blast radius 💣 (#2887)
## Description: Doesn't need a description :D https://github.com/user-attachments/assets/8de576fd-050b-4b35-8526-e4c88d1a9f25 https://github.com/user-attachments/assets/c99147a1-efdf-426b-96d1-e996e01f89aa ## 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 |
||
|
|
0e3ced3bfa |
Pathfinding Refactor pt. 2 (#2866)
## Playtest https://pf-pt-2.openfront.dev/ ## Pathfinding Refactor pt. 2 <img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/9477958e-54b7-4c83-b317-ba789e809e9e" /> This is a follow-up to a previous PR introducing pathfinding changes. This time, it introduces a complete refactor of `pathfinding` directory and breakdown into composable pieces. ### Unified PathFinder interface `PathFinder<T>` and `SteppingPathFinder<T>` are introduced to unify **all** pathfinding across the application. First one exposes complete path, while stepping variant allows the callee to iterate over the path by calling `.next`. All pathfinders share this one common interface, which makes them easy to use in any scenario - `PathFinding.Water(game).search(from, to)`. `SteppingPathFinder<T>` extends `PathFinder<T>` with an ability to iterate over the path. It handles caching, storing current index and invalidation. This allows the units to not care about the inner workings of the pathfinder and just call `pf.next(current, target)` and receive instructions on what to do next. ### Common entry point All pathfinders are now exposed from common `PathFinding` entrypoint: - `PathFinding.Water` - `PathFinding.Rail` - `PathFinding.Stations` - `PathFinding.Rail` Additional entry point is introduced for pathfinders which need to work both in the worker, but also on the frontend, which lacks `Game` interface. Currently only `UniversalPathFinding.Parabola` is available. ### Spatial Query New module has been introduced close to `pathfinding` - `SpatialQuery`. It aims to resolve any questions game may have about finding tiles meeting criteria. Currently `SpatialQuery.closestShore(player, target)` and `SpatialQuery.closestShoreByWater(player, target)` are available - they help answering questions about naval invasion: "What is the best landing location from user's click?" and "Which our tile should be used to launch the transport ship?". Under the hood they use very similar mechanics to pathfinding, so it felt right to put them close by. ### Modular architecture Pathfinders now support transformers: `MiniMapTransformer`, `ShoreCoercingTransformer`, `ComponentCheckTransformer`, `SmoothingTransformer`. Transformers functions like a middleware in the pathfinding chain. They wrap around the pathfinder and provide additional functionality. This allows the pathfinder to focus on actually finding the path instead of doing unrelated things. Example chain for simple (A*) water pathfinding: ```ts static WaterSimple(game: Game): SteppingPathFinder<TileRef> { const miniMap = game.miniMap(); const pf = new AStarWater(miniMap); return PathFinderBuilder.create(pf) .wrap((pf) => new ShoreCoercingTransformer(pf, miniMap)) .wrap((pf) => new MiniMapTransformer(pf, game.map(), miniMap)) .buildWithStepper(tileStepperConfig(game)); } ``` The Pathfinder - here `AStarWater` - does not care about the conversion between minimap and main map tiles. It also does not care if the source or destination is a land tile. The transformers take care of that. The pathfinder gets a set of valid coordinates and produces the path - that's it. Modular approach makes working on a particular set of utilities much easier - for example map upscaling is handled consistently across all pathfinders. Additionally, the pathfinders are not tied to the particular map resolution used. Pass them a different map and they will work the same. ### Algorithms Algorithms used are neatly organized inside `src/core/pathfinding/algorithms`. They are prefixed with the algorithm name and suffixed with the use case. File without suffix exposes generic version ready to traverse any graph with adapters. Specialized versions either use an adapter or inline logic when performance is critical - using adapters leads to 20-30% performance loss. The directory includes `A*` and `BFS` but also other useful utils, such as `AbstractGraph` used to generate... an abstract graph on top of the tile map and `ConnectedComponents` helping to identify whether two tiles are connected by a path without actually computing the path. ### Playground The playground have been updated with new algorithms, including tweaked very greedy `A*`. <img width="2175" height="1424" alt="image" src="https://github.com/user-attachments/assets/1f833651-0024-4299-bf86-882f5368358c" /> ### Tests Yeah, there are some, a little too many if I say so myself. But there are no useless tests. I had to ensure refactored code works somehow reliably. This PR comes with trust me bro guarantee, but I would appreciate someone confirming **naval invasions, nukes (esp. MIRV) and warships**. ### Discord `moleole` GL & HF |
||
|
|
e130574c5c |
Very small UI fix 🔧 (#2862)
## Description: Didier map name had a little UI problem. And unrelated change: Because Lewis today said "it should be super low" I turned down the frequency of this map from 2 to 1. Previous: <img width="1916" height="1312" alt="image2" src="https://github.com/user-attachments/assets/0a84160b-91a8-4d02-b707-fa9eea1a15fd" /> Fixed: <img width="562" height="476" alt="image" src="https://github.com/user-attachments/assets/31fed7b5-c128-45cd-a63d-0aab3345cea3" /> ## 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 |
||
|
|
dc118708c0 |
Second Didier Map for Fuze 🧸 (#2846)
## Description: Discussed that with Lewis. Fuze liked the Didier map without the real france more... So here it is. It won't get added to the playlist, the france version stays in the playlist. (Unrelated: Also quickly changed "Europe (classic)" to Europe (Classic)" to match with "Britannia (Classic)") <img width="934" height="839" alt="Screenshot 2026-01-10 005646" src="https://github.com/user-attachments/assets/64925635-c15a-4167-a5bc-5cf7b3b140f8" /> <img width="1064" height="961" alt="Screenshot 2026-01-10 003335" src="https://github.com/user-attachments/assets/9b6aa936-2c33-4a24-8076-a74a4704adc4" /> <img width="635" height="427" alt="Screenshot 2026-01-10 003316" src="https://github.com/user-attachments/assets/e2b46db8-ef0b-4b45-8ea7-711b9b8f7524" /> ## 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 |
||
|
|
8b6dfeaf4a |
Feat: Sierpinski map - live play-tested (#2819)
## Description: Map requested by creator on [dev discord](https://discord.com/channels/1359946986937258015/1458638914012315741/1458638914012315741) and playtested by 42 players on [Rex's stream](https://www.youtube.com/watch?v=r9w9nr5Toso), adds map Sierpinski, which is a sierpinski carpet shape. The map is a "party map" great for private matches, but is not part of the public map rotation. <img width="1400" height="1400" alt="image" src="https://github.com/user-attachments/assets/8eead359-73d5-497f-8fee-40f413a22d0e" /> https://github.com/user-attachments/assets/3a726343-18e4-4f91-9f5c-1fff459d5a5f ## 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 --------- Co-authored-by: iamlewis <lewismmmm@gmail.com> |
||
|
|
971e7f4a45 |
Move UI elements from the FX layer to a new UI layer (#2827)
## Description: Some FX animations were previously used as UI elements (e.g. nuke area, naval invasion target, gold text). This PR moves those animations to a dedicated UI layer. Those UI elements handle correctly the current zoom level and remain sharply rendered at all zoom levels. The new UI layer can be disabled using the same setting that disables the FX layer. Performance-wise, this layer is equivalent to the FX layer, as it reuses the same animations. ### Naval target Don't scale with the zoom level, but has a minimum zoom level so the targeted tile can still be easily highlighted by zooming  ### Nukes Has to scale because the size is set, but the border radius is not so the area is more visible from afar.  ### Popup text Scale with zoom level, and stop showing when zoomed-out:  ## 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 |
||
|
|
b090f2f624 |
HPA* Pathfinding (#2815)
## Pathfinding with HPA*
Hi! The primary objective of this PR is to replace per-tile A* with
hierarchical pathfinding - HPA*. In practice, this means we create an
abstract graph on top of the actual map with far fewer points and use it
to decide on general path structure. Only then we go back to tile-level
and build path between selected waypoints. This speeds up long distance
pathfinding by over 1000x in some cases. To make the review easier, it
comes with a benchmark and visual playground.
## PREPROCESSING
H part of HPA* means "hierarchical" and requires preprocessing.
This PR includes pre-processing as part inside `new Game()` constructor.
It takes about 135ms for `giantworldmap` on my machine, which increases
the effective initialization from ~95ms to ~230ms. This time could be
reduced in different ways, which are **out of scope** for this PR.
After confirming the initialization time is bearable on low-end devices,
I argue merging this PR as-is is acceptable tradeoff. It creates small
lag at the beginning of a round but pays for itself in the first minute
of the match.
## Nerdy details
**Architecture**
- HPA*-style hierarchical pathfinding
- 32×32 sectors on minimap with gateway nodes on borders
- Gateway graph built via BFS during preprocessing
- Water component optimization skips unreachable gateway pairs
- A* on gateway graph → local A* within sectors → Bresenham path
smoothing
- Minimap upscaling identical to currently used in MiniAStar
**Key Optimizations**
- Typed arrays instead of high-level primitives
- Stamp-based visited tracking (no need to recreate buffers, O(1)
clearing)
- Optional - enabled by default - caching of tile paths between gateways
- Line of sight smoothing for the final path
## Review Focus
Play with included tools, benchmark and visualization. Pathfinding
should be safe to merge as a black box - you do not need to understand
the details. Outcomes can be tested empirically in-game. Visualize (and
share!) edge cases with included playground. Confirm the 100x speedup is
real with benchmark.
If you plan to dive into the code, I suggest the following order:
- Pathfinding abstraction in `src/core/pathfinding/`
- Pathfinding tests in `tests/core/pathfinding/`
- NavMesh in `src/core/pathfinding/navmesh/` + integration with
`Game.ts`
- Benchmark in `tests/pathfinding/benchmark/`
Do not look at playground's code, it has been created with a clanker.
The design is 100% mine and I spent way too long polishing it, but I
haven't even once edited the code manually. There is probably no
abstraction whatsoever, just do not look at the code, let it play.
## Core Changes
#### Pathfinding (`src/core/pathfinding/navmesh/`)
- HPA* + refinement -> three phased pathfinding: A* over the graph ->
naive path -> refinement
- comes with A* and BFS optimized for for specific needs
#### Pre-Processing (`src/core/pathfinding/navmesh/`)
- identify water bodies to avoid pathfinding between disconnected nodes
- create high-level graph of gateways on top of tile map
#### Abstraction (`src/core/pathfinding/`)
- common `PathFinder` interface that can return full path and also act
as state machine (`.next()`)
- adapters for both new and legacy algorithm with fallback to legacy if
navigation mesh not available
#### Benchmark (`tests/pathfinding/benchmark/`)
- `npx tsx tests/pathfinding/benchmark/run.ts` - no guesswork, numbers
- `npx tsx tests/pathfinding/benchmark/run.ts --synthetic` - 1000s of
synthetic paths
- `npx tsc tests/pathfinding/benchmark/generate.ts` - generate more as
needed, test new maps
- includes ONE synthetic scenario to avoid PR bloat, generate more
locally / later
#### Playground (`tests/pathfinding/playground/`)
- `npx tsx tests/pathfinding/playground/server.ts` - visualize paths
with both new and legacy algorithm
## Benchmarks
### Compared with legacy in default - hand picked - scenario:
```
Initialization: 95.95ms -> 227.29ms
Pathfinding: 3038.43ms -> 6.45ms
Distance: 26972 -> 26810 tiles
```
### 42,000 synthetic routes across all maps
```
Running 42 synthetic scenarios with hpa.cached adapter...
✅ synthetic/achiran | Init: 93.42ms | Path: 139.07ms | Dist: 1481630 tiles | Routes: 1000/1000
✅ synthetic/africa | Init: 87.14ms | Path: 155.08ms | Dist: 1829414 tiles | Routes: 1000/1000
✅ synthetic/asia | Init: 57.60ms | Path: 112.55ms | Dist:
|
||
|
|
516d268c88 |
Restructured PR for Britannia Remastered Addition (#2813)
## Description: Adds a new detailed Britannia map featuring modern UK, Ireland, and northern France regions. The previous Britannia map has been preserved as "Britannia Classic". <img width="4096" height="5031" alt="United Kingdom" src="https://github.com/user-attachments/assets/8a6ca3ab-bc91-438f-8ca7-7fdf7c5260b8" /> <img width="4096" height="5031" alt="United Kingdom_debug" src="https://github.com/user-attachments/assets/a9a7bcd2-1dc5-40c6-a547-a7a79e636060" /> Changes Added new Britannia map with modern county/region divisions Renamed existing Britannia map to "Britannia Classic" Added map-generator source assets for both maps Updated GameMapType enum with BritanniaClassic Configured player counts: 50/30/20 for both maps Added playlist frequencies: Britannia (5), Britannia Classic (4) Updated language translations New Britannia Nations (partial list) Ireland: Mayo, Kerry, Clare, Meath, and more Scotland: Highland, Argyll and Bute, and more England: North Yorkshire, and more France: Pas-de-Calais Britannia Classic The original Britannia map with historical kingdoms (Dumnonia, Dyfed, Gwent, Gwynedd, Powys, Strathclyde, Dalriata, Wessex, Sussex, Kent, etc.) is now available as "Britannia Classic". ## 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: TSProphet --------- Co-authored-by: Harry <Harry.bath94@gmail.com> Co-authored-by: iamlewis <lewismmmm@gmail.com> |
||
|
|
ebcb654825 |
Added a public game modifier system 😮 For more variety (#2801)
## Description: Added a public game modifier system. It causes that 5% of public games are played on the compact version of the map 10% of public games have "Random Spawn" activated Percentages can easily get changed via `DefaultConfig`. We can also easily add more modifiers. Modifiers can stack, so in rare cases you will play on a compact map with random spawn 😄 More variety! ### "Compact Map" modifier implementation - With the "Compact Map" modifier the lobby max player count gets reduced to 25% and only 25% of the regular bots and only 25% of the regular nations will spawn (because the map has only 25% of its regular size) - In private lobbies and singleplayer the nation reduction happens too (When "Compact Map" is enabled). ### Restrictions - Duos/Trios/Quads team modes do not get Random Spawn (defeats the purpose) - Maps with smallest player count < 50 do not get Compact Map in team games (not enough players after the reduction to 25%). I have calculated all the possible max player counts. ### How it looks like Random Spawn modifier: <img width="528" height="183" alt="Screenshot 2026-01-06 194959" src="https://github.com/user-attachments/assets/2f729da9-80c3-4548-8205-71129da2a76a" /> Very rare case: Two modifiers at the same time and only 10 max players have been chosen from `[GameMapType.FaroeIslands]: [20, 15, 10]`. Because of the 75% reduction in player count only 3 players are allowed (3 is the minimum). I think its funny that you can play a 1v1v1 in rare occasions 😄 <img width="526" height="184" alt="Screenshot 2026-01-06 194938" src="https://github.com/user-attachments/assets/834326eb-df03-41b7-b1db-1efa3f1013b5" /> ### Funny side-effect Team games with random spawn. That will be interesting. No more "Who is better in donating troops to the frontline". Instead you have to heavily coordinate with your teammates. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin |
||
|
|
387190b916 |
New map! "Amazon River" 🏞️ (#2798)
## Description: We didn't have a river map and we didn't have a map with a crazy size. So I made a "Amazon River" map with a crazy size. 280 x 5536! 21 nations based on real locations. Should be interesting gameplay because you don't have many attack options, your only escape is the river. The land tiles size is similar to the achiran and iceland map. <img width="2442" height="147" alt="Screenshot 2026-01-06 150831" src="https://github.com/user-attachments/assets/91c4142d-c1e3-4aee-ac49-529b8d9f60c4" /> <img width="2324" height="139" alt="Screenshot 2026-01-06 150957" src="https://github.com/user-attachments/assets/5e049ae5-f32a-495f-afde-9e20257b3676" /> Because the map is so wide, it looked really ugly stretched in the thumbnails. So I added some CSS which removes the thumbnail stretching of the Amazon River map. We can also use this logic for other thumbnails which shouldn't get stretched. In `Maps.ts`, `PublicLobby.ts` and `GameInfoModal.ts`. ## 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 |
||
|
|
ecc174d248 |
New map! "Didier" 🧸 (#2794)
## Description: Didier map for the big french youtuber Fuze which already published several OpenFront videos. I took the real france, cut away the bordering countries and made it look like Didier 😄 Gave it eyes, hands and feet. Made sure we have some rivers, also put Corsica in the right bottom corner! It's quite large. Similar to the europe map. Has 42 nations (38 french cities and 4 funny custom nations for the youtuber). Made with [TsProphets map generator](https://github.com/TsProphet94/OpenFrontMapGenerator), QGIS and GIMP. For public games I put a rare map frequenzy of 2 because most people probably don't know Fuze. @ibnhalwa from discord gave some insider knowledge about Fuze (He's french, I'm not). <img width="2100" height="2250" alt="image" src="https://github.com/user-attachments/assets/5d1c3c45-4b2e-4f60-a02f-89b26f938652" /> <img width="1278" height="1218" alt="Screenshot 2026-01-05 184540" src="https://github.com/user-attachments/assets/6e300bb0-6e9f-4b0f-bad8-94f031d250b1" />  ## 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 |
||
|
|
af0b8a8d50 |
Configurable immunity timer (#2763)
## Description: Resolve discussions about stalled PR https://github.com/openfrontio/OpenFrontIO/pull/2460 <img width="724" height="348" alt="image" src="https://github.com/user-attachments/assets/c2c9fa79-cace-431a-9ca4-b3656612fa9d" /> Changes: - Added a `Player::canAttackPlayer(other)` function to determine whether a player can be attacked. - This function is now used in most places where a fight can occur: - AttackExecution (land attacks) - Naval invasion - Warship fight - Nukes can't be thrown during the truce - Immunity only affect human players. Nations and bot will fight as usual, and can be fought against. - The immunity timer uses minutes in the modal window. UI: - The immunity phase is displayed with a timer bar at the top. This is from the original PR, to be discussed if it's not deemed visible enough: <img width="632" height="215" alt="image" src="https://github.com/user-attachments/assets/f5ab9aa0-bd4f-4503-b8d6-b40b121fba65" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: newyearnewphil <git@nynp.dev> |
||
|
|
ab5b044362 |
Fix train was destroyed message spam (#2774)
## Description: Trains are made of a primary unit (`TrainExecution.train`) followed by 6 cars (`TrainExecution.cars`). Currently when any of the units is destroyed, a message "Your Train was destroyed" is shown. In worst case scenario, when all cars are blasted by a nuke, 7 messages are displayed to the user, but the train continues. Since the actual logic is unaffected as long as the primary unit stays alive, displaying messages is confusing. This PR fixes it. The message is only displayed when the primary unit is destroyed. The following cars are only there for fancy visuals. ## Screencast ##### Current - multiple messages for single train https://github.com/user-attachments/assets/3df04c71-d899-4f68-af83-36c9d0ffa730 First nuke was a test. Second nuke destroyed the entire train, prompting 7 messages. Third nuke destroyed one full train and then some. Prompting many too many messages. ##### Fixed - one message, only if train was fully destroyed https://github.com/user-attachments/assets/1f3840a7-6c62-487d-af3a-82de39dad9e8 First train was only partially destroyed, no message was shown, delivery mission completed A+. Following 2 trains had the front engine destroyed and therefore were promptly decommissioned. ## 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: moleole |
||
|
|
1eee8b4ddb |
New Map! "Surrounded" 🏝️ (#2770)
## Description: A new map where you basically have to hop from island to island :) We don't have such a map at the moment. There is a special center island which isn't necessary to get 80% of the map. This map could be very interesting in team games. One hydro will destroy an entire island. Size: 1976 x 1976 Nations: 8 <img width="949" height="951" alt="Screenshot 2026-01-02 214219" src="https://github.com/user-attachments/assets/7139bcc9-6a05-414d-90c1-33cc36dd94fb" /> ## 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 |