Execute world track build limit descriptor
This commit is contained in:
parent
b89463d227
commit
43c76adbf0
11 changed files with 439 additions and 9 deletions
|
|
@ -54,6 +54,8 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub world_restore_limited_track_building_amount: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub world_restore_economic_status_code: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub world_restore_absolute_counter_restore_kind: Option<String>,
|
||||
|
|
@ -345,6 +347,14 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(value) = self.world_restore_limited_track_building_amount {
|
||||
if actual.world_restore_limited_track_building_amount != Some(value) {
|
||||
mismatches.push(format!(
|
||||
"world_restore_limited_track_building_amount mismatch: expected {value}, got {:?}",
|
||||
actual.world_restore_limited_track_building_amount
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(code) = self.world_restore_economic_status_code {
|
||||
if actual.world_restore_economic_status_code != Some(code) {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -532,6 +532,7 @@ fn project_save_slice_components(
|
|||
disable_train_crashes_enabled: special_condition_enabled(32),
|
||||
disable_train_crashes_and_breakdowns_enabled: special_condition_enabled(33),
|
||||
ai_ignore_territories_at_startup_enabled: special_condition_enabled(34),
|
||||
limited_track_building_amount: None,
|
||||
economic_status_code: None,
|
||||
absolute_counter_restore_kind: Some(
|
||||
"mode-adjusted-selected-year-lane".to_string(),
|
||||
|
|
@ -1034,6 +1035,9 @@ fn lower_condition_targets_in_effect(
|
|||
key: key.clone(),
|
||||
value: *value,
|
||||
},
|
||||
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
|
||||
RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value }
|
||||
}
|
||||
RuntimeEffect::SetEconomicStatusCode { value } => {
|
||||
RuntimeEffect::SetEconomicStatusCode { value: *value }
|
||||
}
|
||||
|
|
@ -1358,6 +1362,9 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
key: key.clone(),
|
||||
value: *value,
|
||||
}),
|
||||
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
|
||||
Ok(RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value })
|
||||
}
|
||||
RuntimeEffect::SetEconomicStatusCode { value } => {
|
||||
Ok(RuntimeEffect::SetEconomicStatusCode { value: *value })
|
||||
}
|
||||
|
|
@ -2099,6 +2106,7 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
|||
.iter()
|
||||
.any(runtime_effect_uses_condition_true_company),
|
||||
RuntimeEffect::SetWorldFlag { .. }
|
||||
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||
| RuntimeEffect::SetPlayerCash { .. }
|
||||
| RuntimeEffect::DeactivatePlayer { .. }
|
||||
|
|
@ -2177,6 +2185,7 @@ fn runtime_effect_company_target_import_blocker(
|
|||
runtime_effect_company_target_import_blocker(nested, company_context)
|
||||
}),
|
||||
RuntimeEffect::SetWorldFlag { .. }
|
||||
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
|
|
@ -2799,6 +2808,32 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn real_limited_track_building_amount_row(
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 122,
|
||||
descriptor_label: Some("Limited Track Building Amount".to_string()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("world_track_build_limit_scalar".to_string()),
|
||||
opcode: 3,
|
||||
raw_scalar_value: value,
|
||||
value_byte_0x09: 0,
|
||||
value_dword_0x0d: 0,
|
||||
value_byte_0x11: 0,
|
||||
value_byte_0x12: 0,
|
||||
value_word_0x14: 0,
|
||||
value_word_0x16: 0,
|
||||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Limited Track Building Amount to {value}")),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_special_condition_row(
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
|
|
@ -5462,6 +5497,81 @@ mod tests {
|
|||
assert_eq!(import.state.world_restore.economic_status_code, Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_real_limited_track_building_amount_descriptor_into_executable_runtime_record() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
bridge_family: None,
|
||||
profile: None,
|
||||
candidate_availability_table: None,
|
||||
special_conditions_table: None,
|
||||
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 52,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![52],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 52,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(6),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_limited_track_building_amount_row(18)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetLimitedTrackBuildingAmount {
|
||||
value: 18,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"real-limited-track-building-amount-save-slice",
|
||||
None,
|
||||
)
|
||||
.expect("save-slice import should project");
|
||||
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("real limited-track-building-amount descriptor should execute");
|
||||
|
||||
assert_eq!(
|
||||
import.state.world_restore.limited_track_building_amount,
|
||||
Some(18)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_real_special_condition_descriptor_into_executable_runtime_record() {
|
||||
let base_state = state();
|
||||
|
|
|
|||
|
|
@ -255,6 +255,9 @@ pub enum RuntimeEffect {
|
|||
key: String,
|
||||
value: bool,
|
||||
},
|
||||
SetLimitedTrackBuildingAmount {
|
||||
value: i32,
|
||||
},
|
||||
SetEconomicStatusCode {
|
||||
value: i32,
|
||||
},
|
||||
|
|
@ -591,6 +594,8 @@ pub struct RuntimeWorldRestoreState {
|
|||
#[serde(default)]
|
||||
pub ai_ignore_territories_at_startup_enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub limited_track_building_amount: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub economic_status_code: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub absolute_counter_restore_kind: Option<String>,
|
||||
|
|
@ -1136,7 +1141,8 @@ fn validate_runtime_effect(
|
|||
return Err("key must not be empty".to_string());
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetEconomicStatusCode { .. } => {}
|
||||
RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||
| RuntimeEffect::SetEconomicStatusCode { .. } => {}
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
| RuntimeEffect::ConfiscateCompanyAssets { target }
|
||||
| RuntimeEffect::DeactivateCompany { target }
|
||||
|
|
@ -1437,6 +1443,7 @@ mod tests {
|
|||
disable_train_crashes_enabled: Some(false),
|
||||
disable_train_crashes_and_breakdowns_enabled: Some(false),
|
||||
ai_ignore_territories_at_startup_enabled: Some(false),
|
||||
limited_track_building_amount: None,
|
||||
economic_status_code: None,
|
||||
absolute_counter_restore_kind: Some(
|
||||
"mode-adjusted-selected-year-lane".to_string(),
|
||||
|
|
|
|||
|
|
@ -2785,9 +2785,31 @@ fn real_grouped_effect_descriptor_metadata(
|
|||
.iter()
|
||||
.copied()
|
||||
.find(|metadata| metadata.descriptor_id == descriptor_id)
|
||||
.or_else(|| special_condition_world_scalar_descriptor_metadata(descriptor_id))
|
||||
.or_else(|| special_condition_world_toggle_descriptor_metadata(descriptor_id))
|
||||
}
|
||||
|
||||
fn special_condition_world_scalar_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
let slot_index = descriptor_id.checked_sub(110)? as usize;
|
||||
if slot_index != 12 {
|
||||
return None;
|
||||
}
|
||||
let definition = KNOWN_SPECIAL_CONDITION_DEFINITIONS.get(slot_index)?;
|
||||
if definition.hidden {
|
||||
return None;
|
||||
}
|
||||
Some(RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label: definition.label,
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "world_track_build_limit_scalar",
|
||||
runtime_key: None,
|
||||
executable_in_runtime: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn special_condition_world_toggle_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
|
|
@ -3012,6 +3034,15 @@ fn decode_real_grouped_effect_action(
|
|||
});
|
||||
}
|
||||
|
||||
if descriptor_metadata.executable_in_runtime
|
||||
&& descriptor_metadata.descriptor_id == 122
|
||||
&& row.row_shape == "scalar_assignment"
|
||||
{
|
||||
return Some(RuntimeEffect::SetLimitedTrackBuildingAmount {
|
||||
value: row.raw_scalar_value,
|
||||
});
|
||||
}
|
||||
|
||||
if descriptor_metadata.executable_in_runtime
|
||||
&& descriptor_metadata.parameter_family == "world_flag_toggle"
|
||||
&& row.row_shape == "bool_toggle"
|
||||
|
|
@ -3256,6 +3287,7 @@ fn parse_optional_u16_len_prefixed_string(
|
|||
fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||
match effect {
|
||||
RuntimeEffect::SetWorldFlag { .. }
|
||||
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
||||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
|
|
@ -9030,6 +9062,17 @@ mod tests {
|
|||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_limited_track_building_amount_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(122).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Limited Track Building Amount");
|
||||
assert_eq!(metadata.parameter_family, "world_track_build_limit_scalar");
|
||||
assert_eq!(metadata.runtime_key, None);
|
||||
assert!(metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_late_world_toggle_descriptor_metadata() {
|
||||
let metadata =
|
||||
|
|
@ -9276,6 +9319,77 @@ mod tests {
|
|||
assert!(summary.records[0].executable_import_ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_limited_track_building_amount_descriptor_family() {
|
||||
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||
descriptor_id: 122,
|
||||
opcode: 3,
|
||||
raw_scalar_value: 18,
|
||||
value_byte_0x09: 0,
|
||||
value_dword_0x0d: 0,
|
||||
value_byte_0x11: 0,
|
||||
value_byte_0x12: 0,
|
||||
value_word_0x14: 0,
|
||||
value_word_0x16: 0,
|
||||
locomotive_name: None,
|
||||
});
|
||||
let group0_rows = vec![grouped_row];
|
||||
let record_body = build_real_event_record(
|
||||
[b"World", b"", b"", b"", b"", b""],
|
||||
Some(RealCompactControlSpec {
|
||||
mode_byte_0x7ef: 6,
|
||||
primary_selector_0x7f0: 0,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 0,
|
||||
modifier_flag_0x7f9: 0,
|
||||
modifier_flag_0x7fa: 0,
|
||||
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
||||
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
||||
summary_toggle_0x800: 1,
|
||||
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
||||
}),
|
||||
&[],
|
||||
[&group0_rows, &[], &[], &[]],
|
||||
);
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
||||
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
||||
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for word in header_words {
|
||||
bytes.extend_from_slice(&word.to_le_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(&[0x00, 0x00]);
|
||||
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
||||
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
||||
bytes.extend_from_slice(&record_body);
|
||||
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
||||
|
||||
let report = inspect_smp_bytes(&bytes);
|
||||
let summary = report
|
||||
.event_runtime_collection_summary
|
||||
.as_ref()
|
||||
.expect("event runtime collection summary should parse");
|
||||
|
||||
assert_eq!(
|
||||
summary.records[0].grouped_effect_rows[0]
|
||||
.descriptor_label
|
||||
.as_deref(),
|
||||
Some("Limited Track Building Amount")
|
||||
);
|
||||
assert_eq!(
|
||||
summary.records[0].grouped_effect_rows[0]
|
||||
.parameter_family
|
||||
.as_deref(),
|
||||
Some("world_track_build_limit_scalar")
|
||||
);
|
||||
assert_eq!(
|
||||
summary.records[0].decoded_actions,
|
||||
vec![RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18 }]
|
||||
);
|
||||
assert!(summary.records[0].executable_import_ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_recovered_late_world_toggle_descriptor_family() {
|
||||
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||
|
|
|
|||
|
|
@ -311,6 +311,9 @@ fn apply_runtime_effects(
|
|||
RuntimeEffect::SetWorldFlag { key, value } => {
|
||||
state.world_flags.insert(key.clone(), *value);
|
||||
}
|
||||
RuntimeEffect::SetLimitedTrackBuildingAmount { value } => {
|
||||
state.world_restore.limited_track_building_amount = Some(*value);
|
||||
}
|
||||
RuntimeEffect::SetEconomicStatusCode { value } => {
|
||||
state.world_restore.economic_status_code = Some(*value);
|
||||
}
|
||||
|
|
@ -2375,6 +2378,32 @@ mod tests {
|
|||
assert_eq!(state.world_restore.economic_status_code, Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_limited_track_building_amount_effect() {
|
||||
let mut state = RuntimeState {
|
||||
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::SetLimitedTrackBuildingAmount { value: 18 }],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("limited-track-building-amount effect should succeed");
|
||||
|
||||
assert_eq!(state.world_restore.limited_track_building_amount, Some(18));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confiscate_company_assets_zeros_company_and_retires_owned_trains() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub struct RuntimeSummary {
|
|||
pub world_restore_disable_train_crashes_enabled: Option<bool>,
|
||||
pub world_restore_disable_train_crashes_and_breakdowns_enabled: Option<bool>,
|
||||
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
|
||||
pub world_restore_limited_track_building_amount: Option<i32>,
|
||||
pub world_restore_economic_status_code: Option<i32>,
|
||||
pub world_restore_absolute_counter_restore_kind: Option<String>,
|
||||
pub world_restore_absolute_counter_adjustment_context: Option<String>,
|
||||
|
|
@ -139,6 +140,9 @@ impl RuntimeSummary {
|
|||
world_restore_ai_ignore_territories_at_startup_enabled: state
|
||||
.world_restore
|
||||
.ai_ignore_territories_at_startup_enabled,
|
||||
world_restore_limited_track_building_amount: state
|
||||
.world_restore
|
||||
.limited_track_building_amount,
|
||||
world_restore_economic_status_code: state.world_restore.economic_status_code,
|
||||
world_restore_absolute_counter_restore_kind: state
|
||||
.world_restore
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue