mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:40:42 +00:00
Fix map land tile lookup broken by asset URL migration 🗺️ (#3826)
## Description: `MapLandTiles` was fetching map manifests via HTTP at `http://localhost:3000/maps/<map>/manifest.json`. This stopped working after PR #3494 moved all public assets from stable paths (e.g. `/maps/...`) to content-hashed paths under `/_assets/...`. The master server no longer serves `/maps/` -- requests fell through to the SPA handler, returned `index.html`, failed JSON parsing, and silently fell back to the default `1_000_000` land tile count. With 1M tiles, `calculateMapPlayerCounts` produces `[50, 40, 25]` instead of the real values (e.g. `[185, 140, 95]` for Alps), causing all public lobbies to be capped at 25-50 players regardless of map. Fixes this by reading map manifests directly from disk instead of via HTTP: - In production: resolves the source path through `getRuntimeAssetManifest()` to the hashed file under `static/_assets/`, then reads it with `fs.readFile`. - In dev: falls back to `resources/maps/<name>/manifest.json` directly (the Dockerfile removes `resources/maps` in production, so this branch only runs locally). Also adds an in-process cache so each map manifest is only read once per server lifetime. Verified that it works on https://fix-map-land-tiles-asset-manifest.openfront.dev/ and locally. ## 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
This commit is contained in:
+45
-14
@@ -1,26 +1,57 @@
|
||||
import { FetchGameMapLoader } from "src/core/game/FetchGameMapLoader";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { normalizeAssetPath } from "src/core/AssetUrls";
|
||||
import { GameMapType } from "src/core/game/Game";
|
||||
import { GameMapLoader } from "src/core/game/GameMapLoader";
|
||||
import { fileURLToPath } from "url";
|
||||
import { logger } from "./Logger";
|
||||
|
||||
let mapLoader: GameMapLoader | null = null;
|
||||
import { getRuntimeAssetManifest } from "./RuntimeAssetManifest";
|
||||
|
||||
const log = logger.child({ component: "MapLandTiles" });
|
||||
|
||||
// Gets or creates the map loader, uses FetchGameMapLoader pointing to the master server.
|
||||
function getMapLoader(): GameMapLoader {
|
||||
mapLoader ??= new FetchGameMapLoader("http://localhost:3000/maps");
|
||||
return mapLoader;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const staticDir = path.join(__dirname, "../../static");
|
||||
const resourcesDir = path.join(__dirname, "../../resources");
|
||||
|
||||
const landTilesCache = new Map<GameMapType, number>();
|
||||
|
||||
function mapDirName(map: GameMapType): string {
|
||||
const key = (
|
||||
Object.keys(GameMapType) as Array<keyof typeof GameMapType>
|
||||
).find((k) => GameMapType[k] === map);
|
||||
if (!key) throw new Error(`Unknown map: ${map}`);
|
||||
return key.toLowerCase();
|
||||
}
|
||||
|
||||
// Gets the number of land tiles for a map
|
||||
// FetchGameMapLoader already caches maps, so no need for additional caching here.
|
||||
async function readManifestFile(map: GameMapType): Promise<string> {
|
||||
const relativePath = `maps/${mapDirName(map)}/manifest.json`;
|
||||
|
||||
// Production: resolve via the asset manifest to the hashed file under static/_assets/.
|
||||
const assetManifest = await getRuntimeAssetManifest();
|
||||
const hashedUrl = assetManifest[relativePath];
|
||||
if (hashedUrl) {
|
||||
return fs.readFile(
|
||||
path.join(staticDir, normalizeAssetPath(hashedUrl)),
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
// Dev: read directly from resources/. The Dockerfile deletes resources/maps in
|
||||
// production, so this branch only runs locally.
|
||||
return fs.readFile(path.join(resourcesDir, relativePath), "utf8");
|
||||
}
|
||||
|
||||
// Gets the number of land tiles for a map.
|
||||
export async function getMapLandTiles(map: GameMapType): Promise<number> {
|
||||
const cached = landTilesCache.get(map);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
try {
|
||||
const loader = getMapLoader();
|
||||
const mapData = loader.getMapData(map);
|
||||
const manifest = await mapData.manifest();
|
||||
return manifest.map.num_land_tiles;
|
||||
const raw = await readManifestFile(map);
|
||||
const tiles = (JSON.parse(raw) as { map: { num_land_tiles: number } }).map
|
||||
.num_land_tiles;
|
||||
landTilesCache.set(map, tiles);
|
||||
return tiles;
|
||||
} catch (error) {
|
||||
log.error(`Failed to load manifest for ${map}: ${error}`, { map });
|
||||
return 1_000_000; // Default fallback
|
||||
|
||||
Reference in New Issue
Block a user