/**
* NPC «Пепельных луга»: определения (id/имя/позиция v1/диалоги/флаг
* знакомства), размещения по флагу сюжета (Клинт появляется в роще только
* после крючка Милы — контент не живёт вне своей главы). Позиция v1 —
* в тайлах (центр тайла = координата); в воксели конвертирует сцена
* (tileCenter). Перенос v1, тайл-мост тот же.
*/
import { FLAGS, type FlagId } from './ids';
import type { MannequinPalette } from '@rpg/engine';
/** NPC локации: диалоги и флаг сюжета после первого разговора. */
export interface NpcDef {
id: string;
name: string;
/** id области (v1: статичные жили в городе; в v2 область — явно). */
area: string;
/** Позиция ног в тайлах v1 (центр тайла = координата + 0.5). */
pos: { x: number; y: number };
/** Тайл привязки (floor от pos; вычисляется — в контенте не писать). */
tile: { x: number; y: number };
/** Первый диалог (когда флаг ещё не поднят). */
dialogueFirst: string;
/** Повторный диалог. */
dialogueRepeat: string;
/** Флаг, поднимаемый после первого диалога (ставит сам граф). */
flagKey: FlagId;
/** Палитра модели (голова/торс/ноги/рукава) — дизайн персонажа. */
colors: MannequinPalette;
}
/** NPC из raw-определения (тайл-мост общий для статичных и размещений). */
function npcFrom(d: Omit<NpcDef, 'tile'>): NpcDef {
return { ...d, tile: { x: Math.floor(d.pos.x), y: Math.floor(d.pos.y) } };
}
/** Статичные NPC: старейшина и торговка — в Звенце. */
export const NPCS: NpcDef[] = [
npcFrom({
id: 'elder',
name: 'Старейшина Ирвин',
area: 'zvenets',
// Не вплотную ни к двери дома (13,12), ни к колодцу (16,13): кнопка
// действия берёт ближайшую цель, E у двери/колодца не должен звать
// Ирвина — он стоит юго-западнее, вне радиуса их тайлов подхода.
pos: { x: 12.5, y: 15.5 },
dialogueFirst: 'elder_first',
dialogueRepeat: 'elder_repeat',
flagKey: FLAGS.met_elder,
// Старейшина: седая голова, зелёный плащ, тёмные штаны/рукава
colors: { head: '#b8b4a4', torso: '#5d7050', legs: '#4a4440', arms: '#4a4440' },
}),
npcFrom({
id: 'trader',
name: 'Торговка Мила',
area: 'zvenets',
// Не вплотную к двери лавки (43,19): E у двери не должен звать Милу.
pos: { x: 40.5, y: 20.5 },
dialogueFirst: 'trader_first',
dialogueRepeat: 'trader_repeat',
flagKey: FLAGS.met_mila,
// Мила: каштановая голова, ржаво-красный сарафан, светлые штаны
colors: { head: '#8a6a4e', torso: '#a05a44', legs: '#c9b18a', arms: '#a05a44' },
})
];
/**
* Размещения NPC по флагу: персонаж появляется в области, когда флаг сюжета
* поднят (и пропадает, если сценарий его снимет).
*/
export interface NpcPlacement {
/** Флаг, при котором NPC стоит на карте. */
flag: FlagId;
def: NpcDef;
}
export const NPC_PLACEMENTS: NpcPlacement[] = [
{
// Кузнец-починщик стоит у своего резонатора в роще после крючка Милы.
flag: FLAGS.act2_hook,
def: npcFrom({
id: 'clint',
name: 'Клинт',
area: 'rust_grove',
pos: { x: 23.5, y: 17.5 },
dialogueFirst: 'clint_first',
dialogueRepeat: 'clint_repeat',
flagKey: FLAGS.met_clint,
// Клинт: тёмная голова (шапка), медный фартук, тёмные ноги
colors: { head: '#5f5648', torso: '#c07a3a', legs: '#4d4a46', arms: '#8a5230' },
})
}
];
/** NPC области: статичные + размещения по поднятым флагам. */
export function npcsForArea(area: string, hasFlag: (f: FlagId) => boolean): NpcDef[] {
const statics = NPCS.filter((n) => n.area === area);
const placed = NPC_PLACEMENTS.filter((p) => p.def.area === area && hasFlag(p.flag)).map((p) => p.def);
return [...statics, ...placed];
}