feat(@projects/@magic-civilization): add diplomacy event and war/peace functions

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-17 07:29:53 -07:00
parent 3b1e77455d
commit 95635b3804

View file

@ -194,6 +194,102 @@ pub fn break_trades_on_war(ledger: &mut TradeLedger, at_war_a: u8, at_war_b: u8)
broken
}
// ── Diplomacy actions ──────────────────────────────────────────────────────
/// Outcome of a diplomacy action, returned to the turn processor for logging.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiplomacyEvent {
WarDeclared { by: u8, against: u8 },
PeaceOffered { by: u8, to: u8 },
PeaceAccepted { by: u8, with: u8 },
/// EA: AI always rejects player-initiated peace offers.
PeaceRejected { by: u8, with: u8 },
TradeOfferAccepted { by: u8, with: u8, gold: u32, luxury_id: String },
/// EA: AI always rejects gold-for-luxury offers.
TradeOfferRejected { by: u8, with: u8 },
}
/// A player-initiated offer of gold in exchange for a luxury resource.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeOffer {
pub from: u8,
pub to: u8,
/// Gold the offering player pays.
pub gold: u32,
/// Luxury ID the offering player wants in return.
pub luxury_id: String,
}
/// Declare war between two players. Sets relation to War immediately,
/// breaks all active trades between them, resets counters.
/// Returns the event plus any broken agreements.
pub fn declare_war(
relations: &mut BTreeMap<(u8, u8), RelationState>,
ledger: &mut TradeLedger,
by: u8,
against: u8,
) -> (DiplomacyEvent, Vec<TradeAgreement>) {
let key = pair_key(by, against);
let rs = relations.entry(key).or_default();
rs.relation = Relation::War;
rs.peaceful_turns = 0;
rs.trade_turns = 0;
rs.war_idle_turns = 0;
let broken = break_trades_on_war(ledger, by, against);
(DiplomacyEvent::WarDeclared { by, against }, broken)
}
/// Offer peace to an opponent. In Early Access the AI always rejects, so
/// this returns `PeaceRejected`. The caller may choose to accept on behalf
/// of a human opponent by calling `accept_peace` directly.
pub fn offer_peace(by: u8, to: u8) -> DiplomacyEvent {
DiplomacyEvent::PeaceRejected { by, with: to }
}
/// Force-accept a peace offer (used when the receiving side is human or when
/// the armistice auto-fires). Sets relation to Neutral so the peace counter
/// can accumulate toward Peace.
pub fn accept_peace(
relations: &mut BTreeMap<(u8, u8), RelationState>,
by: u8,
with: u8,
) -> DiplomacyEvent {
let key = pair_key(by, with);
let rs = relations.entry(key).or_default();
rs.relation = Relation::Neutral;
rs.war_idle_turns = 0;
DiplomacyEvent::PeaceAccepted { by, with }
}
/// Evaluate a `TradeOffer` (gold ↔ luxury). In EA the AI always rejects
/// player-initiated offers — automated luxury swaps go through `evaluate_trades`.
/// Returns `TradeOfferRejected`. Deduct gold only on acceptance; callers
/// handle the ledger update if they override this for tests.
pub fn evaluate_trade_offer(offer: &TradeOffer) -> DiplomacyEvent {
DiplomacyEvent::TradeOfferRejected { by: offer.to, with: offer.from }
}
/// Apply an accepted trade offer: deduct gold from `from`, credit `luxury_id`
/// into `to`'s `traded_luxuries`. Called only when the offer is accepted
/// (human vs human, or test override).
pub fn apply_trade_offer(
from_gold: &mut i32,
to_traded_luxuries: &mut BTreeSet<String>,
offer: &TradeOffer,
) -> DiplomacyEvent {
*from_gold -= offer.gold as i32;
if *from_gold < 0 {
*from_gold = 0;
}
to_traded_luxuries.insert(offer.luxury_id.clone());
DiplomacyEvent::TradeOfferAccepted {
by: offer.to,
with: offer.from,
gold: offer.gold,
luxury_id: offer.luxury_id.clone(),
}
}
/// Advance all relation states for one turn.
///
/// `combat_pairs`: set of `pair_key(a, b)` where combat occurred this turn.