Files
whale-town-end-v2/src/business/world_npc/world_npc.world.ts

133 lines
8.0 KiB
TypeScript

import { WorldLocation, WorldRouteEdge } from './world_npc.types';
export const WORLD_NPC_PUBLIC_MAP_IDS = ['whale_port', 'work_zone', 'whale_cafe'] as const;
export type WorldNpcPublicMapId = typeof WORLD_NPC_PUBLIC_MAP_IDS[number];
const WORLD_NPC_PUBLIC_MAP_ID_SET = new Set<string>(WORLD_NPC_PUBLIC_MAP_IDS);
export function isWorldNpcPublicMap(mapId: string): mapId is WorldNpcPublicMapId {
return WORLD_NPC_PUBLIC_MAP_ID_SET.has(mapId);
}
export const WORLD_LOCATIONS: readonly WorldLocation[] = [
{
id: 'square_guild_reception', mapId: 'whale_port', name: '公会接待处', x: -60, y: -430,
tags: ['organize', 'socialize'],
slots: [{ x: -300, y: -430 }, { x: -160, y: -430 }, { x: -20, y: -430 }, { x: 120, y: -430 }],
},
{
id: 'square_dock_guide', mapId: 'whale_port', name: '码头向导岗', x: -720, y: 437,
tags: ['organize', 'socialize', 'reflect'],
slots: [{ x: -900, y: 437 }, { x: -780, y: 437 }, { x: -660, y: 437 }, { x: -540, y: 437 }],
},
{
id: 'square_dock_research', mapId: 'whale_port', name: '广场海边研究点', x: -400, y: -180,
tags: ['research', 'reflect'],
slots: [{ x: -470, y: -250 }, { x: -330, y: -250 }, { x: -470, y: -110 }, { x: -330, y: -110 }],
},
{
id: 'square_forum', mapId: 'whale_port', name: '广场交流区', x: 0, y: -280,
tags: ['socialize', 'share'],
// Keep every visual standing point clear of the fountain footprint. The
// last point is used by Niulai; -150 is too close once his sprite height
// and ground anchor are accounted for.
slots: [{ x: 0, y: -430 }, { x: 0, y: -340 }, { x: 0, y: 325 }, { x: 0, y: -240 }],
},
{
id: 'square_notice_board', mapId: 'whale_port', name: '广场公告栏', x: -520, y: 480,
tags: ['organize', 'share'],
slots: [{ x: -700, y: 480 }, { x: -580, y: 480 }, { x: -460, y: 480 }, { x: -340, y: 480 }],
},
{ id: 'square_northwest_walkway', mapId: 'whale_port', name: '广场西北步道', x: -380, y: -380, tags: ['transit'] },
{ id: 'square_north_walkway', mapId: 'whale_port', name: '广场北侧步道', x: 0, y: -380, tags: ['transit'] },
{ id: 'square_west_walkway', mapId: 'whale_port', name: '喷泉西侧步道', x: -380, y: 250, tags: ['transit'] },
{ id: 'square_dock_inland_approach', mapId: 'whale_port', name: '码头内侧通道', x: -500, y: 400, tags: ['transit'] },
{ id: 'square_west_lower_approach', mapId: 'whale_port', name: '广场西侧下行通道', x: -340, y: 470, tags: ['transit'] },
{ id: 'square_south_walkway', mapId: 'whale_port', name: '广场南侧步道', x: -360, y: 650, tags: ['transit'] },
{ id: 'square_south_center_path', mapId: 'whale_port', name: '广场南侧中央通道', x: 0, y: 600, tags: ['transit'] },
{ id: 'square_bottom_gate_path', mapId: 'whale_port', name: '广场底部门前通道', x: 0, y: 760, tags: ['transit'] },
{ id: 'square_work_gate', mapId: 'whale_port', name: '广场南门', x: 0, y: 900, tags: ['transit'] },
{ id: 'work_square_gate', mapId: 'work_zone', name: '打工区北门', x: 0, y: 900, tags: ['transit'] },
{ id: 'work_south_crossroad', mapId: 'work_zone', name: '打工区南侧道路', x: 0, y: 650, tags: ['transit'] },
{ id: 'work_west_crossroad', mapId: 'work_zone', name: '打工区西侧道路', x: -650, y: 650, tags: ['transit'] },
{ id: 'work_cafe_south_approach', mapId: 'work_zone', name: '咖啡馆南侧道路', x: -850, y: 650, tags: ['transit'] },
{ id: 'work_cafe_door_approach', mapId: 'work_zone', name: '咖啡馆门前道路', x: -1085, y: 600, tags: ['transit'] },
{ id: 'work_ai_approach', mapId: 'work_zone', name: 'AI 服务站门前道路', x: 230, y: 925, tags: ['transit'] },
{
id: 'work_ai_station', mapId: 'work_zone', name: 'AI 服务站', x: 450, y: 925,
tags: ['research', 'organize'],
slots: [{ x: 300, y: 925 }, { x: 400, y: 925 }, { x: 500, y: 925 }, { x: 600, y: 925 }],
},
{ id: 'work_cafe_gate', mapId: 'work_zone', name: '鲸鱼咖啡馆入口', x: -1085, y: 445, tags: ['transit', 'socialize'] },
{ id: 'cafe_entrance', mapId: 'whale_cafe', name: '咖啡馆入口', x: 0, y: 392, tags: ['transit'] },
{
id: 'cafe_research_table', mapId: 'whale_cafe', name: '咖啡馆交流区', x: -125, y: 300,
tags: ['research', 'socialize'],
slots: [{ x: -350, y: 300 }, { x: -200, y: 300 }, { x: -50, y: 300 }, { x: 100, y: 300 }],
},
] as const;
export const WORLD_ROUTE_EDGES: readonly WorldRouteEdge[] = [
{ from: 'square_guild_reception', to: 'square_north_walkway', kind: 'walk', bidirectional: true },
{ from: 'square_dock_guide', to: 'square_dock_inland_approach', kind: 'walk', bidirectional: true },
{ from: 'square_dock_inland_approach', to: 'square_notice_board', kind: 'walk', bidirectional: true },
{ from: 'square_dock_research', to: 'square_northwest_walkway', kind: 'walk', bidirectional: true },
{ from: 'square_northwest_walkway', to: 'square_north_walkway', kind: 'walk', bidirectional: true },
{ from: 'square_north_walkway', to: 'square_forum', kind: 'walk', bidirectional: true },
{ from: 'square_northwest_walkway', to: 'square_west_walkway', kind: 'walk', bidirectional: true },
{ from: 'square_west_walkway', to: 'square_dock_inland_approach', kind: 'walk', bidirectional: true },
{ from: 'square_west_walkway', to: 'square_west_lower_approach', kind: 'walk', bidirectional: true },
{ from: 'square_west_lower_approach', to: 'square_south_walkway', kind: 'walk', bidirectional: true },
{ from: 'square_south_walkway', to: 'square_south_center_path', kind: 'walk', bidirectional: true },
{ from: 'square_south_center_path', to: 'square_bottom_gate_path', kind: 'walk', bidirectional: true },
{ from: 'square_bottom_gate_path', to: 'square_work_gate', kind: 'walk', bidirectional: true },
{ from: 'square_work_gate', to: 'work_square_gate', kind: 'transition', bidirectional: true },
{ from: 'work_square_gate', to: 'work_south_crossroad', kind: 'walk', bidirectional: true },
{ from: 'work_south_crossroad', to: 'work_ai_approach', kind: 'walk', bidirectional: true },
{ from: 'work_ai_approach', to: 'work_ai_station', kind: 'walk', bidirectional: true },
{ from: 'work_south_crossroad', to: 'work_west_crossroad', kind: 'walk', bidirectional: true },
{ from: 'work_west_crossroad', to: 'work_cafe_south_approach', kind: 'walk', bidirectional: true },
{ from: 'work_cafe_south_approach', to: 'work_cafe_door_approach', kind: 'walk', bidirectional: true },
{ from: 'work_cafe_door_approach', to: 'work_cafe_gate', kind: 'walk', bidirectional: true },
{ from: 'work_cafe_gate', to: 'cafe_entrance', kind: 'transition', bidirectional: true },
{ from: 'cafe_entrance', to: 'cafe_research_table', kind: 'walk', bidirectional: true },
] as const;
export function getWorldLocation(id: string): WorldLocation {
const location = WORLD_LOCATIONS.find((item) => item.id === id);
if (!location) throw new Error(`Unknown world location: ${id}`);
if (!isWorldNpcPublicMap(location.mapId)) {
throw new Error(`World NPC location is outside the public town: ${id}`);
}
return location;
}
export function findWorldRoute(fromId: string, toId: string): string[] {
if (fromId === toId) return [fromId];
const queue: string[][] = [[fromId]];
const visited = new Set<string>([fromId]);
while (queue.length > 0) {
const path = queue.shift()!;
const current = path[path.length - 1];
for (const edge of WORLD_ROUTE_EDGES) {
let next = '';
if (edge.from === current) next = edge.to;
else if (edge.bidirectional && edge.to === current) next = edge.from;
if (!next || visited.has(next)) continue;
const candidate = [...path, next];
if (next === toId) return candidate;
visited.add(next);
queue.push(candidate);
}
}
throw new Error(`No world route from ${fromId} to ${toId}`);
}
export function getRouteKind(fromId: string, toId: string): WorldRouteEdge['kind'] {
const edge = WORLD_ROUTE_EDGES.find((item) =>
(item.from === fromId && item.to === toId) ||
(item.bidirectional && item.from === toId && item.to === fromId));
if (!edge) throw new Error(`No direct world edge from ${fromId} to ${toId}`);
return edge.kind;
}