extends Node signal quest_registered(quest_id: StringName) signal quest_started(quest_id: StringName) signal quest_step_advanced(quest_id: StringName, step_index: int, step_text: String) signal quest_completed(quest_id: StringName) signal quest_state_changed var _quests: Dictionary = {} var _active_quest_id: StringName = &"" var _active_step_index: int = -1 var _completed_quests: Dictionary = {} func register_quest(definition: Dictionary) -> bool: var raw_id := String(definition.get("id", "")).strip_edges() if raw_id.is_empty(): push_warning("Quest definition is missing an id.") return false var quest_id: StringName = StringName(raw_id) if _quests.has(quest_id): return true var raw_steps: Variant = definition.get("steps", []) if typeof(raw_steps) != TYPE_ARRAY: push_warning("Quest '%s' has invalid steps." % raw_id) return false var normalized_steps: Array[Dictionary] = [] var idx := 0 for raw_step in raw_steps: if typeof(raw_step) != TYPE_DICTIONARY: continue var step_dict: Dictionary = raw_step var step := step_dict.duplicate(true) step["id"] = String(step.get("id", "step_%d" % idx)).strip_edges() step["text"] = String(step.get("text", "")).strip_edges() step["complete_event"] = StringName(String(step.get("complete_event", "")).strip_edges()) normalized_steps.append(step) idx += 1 if normalized_steps.is_empty(): push_warning("Quest '%s' has no usable steps." % raw_id) return false var quest := definition.duplicate(true) quest["id"] = raw_id quest["title"] = String(definition.get("title", raw_id)) quest["description"] = String(definition.get("description", "")) quest["steps"] = normalized_steps _quests[quest_id] = quest quest_registered.emit(quest_id) quest_state_changed.emit() return true func has_quest(quest_id: String) -> bool: return _quests.has(StringName(quest_id)) func start_quest(quest_id: String) -> bool: var id := StringName(quest_id) if not _quests.has(id): push_warning("Cannot start missing quest '%s'." % quest_id) return false _active_quest_id = id _active_step_index = 0 quest_started.emit(_active_quest_id) var step := _get_active_step() if not step.is_empty(): quest_step_advanced.emit(_active_quest_id, _active_step_index, String(step.get("text", ""))) quest_state_changed.emit() return true func emit_event(event_name: StringName, payload: Dictionary = {}) -> void: if event_name == StringName(): return if _active_quest_id == StringName(): return if is_quest_completed(String(_active_quest_id)): return var active_step := _get_active_step() if active_step.is_empty(): return var expected: StringName = active_step.get("complete_event", StringName()) if expected != event_name: return _advance_active_step(payload) func is_quest_completed(quest_id: String) -> bool: return bool(_completed_quests.get(StringName(quest_id), false)) func is_active_quest(quest_id: String) -> bool: return _active_quest_id == StringName(quest_id) func get_active_quest_id() -> StringName: return _active_quest_id func get_active_quest_state() -> Dictionary: if _active_quest_id == StringName() or not _quests.has(_active_quest_id): return { "active": false, } var quest: Dictionary = _quests[_active_quest_id] var steps: Array = quest.get("steps", []) var completed: bool = is_quest_completed(String(_active_quest_id)) var step_text := "" var step_id := "" if not completed and _active_step_index >= 0 and _active_step_index < steps.size(): var step: Dictionary = steps[_active_step_index] step_text = String(step.get("text", "")) step_id = String(step.get("id", "")) return { "active": true, "quest_id": String(_active_quest_id), "title": String(quest.get("title", "")), "description": String(quest.get("description", "")), "current_step_index": _active_step_index, "total_steps": steps.size(), "current_step_id": step_id, "current_step_text": step_text, "completed": completed, } func _get_active_step() -> Dictionary: if _active_quest_id == StringName(): return {} if not _quests.has(_active_quest_id): return {} var quest: Dictionary = _quests[_active_quest_id] var steps: Array = quest.get("steps", []) if _active_step_index < 0 or _active_step_index >= steps.size(): return {} return steps[_active_step_index] func _advance_active_step(_payload: Dictionary) -> void: if _active_quest_id == StringName(): return var quest: Dictionary = _quests.get(_active_quest_id, {}) var steps: Array = quest.get("steps", []) _active_step_index += 1 if _active_step_index >= steps.size(): _completed_quests[_active_quest_id] = true quest_completed.emit(_active_quest_id) quest_state_changed.emit() return var step: Dictionary = steps[_active_step_index] quest_step_advanced.emit(_active_quest_id, _active_step_index, String(step.get("text", ""))) quest_state_changed.emit()