diff --git a/src/client/TerrainMapFileLoader.ts b/src/client/TerrainMapFileLoader.ts index 90cbd5763..f159eee88 100644 --- a/src/client/TerrainMapFileLoader.ts +++ b/src/client/TerrainMapFileLoader.ts @@ -1,6 +1,6 @@ import { assetUrl } from "../core/AssetUrls"; import { FetchGameMapLoader } from "../core/game/FetchGameMapLoader"; -export const terrainMapFileLoader = new FetchGameMapLoader(() => - assetUrl("maps"), +export const terrainMapFileLoader = new FetchGameMapLoader((path) => + assetUrl(`maps/${path}`), ); diff --git a/src/core/game/BinaryLoaderGameMapLoader.ts b/src/core/game/BinaryLoaderGameMapLoader.ts index 892e421b2..fce7522d7 100644 --- a/src/core/game/BinaryLoaderGameMapLoader.ts +++ b/src/core/game/BinaryLoaderGameMapLoader.ts @@ -37,25 +37,25 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader { }) .then((buf) => new Uint8Array(buf)); - const mapBasePath = assetUrl(`maps/${fileName}`); + const mapAssetUrl = (path: string) => assetUrl(`maps/${fileName}/${path}`); const mapData = { - mapBin: this.createLazyLoader(() => loadBinary(`${mapBasePath}/map.bin`)), + mapBin: this.createLazyLoader(() => loadBinary(mapAssetUrl("map.bin"))), map4xBin: this.createLazyLoader(() => - loadBinary(`${mapBasePath}/map4x.bin`), + loadBinary(mapAssetUrl("map4x.bin")), ), map16xBin: this.createLazyLoader(() => - loadBinary(`${mapBasePath}/map16x.bin`), + loadBinary(mapAssetUrl("map16x.bin")), ), manifest: this.createLazyLoader(() => - fetch(`${mapBasePath}/manifest.json`).then((res) => { + fetch(mapAssetUrl("manifest.json")).then((res) => { if (!res.ok) { - throw new Error(`Failed to load ${mapBasePath}/manifest.json`); + throw new Error(`Failed to load ${mapAssetUrl("manifest.json")}`); } return res.json() as Promise; }), ), - webpPath: `${mapBasePath}/thumbnail.webp`, + webpPath: mapAssetUrl("thumbnail.webp"), } satisfies MapData; this.maps.set(map, mapData); diff --git a/src/core/game/FetchGameMapLoader.ts b/src/core/game/FetchGameMapLoader.ts index 2c6f954e2..f7b0e575e 100644 --- a/src/core/game/FetchGameMapLoader.ts +++ b/src/core/game/FetchGameMapLoader.ts @@ -4,7 +4,9 @@ import { GameMapLoader, MapData } from "./GameMapLoader"; export class FetchGameMapLoader implements GameMapLoader { private maps: Map; - public constructor(private readonly prefix: string | (() => string)) { + public constructor( + private readonly pathResolver: string | ((path: string) => string), + ) { this.maps = new Map(); } @@ -35,12 +37,15 @@ export class FetchGameMapLoader implements GameMapLoader { return mapData; } - private getPrefix(): string { - return typeof this.prefix === "function" ? this.prefix() : this.prefix; + private resolveUrl(path: string): string { + if (typeof this.pathResolver === "function") { + return this.pathResolver(path); + } + return `${this.pathResolver}/${path}`; } private url(map: string, path: string) { - return `${this.getPrefix()}/${map}/${path}`; + return this.resolveUrl(`${map}/${path}`); } private async loadBinaryFromUrl(url: string) { diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 3f5b13b54..65e33c6de 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -17,7 +17,7 @@ import { const ctx: Worker = self as any; globalThis.__ASSET_MANIFEST__ = __ASSET_MANIFEST__; let gameRunner: Promise | null = null; -const mapLoader = new FetchGameMapLoader(() => assetUrl("maps")); +const mapLoader = new FetchGameMapLoader((path) => assetUrl(`maps/${path}`)); // Yield threshold; not a backlog cap. Used to avoid monopolizing the worker task // and flooding the main thread with messages during catch-up. const MAX_TICKS_BEFORE_YIELD = 4; diff --git a/tests/core/game/FetchGameMapLoader.test.ts b/tests/core/game/FetchGameMapLoader.test.ts new file mode 100644 index 000000000..d4e9d8319 --- /dev/null +++ b/tests/core/game/FetchGameMapLoader.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test, vi } from "vitest"; +import { FetchGameMapLoader } from "../../../src/core/game/FetchGameMapLoader"; +import { GameMapType } from "../../../src/core/game/Game"; + +describe("FetchGameMapLoader", () => { + test("resolves each map file through the provided path resolver", async () => { + const fetchMock = vi.fn(async (url: string) => ({ + ok: true, + arrayBuffer: async () => new ArrayBuffer(0), + json: async () => ({ url }), + statusText: "OK", + })); + vi.stubGlobal("fetch", fetchMock); + + const loader = new FetchGameMapLoader( + (path) => `/_assets/maps/${path}.hashed`, + ); + const mapData = loader.getMapData(GameMapType.BritanniaClassic); + + expect(mapData.webpPath).toBe( + "/_assets/maps/britanniaclassic/thumbnail.webp.hashed", + ); + + await mapData.manifest(); + + expect(fetchMock).toHaveBeenCalledWith( + "/_assets/maps/britanniaclassic/manifest.json.hashed", + ); + }); +});