import type { NpcDef } from './npcs';
import type { EnemyKindId, EnemyPatrolDef } from './enemies';
import { NPCS } from './npcs';
import type { ItemId } from './items';
import { TILES } from './map';
import { FLAGS } from './ids';
import { INTERACTABLES, type InteractableDef } from './interactables';
/**
* Области демо-среза (улица и интерьер — общий контекст). Карты хранятся
* файлами assets/maps/<id>.map (формат rpg-map, RLE) и загружаются через
* parseMap в BootScene.
*/
export type AreaId = 'meadows' | 'ponds' | 'zvenets' | 'house_elder' | 'shop';
/** Куда ведёт переход: в другую область (с точкой входа) или назад, откуда вошли. */
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 AmbienceLayerDef {
/** Ключ лупа (Game.AUDIO_KEYS). */
key: string;
/** Тайлы-источники (иды из TILES); пусто/нет — точка at. */
tiles?: number[];
/** Точка-источник в тайлах (центр башни и т.п.). */
at?: { x: number; y: number };
/** Радиус слышимости в юнитах (за ним слой молчит); нет at/tiles — глобальный слой. */
radius?: number;
/** Громкость (для глобального — константа; по умолчанию 0.5). */
volume?: number;
}
/** Источник света: точка в тайлах или все тайлы заданных id; радиус в юнитах. */
export interface LightSourceDef {
id: string;
/** Центр источника в тайлах (центр ромба: +0.5); для at/tiles — одно из двух. */
at?: { x: number; y: number };
/** Альтернатива: свет на каждом тайле этого id (позиции из карты). */
tiles?: number[];
/** Радиус в юнитах (1 юнит = тайл; по умолчанию 3). */
radius?: number;
/** Цвет свечения (по умолчанию 0xf2b45a — тёплый F1). */
color?: number;
/** Яркость (по умолчанию 0.9). */
intensity?: number;
/** Амплитуда мерцания 0..1 (по умолчанию 0 — ровный). */
flicker?: number;
/** Источник активен только при наличии (whenFlag) / отсутствии (notFlag) флага. */
whenFlag?: string;
notFlag?: string;
/** Ночной источник: горит при факторе ночи ≥ 0.5 (вечер/ночь по часам). */
whenNight?: boolean;
}
/** Освещение области: ambient (multiply-цвет) + статические источники. */
export interface LightingDef {
/** 0xffffff — не трогает сцену; тёмный/цветной — тон и затемнение сцены. */
ambient?: number;
/** Ночной ambient (когда стоит флаг вечера); нет — ночь не меняет тон. */
nightAmbient?: number;
sources?: LightSourceDef[];
}
export interface AreaDef {
id: AreaId;
name: string;
/** Амбиент области (кроссфейд при переходе, ключ из Game.AUDIO_KEYS); нет — тишина. */
ambience?: string;
/** Музыкальная тема области (ключ из data/music.ts, спек-музыка); нет — тишина. */
theme?: string;
/** Стартовый тайл (новая игра / респаун / фолбэк return). */
spawn: { x: number; y: number };
npcs: NpcDef[];
enemies: { kind: EnemyKindId; tile: { x: number; y: number }; patrol?: EnemyPatrolDef }[];
transitions: TransitionDef[];
/** Интерактивные объекты (сундуки, очаги, прилавки). */
interactables?: InteractableDef[];
/** Атмосферные частицы: 'ash' — пепел, 'fog' — туман прудов, 'none' — интерьер. */
atmosphere: 'ash' | 'fog' | 'none';
/** Освещение: ambient-тон + статические источники (очаг, окна). */
lighting?: LightingDef;
/** Локальные амбиент-слои поверх областного (вода, гул башни, дома). */
ambienceLayers?: AmbienceLayerDef[];
/** Зоны наката (необязательно). */
hazards?: HazardDef[];
/** Сборный тайл (лунный колокольчик): клик по нему — сбор. Нет — в области не собирается. */
harvestTile?: number;
/** Спавны пейзажных оленей (тайлы). */
fauna?: { x: number; y: number }[];
}
export const AREAS: Record<AreaId, AreaDef> = {
meadows: {
id: 'meadows',
name: 'Выжженные луга',
ambience: 'ambience/meadows',
theme: '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 },
patrol: { points: [{ x: 19, y: 6 }, { x: 22, y: 5 }], pauseSec: 1.2 }
},
{ kind: 'crawler', tile: { x: 22, y: 21 } },
// Тяжеловес бродит вдоль юго-западной низины.
{
kind: 'heavy',
tile: { x: 6, y: 22 },
patrol: { points: [{ x: 6, y: 22 }, { x: 4, y: 20 }], pauseSec: 2 }
}
],
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
}
],
atmosphere: 'ash',
// Светлый пепельный день: чуть холоднее белого. Вечером (по часам)
// — синие сумерки: лунные колокольчики светятся.
lighting: {
ambient: 0xd8dade,
nightAmbient: 0x6a7288,
sources: [
{
id: 'bells',
tiles: [TILES.BELLFLOWER],
color: 0xf2cd68,
radius: 1.4,
intensity: 0.5,
flicker: 0.15,
whenNight: true
}
]
},
interactables: INTERACTABLES.meadows,
// Глобальный ветер: порывы свистят выше базового амбиента лугов.
ambienceLayers: [{ key: 'ambience/wind', volume: 0.3 }],
// Безгласные олени у берегов прудов — подходят к звону.
fauna: [
{ x: 9, y: 18 },
{ x: 12, y: 20 }
]
},
ponds: {
id: 'ponds',
name: 'Серые пруды',
ambience: 'ambience/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 } } }
],
atmosphere: 'fog',
// Молочно-серый день у прудов; вечером — глухие синие сумерки.
lighting: { ambient: 0xc4cad2, nightAmbient: 0x767d92 },
// Вода прудов слышна у берега — тихий плеск поверх ветрового амбиента.
// Глобальный ветер + вода у берегов (громкость — от дистанции).
ambienceLayers: [
{ key: 'ambience/wind', volume: 0.3 },
// Вода — только вплотную к берегу и совсем тихо (у нас лужи, не море).
{ key: 'ambience/ponds_water', tiles: [TILES.WATER], radius: 2.5, volume: 0.22 }
],
// Свежий накат — низины у берегов: без вощёного полотна здесь не дышать.
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 }
]
}
],
interactables: INTERACTABLES.ponds,
// Лунные колокольчики собираются только у прудов (квест «Три цветка»).
harvestTile: TILES.BELLFLOWER,
// Один олень на северо-западном берегу.
fauna: [{ x: 4, y: 6 }]
},
zvenets: {
id: 'zvenets',
name: 'Звенец',
ambience: 'ambience/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
},
// Двери: клик по дому — внутрь (вход у проёма; выход — kind 'return').
{
tile: { x: 7, y: 6 },
label: 'Дом Ирвина',
target: { kind: 'area', area: 'house_elder', entry: { x: 5, y: 7 } },
trigger: 'click',
duration: 0.2
},
{
tile: { x: 21, y: 9 },
label: 'Лавка Милы',
target: { kind: 'area', area: 'shop', entry: { x: 5, y: 7 } },
trigger: 'click',
duration: 0.2
}
],
interactables: INTERACTABLES.zvenets,
atmosphere: 'ash',
// Серые сумерки деревни; вечером (по часам) — синие сумерки и тёплые
// окна трёх домов; после звонка — холодный отсвет резонанса от башни.
lighting: {
ambient: 0xdad6d0,
nightAmbient: 0x7b8298,
sources: [
// Окна — на теле спрайта дома (тело занимает тайл выше-левее базового);
// днём не горят, зажигаются вечером.
{ id: 'window_elder', at: { x: 6.5, y: 5.5 }, radius: 1.8, intensity: 0.45, flicker: 0.1, whenNight: true },
{ id: 'window_north', at: { x: 17.5, y: 4.5 }, radius: 1.8, intensity: 0.45, flicker: 0.1, whenNight: true },
{ id: 'window_shop', at: { x: 20.5, y: 8.5 }, radius: 1.8, intensity: 0.45, flicker: 0.1, whenNight: true },
{
id: 'tower_resonance',
at: { x: 14, y: 7 },
color: 0x4a737f,
radius: 2.5,
intensity: 0.6,
flicker: 0.25,
whenFlag: FLAGS.rang_tower
}
]
},
// Гул колокола башни и скрипы вагонных щитов — слышны вблизи источников.
ambienceLayers: [
{ key: 'ambience/tower_hum', at: { x: 14, y: 7 }, radius: 8, volume: 0.5 },
{ key: 'ambience/houses', tiles: [TILES.HOUSE], radius: 5, volume: 0.4 }
]
},
// --- интерьеры: рамка WALL, пол FLOOR, дверной проём снизу ---
house_elder: {
id: 'house_elder',
name: 'Дом Ирвина',
spawn: { x: 5, y: 7 },
npcs: [],
enemies: [],
transitions: [
// Проём в нижней стене: шаг наружу — назад, откуда вошёл.
{ tile: { x: 5, y: 8 }, target: { kind: 'return' }, duration: 0.2 }
],
interactables: INTERACTABLES.house_elder,
atmosphere: 'none',
// Тёмный холодный интерьер (без света окна выглядят как улица);
// очаг: тлеет, пока квест не закрыт, после — горит ровнее и ярче.
lighting: {
ambient: 0x54586a,
sources: [
{
id: 'hearth_low',
at: { x: 8.5, y: 0.5 },
radius: 4,
intensity: 0.75,
flicker: 0.35,
notFlag: FLAGS.quest_bells_done
},
{
id: 'hearth_high',
at: { x: 8.5, y: 0.5 },
radius: 5,
intensity: 1.05,
flicker: 0.2,
whenFlag: FLAGS.quest_bells_done
}
]
}
},
shop: {
id: 'shop',
name: 'Лавка Милы',
spawn: { x: 5, y: 7 },
npcs: [],
enemies: [],
transitions: [{ tile: { x: 5, y: 8 }, target: { kind: 'return' }, duration: 0.2 }],
interactables: INTERACTABLES.shop,
atmosphere: 'none',
// Полутёмная лавка: лампа над прилавком.
lighting: {
ambient: 0x5a5e70,
sources: [{ id: 'counter_lamp', at: { x: 5.5, y: 3.5 }, radius: 3, intensity: 0.7, flicker: 0.05 }]
}
}
};
/** Область по id (фолбэк 'meadows' — для старых сейвов и пустых id). */
export function areaOf(id: string | null | undefined): AreaDef {
return AREAS[(id ?? 'meadows') as AreaId] ?? AREAS.meadows;
}