extends RefCounted ## NEO-97: shared combat-active gate + NPC/telegraph HUD copy for [code]main.gd[/code] and tests. const PrototypeTargetConstants := preload("res://scripts/prototype_target_constants.gd") const NpcRuntimeClient := preload("res://scripts/npc_runtime_client.gd") static func locked_target_id_from_state(state: Dictionary) -> String: var locked_variant: Variant = state.get("lockedTargetId", null) if locked_variant is String: return (locked_variant as String).strip_edges() return "" static func is_known_npc_id(target_id: String) -> bool: return PrototypeTargetConstants.ANCHORS.has(target_id.strip_edges()) static func incoming_aggro_row( dev_player_id: String, npc_runtime_snapshot: Dictionary ) -> Dictionary: if npc_runtime_snapshot.is_empty(): return {} return NpcRuntimeClient.row_for_aggro_holder(dev_player_id, npc_runtime_snapshot) static func is_combat_active( last_target_state: Dictionary, npc_runtime_snapshot: Dictionary, dev_player_id: String ) -> bool: var lock_id := locked_target_id_from_state(last_target_state) if is_known_npc_id(lock_id): return true return not incoming_aggro_row(dev_player_id, npc_runtime_snapshot).is_empty() static func resolve_hud_npc_rows( last_target_state: Dictionary, npc_runtime_snapshot: Dictionary, dev_player_id: String, runtime_client: Node = null ) -> Dictionary: var lock_id := locked_target_id_from_state(last_target_state) var locked_row: Dictionary = {} if is_known_npc_id(lock_id): if is_instance_valid(runtime_client) and runtime_client.has_method("npc_row"): locked_row = runtime_client.call("npc_row", lock_id, npc_runtime_snapshot) as Dictionary else: locked_row = NpcRuntimeClient.npc_row_in_snapshot(lock_id, npc_runtime_snapshot) var incoming_row: Dictionary = {} var aggro_row: Dictionary = incoming_aggro_row(dev_player_id, npc_runtime_snapshot) if not aggro_row.is_empty(): var aggro_id: String = str(aggro_row.get("npcInstanceId", "")).strip_edges() if aggro_id != lock_id: incoming_row = aggro_row return {"locked": locked_row, "incoming": incoming_row} static func build_npc_state_label(rows: Dictionary, runtime_sync_error: String = "") -> String: var lines: PackedStringArray = PackedStringArray() var locked_row: Dictionary = rows.get("locked", {}) as Dictionary if not locked_row.is_empty(): var locked_id: String = str(locked_row.get("npcInstanceId", "")) ( lines . append( ( "NPC state: %s → %s" % [ PrototypeTargetConstants.display_short_name(locked_id), str(locked_row.get("state", "unknown")), ] ) ) ) var incoming_row: Dictionary = rows.get("incoming", {}) as Dictionary if not incoming_row.is_empty(): var incoming_id: String = str(incoming_row.get("npcInstanceId", "")) ( lines . append( ( "Incoming: %s → %s" % [ PrototypeTargetConstants.display_short_name(incoming_id), str(incoming_row.get("state", "unknown")), ] ) ) ) if lines.is_empty(): if not runtime_sync_error.is_empty(): return "NPC state: — (%s)" % runtime_sync_error return "NPC state: —" if not runtime_sync_error.is_empty(): lines.append("(runtime sync: %s)" % runtime_sync_error) return "\n".join(lines) static func format_telegraph_line(row: Dictionary, hud_state: RefCounted = null) -> String: var instance_id: String = str(row.get("npcInstanceId", "")).strip_edges() var telegraph: Variant = row.get("activeTelegraph", null) if telegraph == null or not telegraph is Dictionary: return "" var telegraph_dict: Dictionary = telegraph as Dictionary var remaining: float = float(telegraph_dict.get("windupRemainingSeconds", 0.0)) if hud_state != null and hud_state.has_method("display_windup_remaining"): remaining = float(hud_state.call("display_windup_remaining", instance_id)) if remaining <= 0.0: return "" var short_name: String = PrototypeTargetConstants.display_short_name(instance_id) var telegraph_id: String = str(telegraph_dict.get("telegraphId", "")) var archetype: String = str(telegraph_dict.get("archetypeKind", "")) return "Telegraph: %s %s · %.1fs (%s)" % [short_name, telegraph_id, remaining, archetype] static func build_telegraph_label( rows: Dictionary, hud_state: RefCounted = null, runtime_sync_error: String = "" ) -> String: var locked_row: Dictionary = rows.get("locked", {}) as Dictionary var line: String = format_telegraph_line(locked_row, hud_state) if line.is_empty(): line = format_telegraph_line(rows.get("incoming", {}) as Dictionary, hud_state) if line.is_empty(): if not runtime_sync_error.is_empty(): return "Telegraph: — (%s)" % runtime_sync_error return "Telegraph: —" return line static func has_interpolated_telegraph_display(rows: Dictionary, hud_state: RefCounted) -> bool: if hud_state == null: return false for key: String in ["locked", "incoming"]: var row: Dictionary = rows.get(key, {}) as Dictionary if row.is_empty(): continue var instance_id: String = str(row.get("npcInstanceId", "")).strip_edges() if instance_id.is_empty(): continue var telegraph: Variant = row.get("activeTelegraph", null) if telegraph == null or not telegraph is Dictionary: continue if hud_state.has_method("has_active_windup"): if bool(hud_state.call("has_active_windup", instance_id)): return true return false