mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 06:10:42 +00:00
Allow mappers to omit nation coordinates in manifest.json for random spawn 🎲 (#4156)
## Description: Previously, every nation in a map's manifest.json required explicit coordinates. Additional nations already supported optional coordinates to trigger random spawn placement, but regular nations did not. Idea from PlaysBadly. Reasoning (copied off discord): > I've been working on World Inverted by adding realistic 'nations' in the form sunken ship names with their flags and location. However after searching around for other possible nation locations that are ocean related I realised that I might not have enough info for proper 'realisitc' coverage of the map. Currently Im at ~170 nations with cordinates. This is not including the additional nations with no locations. This will be reduced to ~62 as the default with the rest turning into additional nations. > > The problem is the end process is proving difficult. Trying to blance the nation placment on the map is a little much at this volume. So being able to add a few no-cordinate nations would be a great way to fill in the map. This PR also improves the MapConsistency test to check the additional nations too. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
This commit is contained in:
@@ -34,7 +34,9 @@ export function createNationsForGame(
|
||||
): Nation[] {
|
||||
const toNation = (n: ManifestNation): Nation =>
|
||||
new Nation(
|
||||
new Cell(n.coordinates[0], n.coordinates[1]),
|
||||
n.coordinates !== undefined
|
||||
? new Cell(n.coordinates[0], n.coordinates[1])
|
||||
: undefined,
|
||||
new PlayerInfo(n.name, PlayerType.Nation, null, random.nextID()),
|
||||
);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface MapManifest {
|
||||
}
|
||||
|
||||
export interface Nation {
|
||||
coordinates: [number, number];
|
||||
coordinates?: [number, number];
|
||||
flag?: string;
|
||||
name: string;
|
||||
}
|
||||
@@ -69,10 +69,12 @@ export async function loadTerrainMap(
|
||||
|
||||
if (mapSize === GameMapSize.Compact) {
|
||||
manifest.nations.forEach((nation) => {
|
||||
nation.coordinates = [
|
||||
Math.floor(nation.coordinates[0] / 2),
|
||||
Math.floor(nation.coordinates[1] / 2),
|
||||
];
|
||||
if (nation.coordinates !== undefined) {
|
||||
nation.coordinates = [
|
||||
Math.floor(nation.coordinates[0] / 2),
|
||||
Math.floor(nation.coordinates[1] / 2),
|
||||
];
|
||||
}
|
||||
});
|
||||
manifest.additionalNations?.forEach((nation) => {
|
||||
if (nation.coordinates !== undefined) {
|
||||
|
||||
@@ -294,39 +294,68 @@ describe("Map consistency", () => {
|
||||
const info = JSON.parse(fs.readFileSync(infoPath, "utf8"));
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
|
||||
type NationEntry = { name: string; coordinates: [number, number] };
|
||||
const infoNations: NationEntry[] = (info.nations ?? []).map(
|
||||
(n: NationEntry) => ({ name: n.name, coordinates: n.coordinates }),
|
||||
);
|
||||
const manifestNations: NationEntry[] = (manifest.nations ?? []).map(
|
||||
(n: NationEntry) => ({ name: n.name, coordinates: n.coordinates }),
|
||||
);
|
||||
// ── Compare nations ──────────────────────────────────────────────
|
||||
type NationEntry = {
|
||||
name: string;
|
||||
coordinates?: [number, number];
|
||||
};
|
||||
|
||||
if (infoNations.length !== manifestNations.length) {
|
||||
errors.push(
|
||||
`${key}: nation count mismatch — info.json has ${infoNations.length}, manifest.json has ${manifestNations.length}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare nations by index (order must match; names can be duplicated).
|
||||
for (let i = 0; i < infoNations.length; i++) {
|
||||
const inf = infoNations[i];
|
||||
const man = manifestNations[i];
|
||||
if (inf.name !== man.name) {
|
||||
function compareNationArrays(
|
||||
label: string,
|
||||
infoArr: NationEntry[],
|
||||
manifestArr: NationEntry[],
|
||||
): void {
|
||||
if (infoArr.length !== manifestArr.length) {
|
||||
errors.push(
|
||||
`${key}: nations[${i}] name mismatch — info.json "${inf.name}" vs manifest.json "${man.name}"`,
|
||||
`${key}: ${label} count mismatch — info.json has ${infoArr.length}, manifest.json has ${manifestArr.length}`,
|
||||
);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
const [ix, iy] = inf.coordinates;
|
||||
const [mx, my] = man.coordinates;
|
||||
if (ix !== mx || iy !== my) {
|
||||
errors.push(
|
||||
`${key}: nation "${inf.name}" (index ${i}) coordinates differ — info.json [${ix}, ${iy}] vs manifest.json [${mx}, ${my}]`,
|
||||
);
|
||||
for (let i = 0; i < infoArr.length; i++) {
|
||||
const inf = infoArr[i];
|
||||
const man = manifestArr[i];
|
||||
if (inf.name !== man.name) {
|
||||
errors.push(
|
||||
`${key}: ${label}[${i}] name mismatch — info.json "${inf.name}" vs manifest.json "${man.name}"`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const infHasCoords = inf.coordinates !== undefined;
|
||||
const manHasCoords = man.coordinates !== undefined;
|
||||
if (infHasCoords !== manHasCoords) {
|
||||
errors.push(
|
||||
`${key}: ${label} "${inf.name}" (index ${i}) coordinate presence differs — info.json ${infHasCoords ? "has" : "missing"} coordinates, manifest.json ${manHasCoords ? "has" : "missing"} coordinates`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (inf.coordinates && man.coordinates) {
|
||||
const [ix, iy] = inf.coordinates;
|
||||
const [mx, my] = man.coordinates;
|
||||
if (ix !== mx || iy !== my) {
|
||||
errors.push(
|
||||
`${key}: ${label} "${inf.name}" (index ${i}) coordinates differ — info.json [${ix}, ${iy}] vs manifest.json [${mx}, ${my}]`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toEntry = (n: NationEntry) => ({
|
||||
name: n.name,
|
||||
coordinates: n.coordinates,
|
||||
});
|
||||
|
||||
compareNationArrays(
|
||||
"nation",
|
||||
(info.nations ?? []).map(toEntry),
|
||||
(manifest.nations ?? []).map(toEntry),
|
||||
);
|
||||
|
||||
compareNationArrays(
|
||||
"additionalNation",
|
||||
(info.additionalNations ?? []).map(toEntry),
|
||||
(manifest.additionalNations ?? []).map(toEntry),
|
||||
);
|
||||
} catch (err) {
|
||||
errors.push(`${key}: failed to parse JSON — ${(err as Error).message}`);
|
||||
}
|
||||
|
||||
@@ -243,6 +243,34 @@ describe("createNationsForGame: additionalNations pool", () => {
|
||||
expect(withoutCoords!.spawnCell).toBeUndefined();
|
||||
});
|
||||
|
||||
test("uses coordinates from manifest nations when provided, undefined when omitted", () => {
|
||||
const manifest: ManifestNation[] = [
|
||||
{ name: "WithCoords", coordinates: [10, 20] },
|
||||
{ name: "WithoutCoords" },
|
||||
];
|
||||
const random = new PseudoRandom(5);
|
||||
|
||||
const nations = createNationsForGame(
|
||||
makeGameStart(2),
|
||||
manifest,
|
||||
[],
|
||||
0,
|
||||
random,
|
||||
);
|
||||
|
||||
expect(nations).toHaveLength(2);
|
||||
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(10);
|
||||
expect(withCoords!.spawnCell?.y).toBe(20);
|
||||
expect(withoutCoords!.spawnCell).toBeUndefined();
|
||||
});
|
||||
|
||||
test("produces unique nation names overall", () => {
|
||||
const manifest = makeManifestNations(3);
|
||||
const extras = makeAdditionalNations(["Ex1", "Ex2", "Ex3"]);
|
||||
|
||||
Reference in New Issue
Block a user