import { SceneRegistry, tileToWorld, type Grid, type IsometricTileMap } from '@rpg/engine';
import type { AreaDef } from '../data/locations';
import type { NpcDef } from '../data/npcs';
import type { InteractableDef } from '../data/interactables';
/**
* Объекты сцены на движковом SceneRegistry: единый список «кто на карте»
* (NPC, интерактивы, пропы, фауна, враги) с индексом по тайлам.
* Клик-роутинг, подписи и телесные коллизии спрашивают здесь, а не перебирают
* параллельные списки. Роды задаёт игра: 'npc' | 'interactable' | 'prop' |
* 'fauna' | 'enemy'.
*/
/** Радиус тела NPC (юниты) — для будущих телесных коллизий. */
const NPC_RADIUS = 0.35;
export class SceneObjects {
readonly registry = new SceneRegistry();
constructor(area: AreaDef, private map: IsometricTileMap) {
for (const def of area.npcs) {
this.registry.add({
id: `npc:${def.id}`,
kind: 'npc',
pos: tileToWorld(def.tile.x, def.tile.y),
radius: NPC_RADIUS,
ref: def
});
}
for (const def of area.interactables ?? []) {
this.registry.add({
id: `int:${def.id}`,
kind: 'interactable',
pos: tileToWorld(def.tile.x, def.tile.y),
ref: def
});
}
// Пропы карты: footprint-объекты, занимают все свои тайлы.
for (const prop of map.props) {
this.registry.add({
id: `prop:${prop.x},${prop.y}`,
kind: 'prop',
pos: { x: prop.x + prop.w / 2, y: prop.y + prop.h / 2 },
footprint: { w: prop.w, h: prop.h },
ref: prop
});
}
}
/** NPC на тайле (клик-роутинг). */
npcAt(x: number, y: number): NpcDef | null {
return (this.registry.at(x, y, 'npc')?.ref as NpcDef | undefined) ?? null;
}
/** Интерактивный объект на тайле (клик-роутинг). */
interactableAt(x: number, y: number): InteractableDef | null {
return (this.registry.at(x, y, 'interactable')?.ref as InteractableDef | undefined) ?? null;
}
/** Ключ тайла (как в реестре; NPC не двигаются — кэш навсегда). */
private npcTiles: Set<number> | null = null;
/**
* Grid для построения путей: тайлы NPC считаются занятыми — A* не ведёт
* героя сквозь тело, где его остановит расталкивание. Остальная карта —
* живая (IsometricTileMap учитывает стены и пропы).
*/
walkGrid(): Grid {
if (!this.npcTiles) {
this.npcTiles = new Set<number>();
for (const npc of this.registry.byKind('npc')) {
this.npcTiles.add(npc.tile.y * 4096 + npc.tile.x);
}
}
const npcTiles = this.npcTiles;
return {
width: this.map.width,
height: this.map.height,
isWalkable: (x, y) => this.map.isWalkable(x, y) && !npcTiles.has(y * 4096 + x)
};
}
}