import type { GameState } from '@rpg/engine';
import type { ItemId } from '../data/items';
import {
resolveInteractable,
type InteractableDef,
type InteractCtx,
type InteractPick,
type InteractResponse
} from '../data/interactables';
/**
* Рантайм интерактивных объектов области: поиск по тайлу, флаги used:<id>,
* применение эффектов реакции через sink (сцена подставляет сумку/звук/тост).
*/
/** Эффекты реакции — мост между чистым резолвером и игрой. */
export interface InteractSink {
hasItem(id: ItemId): boolean;
give(id: ItemId, count: number): void;
playSound(key: string): void;
showToast(text: string): void;
}
export class Interactables {
constructor(
private defs: InteractableDef[],
private state: GameState,
private sink: InteractSink
) {}
all(): InteractableDef[] {
return this.defs;
}
/** Объект на тайле (или null). */
defAt(x: number, y: number): InteractableDef | null {
return this.defs.find((d) => d.tile.x === x && d.tile.y === y) ?? null;
}
/** Одноразовый объект уже использован (флаг used:<id> в GameState). */
isUsed(id: string): boolean {
return this.state.hasFlag(`used:${id}`);
}
/** Взаимодействие: выбор реакции + применение эффектов. */
tryInteract(def: InteractableDef): InteractPick {
const ctx: InteractCtx = {
hasFlag: (f) => this.state.hasFlag(f),
hasItem: (i) => this.sink.hasItem(i),
getVar: (id) => this.state.getNumber(id),
used: (id) => this.isUsed(id)
};
const pick = resolveInteractable(def, ctx);
if (pick?.ok) this.apply(def, pick.response);
return pick;
}
/** Применить эффекты реакции (порядок: флаги -> вар -> предмет -> звук -> тост). */
private apply(def: InteractableDef, r: InteractResponse): void {
for (const f of r.setFlags ?? []) this.state.setFlag(f);
for (const f of r.clearFlags ?? []) this.state.clearFlag(f);
if (r.setVar) this.state.setVar(r.setVar.id, r.setVar.value);
if (r.addVar) this.state.setVar(r.addVar.id, this.state.getNumber(r.addVar.id) + r.addVar.by);
if (r.gives) this.sink.give(r.gives, r.count ?? 1);
if (def.once) this.state.setFlag(`used:${def.id}`);
if (r.sound) this.sink.playSound(r.sound);
this.sink.showToast(r.text);
}
}