625 lines
22 KiB
GDScript
625 lines
22 KiB
GDScript
extends Node
|
||
|
||
## NEO-122: quest progress + accept HUD wiring (extracted from main.gd).
|
||
## NEO-131: quest completion reward label on in-session completed transition.
|
||
## NEO-132: emits quest_completion_reward_transition for economy HUD refresh.
|
||
## NEO-142: faction standing label + readable faction_gate_blocked accept deny.
|
||
|
||
signal quest_completion_reward_transition(quest_id: String, summary: Dictionary)
|
||
|
||
const QuestProgressClient := preload("res://scripts/quest_progress_client.gd")
|
||
const FactionStandingClient := preload("res://scripts/faction_standing_client.gd")
|
||
const GATHER_QUEST_ID := "prototype_quest_gather_intro"
|
||
const FACTION_GATE_BLOCKED_REASON := "faction_gate_blocked"
|
||
const ACCEPT_IDLE_HINT := "Quest accept: — (Q gather / Shift+Q next)"
|
||
const ACCEPT_SENDING_HINT := "Quest accept: sending…"
|
||
const REWARD_HEADER := "Quest rewards:"
|
||
const FACTION_STANDING_HEADER := "Faction standing:"
|
||
|
||
var _progress_client: Node = null
|
||
var _defs_client: Node = null
|
||
var _item_defs_client: Node = null
|
||
var _progress_label: Label = null
|
||
var _accept_label: Label = null
|
||
var _reward_label: Label = null
|
||
var _last_snapshot: Dictionary = {}
|
||
var _progress_error: String = ""
|
||
var _defs_error: String = ""
|
||
var _accept_quest_patch: Dictionary = {}
|
||
var _previous_status_by_quest: Dictionary = {}
|
||
var _quest_was_active_in_session: Dictionary = {}
|
||
var _last_reward_quest_id: String = ""
|
||
var _last_reward_summary: Dictionary = {}
|
||
var _inventory_client: Node = null
|
||
var _skill_progression_client: Node = null
|
||
var _faction_standing_client: Node = null
|
||
var _faction_standing_label: Label = null
|
||
var _last_faction_snapshot: Dictionary = {}
|
||
var _faction_standing_error: String = ""
|
||
|
||
|
||
func setup(
|
||
progress_client: Node,
|
||
defs_client: Node,
|
||
progress_label: Label,
|
||
accept_label: Label,
|
||
apply_http_config: Callable,
|
||
reward_label: Label = null,
|
||
item_defs_client: Node = null,
|
||
inventory_client: Node = null,
|
||
skill_progression_client: Node = null,
|
||
faction_standing_client: Node = null,
|
||
faction_standing_label: Label = null
|
||
) -> void:
|
||
_progress_client = progress_client
|
||
_defs_client = defs_client
|
||
_item_defs_client = item_defs_client
|
||
_inventory_client = inventory_client
|
||
_skill_progression_client = skill_progression_client
|
||
_faction_standing_client = faction_standing_client
|
||
_faction_standing_label = faction_standing_label
|
||
_progress_label = progress_label
|
||
_accept_label = accept_label
|
||
_reward_label = reward_label
|
||
if apply_http_config.is_valid():
|
||
apply_http_config.call(progress_client)
|
||
apply_http_config.call(defs_client)
|
||
if is_instance_valid(_faction_standing_client):
|
||
apply_http_config.call(_faction_standing_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")
|
||
)
|
||
if is_instance_valid(_item_defs_client) and _item_defs_client.has_signal("definitions_ready"):
|
||
_item_defs_client.connect("definitions_ready", Callable(self, "_on_item_definitions_ready"))
|
||
if is_instance_valid(_faction_standing_client):
|
||
if _faction_standing_client.has_signal("faction_standing_received"):
|
||
_faction_standing_client.connect(
|
||
"faction_standing_received", Callable(self, "_on_faction_standing_received")
|
||
)
|
||
if _faction_standing_client.has_signal("faction_standing_sync_failed"):
|
||
_faction_standing_client.connect(
|
||
"faction_standing_sync_failed", Callable(self, "_on_faction_standing_sync_failed")
|
||
)
|
||
_render_progress_label()
|
||
_render_accept_feedback_label()
|
||
_render_reward_label()
|
||
_render_faction_standing_label()
|
||
if _defs_client.has_method("request_sync_from_server"):
|
||
_defs_client.call("request_sync_from_server")
|
||
_refresh_faction_standing()
|
||
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 = ""
|
||
_detect_and_apply_completion_transitions(snapshot)
|
||
_last_snapshot = snapshot.duplicate(true)
|
||
_reapply_accept_quest_patch_if_needed()
|
||
_render_progress_label()
|
||
_render_reward_label()
|
||
|
||
|
||
func _on_sync_failed(reason: String) -> void:
|
||
_progress_error = reason
|
||
if _accept_quest_patch.is_empty():
|
||
_last_snapshot = {}
|
||
elif _last_snapshot.is_empty():
|
||
_last_snapshot = QuestProgressClient.merge_quest_row_into_snapshot({}, _accept_quest_patch)
|
||
_render_progress_label()
|
||
_render_reward_label()
|
||
|
||
|
||
func _on_accept_result_received(quest_id: String, result: Dictionary) -> void:
|
||
if bool(result.get("accepted", false)):
|
||
_mark_quest_active_in_session(quest_id)
|
||
_render_accept_feedback_label("Quest accept: %s accepted" % _display_name(quest_id))
|
||
var quest_variant: Variant = result.get("quest", null)
|
||
if quest_variant is Dictionary:
|
||
_accept_quest_patch = (quest_variant as Dictionary).duplicate(true)
|
||
_progress_error = ""
|
||
_last_snapshot = QuestProgressClient.merge_quest_row_into_snapshot(
|
||
_last_snapshot, _accept_quest_patch
|
||
)
|
||
_render_progress_label()
|
||
else:
|
||
var rc := str(result.get("reasonCode", "")).strip_edges()
|
||
if rc == FACTION_GATE_BLOCKED_REASON:
|
||
_render_accept_feedback_label(_format_faction_gate_blocked_deny(quest_id))
|
||
elif 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 not _last_snapshot.is_empty():
|
||
_render_progress_label()
|
||
if not _last_reward_quest_id.is_empty():
|
||
_render_reward_label()
|
||
|
||
|
||
func _on_definitions_sync_failed(reason: String) -> void:
|
||
_defs_error = reason
|
||
_render_progress_label()
|
||
|
||
|
||
func _on_item_definitions_ready(_definitions_by_id: Dictionary) -> void:
|
||
if not _last_reward_quest_id.is_empty():
|
||
_render_reward_label()
|
||
|
||
|
||
func _mark_quest_active_in_session(quest_id: String) -> void:
|
||
var qid := quest_id.strip_edges()
|
||
if not qid.is_empty():
|
||
_quest_was_active_in_session[qid] = true
|
||
|
||
|
||
func _detect_and_apply_completion_transitions(snapshot: Dictionary) -> void:
|
||
var boot_seed := _previous_status_by_quest.is_empty()
|
||
var transitions: Array = []
|
||
var quests_variant: Variant = snapshot.get("quests", null)
|
||
if quests_variant is Array:
|
||
for row_variant in quests_variant as Array:
|
||
if not row_variant is Dictionary:
|
||
continue
|
||
var row: Dictionary = row_variant
|
||
var qid := str(row.get("questId", "")).strip_edges()
|
||
if qid.is_empty():
|
||
continue
|
||
var new_status := str(row.get("status", "not_started"))
|
||
if new_status == "active":
|
||
_mark_quest_active_in_session(qid)
|
||
var prev_status := str(_previous_status_by_quest.get(qid, "not_started"))
|
||
var can_detect := not boot_seed or bool(_quest_was_active_in_session.get(qid, false))
|
||
if can_detect and prev_status != "completed" and new_status == "completed":
|
||
var summary := QuestProgressClient.completion_reward_summary(qid, snapshot)
|
||
if not summary.is_empty():
|
||
transitions.append({"questId": qid, "summary": summary})
|
||
_previous_status_by_quest[qid] = new_status
|
||
if transitions.is_empty():
|
||
return
|
||
var ordered_ids: Array = _quest_ids_in_render_order(snapshot)
|
||
var best_idx := -1
|
||
var best_transition: Dictionary = {}
|
||
for transition_variant in transitions:
|
||
if not transition_variant is Dictionary:
|
||
continue
|
||
var transition: Dictionary = transition_variant
|
||
var quest_id := str(transition.get("questId", ""))
|
||
var idx: int = ordered_ids.find(quest_id)
|
||
if idx < 0:
|
||
idx = ordered_ids.size()
|
||
if idx >= best_idx:
|
||
best_idx = idx
|
||
best_transition = transition
|
||
if best_transition.is_empty():
|
||
return
|
||
_last_reward_quest_id = str(best_transition.get("questId", ""))
|
||
var summary_variant: Variant = best_transition.get("summary", null)
|
||
if summary_variant is Dictionary:
|
||
_last_reward_summary = (summary_variant as Dictionary).duplicate(true)
|
||
else:
|
||
_last_reward_summary = {}
|
||
if not _last_reward_quest_id.is_empty() and not _last_reward_summary.is_empty():
|
||
quest_completion_reward_transition.emit(_last_reward_quest_id, _last_reward_summary)
|
||
_refresh_economy_hud()
|
||
_refresh_faction_standing()
|
||
|
||
|
||
func _refresh_faction_standing() -> void:
|
||
if (
|
||
is_instance_valid(_faction_standing_client)
|
||
and _faction_standing_client.has_method("request_sync_from_server")
|
||
):
|
||
_faction_standing_client.call("request_sync_from_server")
|
||
|
||
|
||
func _on_faction_standing_received(snapshot: Dictionary) -> void:
|
||
_faction_standing_error = ""
|
||
_last_faction_snapshot = snapshot.duplicate(true)
|
||
_render_faction_standing_label()
|
||
|
||
|
||
func _on_faction_standing_sync_failed(reason: String) -> void:
|
||
_faction_standing_error = reason
|
||
_render_faction_standing_label()
|
||
|
||
|
||
func _render_faction_standing_label() -> void:
|
||
if not is_instance_valid(_faction_standing_label):
|
||
return
|
||
var lines: PackedStringArray = [FACTION_STANDING_HEADER]
|
||
if not _faction_standing_error.is_empty():
|
||
lines.append("error — %s" % _faction_standing_error)
|
||
for faction_id in FactionStandingClient.prototype_faction_ids_in_order():
|
||
var fid := str(faction_id)
|
||
var standing: int = FactionStandingClient.standing_for(fid, _last_faction_snapshot)
|
||
var label := FactionStandingClient.display_name_for(fid)
|
||
lines.append(" %s: %d" % [label, standing])
|
||
_faction_standing_label.text = "\n".join(lines)
|
||
|
||
|
||
func _format_faction_gate_blocked_deny(quest_id: String) -> String:
|
||
var rules: Array = _faction_gate_rules_for(quest_id)
|
||
if rules.is_empty():
|
||
return (
|
||
"Quest accept: denied — faction standing too low (%s)" % FACTION_GATE_BLOCKED_REASON
|
||
)
|
||
var first_rule: Variant = rules[0]
|
||
if not first_rule is Dictionary:
|
||
return (
|
||
"Quest accept: denied — faction standing too low (%s)" % FACTION_GATE_BLOCKED_REASON
|
||
)
|
||
var rule: Dictionary = first_rule
|
||
var faction_id := str(rule.get("factionId", "")).strip_edges()
|
||
var min_standing: int = int(rule.get("minStanding", 0))
|
||
if faction_id.is_empty():
|
||
return (
|
||
"Quest accept: denied — faction standing too low (%s)" % FACTION_GATE_BLOCKED_REASON
|
||
)
|
||
var faction_label := _faction_display_name(faction_id)
|
||
var current_standing: int = FactionStandingClient.standing_for(
|
||
faction_id, _last_faction_snapshot
|
||
)
|
||
return (
|
||
"Quest accept: denied — %s standing %d required (have %d)"
|
||
% [faction_label, min_standing, current_standing]
|
||
)
|
||
|
||
|
||
func _faction_gate_rules_for(quest_id: String) -> Array:
|
||
if not is_instance_valid(_defs_client):
|
||
return []
|
||
if not _defs_client.has_method("faction_gate_rules_for"):
|
||
return []
|
||
return _defs_client.call("faction_gate_rules_for", quest_id) as Array
|
||
|
||
|
||
func _faction_display_name(faction_id: String) -> String:
|
||
if (
|
||
is_instance_valid(_faction_standing_client)
|
||
and _faction_standing_client.has_method("display_name_for")
|
||
):
|
||
return str(_faction_standing_client.call("display_name_for", faction_id))
|
||
return FactionStandingClient.display_name_for(faction_id)
|
||
|
||
|
||
func _refresh_economy_hud() -> void:
|
||
if (
|
||
is_instance_valid(_inventory_client)
|
||
and _inventory_client.has_method("request_sync_from_server")
|
||
):
|
||
_inventory_client.call("request_sync_from_server")
|
||
if (
|
||
is_instance_valid(_skill_progression_client)
|
||
and _skill_progression_client.has_method("request_sync_from_server")
|
||
):
|
||
_skill_progression_client.call("request_sync_from_server")
|
||
|
||
|
||
func _quest_ids_in_render_order(snapshot: Dictionary) -> Array:
|
||
var ordered: Array = []
|
||
var defs: Array = _defs_snapshot()
|
||
if not defs.is_empty():
|
||
for def_variant in defs:
|
||
if not def_variant is Dictionary:
|
||
continue
|
||
var qid := str((def_variant as Dictionary).get("id", "")).strip_edges()
|
||
if not qid.is_empty():
|
||
ordered.append(qid)
|
||
return ordered
|
||
var quests_variant: Variant = snapshot.get("quests", null)
|
||
if quests_variant is Array:
|
||
for row_variant in quests_variant as Array:
|
||
if not row_variant is Dictionary:
|
||
continue
|
||
var qid := str((row_variant as Dictionary).get("questId", "")).strip_edges()
|
||
if not qid.is_empty():
|
||
ordered.append(qid)
|
||
return ordered
|
||
|
||
|
||
func _render_progress_label() -> void:
|
||
if not is_instance_valid(_progress_label):
|
||
return
|
||
var header := "Quests:"
|
||
if _last_snapshot.is_empty():
|
||
var lines: PackedStringArray = [header]
|
||
if not _progress_error.is_empty():
|
||
lines.append("error — %s" % _progress_error)
|
||
if not _defs_error.is_empty():
|
||
lines.append("definitions error — %s" % _defs_error)
|
||
lines.append("Loading…")
|
||
_progress_label.text = "\n".join(lines)
|
||
return
|
||
var lines: PackedStringArray = [header]
|
||
if not _progress_error.is_empty():
|
||
lines.append("sync error — %s" % _progress_error)
|
||
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 _render_reward_label() -> void:
|
||
if not is_instance_valid(_reward_label):
|
||
return
|
||
if not _progress_error.is_empty():
|
||
_reward_label.text = "%s\n—" % REWARD_HEADER
|
||
return
|
||
if _last_reward_quest_id.is_empty() or _last_reward_summary.is_empty():
|
||
_reward_label.text = "%s\n—" % REWARD_HEADER
|
||
return
|
||
var lines: PackedStringArray = [REWARD_HEADER, _display_name(_last_reward_quest_id)]
|
||
lines.append_array(_format_reward_summary_lines(_last_reward_summary))
|
||
_reward_label.text = "\n".join(lines)
|
||
|
||
|
||
func _format_reward_summary_lines(summary: Dictionary) -> PackedStringArray:
|
||
var lines: PackedStringArray = PackedStringArray()
|
||
var items_variant: Variant = summary.get("itemGrants", null)
|
||
if items_variant is Array:
|
||
for grant_variant in items_variant as Array:
|
||
if not grant_variant is Dictionary:
|
||
continue
|
||
var grant: Dictionary = grant_variant
|
||
var item_id := str(grant.get("itemId", "")).strip_edges()
|
||
var qty: int = int(grant.get("quantity", 0))
|
||
if item_id.is_empty() or qty <= 0:
|
||
continue
|
||
var label := _item_display_name(item_id)
|
||
lines.append(" %s ×%d" % [label, qty])
|
||
var skills_variant: Variant = summary.get("skillXpGrants", null)
|
||
if skills_variant is Array:
|
||
for grant_variant in skills_variant as Array:
|
||
if not grant_variant is Dictionary:
|
||
continue
|
||
var grant: Dictionary = grant_variant
|
||
var skill_id := str(grant.get("skillId", "")).strip_edges()
|
||
var amount: int = int(grant.get("amount", 0))
|
||
if skill_id.is_empty() or amount <= 0:
|
||
continue
|
||
lines.append(" %s +%d XP" % [_title_case_token(skill_id), amount])
|
||
var rep_variant: Variant = summary.get("reputationGrants", null)
|
||
if rep_variant is Array:
|
||
for grant_variant in rep_variant as Array:
|
||
if not grant_variant is Dictionary:
|
||
continue
|
||
var grant: Dictionary = grant_variant
|
||
var faction_id := str(grant.get("factionId", "")).strip_edges()
|
||
var amount: int = int(grant.get("amount", 0))
|
||
if faction_id.is_empty() or amount <= 0:
|
||
continue
|
||
lines.append(" %s +%d rep" % [_faction_display_name(faction_id), amount])
|
||
if lines.is_empty():
|
||
lines.append(" (no grants)")
|
||
return lines
|
||
|
||
|
||
func _item_display_name(item_id: String) -> String:
|
||
if not is_instance_valid(_item_defs_client):
|
||
return item_id
|
||
if not _item_defs_client.has_method("display_name_for"):
|
||
return item_id
|
||
return str(_item_defs_client.call("display_name_for", item_id))
|
||
|
||
|
||
static func _title_case_token(token: String) -> String:
|
||
var trimmed := token.strip_edges()
|
||
if trimmed.is_empty():
|
||
return trimmed
|
||
return trimmed.substr(0, 1).to_upper() + trimmed.substr(1)
|
||
|
||
|
||
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 _reapply_accept_quest_patch_if_needed() -> void:
|
||
if _accept_quest_patch.is_empty():
|
||
return
|
||
var patch_id := str(_accept_quest_patch.get("questId", "")).strip_edges()
|
||
if patch_id.is_empty():
|
||
return
|
||
var row: Dictionary = _quest_row(patch_id)
|
||
if str(row.get("status", "not_started")) == "not_started":
|
||
_last_snapshot = QuestProgressClient.merge_quest_row_into_snapshot(
|
||
_last_snapshot, _accept_quest_patch
|
||
)
|
||
else:
|
||
_accept_quest_patch = {}
|
||
|
||
|
||
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
|
||
if _quest_status(qid) != "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 _quest_status(pid) != "completed":
|
||
prereqs_met = false
|
||
break
|
||
if prereqs_met:
|
||
return qid
|
||
return ""
|
||
|
||
|
||
func _quest_status(quest_id: String) -> String:
|
||
var row: Dictionary = _quest_row(quest_id)
|
||
if row.is_empty():
|
||
return "not_started"
|
||
return str(row.get("status", "not_started"))
|
||
|
||
|
||
func _request_accept(quest_id: String) -> bool:
|
||
if not is_instance_valid(_progress_client):
|
||
_render_accept_feedback_label("Quest accept: failed — client unavailable")
|
||
return false
|
||
if (
|
||
_progress_client.has_method("is_accept_busy")
|
||
and bool(_progress_client.call("is_accept_busy"))
|
||
):
|
||
_render_accept_feedback_label("Quest accept: busy — try again")
|
||
return false
|
||
if not _progress_client.has_method("request_accept"):
|
||
_render_accept_feedback_label("Quest accept: failed — client unavailable")
|
||
return false
|
||
var started: bool = bool(_progress_client.call("request_accept", quest_id))
|
||
if started:
|
||
_render_accept_feedback_label(ACCEPT_SENDING_HINT)
|
||
else:
|
||
_render_accept_feedback_label("Quest accept: failed — could not start")
|
||
return started
|
||
|
||
|
||
func _request_eligible_accept() -> void:
|
||
if not _progress_error.is_empty():
|
||
_render_accept_feedback_label("Quest accept: progress error — try again")
|
||
return
|
||
if _progress_error.is_empty() and _last_snapshot.is_empty():
|
||
_render_accept_feedback_label("Quest accept: progress loading — try again")
|
||
return
|
||
if not _defs_error.is_empty() and _defs_snapshot().is_empty():
|
||
_render_accept_feedback_label("Quest accept: definitions error — try again")
|
||
return
|
||
if _defs_error.is_empty() and _defs_snapshot().is_empty():
|
||
_render_accept_feedback_label("Quest accept: definitions loading — try again")
|
||
return
|
||
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)
|