Newer
Older
rpg / apps / game / src / data / locations.ts
import type { NpcDef } from './npcs';
import type { EnemyKindId } from './enemies';
import { NPCS } from './npcs';
import type { ItemId } from './items';

/**
 * Области демо-среза (улица и интерьер — общий контекст). Карты хранятся
 * файлами assets/maps/<id>.map (формат rpg-map, RLE) и загружаются через
 * parseMap в BootScene.
 */
export type AreaId = 'meadows' | 'ponds' | 'zvenets';

/** Куда ведёт переход: в другую область (с точкой входа) или назад, откуда вошли. */
export type TransitionTarget =
    | { kind: 'area'; area: AreaId; entry: { x: number; y: number } }
    | { kind: 'return' };

/**
 * Единый механизм переходов: выходы между локациями (step), двери/порталы/
 * колодцы (click). Первая подходящая по тайлу и триггеру — срабатывает.
 */
export interface TransitionDef {
    /** Тайл-триггер. */
    tile: { x: number; y: number };
    /** Подпись для подсказки при близости (позже) и снапшота. */
    label?: string;
    target: TransitionTarget;
    /** step — наступил (по умолчанию), click — клик по тайлу (двери, порталы). */
    trigger?: 'step' | 'click';
    /** Условия: не выполнены — переход не срабатывает, герой получает lockedText. */
    requiresFlag?: string;
    requiresItem?: ItemId;
    lockedText?: string;
    /** Длительность fade (сек), по умолчанию 0.4. */
    duration?: number;
}

/** Зона опасности (накат): герой без нужного предмета еле идёт и рискует голосом. */
export interface HazardDef {
    name: string;
    /** Тайлы низины (свежий пласт пепла). */
    tiles: { x: number; y: number }[];
    /** Без чего нельзя дышать здесь (по умолчанию — полотно-маска). */
    requiresItem?: ItemId;
}

export interface AreaDef {
    id: AreaId;
    name: string;
    /** Музыка области (кроссфейд при переходе). */
    music: string;
    /** Стартовый тайл (новая игра / респаун / фолбэк return). */
    spawn: { x: number; y: number };
    npcs: NpcDef[];
    enemies: { kind: EnemyKindId; tile: { x: number; y: number } }[];
    transitions: TransitionDef[];
    /** Атмосферные частицы: 'ash' — пепел, 'fog' — туман прудов, 'none' — интерьер. */
    ambience: 'ash' | 'fog' | 'none';
    /** Зоны наката (необязательно). */
    hazards?: HazardDef[];
    /** Спавны пейзажных оленей (тайлы). */
    fauna?: { x: number; y: number }[];
}

export const AREAS: Record<AreaId, AreaDef> = {
    meadows: {
        id: 'meadows',
        name: 'Выжженные луга',
        music: 'music/meadows',
        spawn: { x: 14, y: 14 },
        npcs: [],
        enemies: [
            { kind: 'crawler', tile: { x: 8, y: 9 } },
            { kind: 'crawler', tile: { x: 10, y: 7 } },
            { kind: 'spitter', tile: { x: 19, y: 6 } },
            { kind: 'crawler', tile: { x: 22, y: 21 } },
            { kind: 'heavy', tile: { x: 6, y: 22 } }
        ],
        transitions: [
            // Северо-западный угол: тропа на Серые пруды.
            { tile: { x: 2, y: 2 }, target: { kind: 'area', area: 'ponds', entry: { x: 3, y: 3 } } },
            // Тропа в Звенец (деревня-мастерская).
            { tile: { x: 26, y: 14 }, target: { kind: 'area', area: 'zvenets', entry: { x: 3, y: 10 } } },
            // Встречный колодец: звон в Звенец (клик по тайлу колодца).
            {
                tile: { x: 22, y: 20 },
                label: 'Старый колодец',
                target: { kind: 'area', area: 'zvenets', entry: { x: 9, y: 7 } },
                trigger: 'click',
                duration: 0.2
            }
        ],
        ambience: 'ash',
        // Безгласные олени у берегов прудов — подходят к звону.
        fauna: [
            { x: 9, y: 18 },
            { x: 12, y: 20 }
        ]
    },
    ponds: {
        id: 'ponds',
        name: 'Серые пруды',
        music: 'music/ponds',
        spawn: { x: 3, y: 3 },
        npcs: [],
        enemies: [
            { kind: 'crawler', tile: { x: 4, y: 11 } },
            { kind: 'crawler', tile: { x: 9, y: 18 } },
            { kind: 'spitter', tile: { x: 18, y: 10 } },
            { kind: 'spitter', tile: { x: 18, y: 18 } },
            { kind: 'heavy', tile: { x: 13, y: 17 } }
        ],
        transitions: [
            // Обратно на луга.
            { tile: { x: 2, y: 2 }, target: { kind: 'area', area: 'meadows', entry: { x: 3, y: 3 } } }
        ],
        ambience: 'fog',
        // Свежий накат — низины у берегов: без вощёного полотна здесь не дышать.
        hazards: [
            {
                name: 'Свежий накат',
                requiresItem: 'cloth',
                tiles: [
                    { x: 5, y: 16 },
                    { x: 6, y: 17 },
                    { x: 7, y: 17 },
                    { x: 8, y: 18 },
                    { x: 9, y: 18 },
                    { x: 10, y: 18 },
                    { x: 11, y: 19 },
                    { x: 12, y: 19 },
                    { x: 13, y: 18 },
                    { x: 14, y: 18 },
                    { x: 15, y: 17 },
                    { x: 4, y: 9 },
                    { x: 3, y: 8 },
                    { x: 15, y: 6 },
                    { x: 16, y: 4 }
                ]
            }
        ],
        // Один олень на северо-западном берегу.
        fauna: [{ x: 4, y: 6 }]
    },
    zvenets: {
        id: 'zvenets',
        name: 'Звенец',
        music: 'music/zvenets',
        spawn: { x: 14, y: 10 },
        npcs: NPCS,
        enemies: [],
        transitions: [
            // Тропа обратно на луга.
            { tile: { x: 2, y: 10 }, target: { kind: 'area', area: 'meadows', entry: { x: 25, y: 14 } } },
            // Колодец у дома Ирвина: телепорт на луга (клик; встречный лугам).
            {
                tile: { x: 9, y: 6 },
                label: 'Старый колодец',
                target: { kind: 'area', area: 'meadows', entry: { x: 22, y: 21 } },
                trigger: 'click',
                duration: 0.2
            }
        ],
        ambience: 'ash'
    }
};

/** Область по id (фолбэк 'meadows' — для старых сейвов и пустых id). */
export function areaOf(id: string | null | undefined): AreaDef {
    return AREAS[(id ?? 'meadows') as AreaId] ?? AREAS.meadows;
}