126 lines
4.3 KiB
GDScript
126 lines
4.3 KiB
GDScript
extends GdUnitTestSuite
|
|
|
|
## NEO-97: combat-active gate + HUD label copy from snapshot fixtures.
|
|
|
|
const NpcRuntimeClient := preload("res://scripts/npc_runtime_client.gd")
|
|
const PrototypeTargetConstants := preload("res://scripts/prototype_target_constants.gd")
|
|
|
|
|
|
class NpcCombatHudHarness:
|
|
extends RefCounted
|
|
var last_target_state: Dictionary = {}
|
|
var npc_runtime_snapshot: Dictionary = {}
|
|
var dev_player_id: String = "dev-local-1"
|
|
|
|
func is_combat_active() -> bool:
|
|
var lock_id := locked_target_id()
|
|
if PrototypeTargetConstants.ANCHORS.has(lock_id):
|
|
return true
|
|
return not incoming_aggro_row().is_empty()
|
|
|
|
func locked_target_id() -> String:
|
|
var locked_variant: Variant = last_target_state.get("lockedTargetId", null)
|
|
if locked_variant is String:
|
|
return (locked_variant as String).strip_edges()
|
|
return ""
|
|
|
|
func incoming_aggro_row() -> Dictionary:
|
|
if npc_runtime_snapshot.is_empty():
|
|
return {}
|
|
return NpcRuntimeClient.row_for_aggro_holder(dev_player_id, npc_runtime_snapshot)
|
|
|
|
func resolve_hud_npc_rows() -> Dictionary:
|
|
var lock_id := locked_target_id()
|
|
var locked_row: Dictionary = {}
|
|
if PrototypeTargetConstants.ANCHORS.has(lock_id):
|
|
locked_row = NpcRuntimeClient.npc_row(lock_id, npc_runtime_snapshot)
|
|
var incoming_row: Dictionary = {}
|
|
var aggro_row: Dictionary = incoming_aggro_row()
|
|
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}
|
|
|
|
func build_npc_state_label() -> String:
|
|
var rows: Dictionary = resolve_hud_npc_rows()
|
|
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():
|
|
return "NPC state: —"
|
|
return "\n".join(lines)
|
|
|
|
|
|
static func _elite_aggro_snapshot_json() -> String:
|
|
return (
|
|
'{"schemaVersion":1,"serverTimeUtc":"2026-05-29T12:00:00Z","npcInstances":['
|
|
+ '{"npcInstanceId":"prototype_npc_elite","behaviorDefId":"prototype_elite_boss",'
|
|
+ '"state":"aggro","aggroHolderPlayerId":"dev-local-1","activeTelegraph":null},'
|
|
+ '{"npcInstanceId":"prototype_npc_melee","behaviorDefId":"prototype_melee_rush",'
|
|
+ '"state":"idle","aggroHolderPlayerId":null,"activeTelegraph":null}'
|
|
+ "]}"
|
|
)
|
|
|
|
|
|
func test_npc_lock_makes_combat_active_without_aggro_snapshot() -> void:
|
|
# Arrange
|
|
var harness := NpcCombatHudHarness.new()
|
|
harness.last_target_state = {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
|
|
# Act
|
|
var active: bool = harness.is_combat_active()
|
|
# Assert
|
|
assert_that(active).is_true()
|
|
|
|
|
|
func test_aggro_holder_makes_combat_active_without_lock() -> void:
|
|
# Arrange
|
|
var harness := NpcCombatHudHarness.new()
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
|
|
harness.npc_runtime_snapshot = parsed as Dictionary
|
|
# Act
|
|
var active: bool = harness.is_combat_active()
|
|
# Assert
|
|
assert_that(active).is_true()
|
|
|
|
|
|
func test_idle_without_lock_is_not_combat_active() -> void:
|
|
# Arrange
|
|
var harness := NpcCombatHudHarness.new()
|
|
harness.npc_runtime_snapshot = {}
|
|
# Act
|
|
var active: bool = harness.is_combat_active()
|
|
# Assert
|
|
assert_that(active).is_false()
|
|
|
|
|
|
func test_locked_plus_incoming_state_label() -> void:
|
|
# Arrange
|
|
var harness := NpcCombatHudHarness.new()
|
|
harness.last_target_state = {"lockedTargetId": "prototype_npc_melee", "validity": "ok"}
|
|
var parsed: Variant = NpcRuntimeClient.parse_runtime_json(_elite_aggro_snapshot_json())
|
|
harness.npc_runtime_snapshot = parsed as Dictionary
|
|
# Act
|
|
var label: String = harness.build_npc_state_label()
|
|
# Assert
|
|
assert_that(label).contains("NPC state: Melee → idle")
|
|
assert_that(label).contains("Incoming: Elite → aggro")
|