promiscuity/game/scenes/Quests/quest_manager.gd
2026-02-25 19:46:10 -06:00

159 lines
4.8 KiB
GDScript

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()