Add chairman packed event runtime support

This commit is contained in:
Jan Petykiewicz 2026-04-16 16:07:10 -07:00
commit 86cf89b26c
23 changed files with 1431 additions and 41 deletions

View file

@ -3,10 +3,10 @@ use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::{
RuntimeCargoClass, RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget,
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
RuntimePlayerTarget, RuntimeState, RuntimeSummary, RuntimeTerritoryMetric,
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyControllerKind,
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
calendar::BoundaryEventKind,
};
@ -87,6 +87,8 @@ struct AppliedEffectsSummary {
struct ResolvedConditionContext {
matching_company_ids: BTreeSet<u32>,
matching_player_ids: BTreeSet<u32>,
#[allow(dead_code)]
matching_chairman_profile_ids: BTreeSet<u32>,
}
pub fn execute_step_command(
@ -346,6 +348,21 @@ fn apply_runtime_effects(
mutated_player_ids.insert(player_id);
}
}
RuntimeEffect::SetChairmanCash { target, value } => {
let profile_ids = resolve_chairman_target_ids(state, target, condition_context)?;
for profile_id in profile_ids {
let chairman = state
.chairman_profiles
.iter_mut()
.find(|profile| profile.profile_id == profile_id)
.ok_or_else(|| {
format!(
"missing chairman profile_id {profile_id} while applying cash effect"
)
})?;
chairman.current_cash = *value;
}
}
RuntimeEffect::DeactivatePlayer { target } => {
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
for player_id in player_ids {
@ -365,6 +382,39 @@ fn apply_runtime_effects(
}
}
}
RuntimeEffect::DeactivateChairman { target } => {
let profile_ids = resolve_chairman_target_ids(state, target, condition_context)?;
for profile_id in profile_ids.iter().copied() {
let linked_company_id = state
.chairman_profiles
.iter()
.find(|profile| profile.profile_id == profile_id)
.and_then(|profile| profile.linked_company_id);
let chairman = state
.chairman_profiles
.iter_mut()
.find(|profile| profile.profile_id == profile_id)
.ok_or_else(|| {
format!(
"missing chairman profile_id {profile_id} while applying deactivate effect"
)
})?;
chairman.active = false;
chairman.linked_company_id = None;
if state.selected_chairman_profile_id == Some(profile_id) {
state.selected_chairman_profile_id = None;
}
if let Some(linked_company_id) = linked_company_id {
for other in &mut state.chairman_profiles {
if other.profile_id != profile_id
&& other.linked_company_id == Some(linked_company_id)
{
other.linked_company_id = None;
}
}
}
}
}
RuntimeEffect::SetCompanyTerritoryAccess {
target,
territory,
@ -607,6 +657,7 @@ fn evaluate_record_conditions(
}
let mut company_matches: Option<BTreeSet<u32>> = None;
let mut chairman_matches: Option<BTreeSet<u32>> = None;
for condition in conditions {
match condition {
@ -657,6 +708,41 @@ fn evaluate_record_conditions(
return Ok(None);
}
}
RuntimeCondition::ChairmanNumericThreshold {
target,
metric,
comparator,
value,
} => {
let resolved = resolve_chairman_target_ids(
state,
target,
&ResolvedConditionContext::default(),
)?;
let matching = resolved
.into_iter()
.filter(|profile_id| {
state
.chairman_profiles
.iter()
.find(|profile| profile.profile_id == *profile_id)
.is_some_and(|profile| {
compare_condition_value(
chairman_metric_value(profile, *metric),
*comparator,
*value,
)
})
})
.collect::<BTreeSet<_>>();
if matching.is_empty() {
return Ok(None);
}
intersect_chairman_matches(&mut chairman_matches, matching);
if chairman_matches.as_ref().is_some_and(BTreeSet::is_empty) {
return Ok(None);
}
}
RuntimeCondition::CompanyTerritoryNumericThreshold {
target,
territory,
@ -840,6 +926,7 @@ fn evaluate_record_conditions(
Ok(Some(ResolvedConditionContext {
matching_company_ids: company_matches.unwrap_or_default(),
matching_player_ids: BTreeSet::new(),
matching_chairman_profile_ids: chairman_matches.unwrap_or_default(),
}))
}
@ -854,6 +941,17 @@ fn intersect_company_matches(company_matches: &mut Option<BTreeSet<u32>>, next:
}
}
fn intersect_chairman_matches(chairman_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
match chairman_matches {
Some(existing) => {
existing.retain(|profile_id| next.contains(profile_id));
}
None => {
*chairman_matches = Some(next);
}
}
}
fn resolve_company_target_ids(
state: &RuntimeState,
target: &RuntimeCompanyTarget,
@ -1043,6 +1141,53 @@ fn resolve_player_target_ids(
}
}
fn resolve_chairman_target_ids(
state: &RuntimeState,
target: &RuntimeChairmanTarget,
_condition_context: &ResolvedConditionContext,
) -> Result<Vec<u32>, String> {
match target {
RuntimeChairmanTarget::AllActive => Ok(state
.chairman_profiles
.iter()
.filter(|profile| profile.active)
.map(|profile| profile.profile_id)
.collect()),
RuntimeChairmanTarget::Ids { ids } => {
let known_ids = state
.chairman_profiles
.iter()
.map(|profile| profile.profile_id)
.collect::<BTreeSet<_>>();
for profile_id in ids {
if !known_ids.contains(profile_id) {
return Err(format!(
"target references unknown chairman profile_id {profile_id}"
));
}
}
Ok(ids.clone())
}
RuntimeChairmanTarget::SelectedChairman => {
let selected_profile_id = state.selected_chairman_profile_id.ok_or_else(|| {
"target requires selected_chairman_profile_id context".to_string()
})?;
if state
.chairman_profiles
.iter()
.any(|profile| profile.profile_id == selected_profile_id && profile.active)
{
Ok(vec![selected_profile_id])
} else {
Err(
"target requires selected_chairman_profile_id to reference an active chairman profile"
.to_string(),
)
}
}
}
}
fn resolve_territory_target_ids(
state: &RuntimeState,
target: &RuntimeTerritoryTarget,
@ -1090,6 +1235,18 @@ fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyM
}
}
fn chairman_metric_value(
profile: &crate::RuntimeChairmanProfile,
metric: RuntimeChairmanMetric,
) -> i64 {
match metric {
RuntimeChairmanMetric::CurrentCash => profile.current_cash,
RuntimeChairmanMetric::HoldingsValueTotal => profile.holdings_value_total,
RuntimeChairmanMetric::NetWorthTotal => profile.net_worth_total,
RuntimeChairmanMetric::PurchasingPowerTotal => profile.purchasing_power_total,
}
}
fn territory_metric_value(
state: &RuntimeState,
territory_ids: &[u32],
@ -1277,6 +1434,8 @@ mod tests {
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),