Implement runtime variable event conditions
This commit is contained in:
parent
d3790c2ae3
commit
bd9e1421a1
15 changed files with 1442 additions and 29 deletions
|
|
@ -4481,11 +4481,17 @@ mod tests {
|
|||
);
|
||||
let runtime_variable_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-runtime-variable-overlay-fixture.json");
|
||||
let runtime_variable_condition_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join(
|
||||
"../../fixtures/runtime/packed-event-runtime-variable-condition-overlay-fixture.json",
|
||||
);
|
||||
let cargo_economics_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json");
|
||||
let cargo_economics_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json",
|
||||
);
|
||||
let add_building_shell_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-add-building-shell-save-slice-fixture.json");
|
||||
let world_scalar_condition_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-world-scalar-condition-save-slice-fixture.json",
|
||||
);
|
||||
|
|
@ -4599,10 +4605,14 @@ mod tests {
|
|||
.expect("save-slice-backed world-scalar override fixture should summarize");
|
||||
run_runtime_summarize_fixture(&runtime_variable_overlay_fixture)
|
||||
.expect("overlay-backed runtime-variable fixture should summarize");
|
||||
run_runtime_summarize_fixture(&runtime_variable_condition_overlay_fixture)
|
||||
.expect("overlay-backed runtime-variable condition fixture should summarize");
|
||||
run_runtime_summarize_fixture(&cargo_economics_fixture)
|
||||
.expect("save-slice-backed cargo-economics fixture should summarize");
|
||||
run_runtime_summarize_fixture(&cargo_economics_parity_fixture)
|
||||
.expect("save-slice-backed cargo-economics parity fixture should summarize");
|
||||
run_runtime_summarize_fixture(&add_building_shell_fixture)
|
||||
.expect("save-slice-backed add-building shell fixture should summarize");
|
||||
run_runtime_summarize_fixture(&world_scalar_condition_fixture)
|
||||
.expect("save-slice-backed executable world-scalar condition fixture should summarize");
|
||||
run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture)
|
||||
|
|
|
|||
|
|
@ -1302,6 +1302,7 @@ fn lowered_record_decoded_conditions(
|
|||
}
|
||||
|
||||
let lowered_company_target = lowered_condition_true_company_target(record)?;
|
||||
let lowered_player_target = lowered_condition_true_player_target(record)?;
|
||||
let ordinary_rows = record
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
|
|
@ -1313,6 +1314,7 @@ fn lowered_record_decoded_conditions(
|
|||
condition,
|
||||
row,
|
||||
lowered_company_target.as_ref(),
|
||||
lowered_player_target.as_ref(),
|
||||
company_context,
|
||||
)
|
||||
})
|
||||
|
|
@ -1949,9 +1951,19 @@ fn lower_condition_targets_in_condition(
|
|||
condition: &RuntimeCondition,
|
||||
row: &SmpLoadedPackedEventConditionRowSummary,
|
||||
lowered_company_target: Option<&RuntimeCompanyTarget>,
|
||||
lowered_player_target: Option<&RuntimePlayerTarget>,
|
||||
company_context: &ImportRuntimeContext,
|
||||
) -> Result<RuntimeCondition, ImportBlocker> {
|
||||
Ok(match condition {
|
||||
RuntimeCondition::WorldVariableThreshold {
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::WorldVariableThreshold {
|
||||
index: *index,
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::CompanyNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -1966,6 +1978,20 @@ fn lower_condition_targets_in_condition(
|
|||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::CompanyVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::CompanyVariableThreshold {
|
||||
target: lower_condition_true_company_target_in_company_target(
|
||||
target,
|
||||
lowered_company_target,
|
||||
)?,
|
||||
index: *index,
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::ChairmanNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -1977,6 +2003,20 @@ fn lower_condition_targets_in_condition(
|
|||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::PlayerVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::PlayerVariableThreshold {
|
||||
target: lower_condition_true_player_target_in_player_target(
|
||||
target,
|
||||
lowered_player_target,
|
||||
)?,
|
||||
index: *index,
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::TerritoryNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -1988,6 +2028,17 @@ fn lower_condition_targets_in_condition(
|
|||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::TerritoryVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => RuntimeCondition::TerritoryVariableThreshold {
|
||||
target: lower_territory_target_in_condition(target, row, company_context)?,
|
||||
index: *index,
|
||||
comparator: *comparator,
|
||||
value: *value,
|
||||
},
|
||||
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||
target,
|
||||
territory,
|
||||
|
|
@ -2187,11 +2238,17 @@ fn record_uses_condition_true_player(record: &SmpLoadedPackedEventRecordSummary)
|
|||
fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool {
|
||||
match condition {
|
||||
RuntimeCondition::CompanyNumericThreshold { target, .. }
|
||||
| RuntimeCondition::CompanyVariableThreshold { target, .. }
|
||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
||||
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||
}
|
||||
RuntimeCondition::PlayerVariableThreshold { target, .. } => {
|
||||
matches!(target, RuntimePlayerTarget::ConditionTruePlayer)
|
||||
}
|
||||
RuntimeCondition::ChairmanNumericThreshold { .. } => false,
|
||||
RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::TerritoryVariableThreshold { .. }
|
||||
| RuntimeCondition::WorldVariableThreshold { .. }
|
||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
|
||||
|
|
@ -2719,6 +2776,7 @@ fn conditions_provide_company_context(conditions: &[RuntimeCondition]) -> bool {
|
|||
matches!(
|
||||
condition,
|
||||
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||
| RuntimeCondition::CompanyVariableThreshold { .. }
|
||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||
)
|
||||
})
|
||||
|
|
@ -3026,7 +3084,8 @@ fn record_has_world_state_condition_rows(record: &SmpLoadedPackedEventRecordSumm
|
|||
fn runtime_condition_is_world_state(condition: &RuntimeCondition) -> bool {
|
||||
matches!(
|
||||
condition,
|
||||
RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
RuntimeCondition::WorldVariableThreshold { .. }
|
||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
|
||||
| RuntimeCondition::NamedLocomotiveCostThreshold { .. }
|
||||
|
|
@ -3180,15 +3239,25 @@ fn runtime_condition_company_target_import_blocker(
|
|||
company_context: &ImportRuntimeContext,
|
||||
) -> Option<ImportBlocker> {
|
||||
match condition {
|
||||
RuntimeCondition::WorldVariableThreshold { .. } => None,
|
||||
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
||||
company_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::CompanyVariableThreshold { target, .. } => {
|
||||
company_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::PlayerVariableThreshold { target, .. } => {
|
||||
player_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
|
||||
chairman_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
||||
territory_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::TerritoryVariableThreshold { target, .. } => {
|
||||
territory_target_import_blocker(target, company_context)
|
||||
}
|
||||
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||
target, territory, ..
|
||||
} => company_target_import_blocker(target, company_context)
|
||||
|
|
|
|||
|
|
@ -311,24 +311,47 @@ pub enum RuntimeTrackMetric {
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum RuntimeCondition {
|
||||
WorldVariableThreshold {
|
||||
index: u32,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget,
|
||||
metric: RuntimeCompanyMetric,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
CompanyVariableThreshold {
|
||||
target: RuntimeCompanyTarget,
|
||||
index: u32,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
ChairmanNumericThreshold {
|
||||
target: RuntimeChairmanTarget,
|
||||
metric: RuntimeChairmanMetric,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
PlayerVariableThreshold {
|
||||
target: RuntimePlayerTarget,
|
||||
index: u32,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
TerritoryNumericThreshold {
|
||||
target: RuntimeTerritoryTarget,
|
||||
metric: RuntimeTerritoryMetric,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
TerritoryVariableThreshold {
|
||||
target: RuntimeTerritoryTarget,
|
||||
index: u32,
|
||||
comparator: RuntimeConditionComparator,
|
||||
value: i64,
|
||||
},
|
||||
CompanyTerritoryNumericThreshold {
|
||||
target: RuntimeCompanyTarget,
|
||||
territory: RuntimeTerritoryTarget,
|
||||
|
|
@ -1209,6 +1232,7 @@ impl RuntimeState {
|
|||
validate_runtime_condition(
|
||||
condition,
|
||||
&seen_company_ids,
|
||||
&seen_player_ids,
|
||||
&seen_chairman_profile_ids,
|
||||
&seen_territory_ids,
|
||||
)
|
||||
|
|
@ -1810,6 +1834,7 @@ fn validate_event_record_template(
|
|||
validate_runtime_condition(
|
||||
condition,
|
||||
valid_company_ids,
|
||||
valid_player_ids,
|
||||
valid_chairman_profile_ids,
|
||||
valid_territory_ids,
|
||||
)
|
||||
|
|
@ -1842,19 +1867,51 @@ fn validate_event_record_template(
|
|||
fn validate_runtime_condition(
|
||||
condition: &RuntimeCondition,
|
||||
valid_company_ids: &BTreeSet<u32>,
|
||||
valid_player_ids: &BTreeSet<u32>,
|
||||
valid_chairman_profile_ids: &BTreeSet<u32>,
|
||||
valid_territory_ids: &BTreeSet<u32>,
|
||||
) -> Result<(), String> {
|
||||
match condition {
|
||||
RuntimeCondition::WorldVariableThreshold { index, .. } => {
|
||||
if !(1..=4).contains(index) {
|
||||
Err("index must be in 1..=4".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CompanyNumericThreshold { target, .. } => {
|
||||
validate_company_target(target, valid_company_ids)
|
||||
}
|
||||
RuntimeCondition::CompanyVariableThreshold { target, index, .. } => {
|
||||
validate_company_target(target, valid_company_ids)?;
|
||||
if !(1..=4).contains(index) {
|
||||
Err("index must be in 1..=4".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::ChairmanNumericThreshold { target, .. } => {
|
||||
validate_chairman_target(target, valid_chairman_profile_ids)
|
||||
}
|
||||
RuntimeCondition::PlayerVariableThreshold { target, index, .. } => {
|
||||
validate_player_target(target, valid_player_ids)?;
|
||||
if !(1..=4).contains(index) {
|
||||
Err("index must be in 1..=4".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::TerritoryNumericThreshold { target, .. } => {
|
||||
validate_territory_target(target, valid_territory_ids)
|
||||
}
|
||||
RuntimeCondition::TerritoryVariableThreshold { target, index, .. } => {
|
||||
validate_territory_target(target, valid_territory_ids)?;
|
||||
if !(1..=4).contains(index) {
|
||||
Err("index must be in 1..=4".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||
target, territory, ..
|
||||
} => {
|
||||
|
|
|
|||
|
|
@ -333,9 +333,13 @@ pub(crate) fn grouped_effect_descriptor_runtime_status_name(
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RealOrdinaryConditionMetric {
|
||||
WorldVariable(u32),
|
||||
Company(RuntimeCompanyMetric),
|
||||
CompanyVariable(u32),
|
||||
PlayerVariable(u32),
|
||||
Chairman(RuntimeChairmanMetric),
|
||||
Territory(RuntimeTerritoryMetric),
|
||||
TerritoryVariable(u32),
|
||||
CompanyTerritory(RuntimeTrackMetric),
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +515,22 @@ const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 61] = [
|
|||
];
|
||||
|
||||
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
|
||||
const REAL_WORLD_VARIABLE_1_CONDITION_ID: i32 = 2241;
|
||||
const REAL_WORLD_VARIABLE_2_CONDITION_ID: i32 = 2242;
|
||||
const REAL_WORLD_VARIABLE_3_CONDITION_ID: i32 = 2243;
|
||||
const REAL_WORLD_VARIABLE_4_CONDITION_ID: i32 = 2244;
|
||||
const REAL_COMPANY_VARIABLE_1_CONDITION_ID: i32 = 2245;
|
||||
const REAL_COMPANY_VARIABLE_2_CONDITION_ID: i32 = 2246;
|
||||
const REAL_COMPANY_VARIABLE_3_CONDITION_ID: i32 = 2247;
|
||||
const REAL_COMPANY_VARIABLE_4_CONDITION_ID: i32 = 2248;
|
||||
const REAL_PLAYER_VARIABLE_1_CONDITION_ID: i32 = 2249;
|
||||
const REAL_PLAYER_VARIABLE_2_CONDITION_ID: i32 = 2250;
|
||||
const REAL_PLAYER_VARIABLE_3_CONDITION_ID: i32 = 2251;
|
||||
const REAL_PLAYER_VARIABLE_4_CONDITION_ID: i32 = 2252;
|
||||
const REAL_TERRITORY_VARIABLE_1_CONDITION_ID: i32 = 2253;
|
||||
const REAL_TERRITORY_VARIABLE_2_CONDITION_ID: i32 = 2254;
|
||||
const REAL_TERRITORY_VARIABLE_3_CONDITION_ID: i32 = 2255;
|
||||
const REAL_TERRITORY_VARIABLE_4_CONDITION_ID: i32 = 2256;
|
||||
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
|
||||
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
|
||||
const REAL_CHAIRMAN_NET_WORTH_CONDITION_ID: i32 = 2240;
|
||||
|
|
@ -530,7 +550,87 @@ const REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2421;
|
|||
const REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID: i32 = 2547;
|
||||
const REAL_TERRITORY_ACCESS_COST_CONDITION_ID: i32 = 1516;
|
||||
|
||||
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 40] = [
|
||||
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 56] = [
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
|
||||
label: "Game Variable 1",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(1)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_WORLD_VARIABLE_2_CONDITION_ID,
|
||||
label: "Game Variable 2",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(2)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_WORLD_VARIABLE_3_CONDITION_ID,
|
||||
label: "Game Variable 3",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(3)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_WORLD_VARIABLE_4_CONDITION_ID,
|
||||
label: "Game Variable 4",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(4)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_COMPANY_VARIABLE_1_CONDITION_ID,
|
||||
label: "Company Variable 1",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(1)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_COMPANY_VARIABLE_2_CONDITION_ID,
|
||||
label: "Company Variable 2",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(2)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_COMPANY_VARIABLE_3_CONDITION_ID,
|
||||
label: "Company Variable 3",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(3)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_COMPANY_VARIABLE_4_CONDITION_ID,
|
||||
label: "Company Variable 4",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(4)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_PLAYER_VARIABLE_1_CONDITION_ID,
|
||||
label: "Player Variable 1",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(1)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_PLAYER_VARIABLE_2_CONDITION_ID,
|
||||
label: "Player Variable 2",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(2)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
|
||||
label: "Player Variable 3",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(3)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_PLAYER_VARIABLE_4_CONDITION_ID,
|
||||
label: "Player Variable 4",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(4)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_TERRITORY_VARIABLE_1_CONDITION_ID,
|
||||
label: "Territory Variable 1",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(1)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_TERRITORY_VARIABLE_2_CONDITION_ID,
|
||||
label: "Territory Variable 2",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(2)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_TERRITORY_VARIABLE_3_CONDITION_ID,
|
||||
label: "Territory Variable 3",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(3)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
|
||||
label: "Territory Variable 4",
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(4)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 1802,
|
||||
label: "Current Cash",
|
||||
|
|
@ -3580,6 +3680,13 @@ fn decode_real_condition_row(
|
|||
let comparator = decode_real_condition_comparator(row.subtype)?;
|
||||
let value = decode_real_condition_threshold(&row.flag_bytes)?;
|
||||
match metadata.kind {
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(index)) => {
|
||||
Some(RuntimeCondition::WorldVariableThreshold {
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
|
||||
Some(RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
|
|
@ -3588,6 +3695,26 @@ fn decode_real_condition_row(
|
|||
value,
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(index)) => {
|
||||
Some(RuntimeCondition::CompanyVariableThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(index)) => {
|
||||
negative_sentinel_scope.and_then(|scope| {
|
||||
real_condition_player_target(scope).map(|target| {
|
||||
RuntimeCondition::PlayerVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(metric)) => {
|
||||
negative_sentinel_scope.and_then(|scope| {
|
||||
real_condition_chairman_target(scope).map(|target| {
|
||||
|
|
@ -3600,6 +3727,16 @@ fn decode_real_condition_row(
|
|||
})
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(
|
||||
index,
|
||||
)) => negative_sentinel_scope
|
||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||
.map(|_| RuntimeCondition::TerritoryVariableThreshold {
|
||||
target: RuntimeTerritoryTarget::AllTerritories,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
}),
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
|
||||
negative_sentinel_scope
|
||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||
|
|
@ -3727,6 +3864,22 @@ fn real_condition_chairman_target(
|
|||
}
|
||||
}
|
||||
|
||||
fn real_condition_player_target(
|
||||
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
||||
) -> Option<RuntimePlayerTarget> {
|
||||
match scope.player_test_scope {
|
||||
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimePlayerTarget::AllActive),
|
||||
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
|
||||
Some(RuntimePlayerTarget::SelectedPlayer)
|
||||
}
|
||||
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimePlayerTarget::AiPlayers),
|
||||
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
|
||||
Some(RuntimePlayerTarget::HumanPlayers)
|
||||
}
|
||||
RuntimePlayerConditionTestScope::Disabled => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn real_grouped_effect_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
|
|
@ -4981,9 +5134,13 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
|||
|
||||
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
|
||||
match condition {
|
||||
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||
RuntimeCondition::WorldVariableThreshold { .. }
|
||||
| RuntimeCondition::CompanyNumericThreshold { .. }
|
||||
| RuntimeCondition::CompanyVariableThreshold { .. }
|
||||
| RuntimeCondition::PlayerVariableThreshold { .. }
|
||||
| RuntimeCondition::ChairmanNumericThreshold { .. }
|
||||
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::TerritoryVariableThreshold { .. }
|
||||
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||
| RuntimeCondition::SpecialConditionThreshold { .. }
|
||||
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
||||
|
|
@ -11188,6 +11345,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn looks_up_checked_in_chairman_and_governance_condition_metadata() {
|
||||
let world_variable = real_ordinary_condition_metadata(REAL_WORLD_VARIABLE_1_CONDITION_ID)
|
||||
.expect("world-variable condition metadata should exist");
|
||||
assert_eq!(world_variable.label, "Game Variable 1");
|
||||
|
||||
let player_variable = real_ordinary_condition_metadata(REAL_PLAYER_VARIABLE_3_CONDITION_ID)
|
||||
.expect("player-variable condition metadata should exist");
|
||||
assert_eq!(player_variable.label, "Player Variable 3");
|
||||
|
||||
let chairman_cash = real_ordinary_condition_metadata(REAL_CHAIRMAN_CASH_CONDITION_ID)
|
||||
.expect("chairman cash condition metadata should exist");
|
||||
assert_eq!(chairman_cash.label, "Player Cash");
|
||||
|
|
@ -11228,6 +11393,116 @@ mod tests {
|
|||
assert_eq!(book_value.label, "Book Value Per Share");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_world_variable_condition() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&111_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Game Variable 1".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Game Variable 1 == 111".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
decode_real_condition_row(&row, None),
|
||||
Some(RuntimeCondition::WorldVariableThreshold {
|
||||
index: 1,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 111,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_player_variable_condition_from_selected_player_scope() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&333_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Player Variable 3".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Player Variable 3 == 333".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
};
|
||||
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
|
||||
territory_scope_selector_is_0x63: false,
|
||||
source_row_indexes: vec![0],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
decode_real_condition_row(&row, Some(&negative_scope)),
|
||||
Some(RuntimeCondition::PlayerVariableThreshold {
|
||||
target: RuntimePlayerTarget::SelectedPlayer,
|
||||
index: 3,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 333,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_territory_variable_condition_with_world_territory_scope() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
|
||||
subtype: 4,
|
||||
flag_bytes: {
|
||||
let mut bytes = vec![0; 25];
|
||||
bytes[0..4].copy_from_slice(&444_i32.to_le_bytes());
|
||||
bytes
|
||||
},
|
||||
candidate_name: None,
|
||||
comparator: Some("eq".to_string()),
|
||||
metric: Some("Territory Variable 4".to_string()),
|
||||
semantic_family: Some("numeric_threshold".to_string()),
|
||||
semantic_preview: Some("Test Territory Variable 4 == 444".to_string()),
|
||||
recovered_cargo_slot: None,
|
||||
recovered_cargo_class: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
};
|
||||
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
||||
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
||||
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
|
||||
territory_scope_selector_is_0x63: true,
|
||||
source_row_indexes: vec![0],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
decode_real_condition_row(&row, Some(&negative_scope)),
|
||||
Some(RuntimeCondition::TerritoryVariableThreshold {
|
||||
target: RuntimeTerritoryTarget::AllTerritories,
|
||||
index: 4,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 444,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_chairman_cash_condition_from_selected_player_scope() {
|
||||
let row = SmpLoadedPackedEventConditionRowSummary {
|
||||
|
|
|
|||
|
|
@ -782,10 +782,25 @@ fn evaluate_record_conditions(
|
|||
}
|
||||
|
||||
let mut company_matches: Option<BTreeSet<u32>> = None;
|
||||
let mut player_matches: Option<BTreeSet<u32>> = None;
|
||||
let mut chairman_matches: Option<BTreeSet<u32>> = None;
|
||||
|
||||
for condition in conditions {
|
||||
match condition {
|
||||
RuntimeCondition::WorldVariableThreshold {
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let actual = state
|
||||
.world_runtime_variables
|
||||
.get(index)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
if !compare_condition_value(actual, *comparator, *value) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CompanyNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -821,6 +836,37 @@ fn evaluate_record_conditions(
|
|||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::CompanyVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let resolved = resolve_company_target_ids(
|
||||
state,
|
||||
target,
|
||||
&ResolvedConditionContext::default(),
|
||||
)?;
|
||||
let matching = resolved
|
||||
.into_iter()
|
||||
.filter(|company_id| {
|
||||
let actual = state
|
||||
.company_runtime_variables
|
||||
.get(company_id)
|
||||
.and_then(|vars| vars.get(index))
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
compare_condition_value(actual, *comparator, *value)
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
if matching.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
intersect_company_matches(&mut company_matches, matching);
|
||||
if company_matches.as_ref().is_some_and(BTreeSet::is_empty) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::TerritoryNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -833,6 +879,56 @@ fn evaluate_record_conditions(
|
|||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::TerritoryVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let territory_ids = resolve_territory_target_ids(state, target)?;
|
||||
let actual = territory_ids
|
||||
.iter()
|
||||
.map(|territory_id| {
|
||||
state
|
||||
.territory_runtime_variables
|
||||
.get(territory_id)
|
||||
.and_then(|vars| vars.get(index))
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.sum::<i64>();
|
||||
if !compare_condition_value(actual, *comparator, *value) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::PlayerVariableThreshold {
|
||||
target,
|
||||
index,
|
||||
comparator,
|
||||
value,
|
||||
} => {
|
||||
let resolved =
|
||||
resolve_player_target_ids(state, target, &ResolvedConditionContext::default())?;
|
||||
let matching = resolved
|
||||
.into_iter()
|
||||
.filter(|player_id| {
|
||||
let actual = state
|
||||
.player_runtime_variables
|
||||
.get(player_id)
|
||||
.and_then(|vars| vars.get(index))
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
compare_condition_value(actual, *comparator, *value)
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
if matching.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
intersect_player_matches(&mut player_matches, matching);
|
||||
if player_matches.as_ref().is_some_and(BTreeSet::is_empty) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
RuntimeCondition::ChairmanNumericThreshold {
|
||||
target,
|
||||
metric,
|
||||
|
|
@ -1050,7 +1146,7 @@ fn evaluate_record_conditions(
|
|||
|
||||
Ok(Some(ResolvedConditionContext {
|
||||
matching_company_ids: company_matches.unwrap_or_default(),
|
||||
matching_player_ids: BTreeSet::new(),
|
||||
matching_player_ids: player_matches.unwrap_or_default(),
|
||||
matching_chairman_profile_ids: chairman_matches.unwrap_or_default(),
|
||||
}))
|
||||
}
|
||||
|
|
@ -1066,6 +1162,17 @@ fn intersect_company_matches(company_matches: &mut Option<BTreeSet<u32>>, next:
|
|||
}
|
||||
}
|
||||
|
||||
fn intersect_player_matches(player_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
|
||||
match player_matches {
|
||||
Some(existing) => {
|
||||
existing.retain(|player_id| next.contains(player_id));
|
||||
}
|
||||
None => {
|
||||
*player_matches = Some(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_chairman_matches(chairman_matches: &mut Option<BTreeSet<u32>>, next: BTreeSet<u32>) {
|
||||
match chairman_matches {
|
||||
Some(existing) => {
|
||||
|
|
@ -1586,8 +1693,9 @@ mod tests {
|
|||
RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePlayer, RuntimeSaveProfileState, RuntimeServiceState, RuntimeTerritory,
|
||||
RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState,
|
||||
RuntimePlayer, RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState,
|
||||
RuntimeTerritory, RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain,
|
||||
RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
fn state() -> RuntimeState {
|
||||
|
|
@ -2965,6 +3073,122 @@ mod tests {
|
|||
assert_eq!(state.world_flags.get("world_scalar_condition_failed"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_runtime_variable_conditions_before_effects_run() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
players: vec![RuntimePlayer {
|
||||
player_id: 1,
|
||||
current_cash: 50,
|
||||
active: true,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_player_id: Some(1),
|
||||
territories: vec![RuntimeTerritory {
|
||||
territory_id: 7,
|
||||
name: Some("North".to_string()),
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
}],
|
||||
world_runtime_variables: BTreeMap::from([(1, 111)]),
|
||||
company_runtime_variables: BTreeMap::from([(1, BTreeMap::from([(2, 222)]))]),
|
||||
player_runtime_variables: BTreeMap::from([(1, BTreeMap::from([(3, 333)]))]),
|
||||
territory_runtime_variables: BTreeMap::from([(7, BTreeMap::from([(4, 444)]))]),
|
||||
event_runtime_records: vec![
|
||||
RuntimeEventRecord {
|
||||
record_id: 27,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![
|
||||
RuntimeCondition::WorldVariableThreshold {
|
||||
index: 1,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 111,
|
||||
},
|
||||
RuntimeCondition::CompanyVariableThreshold {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
index: 2,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 222,
|
||||
},
|
||||
RuntimeCondition::PlayerVariableThreshold {
|
||||
target: RuntimePlayerTarget::SelectedPlayer,
|
||||
index: 3,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 333,
|
||||
},
|
||||
RuntimeCondition::TerritoryVariableThreshold {
|
||||
target: RuntimeTerritoryTarget::Ids { ids: vec![7] },
|
||||
index: 4,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 444,
|
||||
},
|
||||
],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "runtime_variable_condition_passed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
},
|
||||
RuntimeEventRecord {
|
||||
record_id: 28,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![RuntimeCondition::PlayerVariableThreshold {
|
||||
target: RuntimePlayerTarget::SelectedPlayer,
|
||||
index: 4,
|
||||
comparator: RuntimeConditionComparator::Gt,
|
||||
value: 0,
|
||||
}],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "runtime_variable_condition_failed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
},
|
||||
],
|
||||
..state()
|
||||
};
|
||||
|
||||
let result = execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("runtime-variable conditions should evaluate successfully");
|
||||
|
||||
assert_eq!(result.service_events[0].serviced_record_ids, vec![27]);
|
||||
assert_eq!(
|
||||
state.world_flags.get("runtime_variable_condition_passed"),
|
||||
Some(&true)
|
||||
);
|
||||
assert_eq!(
|
||||
state.world_flags.get("runtime_variable_condition_failed"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_shot_record_only_fires_once() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue