rpfm_extensions/startpos/writer/
mod.rs1use rpfm_lib::error::Result;
16use rpfm_lib::files::esf::{BoolNode, Coordinates2DNode, ESF, F32Node, I32Node, NodeType, U32Node, VecU32Node};
17
18use super::cai::world::{CaiContext, CaiInputs};
19use super::cai::world_view::international_routes;
20use super::layout::{COMPACT_I32, MAX_COMPACT_U32};
21use super::missions::VictoryObjectives;
22use super::trade::TradeNetwork;
23use super::world::calendar::Calendar;
24use super::world::writer::forces::ForcePlan;
25use super::world::writer::{write_world_with_plan, WorldInputs, WorldPlan};
26use crate::lua::harness::LuaSavedValue;
27use self::dynamic_resources::DynamicResourcesInputs;
28
29mod counters;
30mod diplomacy;
31mod dynamic_resources;
32mod header;
33mod international_routes;
34mod map_data;
35mod preopen;
36mod script_variables;
37mod trade;
38pub(crate) mod victory;
39
40#[cfg(test)] pub(crate) mod writer_test;
41
42pub const ROOT_PATH: &str = "CAMPAIGN_STARTPOS";
44
45pub const ENV_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV";
47
48pub const MODEL_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL";
50
51#[derive(Clone, Copy)]
57pub struct StartposInputs<'a> {
58
59 pub world: &'a WorldInputs<'a>,
61
62 pub trade_network: &'a TradeNetwork,
64
65 pub victory_objectives: &'a [VictoryObjectives],
67
68 pub map_data: &'a ESF,
70
71 pub hlp_data: &'a ESF,
73
74 pub dynamic_resources: &'a ESF,
76
77 pub campaign: &'a str,
79
80 pub map: &'a str,
82
83 pub season_name: &'a str,
85
86 pub region_palette: &'a [[u8; 3]],
88
89 pub script_variables: Option<&'a [(String, LuaSavedValue)]>,
91}
92
93pub fn write_startpos<'a>(inputs: &StartposInputs<'a>, plan: &WorldPlan<'a>) -> Result<ESF> {
108 let world = inputs.world;
109 let layout = world.layout;
110
111 let calendar = Calendar::new(world.tables, inputs.campaign)?;
112 let world_record = write_world_with_plan(world, plan)?;
113 let trade_manager = trade::write_trade_manager(layout, world.tables, world.index, plan, inputs.trade_network)?;
114 let cai_inputs = CaiInputs {
115 tables: world.tables,
116 index: world.index,
117 layout,
118 world: &world_record,
119 map_data: inputs.map_data,
120 hlp_data: inputs.hlp_data,
121 trade_splines: inputs.trade_network.splines().len(),
122 trade_routes: &international_routes(layout, &trade_manager)?,
123 campaign: inputs.campaign,
124 };
125 let cai_context = CaiContext::new(&cai_inputs)?;
126 let cai = cai_context.write_interface(&cai_inputs)?;
127 let campaign_director = cai_context.write_campaign_director(layout)?;
128 let index_tables = counters::write_index_tables(layout, &[&world_record, &trade_manager])?;
129
130 let dynamic_resources_inputs = DynamicResourcesInputs { layout, tables: world.tables, index: world.index, plan, map_data: inputs.map_data, dynamic_resources: inputs.dynamic_resources };
131
132 let mut model = layout.builder(MODEL_PATH)?;
133 model.group()?
134 .set_record("WORLD", Some(world_record))?
135 .set_record("CAI_INTERFACE", Some(cai))?
136 .set_record("CAMPAIGN_DIRECTOR", Some(campaign_director))?
137 .set_record("CAMPAIGN_COMMAND_QUEUE_INDEX_TABLES", Some(index_tables))?
138 .set_record("CAMPAIGN_TRADE_MANAGER", Some(trade_manager))?
139 .set_record("DIPLOMACY_MANAGER", Some(diplomacy::write_diplomacy_manager(layout, plan)?))?
140 .set_record("CAMPAIGN_CALENDAR", Some(header::write_calendar(layout, &calendar)?))?
141 .set_record("CAMPAIGN_MAP_DATA", Some(map_data::write_map_data(world, plan, inputs.map)?))?
142 .set_record("DYNAMIC_RESOURCES_MANAGER", Some(dynamic_resources::write_dynamic_resources_manager(&dynamic_resources_inputs)?))?
143 .set_record("EPISODIC_RESTRICTIONS", Some(script_variables::write_episodic_restrictions(layout, inputs.script_variables)?))?;
144
145 let mut env = layout.builder(ENV_PATH)?;
146 env.group()?
147 .set_record("CAMPAIGN_SETUP", Some(preopen::write_campaign_setup(layout, plan, inputs.campaign)?))?
148 .set_record("CAMPAIGN_SETUP_LOCAL", Some(header::write_setup_local(layout, plan)?))?
149 .set_record("CAMPAIGN_MODEL", Some(model.build()?))?;
150
151 let forces = ForcePlan::new(world.tables, world.index, plan)?;
152 let mut root = layout.builder(ROOT_PATH)?;
153 root.group()?
154 .set_record("SAVE_GAME_HEADER", Some(header::write_save_game_header(inputs, plan, &forces, &calendar)?))?
155 .set_record("CAMPAIGN_PREOPEN_MAP_INFO", Some(preopen::write_preopen_map_info(inputs, plan, &forces, &calendar)?))?
156 .set_record("CAMPAIGN_ENV", Some(env.build()?))?;
157
158 let mut startpos = ESF::default();
159 startpos.set_root_node(NodeType::Record(Box::new(root.build()?)));
160 Ok(startpos)
161}
162
163pub(super) fn bool_node(value: bool) -> NodeType {
165 let mut node = BoolNode::default();
166 node.set_value(value);
167 node.set_optimized(true);
168 NodeType::Bool(node)
169}
170
171pub(super) fn u32_node(value: u32) -> NodeType {
173 let mut node = U32Node::default();
174 node.set_value(value);
175 node.set_optimized(value <= MAX_COMPACT_U32);
176 NodeType::U32(node)
177}
178
179pub(super) fn u32s_node(values: Vec<u32>) -> NodeType {
181 let mut node = VecU32Node::default();
182 let optimized = !values.is_empty() && values.iter().all(|value| *value <= MAX_COMPACT_U32);
183 node.set_value(values);
184 node.set_optimized(optimized);
185 NodeType::U32Array(node)
186}
187
188pub(super) fn i32_node(value: i32) -> NodeType {
190 let mut node = I32Node::default();
191 node.set_value(value);
192 node.set_optimized(COMPACT_I32.contains(&value));
193 NodeType::I32(node)
194}
195
196pub(super) fn f32_node(value: f32) -> NodeType {
198 let mut node = F32Node::default();
199 node.set_value(value);
200 node.set_optimized(value == 0.0);
201 NodeType::F32(node)
202}
203
204pub(super) fn coordinates_node(x: f32, y: f32) -> NodeType {
206 let mut node = Coordinates2DNode::default();
207 node.set_x(x);
208 node.set_y(y);
209 NodeType::Coord2d(node)
210}