rpfm_extensions/startpos/cai/
mobiles.rs1use std::collections::HashMap;
15
16use rpfm_lib::error::Result;
17use rpfm_lib::files::esf::RecordNode;
18
19use super::factions::owned_direct;
20use super::map::AreaGroups;
21use super::plan::CaiPlan;
22use super::world_view::{CharacterView, ForceView, WorldView};
23use super::{component, CAI_WORLD_PATH};
24use crate::startpos::coords::HexCell;
25use crate::startpos::layout::{child_path, StartposLayout};
26use crate::startpos::region_map::RegionMap;
27
28#[cfg(test)] #[path = "mobiles_test.rs"] mod mobiles_test;
29
30const FORCE_AGENT_CODES: (u32, u32) = (0, 3);
32
33const AGENT_CODES: (u32, u32) = (1, 0);
35
36#[derive(Debug, Clone, Copy)]
42pub struct MobileInputs<'a> {
43
44 pub layout: &'a StartposLayout,
46
47 pub plan: &'a CaiPlan,
49
50 pub world: &'a WorldView,
52
53 pub region_map: &'a RegionMap,
55
56 pub groups: &'a AreaGroups<'a>,
58
59 pub settlements: &'a HashMap<u32, u32>,
61
62 pub ports: &'a HashMap<u32, HexCell>,
64}
65
66pub fn write_characters(inputs: &MobileInputs) -> Result<RecordNode> {
76 let layout = inputs.layout;
77 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_CHARACTERS");
78 let mut list = layout.builder(&path)?;
79 for (position, faction) in inputs.world.factions().iter().enumerate() {
80 let faction_id = inputs.plan.factions()[position].faction();
81 let posts = &inputs.plan.posts()[position];
82 for (character, ids) in faction.characters().iter().zip(&inputs.plan.characters()[position]) {
83 let force = force_of(faction.forces(), character);
84 let mobile = ids.mobile().unwrap_or_default();
85 let own_unit = force.and_then(|force| force.units().iter().position(|unit| *unit == character.unit())).map(|unit| ids.units()[unit]).unwrap_or_default();
86 let post = faction.posts().iter().position(|(_, holder)| *holder == character.id()).map(|post| posts[post]).unwrap_or_default();
87 let codes = if force.is_none() && ids.mobile().is_some() { AGENT_CODES } else { FORCE_AGENT_CODES };
88
89 let mut owned = layout.builder(&child_path(&path, "OWNED_INDIRECT"))?;
90 owned.group()?.set_u32("^+1", faction_id)?;
91 let mut record = layout.builder(&child_path(&path, "CAI_CHARACTER"))?;
92 record.group()?
93 .set_u32("^+1", mobile)?
94 .set_u32("^+2", own_unit)?
95 .set_u32("^+3", mobile)?
96 .set_u32("^+4", character.id())?
97 .set_u32("^+6", if post == 0 { 0 } else { faction_id })?
98 .set_u32("^+7", codes.0)?
99 .set_u32("^+8", codes.1)?
100 .set_u32("^+9", post)?;
101
102 list.push_group()?
103 .set_record("OWNED_INDIRECT", Some(owned.build()?))?
104 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, ids.character(), vec![])?))?
105 .set_record("CAI_CHARACTER", Some(record.build()?))?;
106 }
107 }
108 list.build()
109}
110
111pub fn mobile_cells(inputs: &MobileInputs) -> Vec<(u32, HexCell)> {
115 let mut cells = vec![];
116 for (position, faction) in inputs.world.factions().iter().enumerate() {
117 for (character, ids) in faction.characters().iter().zip(&inputs.plan.characters()[position]) {
118 let Some(mobile) = ids.mobile() else { continue };
119 let port = force_of(faction.forces(), character).filter(|force| force.garrison_navy_of() != 0).and_then(|force| inputs.ports.get(&force.garrison_navy_of()));
120 let (x, y) = character.cell();
121 cells.push((mobile, port.copied().unwrap_or(HexCell::new(x, y))));
122 }
123 }
124 cells
125}
126
127pub fn write_mobiles(inputs: &MobileInputs) -> Result<RecordNode> {
133 let layout = inputs.layout;
134 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_RESOURCE_MOBILES");
135 let mut cells = mobile_cells(inputs).into_iter();
136 let mut list = layout.builder(&path)?;
137 for (position, faction) in inputs.world.factions().iter().enumerate() {
138 let faction_id = inputs.plan.factions()[position].faction();
139 for (character, ids) in faction.characters().iter().zip(&inputs.plan.characters()[position]) {
140 let Some(mobile) = ids.mobile() else { continue };
141 let force = force_of(faction.forces(), character);
142 let residence = force.map(|force| [force.garrison_army_of(), force.garrison_navy_of(), force.residence()].into_iter().find(|residence| *residence != 0).unwrap_or_default()).unwrap_or_default();
143
144 let cell = cells.next().map(|(_, cell)| cell).unwrap_or_default();
146 let (own_x, own_y) = if force.is_none() { (0, 0) } else { (cell.x(), cell.y()) };
147
148 let mut record = layout.builder(&child_path(&path, "CAI_RESOURCE_MOBILE"))?;
149 record.group()?
150 .set_u32("^+1", ids.character())?
151 .set_u32("^+2", inputs.settlements.get(&residence).copied().unwrap_or_default())?
152 .set_u32s("^+4", vec![ids.character()])?
153 .set_u32s("^+5", ids.units().to_vec())?
154 .set_bool("^+6", !force.is_some_and(ForceView::navy))?
155 .set_u32("^+12", force.map(ForceView::id).unwrap_or_default())?
156 .set_u32("^+17", u32::from(own_x))?
157 .set_u32("^+18", u32::from(own_y))?
158 .set_u32s("^+20", ids.horde_slots().to_vec())?;
159
160 list.push_group()?
161 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, faction_id)?))?
162 .set_record("CAI_SITUATED", Some(situated(inputs, &path, cell)?))?
163 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, mobile, vec![])?))?
164 .set_record("CAI_RESOURCE_MOBILE", Some(record.build()?))?;
165 }
166 }
167 list.build()
168}
169
170pub fn write_units(inputs: &MobileInputs) -> Result<RecordNode> {
176 let layout = inputs.layout;
177 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_UNITS");
178 let mut list = layout.builder(&path)?;
179 for (position, faction) in inputs.world.factions().iter().enumerate() {
180 for (character, ids) in faction.characters().iter().zip(&inputs.plan.characters()[position]) {
181 let Some(force) = force_of(faction.forces(), character) else { continue };
182 for (unit, id) in force.units().iter().zip(ids.units()) {
183 let mut record = layout.builder(&child_path(&path, "CAI_UNIT"))?;
184 record.group()?
185 .set_u32("^+1", if *unit == character.unit() { ids.character() } else { 0 })?
186 .set_u32("^+2", *unit)?
187 .set_u32("^+3", ids.mobile().unwrap_or_default())?;
188
189 list.push_group()?
190 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, *id, vec![])?))?
191 .set_record("CAI_UNIT", Some(record.build()?))?;
192 }
193 }
194 }
195 list.build()
196}
197
198pub(crate) fn situated(inputs: &MobileInputs, parent: &str, cell: HexCell) -> Result<RecordNode> {
201 let region = inputs.region_map.region_index_at(cell).and_then(|region| inputs.plan.regions().get(region)).map(|region| region.id()).unwrap_or_default();
202 let mut record = inputs.layout.builder(&child_path(parent, "CAI_SITUATED"))?;
203 record.group()?
204 .set_u32("^+1", u32::from(cell.x()))?
205 .set_u32("^+2", u32::from(cell.y()))?
206 .set_u32("^+3", region)?
207 .set_u32s("^+4", vec![inputs.plan.theatre()])?
208 .set_u32("^+5", inputs.groups.group_at(cell).unwrap_or_default())?;
209 record.build()
210}
211
212fn force_of<'a>(forces: &'a [ForceView], character: &CharacterView) -> Option<&'a ForceView> {
214 forces.iter().find(|force| character.force() != 0 && force.id() == character.force())
215}