Execute real packed event world and train descriptors
This commit is contained in:
parent
ca208f74e0
commit
e481274243
31 changed files with 3287 additions and 206 deletions
|
|
@ -6,8 +6,7 @@ use crate::{
|
|||
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget,
|
||||
RuntimeState, RuntimeSummary, RuntimeTerritoryMetric, RuntimeTerritoryTarget,
|
||||
RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||
calendar::BoundaryEventKind,
|
||||
RuntimeTrackMetric, RuntimeTrackPieceCounts, calendar::BoundaryEventKind,
|
||||
};
|
||||
|
||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||
|
|
@ -312,6 +311,9 @@ fn apply_runtime_effects(
|
|||
RuntimeEffect::SetWorldFlag { key, value } => {
|
||||
state.world_flags.insert(key.clone(), *value);
|
||||
}
|
||||
RuntimeEffect::SetEconomicStatusCode { value } => {
|
||||
state.world_restore.economic_status_code = Some(*value);
|
||||
}
|
||||
RuntimeEffect::SetCompanyCash { target, value } => {
|
||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||
for company_id in company_ids {
|
||||
|
|
@ -340,6 +342,28 @@ fn apply_runtime_effects(
|
|||
mutated_player_ids.insert(player_id);
|
||||
}
|
||||
}
|
||||
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||
for company_id in company_ids.iter().copied() {
|
||||
let company = state
|
||||
.companies
|
||||
.iter_mut()
|
||||
.find(|company| company.company_id == company_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"missing company_id {company_id} while applying confiscate effect"
|
||||
)
|
||||
})?;
|
||||
company.current_cash = 0;
|
||||
company.debt = 0;
|
||||
company.active = false;
|
||||
mutated_company_ids.insert(company_id);
|
||||
if state.selected_company_id == Some(company_id) {
|
||||
state.selected_company_id = None;
|
||||
}
|
||||
}
|
||||
retire_matching_trains(&mut state.trains, Some(&company_ids), None, None);
|
||||
}
|
||||
RuntimeEffect::DeactivateCompany { target } => {
|
||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||
for company_id in company_ids {
|
||||
|
|
@ -375,6 +399,26 @@ fn apply_runtime_effects(
|
|||
mutated_company_ids.insert(company_id);
|
||||
}
|
||||
}
|
||||
RuntimeEffect::RetireTrains {
|
||||
company_target,
|
||||
territory_target,
|
||||
locomotive_name,
|
||||
} => {
|
||||
let company_ids = company_target
|
||||
.as_ref()
|
||||
.map(|target| resolve_company_target_ids(state, target, condition_context))
|
||||
.transpose()?;
|
||||
let territory_ids = territory_target
|
||||
.as_ref()
|
||||
.map(|target| resolve_territory_target_ids(state, target))
|
||||
.transpose()?;
|
||||
retire_matching_trains(
|
||||
&mut state.trains,
|
||||
company_ids.as_ref(),
|
||||
territory_ids.as_ref(),
|
||||
locomotive_name.as_deref(),
|
||||
);
|
||||
}
|
||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||
for company_id in company_ids {
|
||||
|
|
@ -523,13 +567,17 @@ fn evaluate_record_conditions(
|
|||
let matching = resolved
|
||||
.into_iter()
|
||||
.filter(|company_id| {
|
||||
state.companies.iter().find(|company| company.company_id == *company_id).is_some_and(
|
||||
|company| compare_condition_value(
|
||||
company_metric_value(company, *metric),
|
||||
*comparator,
|
||||
*value,
|
||||
),
|
||||
)
|
||||
state
|
||||
.companies
|
||||
.iter()
|
||||
.find(|company| company.company_id == *company_id)
|
||||
.is_some_and(|company| {
|
||||
compare_condition_value(
|
||||
company_metric_value(company, *metric),
|
||||
*comparator,
|
||||
*value,
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
if matching.is_empty() {
|
||||
|
|
@ -597,10 +645,7 @@ fn evaluate_record_conditions(
|
|||
}))
|
||||
}
|
||||
|
||||
fn intersect_company_matches(
|
||||
company_matches: &mut Option<BTreeSet<u32>>,
|
||||
next: BTreeSet<u32>,
|
||||
) {
|
||||
fn intersect_company_matches(company_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
|
||||
match company_matches {
|
||||
Some(existing) => {
|
||||
existing.retain(|company_id| next.contains(company_id));
|
||||
|
|
@ -790,7 +835,11 @@ fn resolve_player_target_ids(
|
|||
if condition_context.matching_player_ids.is_empty() {
|
||||
Err("target requires player condition-evaluation context".to_string())
|
||||
} else {
|
||||
Ok(condition_context.matching_player_ids.iter().copied().collect())
|
||||
Ok(condition_context
|
||||
.matching_player_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -801,9 +850,11 @@ fn resolve_territory_target_ids(
|
|||
target: &RuntimeTerritoryTarget,
|
||||
) -> Result<Vec<u32>, String> {
|
||||
match target {
|
||||
RuntimeTerritoryTarget::AllTerritories => {
|
||||
Ok(state.territories.iter().map(|territory| territory.territory_id).collect())
|
||||
}
|
||||
RuntimeTerritoryTarget::AllTerritories => Ok(state
|
||||
.territories
|
||||
.iter()
|
||||
.map(|territory| territory.territory_id)
|
||||
.collect()),
|
||||
RuntimeTerritoryTarget::Ids { ids } => {
|
||||
let known_ids = state
|
||||
.territories
|
||||
|
|
@ -812,7 +863,9 @@ fn resolve_territory_target_ids(
|
|||
.collect::<BTreeSet<_>>();
|
||||
for territory_id in ids {
|
||||
if !known_ids.contains(territory_id) {
|
||||
return Err(format!("territory target references unknown territory_id {territory_id}"));
|
||||
return Err(format!(
|
||||
"territory target references unknown territory_id {territory_id}"
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(ids.clone())
|
||||
|
|
@ -832,9 +885,7 @@ fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyM
|
|||
RuntimeCompanyMetric::TrackPiecesTransition => {
|
||||
i64::from(company.track_piece_counts.transition)
|
||||
}
|
||||
RuntimeCompanyMetric::TrackPiecesElectric => {
|
||||
i64::from(company.track_piece_counts.electric)
|
||||
}
|
||||
RuntimeCompanyMetric::TrackPiecesElectric => i64::from(company.track_piece_counts.electric),
|
||||
RuntimeCompanyMetric::TrackPiecesNonElectric => {
|
||||
i64::from(company.track_piece_counts.non_electric)
|
||||
}
|
||||
|
|
@ -846,7 +897,8 @@ fn territory_metric_value(
|
|||
territory_ids: &[u32],
|
||||
metric: RuntimeTerritoryMetric,
|
||||
) -> i64 {
|
||||
state.territories
|
||||
state
|
||||
.territories
|
||||
.iter()
|
||||
.filter(|territory| territory_ids.contains(&territory.territory_id))
|
||||
.map(|territory| {
|
||||
|
|
@ -864,9 +916,12 @@ fn company_territory_metric_value(
|
|||
territory_ids: &[u32],
|
||||
metric: RuntimeTrackMetric,
|
||||
) -> i64 {
|
||||
state.company_territory_track_piece_counts
|
||||
state
|
||||
.company_territory_track_piece_counts
|
||||
.iter()
|
||||
.filter(|entry| entry.company_id == company_id && territory_ids.contains(&entry.territory_id))
|
||||
.filter(|entry| {
|
||||
entry.company_id == company_id && territory_ids.contains(&entry.territory_id)
|
||||
})
|
||||
.map(|entry| track_piece_metric_value(entry.track_piece_counts, metric))
|
||||
.sum()
|
||||
}
|
||||
|
|
@ -920,6 +975,34 @@ fn apply_u64_delta(current: u64, delta: i64, company_id: u32) -> Result<u64, Str
|
|||
}
|
||||
}
|
||||
|
||||
fn retire_matching_trains(
|
||||
trains: &mut [crate::RuntimeTrain],
|
||||
company_ids: Option<&Vec<u32>>,
|
||||
territory_ids: Option<&Vec<u32>>,
|
||||
locomotive_name: Option<&str>,
|
||||
) {
|
||||
for train in trains.iter_mut() {
|
||||
if !train.active || train.retired {
|
||||
continue;
|
||||
}
|
||||
if company_ids.is_some_and(|company_ids| !company_ids.contains(&train.owner_company_id)) {
|
||||
continue;
|
||||
}
|
||||
if territory_ids.is_some_and(|territory_ids| {
|
||||
!train
|
||||
.territory_id
|
||||
.is_some_and(|territory_id| territory_ids.contains(&territory_id))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
if locomotive_name.is_some_and(|name| train.locomotive_name.as_deref() != Some(name)) {
|
||||
continue;
|
||||
}
|
||||
train.active = false;
|
||||
train.retired = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
|
@ -928,7 +1011,8 @@ mod tests {
|
|||
use crate::{
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
|
||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeWorldRestoreState,
|
||||
RuntimeServiceState, RuntimeTerritory, RuntimeTerritoryTarget, RuntimeTrackPieceCounts,
|
||||
RuntimeTrain, RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
fn state() -> RuntimeState {
|
||||
|
|
@ -957,6 +1041,7 @@ mod tests {
|
|||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
|
|
@ -1927,4 +2012,177 @@ mod tests {
|
|||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_economic_status_code_effect() {
|
||||
let mut state = RuntimeState {
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 90,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetEconomicStatusCode { value: 3 }],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("economic-status effect should succeed");
|
||||
|
||||
assert_eq!(state.world_restore.economic_status_code, Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confiscate_company_assets_zeros_company_and_retires_owned_trains() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 50,
|
||||
debt: 7,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 80,
|
||||
debt: 9,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
trains: vec![
|
||||
RuntimeTrain {
|
||||
train_id: 10,
|
||||
owner_company_id: 1,
|
||||
territory_id: None,
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
active: true,
|
||||
retired: false,
|
||||
},
|
||||
RuntimeTrain {
|
||||
train_id: 11,
|
||||
owner_company_id: 2,
|
||||
territory_id: None,
|
||||
locomotive_name: Some("Orca".to_string()),
|
||||
active: true,
|
||||
retired: false,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 91,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::ConfiscateCompanyAssets {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("confiscation effect should succeed");
|
||||
|
||||
assert_eq!(state.companies[0].current_cash, 0);
|
||||
assert_eq!(state.companies[0].debt, 0);
|
||||
assert!(!state.companies[0].active);
|
||||
assert_eq!(state.selected_company_id, None);
|
||||
assert!(state.trains[0].retired);
|
||||
assert!(!state.trains[1].retired);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retire_trains_respects_company_territory_and_locomotive_filters() {
|
||||
let mut state = RuntimeState {
|
||||
territories: vec![
|
||||
RuntimeTerritory {
|
||||
territory_id: 7,
|
||||
name: Some("Appalachia".to_string()),
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
},
|
||||
RuntimeTerritory {
|
||||
territory_id: 8,
|
||||
name: Some("Great Plains".to_string()),
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
},
|
||||
],
|
||||
trains: vec![
|
||||
RuntimeTrain {
|
||||
train_id: 10,
|
||||
owner_company_id: 1,
|
||||
territory_id: Some(7),
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
active: true,
|
||||
retired: false,
|
||||
},
|
||||
RuntimeTrain {
|
||||
train_id: 11,
|
||||
owner_company_id: 1,
|
||||
territory_id: Some(7),
|
||||
locomotive_name: Some("Orca".to_string()),
|
||||
active: true,
|
||||
retired: false,
|
||||
},
|
||||
RuntimeTrain {
|
||||
train_id: 12,
|
||||
owner_company_id: 1,
|
||||
territory_id: Some(8),
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
active: true,
|
||||
retired: false,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 92,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::RetireTrains {
|
||||
company_target: Some(RuntimeCompanyTarget::SelectedCompany),
|
||||
territory_target: Some(RuntimeTerritoryTarget::Ids { ids: vec![7] }),
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
}],
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("retire-trains effect should succeed");
|
||||
|
||||
assert!(state.trains[0].retired);
|
||||
assert!(!state.trains[1].retired);
|
||||
assert!(!state.trains[2].retired);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue