From 9a446054691b3f888400e30af56de95b9003fd15 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Tue, 28 Apr 2026 19:58:43 +0000 Subject: [PATCH] DRY questLog --- src/common/rpg/components/questLog.ts | 68 +++++++++++++++------------ 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/common/rpg/components/questLog.ts b/src/common/rpg/components/questLog.ts index f2f3ae3..7bb23ab 100644 --- a/src/common/rpg/components/questLog.ts +++ b/src/common/rpg/components/questLog.ts @@ -19,19 +19,47 @@ export class QuestLog extends Component { readonly #quests = new Map(); addQuest(quest: Quest): void { - if (!this.#quests.has(quest.id)) { - this.#quests.set(quest.id, { quest, state: { status: 'inactive', stageIndex: 0 } }); + if (this.#quests.has(quest.id)) { + console.warn(`[QuestLog] quest '${quest.id}' is already registered, ignoring duplicate`); + return; } + this.#quests.set(quest.id, { quest, state: { status: 'inactive', stageIndex: 0 } }); + } + + #transition(op: string, questId: string, from: QuestStatus, to: QuestStatus, event: string): boolean { + const entry = this.#quests.get(questId); + if (!entry) { + console.warn(`[QuestLog] ${op}: quest '${questId}' is not registered`); + return false; + } + if (entry.state.status !== from) { + console.warn(`[QuestLog] ${op}: quest '${questId}' cannot transition from status '${entry.state.status}'`); + return false; + } + entry.state.status = to; + if (to === 'active' || to === 'inactive') entry.state.stageIndex = 0; + this.emit(event, { questId }); + return true; } @action start(questId: string): boolean { - const entry = this.#quests.get(questId); - if (!entry || entry.state.status !== 'inactive') return false; - entry.state.status = 'active'; - entry.state.stageIndex = 0; - this.emit('started', { questId }); - return true; + return this.#transition('start', questId, 'inactive', 'active', 'started'); + } + + @action + complete(questId: string): boolean { + return this.#transition('complete', questId, 'active', 'completed', 'completed'); + } + + @action + fail(questId: string): boolean { + return this.#transition('fail', questId, 'active', 'failed', 'failed'); + } + + @action + abandon(questId: string): boolean { + return this.#transition('abandon', questId, 'active', 'inactive', 'abandoned'); } getState(questId: string): QuestRuntimeState | undefined { @@ -46,25 +74,6 @@ export class QuestLog extends Component { return quest.conditions.every(c => evaluateCondition(parseCondition(c), ctx)); } - @action - fail(questId: string): boolean { - const entry = this.#quests.get(questId); - if (!entry || entry.state.status !== 'active') return false; - entry.state.status = 'failed'; - this.emit('failed', { questId }); - return true; - } - - @action - abandon(questId: string): boolean { - const entry = this.#quests.get(questId); - if (!entry || entry.state.status !== 'active') return false; - entry.state.status = 'inactive'; - entry.state.stageIndex = 0; - this.emit('abandoned', { questId }); - return true; - } - getStage(questId: string): QuestStage | undefined { const entry = this.#quests.get(questId); if (!entry || entry.state.status !== 'active') return undefined; @@ -91,10 +100,7 @@ export class QuestLog extends Component { /** @internal called by QuestSystem when a fail condition is met */ _fail(questId: string): void { - const entry = this.#quests.get(questId); - if (!entry) return; - entry.state.status = 'failed'; - this.emit('failed', { questId }); + this.#transition('_fail', questId, 'active', 'failed', 'failed'); } /** @internal called by QuestSystem after stage actions complete */