feat(@projects/@magic-civilization): ✨ ship building action panel signals
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
950aad39cd
commit
e0ca602b16
4 changed files with 177 additions and 32 deletions
|
|
@ -133,37 +133,37 @@ const STATUS: Readonly<Record<string, Status>> = {
|
|||
"scout:trail-mark": "shipped", // p2-53i: TrailMark tag + 5-turn fog-piercing visibility
|
||||
|
||||
// ── Buildings · Barracks (p2-53d) ───────────────────────────────
|
||||
"barracks:drill": "stubbed-rust", // p2-53d: handler wired; building_panel GDScript signal in-flight (ui-wiring2)
|
||||
"barracks:auto-promote": "stubbed-rust", // p2-53d: handler wired; building_panel GDScript signal in-flight
|
||||
"barracks:drill": "shipped", // p2-53d: handler + building_panel.gd _KIND_TO_SIGNAL_BUILDING wired
|
||||
"barracks:auto-promote": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · Watchtower (p2-53d) ─────────────────────────────
|
||||
"watchtower:ranged-fire": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"watchtower:alarm": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"watchtower:fire-arrows": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"watchtower:ranged-fire": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"watchtower:alarm": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"watchtower:fire-arrows": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · Wall (p2-53d) ───────────────────────────────────
|
||||
"city-wall:repair": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"city-wall:murder-holes": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"city-wall:gate": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"city-wall:repair": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"city-wall:murder-holes": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"city-wall:gate": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · City Centre (p2-53d) ────────────────────────────
|
||||
"city-center:raze": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"city-center:annex": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"city-center:raze": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"city-center:annex": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · Workshop (p2-53d) ───────────────────────────────
|
||||
"alchemist-workshop:stockpile": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"alchemist-workshop:overdrive": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"alchemist-workshop:research-aid": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"alchemist-workshop:stockpile": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"alchemist-workshop:overdrive": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"alchemist-workshop:research-aid": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · Ancestor Hall (p2-53d) ──────────────────────────
|
||||
"ancestor-hall:invoke": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"ancestor-hall:inscribe": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"ancestor-hall:invoke": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"ancestor-hall:inscribe": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Buildings · Outpost (p2-53d) ────────────────────────────────
|
||||
"outpost:pack": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"outpost:supply": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"outpost:claim-tile": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"outpost:beacon": "stubbed-rust", // p2-53d: handler wired; building_panel signal in-flight
|
||||
"outpost:pack": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"outpost:supply": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"outpost:claim-tile": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
"outpost:beacon": "shipped", // p2-53d: handler + building_panel.gd wired
|
||||
|
||||
// ── Out-of-Game-1 archetypes (caravan, aerial, heroic) ──────────
|
||||
"caravan:route": "design-only-g2", // Caravan trade routes — g6 oos / g2 design
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
|---|---|---|---|---|---|---|---|
|
||||
| **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 |
|
||||
| **P1** | 43 | 1 | 7 | 0 | 14 | 1 | 66 |
|
||||
| **P2** | 45 | 1 | 7 | 1 | 7 | 6 | 67 |
|
||||
| **P2** | 46 | 1 | 6 | 1 | 7 | 6 | 67 |
|
||||
| **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 |
|
||||
| **total** | **134** | **2** | **14** | **1** | **22** | **26** | **199** |
|
||||
| **total** | **135** | **2** | **13** | **1** | **22** | **26** | **199** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -27,8 +27,8 @@
|
|||
| Team Lead | Remaining |
|
||||
|---|---|
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 6 |
|
||||
| [shipwright](../team-leads/shipwright.md) | 6 |
|
||||
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
|
||||
| [shipwright](../team-leads/shipwright.md) | 5 |
|
||||
| [combat-dev](../team-leads/combat-dev.md) | 4 |
|
||||
| [terraformer](../team-leads/terraformer.md) | 2 |
|
||||
| [simulator-infra](../team-leads/simulator-infra.md) | 1 |
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
| [p2-53a](p2-53a-sentry-guard-action-kind.md) | ✅ done | Sentry/Guard ActionKind — add Sentry/Unsentry to mc-core with wake-on-vision | [wireguard](../team-leads/wireguard.md) | 2026-05-01 |
|
||||
| [p2-53b](p2-53b-building-action-registry.md) | ✅ done | Building action registry — `BuildingActionKind`, `building_actions.json`, `GdBuildingActions` bridge | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-01 |
|
||||
| [p2-53c](p2-53c-rally-vocabulary-expansion.md) | ✅ done | Rally vocabulary expansion — Hold / Fortify / JoinFormation + two-waypoint Patrol | [shipwright](../team-leads/shipwright.md) | 2026-05-01 |
|
||||
| [p2-53d](p2-53d-building-specifics.md) | 🟡 partial | Building specifics — Garrison, Repair, Toggle Active + 18 archetype-specific actions | [shipwright](../team-leads/shipwright.md) | 2026-05-03 |
|
||||
| [p2-53d](p2-53d-building-specifics.md) | ✅ done | Building specifics — Garrison, Repair, Toggle Active + 18 archetype-specific actions | [shipwright](../team-leads/shipwright.md) | 2026-05-03 |
|
||||
| [p2-53e](p2-53e-siege-pillage-embark.md) | 🟡 partial | Siege handlers (Pack/Deploy/Bombard) + Pillage UI wiring + Embark/Disembark handlers | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53f](p2-53f-infantry-specifics.md) | ✅ done | Infantry specifics — Shield Wall, Brace, Shove, Rage, Cleave, War Cry | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
| [p2-53g](p2-53g-ranged-specifics.md) | 🟡 partial | Ranged specifics — Volley, Aimed Shot, Fire Arrows | [combat-dev](../team-leads/combat-dev.md) | 2026-05-01 |
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-05-03T00:05:07Z",
|
||||
"generated_at": "2026-05-03T00:07:00Z",
|
||||
"totals": {
|
||||
"in_progress": 2,
|
||||
"missing": 22,
|
||||
"stub": 1,
|
||||
"oos": 26,
|
||||
"done": 134,
|
||||
"in_progress": 2,
|
||||
"partial": 14,
|
||||
"partial": 13,
|
||||
"done": 135,
|
||||
"total": 199
|
||||
},
|
||||
"objectives": [
|
||||
|
|
@ -1664,7 +1664,7 @@
|
|||
"id": "p2-53d",
|
||||
"title": "Building specifics — Garrison, Repair, Toggle Active + 18 archetype-specific actions",
|
||||
"priority": "p2",
|
||||
"status": "partial",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "shipwright",
|
||||
"updated_at": "2026-05-03",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
//! GDExtension bridge for building action capability queries.
|
||||
//!
|
||||
//! Exposes `GdBuildingActions` to GDScript:
|
||||
//! - `legal_actions_for(building_type, keywords, current_hp, max_hp, is_active,
|
||||
//! garrison_count, garrison_capacity, has_rally_target)` → `Array[Dictionary]`
|
||||
//! - `invoke(state, player_idx, city_idx, building_id, kind)` — queued-request mutation
|
||||
//! - `legal_actions_for(...)` — basic capability query (backwards-compat)
|
||||
//! - `legal_actions_for_ext(...)` — extended query with archetype toggle state
|
||||
//! - `get_building_state(state, player_idx, city_idx, building_id)` — Dictionary of runtime state
|
||||
//! - `invoke(state, player_idx, city_idx, building_id, kind)` — no-unit action
|
||||
//! - `invoke_with_unit(state, player_idx, city_idx, building_id, kind, unit_id)` — GarrisonIn/Out
|
||||
|
||||
use godot::prelude::*;
|
||||
use mc_core::building_action::{
|
||||
|
|
@ -82,11 +84,127 @@ impl GdBuildingActions {
|
|||
availability_to_godot_array(&actions)
|
||||
}
|
||||
|
||||
/// Extended legal-actions query that passes archetype-specific toggle state.
|
||||
/// Preferred over `legal_actions_for` when the caller has a full BuildingState.
|
||||
///
|
||||
/// `state_dict` is a Dictionary with optional bool keys:
|
||||
/// `"continuous_in_progress"`, `"gate_open"`, `"murder_holes_active"`,
|
||||
/// `"ranged_fire_active"`, `"fire_arrows_active"`, `"auto_promote"`,
|
||||
/// `"stockpile_locked"`, `"ancestor_invoked_this_era"`.
|
||||
/// Missing keys default to false. Callers can pass `get_building_state()`
|
||||
/// output directly here.
|
||||
#[func]
|
||||
pub fn legal_actions_for_ext(
|
||||
&self,
|
||||
building_type: GString,
|
||||
keywords: GString,
|
||||
current_hp: i64,
|
||||
max_hp: i64,
|
||||
is_active: bool,
|
||||
garrison_count: i64,
|
||||
garrison_capacity: i64,
|
||||
has_rally_target: bool,
|
||||
state_dict: Dictionary,
|
||||
) -> Array<Dictionary> {
|
||||
let bool_key = |k: &str| -> bool {
|
||||
state_dict.get(k).and_then(|v| v.try_to::<bool>().ok()).unwrap_or(false)
|
||||
};
|
||||
let cap = BuildingCapability {
|
||||
building_type: building_type.to_string(),
|
||||
keywords: keywords
|
||||
.to_string()
|
||||
.split_whitespace()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(String::from)
|
||||
.collect(),
|
||||
current_hp: current_hp.max(0) as u32,
|
||||
max_hp: max_hp.max(0) as u32,
|
||||
is_active,
|
||||
garrison_count: garrison_count.max(0) as u32,
|
||||
garrison_capacity: garrison_capacity.max(0) as u32,
|
||||
has_rally_target,
|
||||
continuous_action_in_progress: bool_key("continuous_in_progress"),
|
||||
gate_open: bool_key("gate_open"),
|
||||
murder_holes_active: bool_key("murder_holes_active"),
|
||||
ranged_fire_active: bool_key("ranged_fire_active"),
|
||||
fire_arrows_active: bool_key("fire_arrows_active"),
|
||||
auto_promote: bool_key("auto_promote"),
|
||||
stockpile_locked: bool_key("stockpile_locked"),
|
||||
ancestor_invoked_this_era: bool_key("ancestor_invoked_this_era"),
|
||||
};
|
||||
let actions = legal_actions_for_building(&cap);
|
||||
availability_to_godot_array(&actions)
|
||||
}
|
||||
|
||||
/// Return runtime BuildingState fields as a Dictionary for status rendering.
|
||||
///
|
||||
/// Keys: `"garrisoned_unit_ids"` (Array[int]), `"is_active"` (bool),
|
||||
/// `"gate_open"` (bool), `"murder_holes_active"` (bool),
|
||||
/// `"ranged_fire_active"` (bool), `"fire_arrows_active"` (bool),
|
||||
/// `"sound_alarm_expiry"` (int), `"continuous_turns_remaining"` (int, 0 = none),
|
||||
/// `"auto_promote"` (bool), `"stockpile_locked"` (bool).
|
||||
///
|
||||
/// Returns an empty Dictionary if no BuildingState exists for this building
|
||||
/// (meaning all fields are at their defaults).
|
||||
#[func]
|
||||
pub fn get_building_state(
|
||||
&self,
|
||||
state: Gd<crate::GdGameState>,
|
||||
player_idx: i64,
|
||||
city_idx: i64,
|
||||
building_id: GString,
|
||||
) -> Dictionary {
|
||||
let gs = state.bind();
|
||||
let pi = player_idx.max(0) as usize;
|
||||
let ci = city_idx.max(0) as usize;
|
||||
let bid = building_id.to_string();
|
||||
|
||||
let mut d = Dictionary::new();
|
||||
|
||||
let Some(player) = gs.inner.players.get(pi) else {
|
||||
return d;
|
||||
};
|
||||
|
||||
let bs = player.building_states.get(&(ci, bid));
|
||||
|
||||
// garrisoned_unit_ids — always emit (empty array when no state or no garrison)
|
||||
let mut ids: Array<i64> = Array::new();
|
||||
if let Some(bs) = bs {
|
||||
for &uid in &bs.garrisoned_unit_ids {
|
||||
ids.push(uid as i64);
|
||||
}
|
||||
}
|
||||
d.set("garrisoned_unit_ids", ids);
|
||||
|
||||
let bs = match bs {
|
||||
Some(bs) => bs,
|
||||
None => return d, // defaults are all false/0; caller can use those
|
||||
};
|
||||
|
||||
d.set("is_active", bs.is_active);
|
||||
d.set("gate_open", bs.gate_open);
|
||||
d.set("murder_holes_active", bs.murder_holes_active);
|
||||
d.set("ranged_fire_active", bs.ranged_fire_active);
|
||||
d.set("fire_arrows_active", bs.fire_arrows_active);
|
||||
d.set("sound_alarm_expiry", bs.sound_alarm_expiry as i64);
|
||||
d.set("auto_promote", bs.auto_promote);
|
||||
d.set("stockpile_locked", bs.stockpile_locked);
|
||||
d.set(
|
||||
"continuous_turns_remaining",
|
||||
bs.current_continuous
|
||||
.as_ref()
|
||||
.map(|a| a.turns_remaining() as i64)
|
||||
.unwrap_or(0),
|
||||
);
|
||||
d
|
||||
}
|
||||
|
||||
/// Queue a building action onto `state.pending_building_actions`.
|
||||
/// Drained at the start of the next turn by the turn processor.
|
||||
///
|
||||
/// `kind` must be a valid `BuildingActionKind::as_str()` value, e.g. `"set_rally"`.
|
||||
/// Unknown kinds are silently dropped (programmer error — log and investigate).
|
||||
/// For GarrisonIn/Out use `invoke_with_unit` instead.
|
||||
#[func]
|
||||
pub fn invoke(
|
||||
&self,
|
||||
|
|
@ -107,7 +225,34 @@ impl GdBuildingActions {
|
|||
city_idx: city_idx.max(0) as usize,
|
||||
building_id: building_id.to_string(),
|
||||
kind: action_kind,
|
||||
unit_id: None, // GarrisonIn/Out require unit_id via invoke_with_unit
|
||||
unit_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Queue GarrisonIn or GarrisonOut with a specific unit ID.
|
||||
/// `unit_id` is the MapUnit::id of the unit to garrison or extract.
|
||||
#[func]
|
||||
pub fn invoke_with_unit(
|
||||
&self,
|
||||
state: Gd<crate::GdGameState>,
|
||||
player_idx: i64,
|
||||
city_idx: i64,
|
||||
building_id: GString,
|
||||
kind: GString,
|
||||
unit_id: i64,
|
||||
) {
|
||||
let kind_str = kind.to_string();
|
||||
let Some(action_kind) = BuildingActionKind::from_str(&kind_str) else {
|
||||
godot_error!("GdBuildingActions::invoke_with_unit: unknown BuildingActionKind {:?}", kind_str);
|
||||
return;
|
||||
};
|
||||
let mut gs = state.clone();
|
||||
gs.bind_mut().inner.pending_building_actions.push(BuildingActionRequest {
|
||||
player_idx: player_idx.max(0) as usize,
|
||||
city_idx: city_idx.max(0) as usize,
|
||||
building_id: building_id.to_string(),
|
||||
kind: action_kind,
|
||||
unit_id: Some(unit_id.max(0) as u32),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue