1use getset::Getters;
14
15use std::collections::HashMap;
16
17use rpfm_lib::error::Result;
18use rpfm_lib::files::esf::{RecordNode, ESF};
19
20use super::factions::{capitals, faction_links, victory_factions, write_factions, write_technologies, write_technology_trees};
21use super::lists::{write_boundaries, write_faction_seas, write_governorships, write_posts, write_provinces, write_trade_routes, write_trade_segments};
22use super::map::{pathfinder_nodes, theatre_key, AreaGroups, MapRegion, PathfinderNode};
23use super::mobiles::{mobile_cells, write_characters, write_mobiles, write_units, MobileInputs};
24use super::plan::{CaiPlan, PlanInputs};
25use super::regions::{region_links, region_positions, write_hlcis, write_regions, write_theatres, LinkInputs};
26use super::settlements::{port_cells, settlement_components, write_building_slots, write_settlements};
27use super::technologies::faction_technologies;
28use super::world_view::WorldView;
29use super::{component, CAI_WORLD_PATH};
30use crate::startpos::layout::StartposLayout;
31use crate::startpos::region_map::RegionMap;
32use crate::startpos::settlements::SettlementLayout;
33use crate::startpos::tables::StartposTables;
34use crate::startpos::world::CampaignIndex;
35
36#[cfg(test)] #[path = "world_test.rs"] mod world_test;
37
38const WORLD_COMPONENT: u32 = 3;
40
41const TIME_COMPONENT: u32 = 4;
43
44#[derive(Debug, Clone, Copy)]
50pub struct CaiInputs<'a> {
51
52 pub tables: &'a StartposTables,
54
55 pub index: &'a CampaignIndex,
57
58 pub layout: &'a StartposLayout,
60
61 pub world: &'a RecordNode,
63
64 pub map_data: &'a ESF,
66
67 pub hlp_data: &'a ESF,
69
70 pub trade_splines: usize,
72
73 pub trade_routes: &'a [(u32, u32)],
75
76 pub campaign: &'a str,
78}
79
80#[derive(Debug, Clone, Getters)]
82#[getset(get = "pub")]
83pub struct CaiContext {
84
85 world: WorldView,
87
88 map_regions: Vec<MapRegion>,
90
91 nodes: HashMap<usize, Vec<PathfinderNode>>,
93
94 region_map: RegionMap,
96
97 settlement_layouts: Vec<SettlementLayout>,
99
100 technologies: Vec<Vec<String>>,
102
103 theatre_key: String,
105
106 plan: CaiPlan,
108}
109
110impl CaiContext {
115
116 pub fn new(inputs: &CaiInputs) -> Result<Self> {
122 let world = WorldView::new(inputs.layout, inputs.world)?;
123 let map_regions = MapRegion::all_from_map_data(inputs.map_data)?;
124 let nodes = pathfinder_nodes(inputs.hlp_data)?;
125 let region_map = RegionMap::from_map_data(inputs.map_data)?;
126 let settlement_layouts = SettlementLayout::all_from_map_data(inputs.map_data, |_| false)?;
127 let theatre_key = theatre_key(inputs.map_data)?;
128 let technologies = world.factions().iter()
129 .map(|faction| match inputs.index.faction(faction.key()) {
130 Some(entry) => faction_technologies(inputs.tables, inputs.campaign, entry),
131 None => Ok(vec![]),
132 })
133 .collect::<Result<Vec<_>>>()?;
134 let technology_counts = technologies.iter().map(Vec::len).collect::<Vec<_>>();
135 let playable = world.factions().iter().map(|faction| inputs.index.faction(faction.key()).is_some_and(|entry| entry.playable())).collect::<Vec<_>>();
136 let plan = CaiPlan::new(&PlanInputs { map_regions: &map_regions, world: &world, technology_counts: &technology_counts, trade_splines: inputs.trade_splines, playable: &playable, trade_routes: inputs.trade_routes.len() });
137 Ok(Self { world, map_regions, nodes, region_map, settlement_layouts, technologies, theatre_key, plan })
138 }
139
140 pub fn write_world(&self, inputs: &CaiInputs) -> Result<RecordNode> {
146 self.with_mobiles(inputs, |mobile_inputs| self.write_world_with(inputs, mobile_inputs))
147 }
148
149 pub(crate) fn with_mobiles<T>(&self, inputs: &CaiInputs, write: impl FnOnce(&MobileInputs) -> Result<T>) -> Result<T> {
151 let (plan, world, map_regions) = (&self.plan, &self.world, &self.map_regions[..]);
152 let groups = AreaGroups::new(&self.region_map, map_regions);
153 let settlements = settlement_components(world, plan, map_regions);
154 let ports = port_cells(world, map_regions, &groups);
155 write(&MobileInputs { layout: inputs.layout, plan, world, region_map: &self.region_map, groups: &groups, settlements: &settlements, ports: &ports })
156 }
157
158 pub(crate) fn write_world_with(&self, inputs: &CaiInputs, mobile_inputs: &MobileInputs) -> Result<RecordNode> {
160 let layout = inputs.layout;
161 let (plan, world, map_regions) = (&self.plan, &self.world, &self.map_regions[..]);
162 let ports = mobile_inputs.ports;
163 let mobiles = mobile_cells(mobile_inputs);
164 let victory = victory_factions(inputs.tables, inputs.index, world, plan)?;
165 let links = region_links(&LinkInputs { plan, world, map_regions, region_map: &self.region_map, mobiles: &mobiles, ports, victory_factions: &victory });
166 let positions = region_positions(map_regions, &self.nodes);
167 let capitals = capitals(inputs.index, world, plan, map_regions);
168 let faction_links = faction_links(inputs.tables, inputs.index, world, plan, map_regions)?;
169 let (provinces, faction_provinces) = write_provinces(layout, plan, world, map_regions, inputs.index)?;
170 let mobile_ids = mobiles.iter().map(|(mobile, _)| *mobile).collect();
171
172 let mut record = layout.builder(CAI_WORLD_PATH)?;
173 record.group()?
174 .set_record("CAI_WORLD_THEATRES", Some(write_theatres(layout, plan, &self.theatre_key, mobile_ids)?))?
175 .set_record("CAI_WORLD_FACTIONS", Some(write_factions(layout, plan, &faction_links)?))?
176 .set_record("CAI_WORLD_REGIONS", Some(write_regions(layout, plan, map_regions, &self.nodes, &links)?))?
177 .set_record("CAI_WORLD_REGION_BOUNDARIES", Some(write_boundaries(layout, plan)?))?
178 .set_record("CAI_WORLD_SETTLEMENTS", Some(write_settlements(mobile_inputs, map_regions, &positions, &capitals)?))?
179 .set_record("CAI_WORLD_BUILDING_SLOTS", Some(write_building_slots(layout, plan, world, map_regions, &self.settlement_layouts)?))?
180 .set_record("CAI_WORLD_RESOURCE_MOBILES", Some(write_mobiles(mobile_inputs)?))?
181 .set_record("CAI_WORLD_UNITS", Some(write_units(mobile_inputs)?))?
182 .set_record("CAI_WORLD_CHARACTERS", Some(write_characters(mobile_inputs)?))?
183 .set_record("CAI_BDI_COMPONENT", Some(component(layout, CAI_WORLD_PATH, WORLD_COMPONENT, vec![])?))?
184 .set_record("CAI_BDI_COMPONENT@2", Some(component(layout, CAI_WORLD_PATH, TIME_COMPONENT, vec![])?))?
185 .set_record("CAI_WORLD_TRADE_ROUTE_SEGMENTS", Some(write_trade_segments(layout, plan, inputs.trade_splines)?))?
186 .set_record("CAI_WORLD_TRADE_ROUTES", Some(write_trade_routes(layout, plan, world, inputs.trade_routes)?))?
187 .set_record("CAI_WORLD_TECHNOLOGY_TREES", Some(write_technology_trees(layout, plan)?))?
188 .set_record("CAI_WORLD_TECHNOLOGIES", Some(write_technologies(layout, plan, &self.technologies)?))?
189 .set_record("CAI_WORLD_GOVERNORSHIPS", Some(write_governorships(layout, plan)?))?
190 .set_record("CAI_WORLD_REGION_HLCIS", Some(write_hlcis(layout, plan, map_regions, &self.nodes)?))?
191 .set_record("CAI_CHARACTER_POSTS", Some(write_posts(layout, plan, world)?))?
192 .set_record("CAI_PROVINCES", Some(provinces))?
193 .set_record("CAI_FACTION_PROVINCES", Some(faction_provinces))?
194 .set_record("CAI_FACTION_SEA_REGIONS", Some(write_faction_seas(layout, plan, world, map_regions)?))?;
195 record.build()
196 }
197}