mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +00:00
19280c0b37
## Description: Adds an optional `additionalNations` array to map manifests (info.json / manifest.json), used as a name pool when a game requests more nations than the map defines (HvN, private lobbies, solo games). Suggested by mapmaker PatrickPlaysBadly. When the requested nation count exceeds `nations.length`: 1. The deficit is filled by random picks from `additionalNations` (collisions with manifest names are skipped). 2. If `additionalNations` still does not cover the deficit, the remainder is generated procedurally as before. Each entry supports `name`, optional `flag` and optional `coordinates`. If `coordinates` are provided, the picked nation gets a spawn cell (otherwise it spawns like the procedurally generated ones, with no fixed location). `Nation.flag` is also relaxed to optional, since many existing manifest entries already omit it. ## 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
264 lines
6.7 KiB
TypeScript
264 lines
6.7 KiB
TypeScript
import {
|
|
Difficulty,
|
|
GameMapSize,
|
|
GameMapType,
|
|
GameMode,
|
|
GameType,
|
|
Nation,
|
|
} from "../src/core/game/Game";
|
|
import { createNationsForGame } from "../src/core/game/NationCreation";
|
|
import {
|
|
AdditionalNation,
|
|
Nation as ManifestNation,
|
|
} from "../src/core/game/TerrainMapLoader";
|
|
import { PseudoRandom } from "../src/core/PseudoRandom";
|
|
import { GameConfig, GameStartInfo } from "../src/core/Schemas";
|
|
|
|
function makeManifestNations(count: number): ManifestNation[] {
|
|
const result: ManifestNation[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
result.push({
|
|
coordinates: [i, i],
|
|
flag: "",
|
|
name: `Manifest${i}`,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function makeAdditionalNations(names: string[]): AdditionalNation[] {
|
|
return names.map((name) => ({ name }));
|
|
}
|
|
|
|
function makeGameStart(targetNations: number): GameStartInfo {
|
|
const config: GameConfig = {
|
|
gameMap: GameMapType.World,
|
|
gameMapSize: GameMapSize.Normal,
|
|
gameMode: GameMode.FFA,
|
|
gameType: GameType.Singleplayer,
|
|
difficulty: Difficulty.Medium,
|
|
nations: targetNations,
|
|
donateGold: false,
|
|
donateTroops: false,
|
|
bots: 0,
|
|
infiniteGold: false,
|
|
infiniteTroops: false,
|
|
instantBuild: false,
|
|
randomSpawn: false,
|
|
};
|
|
return {
|
|
gameID: "test1234",
|
|
lobbyCreatedAt: 0,
|
|
config,
|
|
players: [],
|
|
};
|
|
}
|
|
|
|
function nationNames(nations: Nation[]): string[] {
|
|
return nations.map((n) => n.playerInfo.name);
|
|
}
|
|
|
|
describe("createNationsForGame: additionalNations pool", () => {
|
|
test("does not draw from the pool when manifest already covers the count", () => {
|
|
const manifest = makeManifestNations(4);
|
|
const extras = makeAdditionalNations(["ExtraA", "ExtraB", "ExtraC"]);
|
|
const random = new PseudoRandom(1);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(3),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
expect(nations).toHaveLength(3);
|
|
for (const name of nationNames(nations)) {
|
|
expect(name.startsWith("Manifest")).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("fills the deficit entirely from the pool when it is large enough", () => {
|
|
const manifest = makeManifestNations(2);
|
|
const extras = makeAdditionalNations([
|
|
"ExtraA",
|
|
"ExtraB",
|
|
"ExtraC",
|
|
"ExtraD",
|
|
"ExtraE",
|
|
]);
|
|
const random = new PseudoRandom(7);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(5),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
expect(nations).toHaveLength(5);
|
|
const names = nationNames(nations);
|
|
|
|
expect(names.filter((n) => n.startsWith("Manifest"))).toHaveLength(2);
|
|
|
|
const fromPool = names.filter((n) => n.startsWith("Extra"));
|
|
expect(fromPool).toHaveLength(3);
|
|
for (const name of fromPool) {
|
|
expect(extras.some((e) => e.name === name)).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("randomly selects from the pool when it has more entries than needed", () => {
|
|
const manifest = makeManifestNations(2);
|
|
const pool = [
|
|
"ExtraA",
|
|
"ExtraB",
|
|
"ExtraC",
|
|
"ExtraD",
|
|
"ExtraE",
|
|
"ExtraF",
|
|
"ExtraG",
|
|
"ExtraH",
|
|
];
|
|
const extras = makeAdditionalNations(pool);
|
|
|
|
const seen = new Set<string>();
|
|
for (let seed = 1; seed <= 25; seed++) {
|
|
const random = new PseudoRandom(seed);
|
|
const nations = createNationsForGame(
|
|
makeGameStart(4),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
expect(nations).toHaveLength(4);
|
|
|
|
const fromPool = nationNames(nations).filter((n) =>
|
|
n.startsWith("Extra"),
|
|
);
|
|
expect(fromPool).toHaveLength(2);
|
|
for (const name of fromPool) {
|
|
expect(pool).toContain(name);
|
|
}
|
|
fromPool.forEach((n) => seen.add(n));
|
|
}
|
|
expect(seen.size).toBeGreaterThan(2);
|
|
});
|
|
|
|
test("falls back to generated names when the pool is too small", () => {
|
|
const manifest = makeManifestNations(1);
|
|
const extras = makeAdditionalNations(["ExtraA", "ExtraB"]);
|
|
const random = new PseudoRandom(42);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(5),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
expect(nations).toHaveLength(5);
|
|
const names = nationNames(nations);
|
|
|
|
expect(names.filter((n) => n.startsWith("Manifest"))).toHaveLength(1);
|
|
|
|
const fromPool = names.filter((n) => extras.some((e) => e.name === n));
|
|
expect(fromPool).toHaveLength(2);
|
|
|
|
const generated = names.filter(
|
|
(n) => !n.startsWith("Manifest") && !extras.some((e) => e.name === n),
|
|
);
|
|
expect(generated).toHaveLength(2);
|
|
});
|
|
|
|
test("falls back to generated names when the pool is empty", () => {
|
|
const manifest = makeManifestNations(2);
|
|
const random = new PseudoRandom(11);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(4),
|
|
manifest,
|
|
[],
|
|
0,
|
|
random,
|
|
);
|
|
|
|
expect(nations).toHaveLength(4);
|
|
expect(
|
|
nationNames(nations).filter((n) => n.startsWith("Manifest")),
|
|
).toHaveLength(2);
|
|
});
|
|
|
|
test("skips pool entries whose name collides with a manifest nation", () => {
|
|
const manifest = makeManifestNations(2);
|
|
const extras = makeAdditionalNations([
|
|
"Manifest0",
|
|
"Manifest1",
|
|
"UniqueExtra",
|
|
]);
|
|
const random = new PseudoRandom(3);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(3),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
const names = nationNames(nations);
|
|
expect(names).toHaveLength(3);
|
|
expect(new Set(names).size).toBe(3);
|
|
expect(names).toContain("UniqueExtra");
|
|
});
|
|
|
|
test("uses coordinates from additional nations when provided", () => {
|
|
const manifest = makeManifestNations(1);
|
|
const extras: AdditionalNation[] = [
|
|
{ name: "WithCoords", coordinates: [42, 99] },
|
|
{ name: "WithoutCoords" },
|
|
];
|
|
const random = new PseudoRandom(5);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(3),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
const withCoords = nations.find((n) => n.playerInfo.name === "WithCoords");
|
|
const withoutCoords = nations.find(
|
|
(n) => n.playerInfo.name === "WithoutCoords",
|
|
);
|
|
|
|
expect(withCoords).toBeDefined();
|
|
expect(withoutCoords).toBeDefined();
|
|
expect(withCoords!.spawnCell?.x).toBe(42);
|
|
expect(withCoords!.spawnCell?.y).toBe(99);
|
|
expect(withoutCoords!.spawnCell).toBeUndefined();
|
|
});
|
|
|
|
test("produces unique nation names overall", () => {
|
|
const manifest = makeManifestNations(3);
|
|
const extras = makeAdditionalNations(["Ex1", "Ex2", "Ex3"]);
|
|
const random = new PseudoRandom(99);
|
|
|
|
const nations = createNationsForGame(
|
|
makeGameStart(8),
|
|
manifest,
|
|
extras,
|
|
0,
|
|
random,
|
|
);
|
|
|
|
const names = nationNames(nations);
|
|
expect(names).toHaveLength(8);
|
|
expect(new Set(names).size).toBe(8);
|
|
});
|
|
});
|