1use std::collections::HashMap;
14
15use rpfm_lib::error::Result;
16use rpfm_lib::files::esf::RecordNode;
17
18use super::armies::ArmyWriter;
19use super::characters::CharacterWriter;
20use super::government::{write_character_posts, PostPlan};
21use super::provinces::write_mercenary_pool;
22use super::sight::{write_shroud, SightPlan};
23use super::{child, next_id, start_pos_faction_row, WorldInputs, WorldPlan, WORLD_PATH};
24use crate::startpos::cai::technologies::faction_technologies;
25use crate::startpos::layout::{child_path, StartposLayout};
26use crate::startpos::vision::Vision;
27use crate::startpos::world::agents::recruitment_pool_agents;
28use crate::startpos::world::diplomacy::Diplomacy;
29use crate::startpos::world::government::political_parties;
30use crate::startpos::world::pools::{faction_mercenary_pool, levy_pools};
31use crate::startpos::world::FactionEntry;
32use crate::startpos::world::factions::{faction_flags, faction_summary, FactionFlag};
33
34const FACTION_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/FACTION_ARRAY/FACTION";
36
37const SCREEN_NAME_PREFIX: &str = "factions_screen_name_";
39
40const EMPTY_MEMORY_HONOUR: i64 = 129;
42
43const EVENT_COUNTERS: usize = 151;
46
47const NOMADIC_CULTURE: &str = "att_cult_nomadic";
49
50const DEFAULT_MEMORY: [u8; 22] = [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
52
53pub struct FactionContext<'c, 'a, 'b> {
59
60 pub armies: &'c ArmyWriter<'a, 'b>,
62
63 pub characters: &'c CharacterWriter<'a, 'b>,
65
66 pub posts: &'c PostPlan,
68
69 pub levy_pools: &'c [PlannedLevyPool],
71
72 pub diplomacy: &'c Diplomacy,
74
75 pub sight: &'c SightPlan,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct PlannedLevyPool {
82
83 id: u32,
85
86 overlord: String,
88
89 subject: u32,
91
92 province: u32,
94}
95
96pub fn write_rebel_faction(layout: &StartposLayout, plan: &WorldPlan) -> Result<RecordNode> {
106 let path = child_path(WORLD_PATH, "REBEL_FACTION");
107 let region_count = plan.regions().iter().filter(|region| region.owner().is_none()).count();
108 let mut faction = layout.builder(&child_path(&path, "FACTION"))?;
109 faction.group()?.set_u32("FACTION_FLAG_AND_COLOURS@2+14", u32::try_from(region_count).unwrap_or(u32::MAX))?;
110
111 let mut rebels = layout.builder(&path)?;
112 rebels.group()?.set_record("FACTION", Some(faction.build()?))?;
113 rebels.build()
114}
115
116pub fn write_faction_array(inputs: &WorldInputs, plan: &WorldPlan, context: &FactionContext) -> Result<RecordNode> {
122 let mut array = child(inputs.layout, WORLD_PATH, "FACTION_ARRAY")?;
123 for faction in plan.factions() {
124 array.push_group()?.set_record("FACTION", Some(write_faction(inputs, plan, context, faction)?))?;
125 }
126 array.build()
127}
128
129pub fn plan_levy_pools(inputs: &WorldInputs, plan: &WorldPlan, province_ids: &HashMap<String, u32>, first_pool: u32) -> Result<Vec<PlannedLevyPool>> {
143 let mut pools = levy_pools(inputs.tables, inputs.index)?.into_iter()
144 .map(|pool| PlannedLevyPool {
145 id: 0,
146 subject: plan.faction_id(pool.subject()).unwrap_or_default(),
147 province: province_ids.get(pool.province()).copied().unwrap_or_default(),
148 overlord: pool.overlord().to_owned(),
149 })
150 .collect::<Vec<_>>();
151 pools.sort_by_key(|pool| (pool.subject, pool.province));
152
153 let mut next_pool = first_pool;
154 for pool in &mut pools {
155 pool.id = next_id(&mut next_pool);
156 }
157 Ok(pools)
158}
159
160pub fn write_faction(inputs: &WorldInputs, plan: &WorldPlan, context: &FactionContext, faction: &FactionEntry) -> Result<RecordNode> {
166 let layout = inputs.layout;
167 let key = faction.key();
168 let row = start_pos_faction_row(inputs.tables, faction)?;
169 let summary = faction_summary(inputs.tables, inputs.index, faction)?;
170 let faction_id = |key: &Option<String>| key.as_deref().and_then(|key| plan.faction_id(key)).unwrap_or_default();
171
172 let mut setup = child(layout, FACTION_PATH, "CAMPAIGN_PLAYER_SETUP")?;
173 setup.group()?
174 .set_str("CAMPAIGN_PLAYER_SETUP_INGAME_MODIFIABLES+1", key)?
175 .set_bool("CAMPAIGN_PLAYER_SETUP_INGAME_MODIFIABLES+3", faction.playable())?;
176
177 let mut economics = child(layout, FACTION_PATH, "FACTION_ECONOMICS")?;
178 economics.group()?.set_i32("history+1", i32::try_from(row.int("treasury")?).unwrap_or(i32::MAX))?;
179
180 let posts = write_character_posts(inputs, context.posts, key, context.characters.leader_id(faction))?;
181 let mut government = child(layout, FACTION_PATH, "GOVERNMENT")?;
182 government.group()?
183 .set_str("^+2", &row.str("government_type")?)?
184 .set_record("CHARACTER_POSTS", Some(posts))?;
185
186 let mut religion = child(layout, FACTION_PATH, "FACTION_RELIGION_MANAGER")?;
187 religion.group()?.set_str("^+1", &row.str("state_religion")?)?;
188
189 let mut localisation = child(layout, FACTION_PATH, "CAMPAIGN_LOCALISATION")?;
190 localisation.group()?.set_str("^+1", &format!("{SCREEN_NAME_PREFIX}{key}"))?.unset("^+2")?;
191
192 let [flag, republican_flag] = faction_flags(inputs.tables, key)?;
193 let honour = row.int("honour")?;
194 let memory = if honour >= EMPTY_MEMORY_HONOUR { vec![0; DEFAULT_MEMORY.len()] } else { DEFAULT_MEMORY.to_vec() };
195
196 let mut horde = None;
197 if let Some(is_horde) = summary.horde() {
198 let mut manager = child(layout, FACTION_PATH, "FACTION_HORDE_MANAGER")?;
199 manager.group()?.set_i32("^+1", i32::from(is_horde))?;
200 horde = Some(manager.build()?);
201 }
202
203 let mission_manager = faction.playable().then(|| layout.default_record(&format!("{FACTION_PATH}/CAMPAIGN_MISSION_MANAGER"))).transpose()?;
204 let shroud = if faction.playable() { Some(write_shroud(layout, FACTION_PATH, context.sight, &faction_vision(inputs, context, faction)?)?) } else { None };
205
206 let mut record = layout.builder(FACTION_PATH)?;
207 record.group()?
208 .set_u32("^+1", plan.faction_id(key).unwrap_or_default())?
209 .set_str("^+2", key)?
210 .set_record("CAMPAIGN_PLAYER_SETUP", Some(setup.build()?))?
211 .set_record("FACTION_ECONOMICS", Some(economics.build()?))?
212 .set_record("GOVERNMENT", Some(government.build()?))?
213 .set_record("FACTION_HORDE_MANAGER", horde)?
214 .set_bool("GOVERNMENT+3", row.bool("is_major")?)?
215 .set_record("FACTION_RELIGION_MANAGER", Some(religion.build()?))?
216 .set_record("CHARACTER_ARRAY", Some(context.characters.write_character_array(faction)?))?
217 .set_record("FACTION_TECHNOLOGY_MANAGER", Some(write_technologies(inputs, faction)?))?
218 .set_record("ARMY_ARRAY", Some(context.armies.write_army_array(faction)?))?
219 .set_bool("MORGUE+1", faction.playable())?
220 .set_strings("MORGUE+2", summary.victory_regions().to_vec())?
221 .set_u32("MORGUE+3", summary.capital().as_deref().and_then(|capital| plan.region_id(capital)).unwrap_or_default())?
222 .set_u32("MORGUE+4", summary.capital().as_deref().and_then(|capital| plan.region_id(capital)).unwrap_or_default())?
223 .set_bool("PRESTIGE+1", summary.without_characters())?
224 .set_str("PRESTIGE+6", &row.str("cdir_military_generator_config")?)?
225 .set_record("FACTION_FLAG_AND_COLOURS", Some(write_flag(inputs, FACTION_PATH, &flag)?))?
226 .set_record("FACTION_FLAG_AND_COLOURS@2", Some(write_flag(inputs, FACTION_PATH, &republican_flag)?))?
227 .set_u32("FACTION_FLAG_AND_COLOURS@2+1", faction_id(summary.client_state_of()))?
228 .set_u32("FACTION_FLAG_AND_COLOURS@2+2", faction_id(summary.vassal_of()))?
229 .set_bool("FACTION_FLAG_AND_COLOURS@2+5", summary.has_war_or_vassal())?
230 .set_u32("FACTION_FLAG_AND_COLOURS@2+14", u32::try_from(summary.region_count()).unwrap_or(u32::MAX))?
231 .set_bytes("FACTION_FLAG_AND_COLOURS@2+21", memory)?
232 .set_record("CAMPAIGN_LOCALISATION", Some(localisation.build()?))?
233 .set_strings("FACTION_ACTION_LOG_BLOCK+2", summary.subjects().to_vec())?
234 .set_record("FACTION_MERCENARY_POOL", Some(write_faction_mercenary_pool(inputs, plan, faction)?))?
235 .set_record("LEVY_MERCENARY_MANAGER", Some(write_levy_pools(inputs, context.levy_pools, faction)?))?
236 .set_record("CHARACTER_RECRUITMENT_POOL_MANAGER", Some(write_recruitment_pools(inputs, faction)?))?
237 .set_record("CAMPAIGN_POLITICS", Some(write_politics(inputs, faction)?))?
238 .set_record("OLD_DIPLOMACY_MANAGER", Some(write_diplomacy(inputs, plan, context.diplomacy, faction)?))?
239 .set_record("MILITARY_FORCE_LEGACY_NAMES_AND_EMBLEMS", Some(context.armies.write_used_legacies(faction)?))?
240 .set_record("CAMPAIGN_MISSION_MANAGER", mission_manager)?
241 .set_record("CAMPAIGN_SHROUD", shroud)?
242 .set_u32s("FACTION_FLAG_AND_COLOURS@2+19", vec![0; EVENT_COUNTERS])?
243 .set_u32("FACTION_ACTION_LOG_BLOCK+11", context.characters.progenitor_family_id(faction))?
244 .set_bool("FACTION_ACTION_LOG_BLOCK+12", *faction.culture() != NOMADIC_CULTURE)?;
245
246 record.build()
247}
248
249fn faction_vision(inputs: &WorldInputs, context: &FactionContext, faction: &FactionEntry) -> Result<Vision> {
251 let mut vision = Vision::default();
252 for region in inputs.index.regions_of(faction.key()) {
253 for area in [context.sight.region(region.key()), context.sight.settlement(region.key())].into_iter().flatten() {
254 vision.extend(area);
255 }
256 }
257 for character in context.characters.characters_of(faction) {
258 if let Some(area) = context.characters.vision(character)? {
259 vision.extend(&area);
260 }
261 }
262 Ok(vision)
263}
264
265fn write_technologies(inputs: &WorldInputs, faction: &FactionEntry) -> Result<RecordNode> {
269 let path = format!("{FACTION_PATH}/FACTION_TECHNOLOGY_MANAGER");
270 let tree = faction_technologies(inputs.tables, inputs.index.campaign(), faction)?;
271 let mut researched = child(inputs.layout, &path, "RESEARCHED_TECHS")?;
272 for row in inputs.tables.table("start_pos_technologies")?.rows() {
273 let technology = row.str("technology")?;
274 if row.str("faction")? == *faction.id() && tree.iter().any(|known| *known == technology) {
275 researched.push_group()?
276 .set_str("^+1", &technology)?
277 .set_bool("^+2", true)?;
278 }
279 }
280
281 let mut record = inputs.layout.builder(&path)?;
282 record.group()?
283 .set_str("^+1", faction.key())?
284 .set_record("RESEARCHED_TECHS", Some(researched.build()?))?;
285 record.build()
286}
287
288fn write_faction_mercenary_pool(inputs: &WorldInputs, plan: &WorldPlan, faction: &FactionEntry) -> Result<RecordNode> {
290 let path = format!("{FACTION_PATH}/FACTION_MERCENARY_POOL");
291 let units = faction_mercenary_pool(inputs.tables, faction.key())?;
292 let mut record = inputs.layout.builder(&path)?;
293 record.group()?
294 .set_u32("^+1", plan.faction_id(faction.key()).unwrap_or_default())?
295 .set_record("MERCENARY_POOL", Some(write_mercenary_pool(inputs, &path, &units)?))?;
296 record.build()
297}
298
299fn write_levy_pools(inputs: &WorldInputs, pools: &[PlannedLevyPool], faction: &FactionEntry) -> Result<RecordNode> {
301 let path = format!("{FACTION_PATH}/LEVY_MERCENARY_MANAGER");
302 let pool_path = format!("{path}/LEVY_POOLS_BLOCK/LEVY_MERCENARY_POOL");
303 let mut block = child(inputs.layout, &path, "LEVY_POOLS_BLOCK")?;
304 for pool in pools.iter().filter(|pool| pool.overlord == *faction.key()) {
305 let mut record = inputs.layout.builder(&pool_path)?;
306 record.group()?
307 .set_u32("^+1", pool.id)?
308 .set_record("MERCENARY_POOL", Some(write_mercenary_pool(inputs, &pool_path, &[])?))?
309 .set_u32("MERCENARY_POOL+1", pool.subject)?
310 .set_u32("MERCENARY_POOL+2", pool.province)?;
311 block.push_group()?.set_record("LEVY_MERCENARY_POOL", Some(record.build()?))?;
312 }
313
314 let mut manager = inputs.layout.builder(&path)?;
315 manager.group()?.set_record("LEVY_POOLS_BLOCK", Some(block.build()?))?;
316 manager.build()
317}
318
319fn write_recruitment_pools(inputs: &WorldInputs, faction: &FactionEntry) -> Result<RecordNode> {
321 let path = format!("{FACTION_PATH}/CHARACTER_RECRUITMENT_POOL_MANAGER");
322 let mut block = child(inputs.layout, &path, "CHARACTER_RECRUITMENT_POOL_BLOCK")?;
323 for agent in recruitment_pool_agents(inputs.tables, faction)? {
324 block.push_group()?.set_str("^+1", &agent)?;
325 }
326
327 let mut manager = inputs.layout.builder(&path)?;
328 manager.group()?.set_record("CHARACTER_RECRUITMENT_POOL_BLOCK", Some(block.build()?))?;
329 manager.build()
330}
331
332fn write_diplomacy(inputs: &WorldInputs, plan: &WorldPlan, diplomacy: &Diplomacy, faction: &FactionEntry) -> Result<RecordNode> {
335 let path = format!("{FACTION_PATH}/OLD_DIPLOMACY_MANAGER");
336 let relationship_path = format!("{path}/DIPLOMACY_RELATIONSHIPS_ARRAY/OLD_DIPLOMACY_RELATIONSHIP");
337 let mut array = child(inputs.layout, &path, "DIPLOMACY_RELATIONSHIPS_ARRAY")?;
338 for other in plan.factions().iter().filter(|other| other.key() != faction.key()) {
339 let relationship = diplomacy.relationship(faction.key(), other.key());
340 let other_id = plan.faction_id(other.key()).unwrap_or_default();
341
342 let mut allied = child(inputs.layout, &relationship_path, "ALLIED_IN_WAR_AGAINST")?;
343 for (enemy, defensive_ally) in relationship.allied_against() {
344 allied.push_group()?
345 .set_u32("^+1", plan.faction_id(enemy).unwrap_or_default())?
346 .set_i32("^+2", if *defensive_ally { -1 } else { 0 })?;
347 }
348
349 let (vassal_value, master_value) = relationship.vassal_values();
350 let mut record = inputs.layout.builder(&relationship_path)?;
351 record.group()?
352 .set_u32("^+1", other_id)?
353 .set_bool("^+2", relationship.trade())?
354 .set_i32("^+3", if relationship.allied_against().is_empty() { 0 } else { -1 })?
355 .set_str("^+4", relationship.stance())?
356 .set_i32("^+8", vassal_value)?
357 .set_i32("^+9", master_value)?
358 .set_record("ALLIED_IN_WAR_AGAINST", Some(allied.build()?))?
359 .set_bool("HOSTAGES+2", relationship.known())?
360 .set_bool("HOSTAGES+3", relationship.non_aggression())?
361 .set_i32("HOSTAGES+4", if relationship.military_access() { -1 } else { 0 })?;
362 array.push_group()?
363 .set_u32("^+1", other_id)?
364 .set_record("OLD_DIPLOMACY_RELATIONSHIP", Some(record.build()?))?;
365 }
366
367 let mut manager = inputs.layout.builder(&path)?;
368 manager.group()?.set_record("DIPLOMACY_RELATIONSHIPS_ARRAY", Some(array.build()?))?;
369 manager.build()
370}
371
372fn write_politics(inputs: &WorldInputs, faction: &FactionEntry) -> Result<RecordNode> {
374 let path = format!("{FACTION_PATH}/CAMPAIGN_POLITICS");
375 let parties_path = format!("{path}/POLITICAL_PARTIES");
376 let parties = political_parties(inputs.tables, faction.key())?;
377 let mut array = child(inputs.layout, &path, "POLITICAL_PARTIES")?;
378 for party in &parties {
379 let mut record = child(inputs.layout, &parties_path, "CAMPAIGN_POLITICAL_PARTY")?;
380 record.group()?
381 .set_str("^+1", party.key())?
382 .set_u32("^+2", party.power())?;
383 array.push_group()?
384 .set_str("^+1", party.key())?
385 .set_record("CAMPAIGN_POLITICAL_PARTY", Some(record.build()?))?;
386 }
387
388 let mut politics = inputs.layout.builder(&path)?;
389 politics.group()?
390 .set_record("POLITICAL_PARTIES", Some(array.build()?))?
391 .set_bool("POLITICAL_PARTIES+1", !parties.is_empty())?;
392 politics.build()
393}
394
395pub(super) fn write_flag(inputs: &WorldInputs, parent: &str, flag: &FactionFlag) -> Result<RecordNode> {
398 let mut record = child(inputs.layout, parent, "FACTION_FLAG_AND_COLOURS")?;
399 let group = record.group()?;
400 group.set_str("^+1", flag.path())?;
401 for (index, value) in flag.colours().iter().flatten().enumerate() {
402 group.set_u32(&format!("^+{}", index + 2), u32::from(*value))?;
403 }
404 record.build()
405}