rpfm_extensions/startpos/writer/
mod.rs1use std::collections::HashMap;
20
21use rpfm_lib::error::Result;
22use rpfm_lib::files::esf::{BoolNode, Coordinates2DNode, ESF, F32Node, I32Node, NodeType, RecordNode, U32Node, VecU32Node};
23
24use super::cai::world::{CaiContext, CaiInputs};
25use super::cai::world_view::international_routes;
26use super::layout::{COMPACT_I32, MAX_COMPACT_U32};
27use super::missions::VictoryObjectives;
28use super::trade::TradeNetwork;
29use super::world::calendar::Calendar;
30use super::world::writer::forces::ForcePlan;
31use super::world::writer::{write_world_with_plan, WorldInputs, WorldPlan};
32use crate::lua::harness::LuaSavedValue;
33use self::dynamic_resources::DynamicResourcesInputs;
34
35mod counters;
36mod diplomacy;
37mod dynamic_resources;
38mod header;
39mod international_routes;
40mod map_data;
41mod preopen;
42mod script_variables;
43mod trade;
44pub(crate) mod victory;
45
46#[cfg(test)] pub(crate) mod writer_test;
47
48pub const ROOT_PATH: &str = "CAMPAIGN_STARTPOS";
50
51pub const ENV_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV";
53
54pub const MODEL_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL";
56
57pub(crate) const AI_HISTORY: &str = "CAI_HISTORY";
59
60#[derive(Clone, Copy)]
66pub struct StartposInputs<'a> {
67
68 pub world: &'a WorldInputs<'a>,
70
71 pub trade_network: &'a TradeNetwork,
73
74 pub victory_objectives: &'a [VictoryObjectives],
76
77 pub map_data: &'a ESF,
79
80 pub hlp_data: &'a ESF,
82
83 pub dynamic_resources: &'a ESF,
85
86 pub game: &'a str,
88
89 pub campaign: &'a str,
91
92 pub map: &'a str,
94
95 pub season_name: &'a str,
97
98 pub texts: &'a HashMap<String, String>,
100
101 pub region_palette: &'a [[u8; 3]],
103
104 pub script_variables: Option<&'a [(String, LuaSavedValue)]>,
106}
107
108pub fn write_startpos<'a>(inputs: &StartposInputs<'a>, plan: &WorldPlan<'a>) -> Result<ESF> {
123 let world = inputs.world;
124 let layout = world.layout;
125
126 let calendar = Calendar::new(world.tables, inputs.campaign)?;
127 let trading = international_routes::trading_factions(world.tables, world.index, plan, inputs.trade_network)?;
128 let world_record = write_world_with_plan(&WorldInputs { trading: Some(&trading), ..*world }, plan)?;
129 let estates = header::estates_by_owner(&world_record);
130 let trade_manager = trade::write_trade_manager(layout, world.tables, world.index, plan, inputs.trade_network)?;
131 let cai_inputs = CaiInputs {
132 tables: world.tables,
133 index: world.index,
134 layout,
135 world: &world_record,
136 map_data: inputs.map_data,
137 hlp_data: inputs.hlp_data,
138 trade_splines: inputs.trade_network.splines().len(),
139 trade_routes: &international_routes(layout, &trade_manager)?,
140 campaign: inputs.campaign,
141 };
142 let cai_context = CaiContext::new(&cai_inputs)?;
143 let cai = cai_context.write_interface(&cai_inputs)?;
144 let campaign_director = cai_context.write_campaign_director(layout)?;
145 let index_tables = counters::write_index_tables(layout, &[&world_record, &trade_manager])?;
146
147 let dynamic_resources_inputs = DynamicResourcesInputs { layout, tables: world.tables, index: world.index, plan, map_data: inputs.map_data, dynamic_resources: inputs.dynamic_resources };
148
149 let mut model = layout.builder(MODEL_PATH)?;
150 model.group()?
151 .set_record("WORLD", Some(world_record))?
152 .set_record("CAI_INTERFACE", Some(cai))?
153 .set_record("CAMPAIGN_DIRECTOR", Some(campaign_director))?
154 .set_record("CAMPAIGN_COMMAND_QUEUE_INDEX_TABLES", Some(index_tables))?
155 .set_record("CAMPAIGN_TRADE_MANAGER", Some(trade_manager))?
156 .set_record("DIPLOMACY_MANAGER", Some(diplomacy::write_diplomacy_manager(layout, plan)?))?
157 .set_record("CAMPAIGN_CALENDAR", Some(header::write_calendar(layout, &calendar)?))?
158 .set_record("CAMPAIGN_MAP_DATA", Some(map_data::write_map_data(world, plan, inputs.map)?))?
159 .set_record("DYNAMIC_RESOURCES_MANAGER", Some(dynamic_resources::write_dynamic_resources_manager(&dynamic_resources_inputs)?))?
160 .set_record("EPISODIC_RESTRICTIONS", Some(script_variables::write_episodic_restrictions(layout, inputs.script_variables)?))?;
161
162 let mut model = model.build()?;
163 remove_child_record(&mut model, AI_HISTORY);
164 let mut env = layout.builder(ENV_PATH)?;
165 env.group()?
166 .set_record("CAMPAIGN_SETUP", Some(preopen::write_campaign_setup(layout, plan, inputs.campaign)?))?
167 .set_record("CAMPAIGN_SETUP_LOCAL", Some(header::write_setup_local(layout, plan)?))?
168 .set_record("CAMPAIGN_MODEL", Some(model))?;
169
170 let forces = ForcePlan::new(world.tables, world.index, plan)?;
171 let mut root = layout.builder(ROOT_PATH)?;
172 root.group()?
173 .set_record("SAVE_GAME_HEADER", Some(header::write_save_game_header(inputs, plan, &forces, &calendar, &estates)?))?
174 .set_record("CAMPAIGN_PREOPEN_MAP_INFO", Some(preopen::write_preopen_map_info(inputs, plan, &forces, &calendar)?))?
175 .set_record("CAMPAIGN_ENV", Some(env.build()?))?;
176
177 let mut startpos = ESF::default();
178 startpos.set_root_node(NodeType::Record(Box::new(root.build()?)));
179 Ok(startpos)
180}
181
182fn remove_child_record(record: &mut RecordNode, name: &str) {
184 for group in record.children_mut() {
185 group.retain(|node| !matches!(node, NodeType::Record(child) if child.name() == name));
186 }
187}
188
189pub(super) fn bool_node(value: bool) -> NodeType {
191 let mut node = BoolNode::default();
192 node.set_value(value);
193 node.set_optimized(true);
194 NodeType::Bool(node)
195}
196
197pub(super) fn u32_node(value: u32) -> NodeType {
199 let mut node = U32Node::default();
200 node.set_value(value);
201 node.set_optimized(value <= MAX_COMPACT_U32);
202 NodeType::U32(node)
203}
204
205pub(super) fn u32s_node(values: Vec<u32>) -> NodeType {
207 let mut node = VecU32Node::default();
208 let optimized = !values.is_empty() && values.iter().all(|value| *value <= MAX_COMPACT_U32);
209 node.set_value(values);
210 node.set_optimized(optimized);
211 NodeType::U32Array(node)
212}
213
214pub(super) fn i32_node(value: i32) -> NodeType {
216 let mut node = I32Node::default();
217 node.set_value(value);
218 node.set_optimized(COMPACT_I32.contains(&value));
219 NodeType::I32(node)
220}
221
222pub(super) fn f32_node(value: f32) -> NodeType {
224 let mut node = F32Node::default();
225 node.set_value(value);
226 node.set_optimized(value == 0.0);
227 NodeType::F32(node)
228}
229
230pub(super) fn coordinates_node(x: f32, y: f32) -> NodeType {
232 let mut node = Coordinates2DNode::default();
233 node.set_x(x);
234 node.set_y(y);
235 NodeType::Coord2d(node)
236}