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
This commit is contained in:
VariableVince
2025-11-24 19:30:18 +01:00
committed by GitHub
parent 049485cd39
commit 9b125c8cfe
6 changed files with 29 additions and 14 deletions
+7 -2
View File
@@ -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,
),
),
);
+1
View File
@@ -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;
+15 -4
View File
@@ -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;
+1 -2
View File
@@ -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);
}
+5 -5
View File
@@ -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}`);
-1
View File
@@ -11,7 +11,6 @@ describe("assignTeams", () => {
PlayerType.Human,
null, // clientID (null for testing)
id,
null, // nation (null for testing)
);
};