import { CutsceneRunner, IsometricTileMap, tileToWorld, worldToScreen, type Vec2 } from '@rpg/engine';
import { Game } from '../../Game';
import { VARS } from '../../data/ids';
import { TILES } from '../../data/map';
import { QUEST_FLOWERS, QUEST_PLANT_TILES, QUEST_TOWER_GREEN, QUEST_TOWER_TILE } from '../../data/quests';
import { playSfx } from '../../data/sfxSpecs';
/**
* Квест «Три цветка» (docs/world.md, акт 1): сбор лунных колокольчиков,
* посадка поляны у тропы и кат-сцена сдачи (колокол башни, зелень).
* Изменения тайлов идут через map.setTile с поддержанием индекса
* layerTilePos — свет по тайлам-источникам остаётся живым.
*/
export class FlowerQuest {
constructor(
private game: Game,
private map: IsometricTileMap,
/** Позиции тайлов-источников по id (свет/слои): меняем при setTile. */
private layerTilePos: Map<number, Vec2[]>,
/** Экранный центр ромба тайла (с Origin карты). */
private tileCenter: (tx: number, ty: number) => { x: number; y: number },
/** Всплеск пеплинок в точке (CombatViews.hitBurst). */
private burst: (s: { x: number; y: number }) => void,
private showToast: (text: string) => void,
private cutscene: CutsceneRunner
) {}
/** id тайла карты. */
tileId(x: number, y: number): number {
return this.map.data.tiles[y * this.map.data.width + x];
}
/** Смена тайла с поддержанием индекса layerTilePos (свет по тайлам живой). */
setTileTracked(x: number, y: number, id: number): void {
const prev = this.tileId(x, y);
this.map.setTile(x, y, id);
const from = this.layerTilePos.get(prev);
if (from) {
const i = from.findIndex((p) => p.x === x && p.y === y);
if (i >= 0) from.splice(i, 1);
if (from.length === 0) this.layerTilePos.delete(prev);
}
const list = this.layerTilePos.get(id) ?? [];
list.push({ x, y });
this.layerTilePos.set(id, list);
}
/** Сбор лунного колокольчика: тайл зеленеет, цветок — в сумку, прогресс — в vars. */
collectFlower(x: number, y: number): void {
this.setTileTracked(x, y, TILES.GRASS);
this.game.inventory.add('bellflower');
const n = this.game.state.getNumber(VARS.flowers) + 1;
this.game.state.setVar(VARS.flowers, n);
this.game.engine.events.emit('quest:flower', { n });
playSfx(this.game.audio, 'sfx/bell_hit', 0.6);
this.burst(this.tileCenter(x, y));
this.showToast(`Лунный колокольчик (${Math.min(n, QUEST_FLOWERS)}/${QUEST_FLOWERS})`);
}
/** Посадка цветов у тропы: поляна гудит колокольчиками и разрастается. */
plantFlowers(): void {
const left = this.game.state.getNumber(VARS.flowers) - QUEST_FLOWERS;
this.game.state.setVar(VARS.flowers, Math.max(0, left));
for (const [tx, ty] of QUEST_PLANT_TILES) {
this.setTileTracked(tx, ty, TILES.BELLFLOWER);
this.burst(this.tileCenter(tx, ty));
}
// Пересев: поляна расползается на соседние тайлы (якорь «надежда растёт»).
for (const [tx, ty] of QUEST_PLANT_TILES) {
for (const [dx, dy] of [
[1, 0],
[-1, 0],
[0, 1],
[0, -1]
]) {
const id = this.tileId(tx + dx, ty + dy);
if (id === TILES.GRASS || id === TILES.ASH) this.setTileTracked(tx + dx, ty + dy, TILES.BELLFLOWER);
}
}
void this.game.audio.play('sfx/bell_low', 0.8);
this.showToast('Цветы в земле. Поляна гудит.');
}
/** Кат-сцена сдачи: посадка, камера к башне, удар колокола, зелень — финал акта 1. */
startHandInCutscene(): void {
// Башня Звенца: тайл в данных, камере нужны юниты.
const tower = tileToWorld(QUEST_TOWER_TILE.x, QUEST_TOWER_TILE.y);
const s = worldToScreen(tower.x, tower.y);
this.cutscene.play([
// Пересев: поляна гудит, герой стоит.
{ kind: 'call', fn: () => this.plantFlowers(), seconds: 0.8 },
// Камера уходит к башне.
{ kind: 'cameraMove', x: tower.x, y: tower.y, seconds: 1.2, camera: this.game.camera },
// Удар колокола: низкий звон, тряска, кольцо резонанса.
{
kind: 'burst',
fn: () => {
void this.game.audio.play('sfx/bell_low', 1);
this.game.camera.addShake(2.5, 0.4);
this.burst(s);
},
seconds: 1
},
// Зелень поднимается у подножия башни (визуальный финал акта).
{
kind: 'burst',
fn: () => {
for (const [tx, ty] of QUEST_TOWER_GREEN) {
const id = this.tileId(tx, ty);
if (id === TILES.GRASS || id === TILES.ASH) {
this.setTileTracked(tx, ty, TILES.BELLFLOWER);
this.burst(this.tileCenter(tx, ty));
}
}
},
seconds: 1.2
},
// Тост-финал акта 1.
{ kind: 'call', fn: () => this.showToast('Пепел отступил у тропы. Машина дышит тише... но дышит.') }
]);
}
}