NEO-122: Extract quest HUD controller to satisfy gdlint limits.
Move quest progress/accept wiring out of main.gd so pre-push gdlint passes max line length and max file lines checks.pull/161/head
parent
ba75aeed79
commit
eba0d09d70
|
|
@ -17,8 +17,8 @@ extends Node3D
|
||||||
## Steep ramp: **2 m / 1.5 m** rise/run (~53°) so slope exceeds `Player` `floor_max_angle` (~50°)
|
## Steep ramp: **2 m / 1.5 m** rise/run (~53°) so slope exceeds `Player` `floor_max_angle` (~50°)
|
||||||
## and is not walkable; gentle ramp stays shallower than `NavigationMesh.agent_max_climb`. Steep is
|
## and is not walkable; gentle ramp stays shallower than `NavigationMesh.agent_max_climb`. Steep is
|
||||||
## under `World` only (not nav source geometry).
|
## under `World` only (not nav source geometry).
|
||||||
## Prototype HUD: world `CharacterBody3D` position in `UICanvas/HudRootScroll/HudRoot/PlayerPositionLabel`
|
## Prototype HUD: player position in PlayerPositionLabel (HudRootScroll/HudRoot);
|
||||||
## (updated in `_physics_process` so it matches physics ticks).
|
## updated in `_physics_process` to match physics ticks.
|
||||||
|
|
||||||
const MOVE_REJECT_MSG_SECONDS: float = 4.0
|
const MOVE_REJECT_MSG_SECONDS: float = 4.0
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ const REFINE_SKILL_ID := "refine"
|
||||||
const BREACH_GIG_ID := "breach"
|
const BREACH_GIG_ID := "breach"
|
||||||
const PROTOTYPE_ENCOUNTER_ID := "prototype_combat_pocket"
|
const PROTOTYPE_ENCOUNTER_ID := "prototype_combat_pocket"
|
||||||
const PROTOTYPE_ENCOUNTER_REQUIRED_TARGETS := 3
|
const PROTOTYPE_ENCOUNTER_REQUIRED_TARGETS := 3
|
||||||
const PROTOTYPE_QUEST_GATHER_ID := "prototype_quest_gather_intro"
|
const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
||||||
|
|
||||||
## Bump on each rejection so older one-shot timers do not clear a newer message.
|
## Bump on each rejection so older one-shot timers do not clear a newer message.
|
||||||
var _move_reject_msg_token: int = 0
|
var _move_reject_msg_token: int = 0
|
||||||
|
|
@ -109,9 +109,7 @@ var _last_gig_snapshot: Dictionary = {}
|
||||||
var _gig_error: String = ""
|
var _gig_error: String = ""
|
||||||
var _last_encounter_progress_snapshot: Dictionary = {}
|
var _last_encounter_progress_snapshot: Dictionary = {}
|
||||||
var _encounter_progress_error: String = ""
|
var _encounter_progress_error: String = ""
|
||||||
var _last_quest_progress_snapshot: Dictionary = {}
|
var _quest_hud: Node = null
|
||||||
var _quest_progress_error: String = ""
|
|
||||||
var _quest_defs_error: String = ""
|
|
||||||
var _gather_pre_scrap_qty: int = 0
|
var _gather_pre_scrap_qty: int = 0
|
||||||
var _gather_pending_interactable_id: String = ""
|
var _gather_pending_interactable_id: String = ""
|
||||||
var _gather_awaiting_inventory_finalize: bool = false
|
var _gather_awaiting_inventory_finalize: bool = false
|
||||||
|
|
@ -129,22 +127,22 @@ var _dev_pulse_bootstrap_attempted: bool = false
|
||||||
@onready var _radius_preview: Node3D = $World/InteractionMarkers
|
@onready var _radius_preview: Node3D = $World/InteractionMarkers
|
||||||
@onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel
|
@onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel
|
||||||
@onready var _hud_root: VBoxContainer = $UICanvas/HudRootScroll/HudRoot
|
@onready var _hud_root: VBoxContainer = $UICanvas/HudRootScroll/HudRoot
|
||||||
@onready var _player_pos_label: Label = $UICanvas/HudRootScroll/HudRoot/PlayerPositionLabel
|
@onready var _player_pos_label: Label = _hud_root.get_node("PlayerPositionLabel")
|
||||||
@onready var _target_lock_label: Label = $UICanvas/HudRootScroll/HudRoot/TargetLockLabel
|
@onready var _target_lock_label: Label = _hud_root.get_node("TargetLockLabel")
|
||||||
@onready var _cast_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/CastFeedbackLabel
|
@onready var _cast_feedback_label: Label = _hud_root.get_node("CastFeedbackLabel")
|
||||||
@onready var _combat_target_hp_label: Label = $UICanvas/HudRootScroll/HudRoot/CombatTargetHpLabel
|
@onready var _combat_target_hp_label: Label = _hud_root.get_node("CombatTargetHpLabel")
|
||||||
@onready var _player_combat_hp_label: Label = $UICanvas/HudRootScroll/HudRoot/PlayerCombatHpLabel
|
@onready var _player_combat_hp_label: Label = _hud_root.get_node("PlayerCombatHpLabel")
|
||||||
@onready var _encounter_progress_label: Label = $UICanvas/HudRootScroll/HudRoot/EncounterProgressLabel
|
@onready var _encounter_progress_label: Label = _hud_root.get_node("EncounterProgressLabel")
|
||||||
@onready var _encounter_complete_label: Label = $UICanvas/HudRootScroll/HudRoot/EncounterCompleteLabel
|
@onready var _encounter_complete_label: Label = _hud_root.get_node("EncounterCompleteLabel")
|
||||||
@onready var _quest_progress_label: Label = $UICanvas/HudRootScroll/HudRoot/QuestProgressLabel
|
@onready var _quest_progress_label: Label = _hud_root.get_node("QuestProgressLabel")
|
||||||
@onready var _quest_accept_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/QuestAcceptFeedbackLabel
|
@onready var _quest_accept_feedback_label: Label = _hud_root.get_node("QuestAcceptFeedbackLabel")
|
||||||
@onready var _npc_state_label: Label = $UICanvas/HudRootScroll/HudRoot/NpcStateLabel
|
@onready var _npc_state_label: Label = _hud_root.get_node("NpcStateLabel")
|
||||||
@onready var _telegraph_label: Label = $UICanvas/HudRootScroll/HudRoot/TelegraphLabel
|
@onready var _telegraph_label: Label = _hud_root.get_node("TelegraphLabel")
|
||||||
@onready var _cooldown_slots_label: Label = $UICanvas/HudRootScroll/HudRoot/CooldownSlotsLabel
|
@onready var _cooldown_slots_label: Label = _hud_root.get_node("CooldownSlotsLabel")
|
||||||
@onready var _economy_hud_section: VBoxContainer = $UICanvas/EconomyHudSection
|
@onready var _economy_hud_section: VBoxContainer = $UICanvas/EconomyHudSection
|
||||||
@onready var _inventory_label: Label = _economy_hud_section.get_node("BodyScroll/Body/InventoryLabel")
|
@onready var _inventory_label: Label = _economy_hud_section.get_node("BodyScroll/Body/InventoryLabel")
|
||||||
@onready var _gather_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/GatherFeedbackLabel
|
@onready var _gather_feedback_label: Label = _hud_root.get_node("GatherFeedbackLabel")
|
||||||
@onready var _craft_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/CraftFeedbackLabel
|
@onready var _craft_feedback_label: Label = _hud_root.get_node("CraftFeedbackLabel")
|
||||||
@onready
|
@onready
|
||||||
var _skill_progression_label: Label = _economy_hud_section.get_node("BodyScroll/Body/SkillProgressionLabel")
|
var _skill_progression_label: Label = _economy_hud_section.get_node("BodyScroll/Body/SkillProgressionLabel")
|
||||||
@onready var _gig_xp_label: Label = _economy_hud_section.get_node("BodyScroll/Body/GigXpLabel")
|
@onready var _gig_xp_label: Label = _economy_hud_section.get_node("BodyScroll/Body/GigXpLabel")
|
||||||
|
|
@ -855,250 +853,27 @@ func _request_encounter_progress_refresh() -> void:
|
||||||
|
|
||||||
func _setup_quest_progress_sync() -> void:
|
func _setup_quest_progress_sync() -> void:
|
||||||
# NEO-122: quest progress + accept HUD; boot hydrate + refresh after gather/craft/defeat.
|
# NEO-122: quest progress + accept HUD; boot hydrate + refresh after gather/craft/defeat.
|
||||||
_apply_authority_http_config_to_client(_quest_progress_client)
|
_quest_hud = QuestHudController.new()
|
||||||
_apply_authority_http_config_to_client(_quest_defs_client)
|
add_child(_quest_hud)
|
||||||
if _quest_progress_client.has_signal("quest_progress_received"):
|
_quest_hud.call(
|
||||||
_quest_progress_client.connect(
|
"setup",
|
||||||
"quest_progress_received", Callable(self, "_on_quest_progress_received")
|
_quest_progress_client,
|
||||||
)
|
_quest_defs_client,
|
||||||
if _quest_progress_client.has_signal("quest_sync_failed"):
|
_quest_progress_label,
|
||||||
_quest_progress_client.connect(
|
_quest_accept_feedback_label,
|
||||||
"quest_sync_failed", Callable(self, "_on_quest_progress_sync_failed")
|
Callable(self, "_apply_authority_http_config_to_client")
|
||||||
)
|
|
||||||
if _quest_progress_client.has_signal("quest_accept_result_received"):
|
|
||||||
_quest_progress_client.connect(
|
|
||||||
"quest_accept_result_received", Callable(self, "_on_quest_accept_result_received")
|
|
||||||
)
|
|
||||||
if _quest_progress_client.has_signal("quest_accept_failed"):
|
|
||||||
_quest_progress_client.connect(
|
|
||||||
"quest_accept_failed", Callable(self, "_on_quest_accept_failed")
|
|
||||||
)
|
|
||||||
if _quest_defs_client.has_signal("definitions_ready"):
|
|
||||||
_quest_defs_client.connect("definitions_ready", Callable(self, "_on_quest_definitions_ready"))
|
|
||||||
if _quest_defs_client.has_signal("definitions_sync_failed"):
|
|
||||||
_quest_defs_client.connect(
|
|
||||||
"definitions_sync_failed", Callable(self, "_on_quest_definitions_sync_failed")
|
|
||||||
)
|
|
||||||
_render_quest_progress_label()
|
|
||||||
_render_quest_accept_feedback_label()
|
|
||||||
if _quest_defs_client.has_method("request_sync_from_server"):
|
|
||||||
_quest_defs_client.call("request_sync_from_server")
|
|
||||||
if _quest_progress_client.has_method("request_sync_from_server"):
|
|
||||||
_quest_progress_client.call("request_sync_from_server")
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_progress_received(snapshot: Dictionary) -> void:
|
|
||||||
_quest_progress_error = ""
|
|
||||||
_last_quest_progress_snapshot = snapshot.duplicate(true)
|
|
||||||
_render_quest_progress_label()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_progress_sync_failed(reason: String) -> void:
|
|
||||||
_quest_progress_error = reason
|
|
||||||
_last_quest_progress_snapshot = {}
|
|
||||||
_render_quest_progress_label()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_accept_result_received(quest_id: String, result: Dictionary) -> void:
|
|
||||||
if bool(result.get("accepted", false)):
|
|
||||||
_render_quest_accept_feedback_label(
|
|
||||||
"Quest accept: %s accepted" % _quest_display_name(quest_id)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
var rc := str(result.get("reasonCode", "")).strip_edges()
|
|
||||||
if rc.is_empty():
|
|
||||||
_render_quest_accept_feedback_label("Quest accept: denied (no reasonCode)")
|
|
||||||
else:
|
|
||||||
_render_quest_accept_feedback_label("Quest accept: denied — %s" % rc)
|
|
||||||
_request_quest_progress_refresh()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_accept_failed(_quest_id: String, reason: String) -> void:
|
|
||||||
_render_quest_accept_feedback_label("Quest accept: failed — %s" % reason)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_definitions_ready(_quests: Array) -> void:
|
|
||||||
_quest_defs_error = ""
|
|
||||||
if _quest_progress_error.is_empty() and not _last_quest_progress_snapshot.is_empty():
|
|
||||||
_render_quest_progress_label()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_quest_definitions_sync_failed(reason: String) -> void:
|
|
||||||
_quest_defs_error = reason
|
|
||||||
_render_quest_progress_label()
|
|
||||||
|
|
||||||
|
|
||||||
func _render_quest_progress_label() -> void:
|
|
||||||
if not is_instance_valid(_quest_progress_label):
|
|
||||||
return
|
|
||||||
var header := "Quests:"
|
|
||||||
if not _quest_progress_error.is_empty():
|
|
||||||
_quest_progress_label.text = "%s\nerror — %s" % [header, _quest_progress_error]
|
|
||||||
return
|
|
||||||
if _last_quest_progress_snapshot.is_empty():
|
|
||||||
if not _quest_defs_error.is_empty():
|
|
||||||
_quest_progress_label.text = (
|
|
||||||
"%s\ndefinitions error — %s\nLoading…" % [header, _quest_defs_error]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_quest_progress_label.text = "%s\nLoading…" % header
|
|
||||||
return
|
|
||||||
var lines: PackedStringArray = [header]
|
|
||||||
if not _quest_defs_error.is_empty():
|
|
||||||
lines.append("definitions error — %s" % _quest_defs_error)
|
|
||||||
var defs: Array = _quest_defs_snapshot()
|
|
||||||
if defs.is_empty():
|
|
||||||
var quests: Variant = _last_quest_progress_snapshot.get("quests", null)
|
|
||||||
if quests is Array:
|
|
||||||
for row_variant in quests as Array:
|
|
||||||
if not row_variant is Dictionary:
|
|
||||||
continue
|
|
||||||
var row: Dictionary = row_variant
|
|
||||||
var qid: String = str(row.get("questId", ""))
|
|
||||||
lines.append(" %s" % _format_quest_status_line(qid, qid, row))
|
|
||||||
else:
|
|
||||||
for def_variant in defs:
|
|
||||||
if not def_variant is Dictionary:
|
|
||||||
continue
|
|
||||||
var def: Dictionary = def_variant
|
|
||||||
var qid: String = str(def.get("id", ""))
|
|
||||||
if qid.is_empty():
|
|
||||||
continue
|
|
||||||
var row: Dictionary = _quest_row(qid)
|
|
||||||
var label: String = _quest_display_name(qid)
|
|
||||||
lines.append(" %s" % _format_quest_status_line(qid, label, row))
|
|
||||||
_quest_progress_label.text = "\n".join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
func _format_quest_status_line(_quest_id: String, display_name: String, row: Dictionary) -> String:
|
|
||||||
var status: String = str(row.get("status", "not_started"))
|
|
||||||
match status:
|
|
||||||
"not_started":
|
|
||||||
return "%s: not started" % display_name
|
|
||||||
"active":
|
|
||||||
var step_index: int = int(row.get("currentStepIndex", 0))
|
|
||||||
var counter_text: String = _format_objective_counters_summary(
|
|
||||||
row.get("objectiveCounters", {})
|
|
||||||
)
|
|
||||||
if counter_text.is_empty():
|
|
||||||
return "%s: active step %d" % [display_name, step_index + 1]
|
|
||||||
return "%s: active step %d %s" % [display_name, step_index + 1, counter_text]
|
|
||||||
"completed":
|
|
||||||
var completed_at: String = str(row.get("completedAt", "")).strip_edges()
|
|
||||||
if completed_at.is_empty():
|
|
||||||
return "%s: completed" % display_name
|
|
||||||
return "%s: completed (%s)" % [display_name, completed_at]
|
|
||||||
_:
|
|
||||||
return "%s: unknown status (%s)" % [display_name, status]
|
|
||||||
|
|
||||||
|
|
||||||
func _format_objective_counters_summary(counters: Variant) -> String:
|
|
||||||
if counters == null or not counters is Dictionary:
|
|
||||||
return ""
|
|
||||||
var d: Dictionary = counters
|
|
||||||
if d.is_empty():
|
|
||||||
return ""
|
|
||||||
var parts: PackedStringArray = PackedStringArray()
|
|
||||||
for key in d.keys():
|
|
||||||
parts.append("%s=%s" % [str(key), str(d[key])])
|
|
||||||
return "(%s)" % ", ".join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
func _render_quest_accept_feedback_label(text: String = "Quest accept: — (Q gather / Shift+Q next)") -> void:
|
|
||||||
if is_instance_valid(_quest_accept_feedback_label):
|
|
||||||
_quest_accept_feedback_label.text = text
|
|
||||||
|
|
||||||
|
|
||||||
func _quest_row(quest_id: String) -> Dictionary:
|
|
||||||
if not is_instance_valid(_quest_progress_client):
|
|
||||||
return {}
|
|
||||||
if not _quest_progress_client.has_method("quest_row"):
|
|
||||||
return {}
|
|
||||||
var row: Variant = _quest_progress_client.call(
|
|
||||||
"quest_row", quest_id, _last_quest_progress_snapshot
|
|
||||||
)
|
)
|
||||||
return row as Dictionary
|
|
||||||
|
|
||||||
|
|
||||||
func _quest_display_name(quest_id: String) -> String:
|
|
||||||
if not is_instance_valid(_quest_defs_client):
|
|
||||||
return quest_id
|
|
||||||
if not _quest_defs_client.has_method("display_name_for"):
|
|
||||||
return quest_id
|
|
||||||
return str(_quest_defs_client.call("display_name_for", quest_id))
|
|
||||||
|
|
||||||
|
|
||||||
func _quest_defs_snapshot() -> Array:
|
|
||||||
if not is_instance_valid(_quest_defs_client):
|
|
||||||
return []
|
|
||||||
if not _quest_defs_client.has_method("quests_snapshot"):
|
|
||||||
return []
|
|
||||||
return _quest_defs_client.call("quests_snapshot") as Array
|
|
||||||
|
|
||||||
|
|
||||||
func _request_quest_progress_refresh() -> void:
|
func _request_quest_progress_refresh() -> void:
|
||||||
if not is_instance_valid(_quest_progress_client):
|
if is_instance_valid(_quest_hud):
|
||||||
return
|
_quest_hud.call("request_progress_refresh")
|
||||||
if _quest_progress_client.has_method("request_sync_from_server"):
|
|
||||||
_quest_progress_client.call("request_sync_from_server")
|
|
||||||
|
|
||||||
|
|
||||||
func _find_first_eligible_quest_id() -> String:
|
|
||||||
var defs: Array = _quest_defs_snapshot()
|
|
||||||
for def_variant in defs:
|
|
||||||
if not def_variant is Dictionary:
|
|
||||||
continue
|
|
||||||
var def: Dictionary = def_variant
|
|
||||||
var qid: String = str(def.get("id", ""))
|
|
||||||
if qid.is_empty():
|
|
||||||
continue
|
|
||||||
var row: Dictionary = _quest_row(qid)
|
|
||||||
if str(row.get("status", "")) != "not_started":
|
|
||||||
continue
|
|
||||||
var prereqs: Variant = def.get("prerequisiteQuestIds", [])
|
|
||||||
if not prereqs is Array:
|
|
||||||
continue
|
|
||||||
var prereqs_met := true
|
|
||||||
for p_variant in prereqs as Array:
|
|
||||||
var pid: String = str(p_variant)
|
|
||||||
if str(_quest_row(pid).get("status", "")) != "completed":
|
|
||||||
prereqs_met = false
|
|
||||||
break
|
|
||||||
if prereqs_met:
|
|
||||||
return qid
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
func _request_quest_accept(quest_id: String) -> void:
|
|
||||||
if not is_instance_valid(_quest_progress_client):
|
|
||||||
return
|
|
||||||
if _quest_progress_client.has_method("is_accept_busy") and bool(_quest_progress_client.call("is_accept_busy")):
|
|
||||||
return
|
|
||||||
if _quest_progress_client.has_method("request_accept"):
|
|
||||||
_quest_progress_client.call("request_accept", quest_id)
|
|
||||||
|
|
||||||
|
|
||||||
func _request_eligible_quest_accept() -> void:
|
|
||||||
var eligible_id: String = _find_first_eligible_quest_id()
|
|
||||||
if eligible_id.is_empty():
|
|
||||||
_render_quest_accept_feedback_label("Quest accept: no eligible quest")
|
|
||||||
return
|
|
||||||
_request_quest_accept(eligible_id)
|
|
||||||
|
|
||||||
|
|
||||||
func _try_quest_accept_key_input(event: InputEvent) -> bool:
|
func _try_quest_accept_key_input(event: InputEvent) -> bool:
|
||||||
if not event is InputEventKey:
|
if is_instance_valid(_quest_hud):
|
||||||
return false
|
return bool(_quest_hud.call("try_accept_key_input", event))
|
||||||
var k := event as InputEventKey
|
return false
|
||||||
if not k.pressed or k.echo:
|
|
||||||
return false
|
|
||||||
if k.physical_keycode != KEY_Q and k.keycode != KEY_Q:
|
|
||||||
return false
|
|
||||||
if k.shift_pressed:
|
|
||||||
_request_eligible_quest_accept()
|
|
||||||
else:
|
|
||||||
_request_quest_accept(PROTOTYPE_QUEST_GATHER_ID)
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func _render_gather_feedback_label(text: String = "Gather: —") -> void:
|
func _render_gather_feedback_label(text: String = "Gather: —") -> void:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## NEO-122: quest progress + accept HUD wiring (extracted from main.gd).
|
||||||
|
|
||||||
|
const GATHER_QUEST_ID := "prototype_quest_gather_intro"
|
||||||
|
const ACCEPT_IDLE_HINT := "Quest accept: — (Q gather / Shift+Q next)"
|
||||||
|
|
||||||
|
var _progress_client: Node = null
|
||||||
|
var _defs_client: Node = null
|
||||||
|
var _progress_label: Label = null
|
||||||
|
var _accept_label: Label = null
|
||||||
|
var _last_snapshot: Dictionary = {}
|
||||||
|
var _progress_error: String = ""
|
||||||
|
var _defs_error: String = ""
|
||||||
|
|
||||||
|
|
||||||
|
func setup(
|
||||||
|
progress_client: Node,
|
||||||
|
defs_client: Node,
|
||||||
|
progress_label: Label,
|
||||||
|
accept_label: Label,
|
||||||
|
apply_http_config: Callable
|
||||||
|
) -> void:
|
||||||
|
_progress_client = progress_client
|
||||||
|
_defs_client = defs_client
|
||||||
|
_progress_label = progress_label
|
||||||
|
_accept_label = accept_label
|
||||||
|
if apply_http_config.is_valid():
|
||||||
|
apply_http_config.call(progress_client)
|
||||||
|
apply_http_config.call(defs_client)
|
||||||
|
if _progress_client.has_signal("quest_progress_received"):
|
||||||
|
_progress_client.connect("quest_progress_received", Callable(self, "_on_progress_received"))
|
||||||
|
if _progress_client.has_signal("quest_sync_failed"):
|
||||||
|
_progress_client.connect("quest_sync_failed", Callable(self, "_on_sync_failed"))
|
||||||
|
if _progress_client.has_signal("quest_accept_result_received"):
|
||||||
|
_progress_client.connect(
|
||||||
|
"quest_accept_result_received", Callable(self, "_on_accept_result_received")
|
||||||
|
)
|
||||||
|
if _progress_client.has_signal("quest_accept_failed"):
|
||||||
|
_progress_client.connect("quest_accept_failed", Callable(self, "_on_accept_failed"))
|
||||||
|
if _defs_client.has_signal("definitions_ready"):
|
||||||
|
_defs_client.connect("definitions_ready", Callable(self, "_on_definitions_ready"))
|
||||||
|
if _defs_client.has_signal("definitions_sync_failed"):
|
||||||
|
_defs_client.connect("definitions_sync_failed", Callable(self, "_on_definitions_sync_failed"))
|
||||||
|
_render_progress_label()
|
||||||
|
_render_accept_feedback_label()
|
||||||
|
if _defs_client.has_method("request_sync_from_server"):
|
||||||
|
_defs_client.call("request_sync_from_server")
|
||||||
|
request_progress_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func request_progress_refresh() -> void:
|
||||||
|
if not is_instance_valid(_progress_client):
|
||||||
|
return
|
||||||
|
if _progress_client.has_method("request_sync_from_server"):
|
||||||
|
_progress_client.call("request_sync_from_server")
|
||||||
|
|
||||||
|
|
||||||
|
func try_accept_key_input(event: InputEvent) -> bool:
|
||||||
|
if not event is InputEventKey:
|
||||||
|
return false
|
||||||
|
var k := event as InputEventKey
|
||||||
|
if not k.pressed or k.echo:
|
||||||
|
return false
|
||||||
|
if k.physical_keycode != KEY_Q and k.keycode != KEY_Q:
|
||||||
|
return false
|
||||||
|
if k.shift_pressed:
|
||||||
|
_request_eligible_accept()
|
||||||
|
else:
|
||||||
|
_request_accept(GATHER_QUEST_ID)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func _on_progress_received(snapshot: Dictionary) -> void:
|
||||||
|
_progress_error = ""
|
||||||
|
_last_snapshot = snapshot.duplicate(true)
|
||||||
|
_render_progress_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_sync_failed(reason: String) -> void:
|
||||||
|
_progress_error = reason
|
||||||
|
_last_snapshot = {}
|
||||||
|
_render_progress_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_accept_result_received(quest_id: String, result: Dictionary) -> void:
|
||||||
|
if bool(result.get("accepted", false)):
|
||||||
|
_render_accept_feedback_label("Quest accept: %s accepted" % _display_name(quest_id))
|
||||||
|
else:
|
||||||
|
var rc := str(result.get("reasonCode", "")).strip_edges()
|
||||||
|
if rc.is_empty():
|
||||||
|
_render_accept_feedback_label("Quest accept: denied (no reasonCode)")
|
||||||
|
else:
|
||||||
|
_render_accept_feedback_label("Quest accept: denied — %s" % rc)
|
||||||
|
request_progress_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_accept_failed(_quest_id: String, reason: String) -> void:
|
||||||
|
_render_accept_feedback_label("Quest accept: failed — %s" % reason)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_definitions_ready(_quests: Array) -> void:
|
||||||
|
_defs_error = ""
|
||||||
|
if _progress_error.is_empty() and not _last_snapshot.is_empty():
|
||||||
|
_render_progress_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_definitions_sync_failed(reason: String) -> void:
|
||||||
|
_defs_error = reason
|
||||||
|
_render_progress_label()
|
||||||
|
|
||||||
|
|
||||||
|
func _render_progress_label() -> void:
|
||||||
|
if not is_instance_valid(_progress_label):
|
||||||
|
return
|
||||||
|
var header := "Quests:"
|
||||||
|
if not _progress_error.is_empty():
|
||||||
|
_progress_label.text = "%s\nerror — %s" % [header, _progress_error]
|
||||||
|
return
|
||||||
|
if _last_snapshot.is_empty():
|
||||||
|
if not _defs_error.is_empty():
|
||||||
|
_progress_label.text = (
|
||||||
|
"%s\ndefinitions error — %s\nLoading…" % [header, _defs_error]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_progress_label.text = "%s\nLoading…" % header
|
||||||
|
return
|
||||||
|
var lines: PackedStringArray = [header]
|
||||||
|
if not _defs_error.is_empty():
|
||||||
|
lines.append("definitions error — %s" % _defs_error)
|
||||||
|
var defs: Array = _defs_snapshot()
|
||||||
|
if defs.is_empty():
|
||||||
|
var quests: Variant = _last_snapshot.get("quests", null)
|
||||||
|
if quests is Array:
|
||||||
|
for row_variant in quests as Array:
|
||||||
|
if not row_variant is Dictionary:
|
||||||
|
continue
|
||||||
|
var row: Dictionary = row_variant
|
||||||
|
var qid: String = str(row.get("questId", ""))
|
||||||
|
lines.append(" %s" % _format_status_line(qid, qid, row))
|
||||||
|
else:
|
||||||
|
for def_variant in defs:
|
||||||
|
if not def_variant is Dictionary:
|
||||||
|
continue
|
||||||
|
var def: Dictionary = def_variant
|
||||||
|
var qid: String = str(def.get("id", ""))
|
||||||
|
if qid.is_empty():
|
||||||
|
continue
|
||||||
|
var row: Dictionary = _quest_row(qid)
|
||||||
|
var label: String = _display_name(qid)
|
||||||
|
lines.append(" %s" % _format_status_line(qid, label, row))
|
||||||
|
_progress_label.text = "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
func _format_status_line(_quest_id: String, display_name: String, row: Dictionary) -> String:
|
||||||
|
var status: String = str(row.get("status", "not_started"))
|
||||||
|
match status:
|
||||||
|
"not_started":
|
||||||
|
return "%s: not started" % display_name
|
||||||
|
"active":
|
||||||
|
var step_index: int = int(row.get("currentStepIndex", 0))
|
||||||
|
var counter_text: String = _format_objective_counters_summary(
|
||||||
|
row.get("objectiveCounters", {})
|
||||||
|
)
|
||||||
|
if counter_text.is_empty():
|
||||||
|
return "%s: active step %d" % [display_name, step_index + 1]
|
||||||
|
return "%s: active step %d %s" % [display_name, step_index + 1, counter_text]
|
||||||
|
"completed":
|
||||||
|
var completed_at: String = str(row.get("completedAt", "")).strip_edges()
|
||||||
|
if completed_at.is_empty():
|
||||||
|
return "%s: completed" % display_name
|
||||||
|
return "%s: completed (%s)" % [display_name, completed_at]
|
||||||
|
_:
|
||||||
|
return "%s: unknown status (%s)" % [display_name, status]
|
||||||
|
|
||||||
|
|
||||||
|
func _format_objective_counters_summary(counters: Variant) -> String:
|
||||||
|
if counters == null or not counters is Dictionary:
|
||||||
|
return ""
|
||||||
|
var d: Dictionary = counters
|
||||||
|
if d.is_empty():
|
||||||
|
return ""
|
||||||
|
var parts: PackedStringArray = PackedStringArray()
|
||||||
|
for key in d.keys():
|
||||||
|
parts.append("%s=%s" % [str(key), str(d[key])])
|
||||||
|
return "(%s)" % ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
func _render_accept_feedback_label(text: String = ACCEPT_IDLE_HINT) -> void:
|
||||||
|
if is_instance_valid(_accept_label):
|
||||||
|
_accept_label.text = text
|
||||||
|
|
||||||
|
|
||||||
|
func _quest_row(quest_id: String) -> Dictionary:
|
||||||
|
if not is_instance_valid(_progress_client):
|
||||||
|
return {}
|
||||||
|
if not _progress_client.has_method("quest_row"):
|
||||||
|
return {}
|
||||||
|
var row: Variant = _progress_client.call("quest_row", quest_id, _last_snapshot)
|
||||||
|
return row as Dictionary
|
||||||
|
|
||||||
|
|
||||||
|
func _display_name(quest_id: String) -> String:
|
||||||
|
if not is_instance_valid(_defs_client):
|
||||||
|
return quest_id
|
||||||
|
if not _defs_client.has_method("display_name_for"):
|
||||||
|
return quest_id
|
||||||
|
return str(_defs_client.call("display_name_for", quest_id))
|
||||||
|
|
||||||
|
|
||||||
|
func _defs_snapshot() -> Array:
|
||||||
|
if not is_instance_valid(_defs_client):
|
||||||
|
return []
|
||||||
|
if not _defs_client.has_method("quests_snapshot"):
|
||||||
|
return []
|
||||||
|
return _defs_client.call("quests_snapshot") as Array
|
||||||
|
|
||||||
|
|
||||||
|
func _find_first_eligible_quest_id() -> String:
|
||||||
|
var defs: Array = _defs_snapshot()
|
||||||
|
for def_variant in defs:
|
||||||
|
if not def_variant is Dictionary:
|
||||||
|
continue
|
||||||
|
var def: Dictionary = def_variant
|
||||||
|
var qid: String = str(def.get("id", ""))
|
||||||
|
if qid.is_empty():
|
||||||
|
continue
|
||||||
|
var row: Dictionary = _quest_row(qid)
|
||||||
|
if str(row.get("status", "")) != "not_started":
|
||||||
|
continue
|
||||||
|
var prereqs: Variant = def.get("prerequisiteQuestIds", [])
|
||||||
|
if not prereqs is Array:
|
||||||
|
continue
|
||||||
|
var prereqs_met := true
|
||||||
|
for p_variant in prereqs as Array:
|
||||||
|
var pid: String = str(p_variant)
|
||||||
|
if str(_quest_row(pid).get("status", "")) != "completed":
|
||||||
|
prereqs_met = false
|
||||||
|
break
|
||||||
|
if prereqs_met:
|
||||||
|
return qid
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _request_accept(quest_id: String) -> void:
|
||||||
|
if not is_instance_valid(_progress_client):
|
||||||
|
return
|
||||||
|
var accept_busy := false
|
||||||
|
if _progress_client.has_method("is_accept_busy"):
|
||||||
|
accept_busy = bool(_progress_client.call("is_accept_busy"))
|
||||||
|
if accept_busy:
|
||||||
|
return
|
||||||
|
if _progress_client.has_method("request_accept"):
|
||||||
|
_progress_client.call("request_accept", quest_id)
|
||||||
|
|
||||||
|
|
||||||
|
func _request_eligible_accept() -> void:
|
||||||
|
var eligible_id: String = _find_first_eligible_quest_id()
|
||||||
|
if eligible_id.is_empty():
|
||||||
|
_render_accept_feedback_label("Quest accept: no eligible quest")
|
||||||
|
return
|
||||||
|
_request_accept(eligible_id)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c8k3m2questhudctrl01
|
||||||
|
|
@ -239,7 +239,8 @@ func test_request_accept_emits_quest_accept_result_received() -> void:
|
||||||
# Assert
|
# Assert
|
||||||
assert_that(started).is_true()
|
assert_that(started).is_true()
|
||||||
assert_that(_accept_capture.get("questId", "")).is_equal(PROTOTYPE_QUEST_GATHER_ID)
|
assert_that(_accept_capture.get("questId", "")).is_equal(PROTOTYPE_QUEST_GATHER_ID)
|
||||||
assert_that(bool((_accept_capture.get("result", {}) as Dictionary).get("accepted", false))).is_true()
|
var accept_result: Dictionary = _accept_capture.get("result", {}) as Dictionary
|
||||||
|
assert_that(bool(accept_result.get("accepted", false))).is_true()
|
||||||
assert_that(transport.last_url).contains("/accept")
|
assert_that(transport.last_url).contains("/accept")
|
||||||
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue