import type { Vec2 } from '@rpg/engine';
import { FLAGS, type FlagId } from './ids';
/**
* NPC локации: позиция в юнитах, спрайт/атлас, диалоги и флаг сюжета
* после первого разговора. Тайл привязки вычисляется из pos при загрузке
* модуля — снапшот/валидатор/клики читают `tile`, реестр объектов — `pos`.
*/
export interface NpcDef {
id: string;
name: string;
/** Позиция ног в мировых юнитах (центр тайла = координата + 0.5). */
pos: Vec2;
/** Тайл привязки (floor от pos; вычисляется — в контенте не писать). */
tile: { x: number; y: number };
/** Ключ спрайта в ассетах (chars/<sprite>.png). */
sprite: string;
/** Атлас idle-анимации (по умолчанию chars/npcs_sheet.json). */
atlas?: string;
/** Первый диалог (когда флаг ещё не поднят). */
dialogueFirst: string;
/** Повторный диалог. */
dialogueRepeat: string;
/** Флаг, поднимаемый после первого диалога. */
flagKey: FlagId;
}
const NPCS_RAW: Omit<NpcDef, 'tile'>[] = [
{
id: 'elder',
name: 'Старейшина Ирвин',
pos: { x: 12.5, y: 9.5 },
sprite: 'elder_irwin',
dialogueFirst: 'elder_first',
dialogueRepeat: 'elder_repeat',
flagKey: FLAGS.met_elder
},
{
id: 'trader',
name: 'Торговка Мила',
pos: { x: 17.5, y: 11.5 },
sprite: 'trader_mila',
dialogueFirst: 'trader_first',
dialogueRepeat: 'trader_repeat',
flagKey: FLAGS.met_mila
}
];
export const NPCS: NpcDef[] = NPCS_RAW.map((d) => ({
...d,
tile: { x: Math.floor(d.pos.x), y: Math.floor(d.pos.y) }
}));
/**
* Размещения NPC по флагу: персонаж появляется в области, когда флаг сюжета
* поднят (и пропадает, если сценарий его снимет). Так акт 2 заселяет рощу
* только после крючка Милы — контент не живёт вне своей главы.
*/
export interface NpcPlacement {
/** id области (из locations.ts — строкой, чтобы не зациклить импорт). */
area: string;
/** Флаг, при котором NPC стоит на карте. */
flag: FlagId;
def: NpcDef;
}
/** Клинт из raw-определения (общий с NPCS тайловый мост). */
function npcFrom(d: Omit<NpcDef, 'tile'>): NpcDef {
return { ...d, tile: { x: Math.floor(d.pos.x), y: Math.floor(d.pos.y) } };
}
export const NPC_PLACEMENTS: NpcPlacement[] = [
{
// Кузнец-починщик стоит у своего резонатора в роще после крючка Милы.
area: 'rust_grove',
flag: FLAGS.act2_hook,
def: npcFrom({
id: 'clint',
name: 'Клинт',
pos: { x: 11.5, y: 9.5 },
sprite: 'clint',
dialogueFirst: 'clint_first',
dialogueRepeat: 'clint_repeat',
flagKey: FLAGS.met_clint
})
}
];
/** NPC области: статичные (area.npcs) + размещения по поднятым флагам. */
export function npcsForArea(area: string, hasFlag: (f: FlagId) => boolean): NpcDef[] {
return NPC_PLACEMENTS.filter((p) => p.area === area && hasFlag(p.flag)).map((p) => p.def);
}