rpfm_extensions/startpos/world/writer/
mod.rs1use std::collections::{HashMap, HashSet};
29
30use rpfm_lib::error::{RLibError, Result};
31use rpfm_lib::files::esf::RecordNode;
32
33use self::armies::ArmyWriter;
34use self::characters::CharacterWriter;
35use self::factions::FactionContext;
36use self::forces::ForcePlan;
37use self::government::PostPlan;
38use self::sight::SightPlan;
39use super::{CampaignIndex, CharacterEntry, FactionEntry, RegionEntry};
40use super::diplomacy::Diplomacy;
41use crate::startpos::layout::{child_path, RecordBuilder, StartposLayout};
42use crate::startpos::region_map::RegionMap;
43use crate::startpos::settlements::SettlementLayout;
44use crate::startpos::tables::{Row, StartposTables};
45
46pub mod armies;
47pub mod characters;
48pub mod factions;
49pub mod forces;
50pub mod government;
51pub mod provinces;
52pub mod regions;
53pub mod sight;
54pub mod slaves;
55pub mod spying;
56
57#[cfg(test)] pub(crate) mod writer_test;
58
59pub const WORLD_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD";
61
62pub(crate) const HORDE_BUILDING_COLUMNS: [&str; 11] = [
64 "primary_building", "secondary_building_1", "secondary_building_2", "secondary_building_3", "secondary_building_4",
65 "secondary_building_5", "secondary_building_6", "secondary_building_7", "secondary_building_8",
66 "secondary_building_9", "secondary_building_10",
67];
68
69pub const REBEL_FACTION_ID: u32 = 1;
71
72#[derive(Debug, Clone, Copy)]
78pub struct WorldInputs<'a> {
79
80 pub tables: &'a StartposTables,
82
83 pub index: &'a CampaignIndex,
85
86 pub layout: &'a StartposLayout,
88
89 pub settlements: &'a [SettlementLayout],
91
92 pub region_map: &'a RegionMap,
94
95 pub legacy_overrides: &'a HashSet<String>,
98}
99
100#[derive(Debug, Clone, Default)]
102pub struct WorldPlan<'a> {
103
104 factions: Vec<&'a FactionEntry>,
106
107 faction_ids: HashMap<String, u32>,
109
110 regions: Vec<&'a RegionEntry>,
112
113 region_ids: HashMap<String, u32>,
115
116 character_orders: HashMap<String, Vec<&'a CharacterEntry>>,
119}
120
121pub fn write_world(inputs: &WorldInputs) -> Result<RecordNode> {
135 write_world_with_plan(inputs, &WorldPlan::new(inputs.index))
136}
137
138pub fn write_world_with_plan<'a>(inputs: &WorldInputs<'a>, plan: &WorldPlan<'a>) -> Result<RecordNode> {
149 let forces = ForcePlan::new(inputs.tables, inputs.index, plan)?;
150 let residences = regions::settlement_residences(inputs, plan)?;
151
152 let posts = PostPlan::new(inputs, plan)?;
153 let sight = SightPlan::new(inputs, plan);
154 let characters = CharacterWriter::new(inputs, plan, &forces, &posts, &sight)?;
155 let provinces = provinces::write_province_and_sea_managers(inputs, plan, &sight)?;
156 let armies = ArmyWriter::new(inputs, &forces, &characters, &residences, provinces.next_recruitment)?;
157 let levy_pools = factions::plan_levy_pools(inputs, plan, &provinces.province_ids, provinces.next_pool)?;
158 let diplomacy = Diplomacy::new(inputs.tables, inputs.index, inputs.region_map)?;
159 let context = FactionContext { armies: &armies, characters: &characters, posts: &posts, levy_pools: &levy_pools, diplomacy: &diplomacy, sight: &sight };
160
161 let mut world = inputs.layout.builder(WORLD_PATH)?;
162 world.group()?
163 .set_record("FACTION_ARRAY", Some(factions::write_faction_array(inputs, plan, &context)?))?
164 .set_record("REBEL_FACTION", Some(factions::write_rebel_faction(inputs.layout, plan)?))?
165 .set_record("REGION_MANAGER", Some(regions::write_region_manager(inputs, plan, &forces, &sight)?))?
166 .set_record("PROVINCE_MANAGER", Some(provinces.provinces))?
167 .set_record("SEA_REGION_MANAGER", Some(provinces.seas))?
168 .set_record("FAMILY_TREE", Some(characters.write_family_tree()?))?
169 .set_record("SLAVES", Some(slaves::write_slaves(inputs, plan)?))?
170 .set_record("SPYING_ARRAY", Some(spying::write_spying_array(inputs, plan)?))?;
171 world.build()
172}
173
174impl<'a> WorldPlan<'a> {
175
176 pub fn new(index: &'a CampaignIndex) -> Self {
178 Self::with_orders(index.factions_in_startpos_order(), index.regions().iter().collect())
179 }
180
181 pub fn with_orders(factions: Vec<&'a FactionEntry>, regions: Vec<&'a RegionEntry>) -> Self {
188 let faction_ids = factions.iter().zip(REBEL_FACTION_ID + 1..).map(|(faction, id)| (faction.key().to_owned(), id)).collect();
189 let region_ids = regions.iter().zip(1..).map(|(region, id)| (region.key().to_owned(), id)).collect();
190 Self { factions, faction_ids, regions, region_ids, character_orders: HashMap::new() }
191 }
192
193 pub fn set_character_order(&mut self, faction: &str, characters: Vec<&'a CharacterEntry>) {
201 self.character_orders.insert(faction.to_owned(), characters);
202 }
203
204 pub fn character_order(&self, faction: &str) -> Option<&[&'a CharacterEntry]> {
206 self.character_orders.get(faction).map(Vec::as_slice)
207 }
208
209 pub fn factions(&self) -> &[&'a FactionEntry] {
211 &self.factions
212 }
213
214 pub fn regions(&self) -> &[&'a RegionEntry] {
216 &self.regions
217 }
218
219 pub fn region_id(&self, region: &str) -> Option<u32> {
221 self.region_ids.get(region).copied()
222 }
223
224 pub fn faction_id(&self, faction: &str) -> Option<u32> {
226 self.faction_ids.get(faction).copied()
227 }
228}
229
230pub(crate) fn child<'a>(layout: &'a StartposLayout, parent: &str, key: &str) -> Result<RecordBuilder<'a>> {
232 layout.builder(&child_path(parent, key))
233}
234
235pub(crate) fn start_pos_faction_row<'a>(tables: &'a StartposTables, faction: &FactionEntry) -> Result<Row<'a>> {
237 for row in tables.table("start_pos_factions")?.rows() {
238 if row.str("ID")? == *faction.id() {
239 return Ok(row);
240 }
241 }
242 Err(RLibError::StartposInvalidValue("start_pos_factions".to_owned(), "ID".to_owned(), faction.id().to_owned()))
243}
244
245pub(crate) fn horde_details<'a>(inputs: &WorldInputs<'a>) -> Result<Vec<Row<'a>>> {
247 let characters = inputs.index.characters().iter().map(|character| character.id().as_str()).collect::<HashSet<_>>();
248 let mut rows = vec![];
249 for row in inputs.tables.table("start_pos_horde_details")?.rows() {
250 if characters.contains(row.str("general")?.as_ref()) {
251 rows.push(row);
252 }
253 }
254 Ok(rows)
255}
256
257pub(crate) fn next_id(counter: &mut u32) -> u32 {
259 let id = *counter;
260 *counter += 1;
261 id
262}