From 9b125c8cfec3e47ba194690bb492ed854bb373cb Mon Sep 17 00:00:00 2001 From: VariableVince <24507472+VariableVince@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:30:18 +0100 Subject: [PATCH] Bugfix: nation strength undefined in only place it is used (#2498) ## Description: In commit https://github.com/openfrontio/OpenFrontIO/commit/bbf72bd14f7f31146c687523aea8fc0aff31bbe1#diff-ee2fcbca50d87cc09d2c7d2667210defe2e3e111239820c89c40283be5385b64 it was added that startManpower in DefaultConfig used playerInfo.nation.strength to set the starting troops for a Nation. In ExecutionManager, param nation of PlayerInfo was set during the instantiation of a new FakeHumanExecution. However in commit https://github.com/openfrontio/OpenFrontIO/commit/d6a412aa50dd86d474d80c216fd9ba36e7426ef9#diff-2d0a5d8b171d8b504f934891025e42742e142ef0964d6e17712bfdcd30bf050c the changes made it so that **param nation of PlayerInfo was never set**. While startManpower in DefaultConfig still checked for playerinfo.nation.strength. Since it was always undefined, it would use a multiplier of 1 instead of the actual nation strength. This PR fixes it by passing the nation strength to param nationStrength in PlayerInfo. Removing param strength from class Nation. Strength isn't used anywhere else so this isn't a problem and it also consolidates human player info and nation player info even more. We could have also used the Nation.strength directly, but that would have required more code in addPlayers and addPlayer in GameImpl, especially for Teams games. So this PR has the simplest solution. - I did add a config setting useNationStrengthForStartManpower with a comment that explains its reason for being. Namely that in the months that startManpower didn't get to use nation strength because of the bug, FakeHumans have become much harder to fight. Re-enabling higher starting troops from this fix would make them even harder to fight, and i think rebalancing is needed before that. - Or we could decide to scrap Nation strength altogether, as it is only ever used to set starting troops anyway. This would make map making a little easier as a bycatch. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33 --- src/core/GameRunner.ts | 9 +++++++-- src/core/configuration/Config.ts | 1 + src/core/configuration/DefaultConfig.ts | 19 +++++++++++++++---- src/core/game/Game.ts | 3 +-- tests/FakeHumanMIRV.test.ts | 10 +++++----- tests/TeamAssignment.test.ts | 1 - 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index a78e39699..04a76c7cf 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -64,8 +64,13 @@ export async function createGameRunner( (n) => new Nation( new Cell(n.coordinates[0], n.coordinates[1]), - n.strength, - new PlayerInfo(n.name, PlayerType.FakeHuman, null, random.nextID()), + new PlayerInfo( + n.name, + PlayerType.FakeHuman, + null, + random.nextID(), + n.strength, + ), ), ); diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index f1f1b03c6..abe0f0018 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -93,6 +93,7 @@ export interface Config { userSettings(): UserSettings; playerTeams(): TeamCountConfig; + useNationStrengthForStartManpower(): boolean; startManpower(playerInfo: PlayerInfo): number; troopIncreaseRate(player: Player | PlayerView): number; goldAdditionRate(player: Player | PlayerView): Gold; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 066a3e9ac..0dfc68e03 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -805,20 +805,31 @@ export class DefaultConfig implements Config { } } + useNationStrengthForStartManpower(): boolean { + // Currently disabled: FakeHumans became harder to play against due to AI improvements + // nation strength multiplier was unintentionally disabled during those AI improvements (playerInfo.nation was undefined), + // Re-enabling this without rebalancing FakeHuman difficulty elsewhere may make them overpowered + return false; + } + startManpower(playerInfo: PlayerInfo): number { if (playerInfo.playerType === PlayerType.Bot) { return 10_000; } if (playerInfo.playerType === PlayerType.FakeHuman) { + const strength = this.useNationStrengthForStartManpower() + ? (playerInfo.nationStrength ?? 1) + : 1; + switch (this._gameConfig.difficulty) { case Difficulty.Easy: - return 2_500 * (playerInfo?.nation?.strength ?? 1); + return 2_500 * strength; case Difficulty.Medium: - return 5_000 * (playerInfo?.nation?.strength ?? 1); + return 5_000 * strength; case Difficulty.Hard: - return 20_000 * (playerInfo?.nation?.strength ?? 1); + return 20_000 * strength; case Difficulty.Impossible: - return 50_000 * (playerInfo?.nation?.strength ?? 1); + return 50_000 * strength; } } return this.infiniteTroops() ? 1_000_000 : 25_000; diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 1883063bb..2cf908b96 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -305,7 +305,6 @@ export enum Relation { export class Nation { constructor( public readonly spawnCell: Cell, - public readonly strength: number, public readonly playerInfo: PlayerInfo, ) {} } @@ -413,7 +412,7 @@ export class PlayerInfo { public readonly clientID: ClientID | null, // TODO: make player id the small id public readonly id: PlayerID, - public readonly nation?: Nation | null, + public readonly nationStrength?: number, ) { this.clan = getClanTag(name); } diff --git a/tests/FakeHumanMIRV.test.ts b/tests/FakeHumanMIRV.test.ts index 2d9c46cbb..11cad91fd 100644 --- a/tests/FakeHumanMIRV.test.ts +++ b/tests/FakeHumanMIRV.test.ts @@ -79,7 +79,7 @@ describe("FakeHuman MIRV Retaliation", () => { const mirvCountBefore = fakehuman.units(UnitType.MIRV).length; // Initialize fakehuman with FakeHumanExecution to enable retaliation logic - const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info()); + const fakehumanNation = new Nation(new Cell(50, 50), fakehuman.info()); // Try different game IDs to account for hesitation odds const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`); @@ -247,7 +247,7 @@ describe("FakeHuman MIRV Retaliation", () => { const mirvCountBefore = fakehuman.units(UnitType.MIRV).length; // Initialize fakehuman with FakeHumanExecution to enable victory denial logic - const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info()); + const fakehumanNation = new Nation(new Cell(50, 50), fakehuman.info()); // Try different game IDs to account for hesitation odds const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`); @@ -400,7 +400,7 @@ describe("FakeHuman MIRV Retaliation", () => { const mirvCountBefore = fakehuman.units(UnitType.MIRV).length; // Initialize fakehuman with FakeHumanExecution to enable steamroll stop logic - const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info()); + const fakehumanNation = new Nation(new Cell(50, 50), fakehuman.info()); // Try different game IDs to account for hesitation odds const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`); @@ -551,7 +551,7 @@ describe("FakeHuman MIRV Retaliation", () => { const mirvCountBefore = fakehuman.units(UnitType.MIRV).length; // Initialize fakehuman with FakeHumanExecution to enable steamroll stop logic - const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info()); + const fakehumanNation = new Nation(new Cell(50, 50), fakehuman.info()); // Try different game IDs to account for hesitation odds const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`); @@ -685,7 +685,7 @@ describe("FakeHuman MIRV Retaliation", () => { const mirvCountBefore = fakehuman.units(UnitType.MIRV).length; // Initialize fakehuman with FakeHumanExecution to enable team victory denial logic - const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info()); + const fakehumanNation = new Nation(new Cell(50, 50), fakehuman.info()); // Try different game IDs to account for hesitation odds const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`); diff --git a/tests/TeamAssignment.test.ts b/tests/TeamAssignment.test.ts index 5d9f5bcab..c3e11671b 100644 --- a/tests/TeamAssignment.test.ts +++ b/tests/TeamAssignment.test.ts @@ -11,7 +11,6 @@ describe("assignTeams", () => { PlayerType.Human, null, // clientID (null for testing) id, - null, // nation (null for testing) ); };