mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-04 21:11:58 +00:00
Generate a single MapInfo list; move SPECIAL_TEAM_MAPS and en.json map names into info.json (#4231)
**Add approved & assigned issue number here:** N/A — maintainer follow-up to #4227. ## Description: Follow-up to #4227, finishing the "info.json is the single source of truth" refactor. **Maps.gen.ts now generates one `MapInfo` interface and a `maps` list** instead of parallel lookup records. `mapCategories`, `mapTranslationKeys`, and `multiplayerFrequency` are gone — consumers read the list directly (`map.categories`, `map.translationKey`, `map.multiplayerFrequency`). MapPicker got simpler in the process: it renders from `MapInfo` objects, so the reverse `Object.entries(GameMapType)` lookup to recover the enum key is gone. The featured-rank sort moved out of the Go codegen into the picker, where the presentation concern belongs. **`SPECIAL_TEAM_MAPS` moves into info.json** as an optional `special_team_count` field (set on the same 17 maps with the same values). MapPlaylist derives its map from the generated list; `SPECIAL_TEAM_FORCE_CHANCE` and the frequency multiplier behavior are unchanged. **The en.json `map` section is now generated.** A new optional `display_name` field in info.json (defaulting to `name`) is written to `resources/lang/en.json` by the generator, preserving the section's non-map UI keys (`map`, `featured`, `all`, `favorites`, `random`). The 8 maps whose English display name intentionally differs from the frozen enum value (e.g. `MENA`, `Milky Way`, `Europe (Classic)`, `Baikal (Nuke Wars)`) declare it via `display_name`, so no display text changes. The section is emitted alphabetically; since #4232 already sorted en.json and every value matches, regeneration is byte-identical and this PR has no en.json diff. Other languages remain Crowdin-managed. The generator also now validates `translation_key` is exactly `map.<folder>` and `special_team_count >= 2`. MapConsistency tests compare info.json directly against the generated list and the en.json section, and fail with a "run `npm run gen-maps`" message on drift. No behavior changes: enum values, playlist frequencies, special-team counts, featured order, and display names are all byte-identical. ## Please complete the following: - [x] I have added screenshots for all UI updates (no UI changes — internal refactor, rendering output identical) - [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: evanpelle 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+2
-3
@@ -1,10 +1,9 @@
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import {
|
||||
Duos,
|
||||
GameMapType,
|
||||
GameMode,
|
||||
HumansVsNations,
|
||||
mapTranslationKeys,
|
||||
maps,
|
||||
MessageType,
|
||||
PublicGameModifiers,
|
||||
Quads,
|
||||
@@ -24,7 +23,7 @@ export function normaliseMapKey(mapName: string): string {
|
||||
export function getMapName(mapName: string | undefined): string | null {
|
||||
if (!mapName) return null;
|
||||
const translationKey =
|
||||
mapTranslationKeys[mapName as GameMapType] ??
|
||||
maps.find((m) => m.type === mapName)?.translationKey ??
|
||||
`map.${normaliseMapKey(mapName)}`;
|
||||
return translateText(translationKey);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import { assetUrl } from "../../../core/AssetUrls";
|
||||
import {
|
||||
Difficulty,
|
||||
GameMapType,
|
||||
mapCategories,
|
||||
mapTranslationKeys,
|
||||
MapCategory,
|
||||
mapCategoryOrder,
|
||||
MapInfo,
|
||||
maps,
|
||||
} from "../../../core/game/Game";
|
||||
import { translateText } from "../../Utils";
|
||||
import "./MapDisplay";
|
||||
@@ -15,6 +17,19 @@ const randomMap = assetUrl("images/RandomMap.webp");
|
||||
|
||||
type MapTab = "featured" | "all" | "favorites";
|
||||
|
||||
// Featured grid order: ranked maps first (1 = first), unranked alphabetical.
|
||||
const featuredMaps: MapInfo[] = maps
|
||||
.filter((m) => m.categories.includes("featured"))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.featuredRank ?? Number.MAX_SAFE_INTEGER) -
|
||||
(b.featuredRank ?? Number.MAX_SAFE_INTEGER),
|
||||
);
|
||||
|
||||
function mapsInCategory(category: MapCategory): MapInfo[] {
|
||||
return maps.filter((m) => m.categories.includes(category));
|
||||
}
|
||||
|
||||
@customElement("map-picker")
|
||||
export class MapPicker extends LitElement {
|
||||
@property({ type: String }) selectedMap: GameMapType = GameMapType.World;
|
||||
@@ -63,29 +78,26 @@ export class MapPicker extends LitElement {
|
||||
return this.mapWins?.get(mapValue) ?? new Set();
|
||||
}
|
||||
|
||||
private renderMapCard(mapValue: GameMapType) {
|
||||
const mapKey = Object.entries(GameMapType).find(
|
||||
([_, value]) => value === mapValue,
|
||||
)?.[0];
|
||||
private renderMapCard(map: MapInfo) {
|
||||
return html`
|
||||
<div
|
||||
@click=${() => this.handleMapSelection(mapValue)}
|
||||
@click=${() => this.handleMapSelection(map.type)}
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<map-display
|
||||
.mapKey=${mapKey}
|
||||
.selected=${!this.useRandomMap && this.selectedMap === mapValue}
|
||||
.mapKey=${map.id}
|
||||
.selected=${!this.useRandomMap && this.selectedMap === map.type}
|
||||
.showMedals=${this.showMedals}
|
||||
.wins=${this.getWins(mapValue)}
|
||||
.favorite=${this.favorites.includes(mapValue)}
|
||||
.onToggleFavorite=${() => this.handleToggleFavorite(mapValue)}
|
||||
.translation=${translateText(mapTranslationKeys[mapValue])}
|
||||
.wins=${this.getWins(map.type)}
|
||||
.favorite=${this.favorites.includes(map.type)}
|
||||
.onToggleFavorite=${() => this.handleToggleFavorite(map.type)}
|
||||
.translation=${translateText(map.translationKey)}
|
||||
></map-display>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderMapGrid(maps: GameMapType[]) {
|
||||
private renderMapGrid(mapList: MapInfo[]) {
|
||||
// Keyed by map so cards keep their identity when the list shifts
|
||||
// (e.g. the selected map gets prepended to the featured grid) —
|
||||
// positional reuse would leave stale thumbnails behind.
|
||||
@@ -93,9 +105,9 @@ export class MapPicker extends LitElement {
|
||||
class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
|
||||
>
|
||||
${repeat(
|
||||
maps,
|
||||
(mapValue) => mapValue,
|
||||
(mapValue) => this.renderMapCard(mapValue),
|
||||
mapList,
|
||||
(map) => map.id,
|
||||
(map) => this.renderMapCard(map),
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
@@ -108,7 +120,7 @@ export class MapPicker extends LitElement {
|
||||
</h4>`;
|
||||
}
|
||||
|
||||
private renderCategoryBar(categoryKey: string, maps: GameMapType[]) {
|
||||
private renderCategoryBar(categoryKey: MapCategory, mapList: MapInfo[]) {
|
||||
const expanded = this.expandedCategories.has(categoryKey);
|
||||
return html`<div class="w-full">
|
||||
<button
|
||||
@@ -134,19 +146,23 @@ export class MapPicker extends LitElement {
|
||||
</svg>
|
||||
${translateText(`map_categories.${categoryKey}`)}
|
||||
</span>
|
||||
<span class="text-xs font-bold text-white/40">${maps.length}</span>
|
||||
<span class="text-xs font-bold text-white/40">${mapList.length}</span>
|
||||
</button>
|
||||
${expanded
|
||||
? html`<div class="mt-4">${this.renderMapGrid(maps)}</div>`
|
||||
? html`<div class="mt-4">${this.renderMapGrid(mapList)}</div>`
|
||||
: null}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderFeaturedTab() {
|
||||
const featured = mapCategories.featured ?? [];
|
||||
let featuredMapList = featured;
|
||||
if (!this.useRandomMap && !featured.includes(this.selectedMap)) {
|
||||
featuredMapList = [this.selectedMap, ...featured];
|
||||
let featuredMapList = featuredMaps;
|
||||
const selected = maps.find((m) => m.type === this.selectedMap);
|
||||
if (
|
||||
!this.useRandomMap &&
|
||||
selected !== undefined &&
|
||||
!featuredMaps.includes(selected)
|
||||
) {
|
||||
featuredMapList = [selected, ...featuredMaps];
|
||||
}
|
||||
return html`<div class="w-full">
|
||||
${this.renderSectionHeading(translateText("map_categories.featured"))}
|
||||
@@ -156,10 +172,10 @@ export class MapPicker extends LitElement {
|
||||
|
||||
private renderAllTab() {
|
||||
return html`<div class="space-y-3">
|
||||
${Object.entries(mapCategories)
|
||||
.filter(([categoryKey]) => categoryKey !== "featured")
|
||||
.map(([categoryKey, maps]) =>
|
||||
this.renderCategoryBar(categoryKey, maps),
|
||||
${mapCategoryOrder
|
||||
.filter((categoryKey) => categoryKey !== "featured")
|
||||
.map((categoryKey) =>
|
||||
this.renderCategoryBar(categoryKey, mapsInCategory(categoryKey)),
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
@@ -175,9 +191,12 @@ export class MapPicker extends LitElement {
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
const favoriteMaps = this.favorites
|
||||
.map((favorite) => maps.find((m) => m.type === favorite))
|
||||
.filter((m) => m !== undefined);
|
||||
return html`<div class="w-full">
|
||||
${this.renderSectionHeading(translateText("map_categories.favorites"))}
|
||||
${this.renderMapGrid(this.favorites)}
|
||||
${this.renderMapGrid(favoriteMaps)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user