diff --git a/map-generator/assets/maps/beringsea/info.json b/map-generator/assets/maps/beringsea/info.json index 6707a6a43..1fe0e2f65 100644 --- a/map-generator/assets/maps/beringsea/info.json +++ b/map-generator/assets/maps/beringsea/info.json @@ -8,7 +8,7 @@ }, { "coordinates": [1725, 158], - "name": "Utqiaġvik", + "name": "Utqiagvik", "flag": "Alaska" }, { diff --git a/map-generator/assets/maps/bosphorusstraits/info.json b/map-generator/assets/maps/bosphorusstraits/info.json index 2cd6dddea..0d95175a7 100644 --- a/map-generator/assets/maps/bosphorusstraits/info.json +++ b/map-generator/assets/maps/bosphorusstraits/info.json @@ -33,7 +33,7 @@ }, { "coordinates": [534, 425], - "name": "Kadıköy", + "name": "Kadiköy", "flag": "tr" }, { @@ -78,27 +78,27 @@ }, { "coordinates": [459, 157], - "name": "Sarıyer", + "name": "Sariyer", "flag": "tr" }, { "coordinates": [477, 297], - "name": "Beşiktaş", + "name": "Besiktas", "flag": "tr" }, { "coordinates": [171, 379], - "name": "Avcılar", + "name": "Avcilar", "flag": "tr" }, { "coordinates": [308, 412], - "name": "Bakırköy", + "name": "Bakirköy", "flag": "tr" }, { "coordinates": [263, 283], - "name": "Başakşehir", + "name": "Basaksehir", "flag": "tr" }, { diff --git a/map-generator/assets/maps/luna/info.json b/map-generator/assets/maps/luna/info.json index 954369978..8c8168b8c 100644 --- a/map-generator/assets/maps/luna/info.json +++ b/map-generator/assets/maps/luna/info.json @@ -125,12 +125,12 @@ { "coordinates": [755, 3035], "flag": "", - "name": "T▆p░S▅cr▅t░M▊l▊t▅r▆░B▅s▅" + "name": "T0Þ $e¢®ët Mi|¡tªr¥ ß@§£" }, { "coordinates": [628, 921], - "flag": "", - "name": "▊" + "flag": "custom/luna_monolith", + "name": "ΜΟΝΟʟΙȚΗ" } ], "teamGameSpawnAreas": { diff --git a/map-generator/assets/maps/straitofhormuz/info.json b/map-generator/assets/maps/straitofhormuz/info.json index 27fdbecdc..42d4c172f 100644 --- a/map-generator/assets/maps/straitofhormuz/info.json +++ b/map-generator/assets/maps/straitofhormuz/info.json @@ -83,7 +83,7 @@ }, { "coordinates": [159, 756], - "name": "Ar Rayyān", + "name": "Ar Rayyan", "flag": "qa" }, { diff --git a/resources/flags/custom/luna_monolith.svg b/resources/flags/custom/luna_monolith.svg new file mode 100644 index 000000000..d188194c8 --- /dev/null +++ b/resources/flags/custom/luna_monolith.svg @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/resources/maps/beringsea/manifest.json b/resources/maps/beringsea/manifest.json index 7e9b3464a..0f4fcf2a2 100644 --- a/resources/maps/beringsea/manifest.json +++ b/resources/maps/beringsea/manifest.json @@ -24,7 +24,7 @@ { "coordinates": [1725, 158], "flag": "Alaska", - "name": "Utqiaġvik" + "name": "Utqiagvik" }, { "coordinates": [2152, 270], diff --git a/resources/maps/bosphorusstraits/manifest.json b/resources/maps/bosphorusstraits/manifest.json index db673d280..d3a3ce453 100644 --- a/resources/maps/bosphorusstraits/manifest.json +++ b/resources/maps/bosphorusstraits/manifest.json @@ -49,7 +49,7 @@ { "coordinates": [534, 425], "flag": "tr", - "name": "Kadıköy" + "name": "Kadiköy" }, { "coordinates": [559, 568], @@ -94,27 +94,27 @@ { "coordinates": [459, 157], "flag": "tr", - "name": "Sarıyer" + "name": "Sariyer" }, { "coordinates": [477, 297], "flag": "tr", - "name": "Beşiktaş" + "name": "Besiktas" }, { "coordinates": [171, 379], "flag": "tr", - "name": "Avcılar" + "name": "Avcilar" }, { "coordinates": [308, 412], "flag": "tr", - "name": "Bakırköy" + "name": "Bakirköy" }, { "coordinates": [263, 283], "flag": "tr", - "name": "Başakşehir" + "name": "Basaksehir" }, { "coordinates": [402, 272], diff --git a/resources/maps/luna/manifest.json b/resources/maps/luna/manifest.json index f86e0bacb..47d7f263e 100644 --- a/resources/maps/luna/manifest.json +++ b/resources/maps/luna/manifest.json @@ -134,12 +134,12 @@ { "coordinates": [755, 3035], "flag": "", - "name": "T▆p░S▅cr▅t░M▊l▊t▅r▆░B▅s▅" + "name": "T0Þ $e¢®ët Mi|¡tªr¥ ß@§£" }, { "coordinates": [628, 921], - "flag": "", - "name": "▊" + "flag": "custom/luna_monolith", + "name": "ΜΟΝΟʟΙȚΗ" } ], "teamGameSpawnAreas": { diff --git a/resources/maps/straitofhormuz/manifest.json b/resources/maps/straitofhormuz/manifest.json index 514aaf22e..406956896 100644 --- a/resources/maps/straitofhormuz/manifest.json +++ b/resources/maps/straitofhormuz/manifest.json @@ -99,7 +99,7 @@ { "coordinates": [159, 756], "flag": "qa", - "name": "Ar Rayyān" + "name": "Ar Rayyan" }, { "coordinates": [1103, 647], diff --git a/tests/NationName.test.ts b/tests/NationName.test.ts new file mode 100644 index 000000000..26ef3fe03 --- /dev/null +++ b/tests/NationName.test.ts @@ -0,0 +1,78 @@ +import fs from "fs"; +import { globSync } from "glob"; + +type Nation = { + name?: string; +}; + +type Manifest = { + nations?: Nation[]; +}; + +describe("Map manifests: nation name constraints", () => { + test("All nations' names must be ≤ 27 printable Extended-ASCII characters", () => { + const manifestPaths = globSync("resources/maps/**/manifest.json"); + + expect(manifestPaths.length).toBeGreaterThan(0); + + const violations: string[] = []; + + for (const manifestPath of manifestPaths) { + try { + const raw = fs.readFileSync(manifestPath, "utf8"); + const manifest = JSON.parse(raw) as Manifest; + + (manifest.nations ?? []).forEach((nation, idx) => { + const name = nation?.name; + if (typeof name !== "string") { + violations.push( + `${manifestPath} -> nations[${idx}].name is not a string`, + ); + return; + } + if (name.length > 27) { + violations.push( + `${manifestPath} -> nations[${idx}].name "${name}" has length ${name.length} (> 27)`, + ); + return; + } + if (name === "ΜΟΝΟʟΙȚΗ") { + // This exception handles the without-name easter-egg Nation in Luna. + // The MONOLITH nation have UNICODE characters that DO NOT render in the game-map. + // Precisely: each bytes of the UNICODE 16-bit code + // falls **outside** of the Extended-ASCII render-zone: [0x20–0x7E] and [0xA0-0xFF]. + // This magic trick makes its flag stand out, alone, over it's population count. + // However the name renders correctly in other texts (leaderboard, overlay, alliances, alerts, etc.). + return; + } + // Allow only printable safe-extended-ASCII characters + // within [0x20-0x7E] or [0xA0-0xFF], as in https://www.ascii-code.com/. + const excludededCharacters = [...name].filter( + (c) => + c.charCodeAt(0) < 0x20 || + (0x7e < c.charCodeAt(0) && c.charCodeAt(0) < 0xa0) || + 0xff < c.charCodeAt(0), + ); + if (0 < excludededCharacters.length) { + violations.push( + `${manifestPath} -> nations[${idx}].name "${name}" has ${excludededCharacters.length} non valid characters: ${excludededCharacters}`, + ); + return; + } + }); + } catch (err) { + violations.push( + `Failed to parse ${manifestPath}: ${(err as Error).message}`, + ); + } + } + + if (violations.length > 0) { + throw new Error( + "Nation name violations:\n" + + violations.join("\n") + + "\nAll characters must be within non-colored region of the Extended-ASCII table: https://www.ascii-code.com/", + ); + } + }); +}); diff --git a/tests/NationNameLength.test.ts b/tests/NationNameLength.test.ts deleted file mode 100644 index 9d7df858f..000000000 --- a/tests/NationNameLength.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs from "fs"; -import { globSync } from "glob"; - -type Nation = { - name?: string; -}; - -type Manifest = { - nations?: Nation[]; -}; - -describe("Map manifests: nation name length constraint", () => { - test("All nations' names must be ≤ 27 characters", () => { - const manifestPaths = globSync("resources/maps/**/manifest.json"); - - expect(manifestPaths.length).toBeGreaterThan(0); - - const violations: string[] = []; - - for (const manifestPath of manifestPaths) { - try { - const raw = fs.readFileSync(manifestPath, "utf8"); - const manifest = JSON.parse(raw) as Manifest; - - (manifest.nations ?? []).forEach((nation, idx) => { - const name = nation?.name; - if (typeof name !== "string") { - violations.push( - `${manifestPath} -> nations[${idx}].name is not a string`, - ); - return; - } - if (name.length > 27) { - violations.push( - `${manifestPath} -> nations[${idx}].name "${name}" has length ${name.length} (> 27)`, - ); - } - }); - } catch (err) { - violations.push( - `Failed to parse ${manifestPath}: ${(err as Error).message}`, - ); - } - } - - if (violations.length > 0) { - throw new Error( - "Nation name length violations:\n" + violations.join("\n"), - ); - } - }); -});