Newer
Older
rpg / apps / game / src / data / npcs.ts
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);
}