diff --git a/src/server/MapLandTiles.ts b/src/server/MapLandTiles.ts index 83d47bab8..b4fd9d1e4 100644 --- a/src/server/MapLandTiles.ts +++ b/src/server/MapLandTiles.ts @@ -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(); + +function mapDirName(map: GameMapType): string { + const key = ( + Object.keys(GameMapType) as Array + ).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 { + 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 { + 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