feat: add Ref city hover details to public maps

This commit is contained in:
2026-08-13 18:04:11 +00:00
parent 851eb18060
commit a318e7d678
6 changed files with 201 additions and 26 deletions
+7 -1
View File
@@ -1,4 +1,7 @@
import { loadMapDefinitionByName as loadRuntimeMapDefinitionByName } from '@sammo-ts/game-engine/scenario/mapLoader.js';
import {
loadMapDefinitionByName as loadRuntimeMapDefinitionByName,
loadRegionDisplayMapByName as loadRuntimeRegionDisplayMapByName,
} from '@sammo-ts/game-engine/scenario/mapLoader.js';
import type { MapDefinition } from '@sammo-ts/logic';
const mapCache = new Map<string, MapDefinition>();
@@ -15,3 +18,6 @@ export const loadMapDefinitionByName = async (mapName: string): Promise<MapDefin
mapCache.set(mapName, map);
return map;
};
export const loadRegionDisplayMapByName = (mapName: string): Promise<Record<number, string>> =>
loadRuntimeRegionDisplayMapByName(mapName);
+20 -5
View File
@@ -1,7 +1,7 @@
import { loadScenarioDefinitionById } from '@sammo-ts/game-engine/scenario/scenarioLoader.js';
import type { ScenarioDefinition } from '@sammo-ts/logic';
import { loadMapDefinitionByName } from './mapDefinition.js';
import { loadMapDefinitionByName, loadRegionDisplayMapByName } from './mapDefinition.js';
export interface MapLayoutCity {
id: number;
@@ -23,10 +23,22 @@ export interface MapLayout {
export interface MapLayoutLoaderOptions {
loadScenario?: (scenarioId: number) => Promise<ScenarioDefinition>;
loadMap?: typeof loadMapDefinitionByName;
loadRegionMap?: typeof loadRegionDisplayMapByName;
}
const layoutCache = new Map<string, MapLayout>();
const CITY_LEVEL_MAP: Record<number, string> = {
1: '수',
2: '진',
3: '관',
4: '이',
5: '소',
6: '중',
7: '대',
8: '특',
};
const parseScenarioId = (scenario: string): number | null => {
const normalized = scenario.replace(/^scenario_/i, '').replace(/\.json$/i, '');
if (!/^\d+$/.test(normalized)) {
@@ -56,13 +68,16 @@ const resolveMapName = async (
export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOptions = {}): Promise<MapLayout> => {
const mapName = await resolveMapName(scenario, options.loadScenario ?? loadScenarioDefinitionById);
const useCache = !options.loadScenario && !options.loadMap;
const useCache = !options.loadScenario && !options.loadMap && !options.loadRegionMap;
const cached = useCache ? layoutCache.get(mapName) : undefined;
if (cached) {
return cached;
}
const map = await (options.loadMap ?? loadMapDefinitionByName)(mapName);
const [map, regionMap] = await Promise.all([
(options.loadMap ?? loadMapDefinitionByName)(mapName),
(options.loadRegionMap ?? loadRegionDisplayMapByName)(mapName),
]);
const layout: MapLayout = {
mapName,
cityList: map.cities.map((city) => ({
@@ -74,8 +89,8 @@ export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOp
y: city.position.y,
path: [...city.connections],
})),
regionMap: {},
levelMap: {},
regionMap,
levelMap: CITY_LEVEL_MAP,
};
if (useCache) {
+13 -3
View File
@@ -1,8 +1,14 @@
import { describe, expect, it, vi } from 'vitest';
import { loadRegionDisplayMapByName } from '../src/maps/mapDefinition.js';
import { loadMapLayout } from '../src/maps/mapLayout.js';
describe('map layout resource adapter', () => {
it('loads the Ref theme region labels from the shared resource', async () => {
await expect(loadRegionDisplayMapByName('che')).resolves.toMatchObject({ 1: '하북', 8: '동이' });
await expect(loadRegionDisplayMapByName('chess')).resolves.toMatchObject({ 1: '킹', 7: '빈칸' });
});
it('resolves the scenario map through the shared runtime loaders', async () => {
const loadScenario = vi.fn().mockResolvedValue({
config: { environment: { mapName: 'custom-map' } },
@@ -20,7 +26,9 @@ describe('map layout resource adapter', () => {
],
});
await expect(loadMapLayout('scenario_2601.json', { loadScenario, loadMap })).resolves.toEqual({
const loadRegionMap = vi.fn().mockResolvedValue({ 2: '테스트권역' });
await expect(loadMapLayout('scenario_2601.json', { loadScenario, loadMap, loadRegionMap })).resolves.toEqual({
mapName: 'custom-map',
cityList: [
{
@@ -33,11 +41,12 @@ describe('map layout resource adapter', () => {
path: [8],
},
],
regionMap: {},
levelMap: {},
regionMap: { 2: '테스트권역' },
levelMap: { 1: '수', 2: '진', 3: '관', 4: '이', 5: '소', 6: '중', 7: '대', 8: '특' },
});
expect(loadScenario).toHaveBeenCalledWith(2601);
expect(loadMap).toHaveBeenCalledWith('custom-map');
expect(loadRegionMap).toHaveBeenCalledWith('custom-map');
});
it('retains the che fallback for unknown preserved scenarios', async () => {
@@ -47,6 +56,7 @@ describe('map layout resource adapter', () => {
loadMapLayout('custom-runtime', {
loadScenario: vi.fn(),
loadMap,
loadRegionMap: vi.fn().mockResolvedValue({}),
})
).resolves.toMatchObject({ mapName: 'che' });
expect(loadMap).toHaveBeenCalledWith('che');