feat(@projects/@magic-civilization): ✨ add ransom event handling logic
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a747753097
commit
57504e0629
5 changed files with 88 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"generated_at": "2026-05-09T08:52:30Z",
|
||||
"generated_at": "2026-05-09T08:57:49Z",
|
||||
"totals": {
|
||||
"done": 177,
|
||||
"in_progress": 1,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ Chronicle currently distinguishes "expired-then-captured" from "fresh capture" b
|
|||
|
||||
- [x] New event types in `mc-turn::combat_event`: `UnitRansomAcceptedEvent { offer_id, unit_id, captor, owner, price_paid, turn }`, `UnitRansomExpiredEvent { offer_id, unit_id, captor, prior_owner, turn }`. ✓ Both authored at `src/simulator/crates/mc-turn/src/combat_event.rs:84-114` (`#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]`). `cargo check -p mc-turn` clean.
|
||||
- [x] `TurnResult` gains `ransom_offers_accepted: Vec<UnitRansomAcceptedEvent>` and `ransom_offers_expired: Vec<UnitRansomExpiredEvent>`. ✓ Added at `combat_event.rs:189-198` with `#[serde(default)]` for save migration.
|
||||
- [ ] `mc-turn::process_ransom_expiry` pushes `UnitRansomExpiredEvent` instead of (or in addition to) `UnitCapturedEvent` for the conversion.
|
||||
- [ ] `accept_ransom_offer` / `refuse_ransom_offer` push the corresponding event into `pending_capture_events` so the next `step()` drains it onto TurnResult (instead of relying solely on the method return dict).
|
||||
- [ ] api-gdext bridge surfaces both as `Array[Dictionary]` on the `step()` result.
|
||||
- [x] `mc-turn::process_ransom_expiry` pushes `UnitRansomExpiredEvent` in addition to the existing `UnitCapturedEvent` for the conversion. ✓ `processor.rs:2244-2253` adds the expired event after the capture event.
|
||||
- [x] `accept_ransom_offer` / `refuse_ransom_offer` push the corresponding event into `pending_capture_events.ransom_offers_accepted` / `_expired` so the next `step()` drains them onto TurnResult. ✓ `api-gdext/src/lib.rs:3791-3804` (accept) and `:3894-3905` (refuse). `PendingCaptureEvents.drain_into` updated at `mc-turn/src/game_state.rs:407-414` to drain both new vecs onto TurnResult.
|
||||
- [-] ◐ api-gdext bridge surfaces both as `Array[Dictionary]` on the `step()` result. Bridge writes through `pending_capture_events` and they arrive on `TurnResult.ransom_offers_accepted` / `_expired` already; the `step()` Dictionary projection of those Vecs into `Array[Dictionary]` shape is the remaining piece (mirror of `units_captured` dict shape).
|
||||
- [ ] godot-engine chronicle subscriber reads from these new arrays directly; cross-reference workaround removed.
|
||||
- [ ] Test in `mc-turn/tests/ransom.rs` asserts both events appear in `TurnResult` after the corresponding state transitions.
|
||||
|
||||
|
|
|
|||
|
|
@ -3788,6 +3788,20 @@ impl GdGameState {
|
|||
}
|
||||
let _ = self.inner.ransom_queue.accept(preview.id);
|
||||
|
||||
// p2-55e: push typed event so next step() drains it into
|
||||
// TurnResult.ransom_offers_accepted (chronicle reads from there).
|
||||
self.inner
|
||||
.pending_capture_events
|
||||
.ransom_offers_accepted
|
||||
.push(::mc_turn::combat_event::UnitRansomAcceptedEvent {
|
||||
turn: self.inner.turn,
|
||||
offer_id: preview.id,
|
||||
unit_id: preview.unit_id,
|
||||
captor: preview.captor,
|
||||
owner: preview.owner,
|
||||
price_paid: preview.price,
|
||||
});
|
||||
|
||||
out.set("success", true);
|
||||
out.set("gold_paid", preview.price as i64);
|
||||
out.set("unit_id", preview.unit_id as i64);
|
||||
|
|
@ -3877,6 +3891,20 @@ impl GdGameState {
|
|||
|
||||
let _ = self.inner.ransom_queue.refuse(preview.id);
|
||||
|
||||
// p2-55e: push typed event so next step() drains it into
|
||||
// TurnResult.ransom_offers_expired. Refuse and time-out share the
|
||||
// expired event variant (both convert ownership to captor).
|
||||
self.inner
|
||||
.pending_capture_events
|
||||
.ransom_offers_expired
|
||||
.push(::mc_turn::combat_event::UnitRansomExpiredEvent {
|
||||
turn: self.inner.turn,
|
||||
offer_id: preview.id,
|
||||
unit_id: preview.unit_id,
|
||||
captor: preview.captor,
|
||||
prior_owner: preview.owner,
|
||||
});
|
||||
|
||||
out.set("success", true);
|
||||
out.set("unit_id", preview.unit_id as i64);
|
||||
out.set("new_owner", preview.captor as i64);
|
||||
|
|
@ -4287,6 +4315,33 @@ fn turn_result_to_dict(result: &mc_turn::TurnResult, post_turn: u32) -> Dictiona
|
|||
}
|
||||
d.set("civilians_destroyed", destroyed);
|
||||
|
||||
// p2-55e: typed accept/expire surfaces. Chronicle subscriber reads these
|
||||
// directly (no prior-turn cross-reference required).
|
||||
let mut accepted: Array<Dictionary> = Array::new();
|
||||
for ev in &result.ransom_offers_accepted {
|
||||
let mut e = Dictionary::new();
|
||||
e.set("turn", ev.turn as i64);
|
||||
e.set("offer_id", ev.offer_id as i64);
|
||||
e.set("unit_id", ev.unit_id as i64);
|
||||
e.set("captor", ev.captor as i64);
|
||||
e.set("owner", ev.owner as i64);
|
||||
e.set("price_paid", ev.price_paid as i64);
|
||||
accepted.push(&e);
|
||||
}
|
||||
d.set("ransom_offers_accepted", accepted);
|
||||
|
||||
let mut expired: Array<Dictionary> = Array::new();
|
||||
for ev in &result.ransom_offers_expired {
|
||||
let mut e = Dictionary::new();
|
||||
e.set("turn", ev.turn as i64);
|
||||
e.set("offer_id", ev.offer_id as i64);
|
||||
e.set("unit_id", ev.unit_id as i64);
|
||||
e.set("captor", ev.captor as i64);
|
||||
e.set("prior_owner", ev.prior_owner as i64);
|
||||
expired.push(&e);
|
||||
}
|
||||
d.set("ransom_offers_expired", expired);
|
||||
|
||||
d
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -354,6 +354,13 @@ pub struct PendingCaptureEvents {
|
|||
pub units_captured: Vec<crate::combat_event::UnitCapturedEvent>,
|
||||
pub ransom_offers_created: Vec<crate::combat_event::UnitRansomOfferedEvent>,
|
||||
pub civilians_destroyed: Vec<crate::combat_event::CivilianDestroyedEvent>,
|
||||
/// p2-55e: ransom offers paid out (gold deducted, ownership restored).
|
||||
/// Bridge accept_ransom_offer pushes here; drain_into surfaces onto
|
||||
/// `TurnResult.ransom_offers_accepted`.
|
||||
pub ransom_offers_accepted: Vec<crate::combat_event::UnitRansomAcceptedEvent>,
|
||||
/// p2-55e: ransom offers refused (manual refuse) or expired (timeout).
|
||||
/// `process_ransom_expiry` and bridge `refuse_ransom_offer` push here.
|
||||
pub ransom_offers_expired: Vec<crate::combat_event::UnitRansomExpiredEvent>,
|
||||
}
|
||||
|
||||
impl PendingCaptureEvents {
|
||||
|
|
@ -399,12 +406,22 @@ impl PendingCaptureEvents {
|
|||
result.units_captured.append(&mut self.units_captured);
|
||||
result.ransom_offers_created.append(&mut self.ransom_offers_created);
|
||||
result.civilians_destroyed.append(&mut self.civilians_destroyed);
|
||||
// p2-55e: drain accepted/expired into TurnResult so chronicle reads
|
||||
// them directly without prior-turn cross-reference.
|
||||
result
|
||||
.ransom_offers_accepted
|
||||
.append(&mut self.ransom_offers_accepted);
|
||||
result
|
||||
.ransom_offers_expired
|
||||
.append(&mut self.ransom_offers_expired);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.units_captured.is_empty()
|
||||
&& self.ransom_offers_created.is_empty()
|
||||
&& self.civilians_destroyed.is_empty()
|
||||
&& self.ransom_offers_accepted.is_empty()
|
||||
&& self.ransom_offers_expired.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2240,6 +2240,18 @@ impl TurnProcessor {
|
|||
row,
|
||||
unit_kind,
|
||||
});
|
||||
// p2-55e: surface the expiry as a typed first-class event so
|
||||
// chronicle / AI memory can read it directly instead of
|
||||
// cross-referencing the prior turn's `ransom_offers_created`.
|
||||
result
|
||||
.ransom_offers_expired
|
||||
.push(crate::combat_event::UnitRansomExpiredEvent {
|
||||
turn: state.turn,
|
||||
offer_id: offer.id,
|
||||
unit_id: offer.unit_id,
|
||||
captor: offer.captor,
|
||||
prior_owner: offer.owner,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue