diff --git a/src/game/engine/scenes/hud/action_button.tscn b/src/game/engine/scenes/hud/action_button.tscn new file mode 100644 index 00000000..e35da423 --- /dev/null +++ b/src/game/engine/scenes/hud/action_button.tscn @@ -0,0 +1,5 @@ +[gd_scene format=3 uid="uid://a1c2t3i4o5n6b"] + +[node name="ActionButton" type="Button"] +custom_minimum_size = Vector2(0, 28) +size_flags_horizontal = 3 diff --git a/src/game/engine/src/entities/unit.gd b/src/game/engine/src/entities/unit.gd index 91f758e1..47cf1d19 100644 --- a/src/game/engine/src/entities/unit.gd +++ b/src/game/engine/src/entities/unit.gd @@ -32,9 +32,11 @@ var unit_type: String = "" var domain: String = "land" # ── Capability flags ───────────────────────────────────────────────── -## True if this unit can found cities (derived from "found_city" keyword). +## Keywords from unit JSON (replaces legacy flags/can_found_city/can_build_improvements). +var keywords: Array[String] = [] +## True if this unit can found cities — derived from "founder" keyword. var can_found_city: bool = false -## True if this unit can build tile improvements (derived from "build" keyword). +## True if this unit can build tile improvements — derived from "worker" keyword. var can_build_improvements: bool = false # ── Position & ownership ────────────────────────────────────────────── @@ -143,16 +145,12 @@ func _populate_from_data() -> void: domain = String(data.get("domain", "land")) if display_name.is_empty(): display_name = data.get("name", unit_id.capitalize()) - var is_founder: bool = ( - unit_id.contains("founder") - ) - can_found_city = data.get("can_found_city", is_founder) - var is_builder: bool = ( - unit_id.contains("worker") or unit_id.contains("engineer") - ) - can_build_improvements = data.get( - "can_build_improvements", is_builder - ) + var raw_keywords: Array = data.get("keywords", []) + keywords.clear() + for k: Variant in raw_keywords: + keywords.append(String(k)) + can_found_city = "founder" in keywords + can_build_improvements = "worker" in keywords # ── Per-turn refresh (called by turn_processor.gd) ──────────────────── @@ -261,18 +259,47 @@ func _combat_type() -> String: return data.get("combat_type", "") -## List of keyword strings for this unit, sourced from JSON unit data. -## Returns an empty array for units whose `unit_id` has no DataLoader entry. -func get_keywords() -> Array: - if unit_id.is_empty(): - return [] - var data: Dictionary = DataLoader.get_unit(unit_id) - return data.get("keywords", []) +## List of keyword strings for this unit (populated from JSON at init). +func get_keywords() -> Array[String]: + return keywords -## True if this unit has `keyword` in its JSON keyword list. +## True if this unit has `keyword` in its keyword list. func has_keyword(keyword: String) -> bool: - return keyword in get_keywords() + return keyword in keywords + + +## Space-separated keywords string for the GdUnitActions bridge. +func get_keywords_str() -> String: + return " ".join(keywords) + + +## Return the legal actions for this unit's current state via GdUnitActions. +## Each entry is a Dictionary {kind: String, enabled: bool, disabled_reason: String}. +func get_legal_actions() -> Array[Dictionary]: + if not ClassDB.class_exists("GdUnitActions"): + return [] + var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted + return bridge.legal_actions_for( + unit_type, + get_keywords_str(), + movement_remaining > 0, + is_fortified + ) + + +## Returns true if `kind` (e.g. "fortify") is a legal enabled action right now. +func can_invoke_action(kind: String) -> bool: + if not ClassDB.class_exists("GdUnitActions"): + return false + var bridge: RefCounted = ClassDB.instantiate("GdUnitActions") as RefCounted + return bridge.can_invoke( + unit_type, + get_keywords_str(), + movement_remaining > 0, + is_fortified, + kind + ) # ── Movement / damage / healing helpers ─────────────────────────────── diff --git a/src/simulator/crates/mc-ai/src/tactical/movement.rs b/src/simulator/crates/mc-ai/src/tactical/movement.rs index 8f62b7e8..ab17790e 100644 --- a/src/simulator/crates/mc-ai/src/tactical/movement.rs +++ b/src/simulator/crates/mc-ai/src/tactical/movement.rs @@ -876,9 +876,13 @@ mod tests { vec![-1, 0], ); let actions = decide_movement(&state(0, vec![me, enemy]), &weights(), &mut rng()); + // Registry integration: sole garrison defender with no movement target + // now fortifies in place rather than idling (Action::Fortify sourced + // from legal_actions). + assert_eq!(actions.len(), 1, "expected one action (Fortify), got {actions:?}"); assert!( - actions.is_empty(), - "sole garrison defender should hold, got {actions:?}" + matches!(actions[0], Action::Fortify { .. }), + "sole garrison defender should fortify, got {actions:?}" ); }