Skip to main content

rpfm_extensions/startpos/cai/
managers.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Writer of the AI faction managers (`CAI_INTERFACE_MANAGERS`).
12//!
13//! Each faction owning regions or mobiles, and the rebels, has a manager: a pool of desires (analysers and behaviour
14//! managers, of fixed types), two beliefs about the faction's finances and strategic context, and intentions (one for
15//! the faction, one per character and one per mobile, to manage their skills). Every faction gets the AI level 7
16//! manager (28 desires); the rebels get the level 2 one (the first 18 desires, nothing else).
17//!
18//! Each object starts from the layout of its type, with its ids and references set. State the game computes on its
19//! first update is written as its constructors leave it: the finance manager, the stance manager's empire data and
20//! the components' update flags.
21
22use std::collections::HashMap;
23
24use rpfm_lib::error::Result;
25use rpfm_lib::files::esf::{hash_map_order, NodeType, RecordNode, VecI32Node, VecU32Node};
26
27use super::plan::{CaiPlan, ManagerIds};
28use super::stances::StartingStances;
29use super::CAI_PATH;
30use crate::startpos::layout::{child_path, variant_path, GroupBuilder, RecordBuilder, StartposLayout};
31use crate::startpos::writer::{bool_node, i32_node, u32_node};
32
33#[cfg(test)] #[path = "managers_test.rs"] mod managers_test;
34
35/// Type codes of a faction manager's desires, in creation order.
36pub const FACTION_DESIRES: [u32; 28] = [39, 44, 37, 41, 38, 40, 34, 94, 108, 150, 23, 82, 152, 144, 114, 141, 140, 25, 171, 98, 175, 185, 84, 173, 183, 90, 88, 159];
37
38/// Type codes of a human manager's desires, in creation order: the rebels' ones, then two more.
39pub const HUMAN_DESIRES: [u32; 20] = [39, 44, 37, 41, 38, 40, 34, 94, 108, 150, 23, 82, 152, 144, 114, 141, 140, 25, 159, 90];
40
41/// Desires of the rebels' manager: the first ones of [`FACTION_DESIRES`].
42pub const REBEL_DESIRES: usize = 18;
43
44/// AI level of the AI factions' managers.
45pub(crate) const FACTION_LEVEL: u32 = 7;
46
47/// AI level of the playable factions' managers.
48pub(crate) const HUMAN_LEVEL: u32 = 6;
49
50/// AI level of the rebels' manager.
51pub(crate) const REBEL_LEVEL: u32 = 2;
52
53/// Value of `CAI_BDI_POOL_FAILED+1` in the human and rebel pools (the AI pools keep the layout's).
54const NON_AI_POOL_VALUE: u32 = 3;
55
56/// Priorities of the human managers' desires that differ from the AI managers' ones.
57const HUMAN_PRIORITIES: [(u32, f32); 2] = [(TAXATION, 500.0), (CONSTRUCTION, 100.0)];
58
59/// Slots of `CAI_FACTION_BDI_POOL` holding a desire, with the desire's type. The other slots hold 0.
60const POOL_SLOTS: [(u32, u32); 17] = [
61    (3, 23), (4, 82), (5, 152), (6, 144), (7, 114), (8, 141), (9, 140), (20, 39), (23, 44), (25, 37), (26, 41), (27, 38),
62    (30, 40), (32, 34), (36, 94), (37, 108), (38, 150),
63];
64
65/// Last numbered slot of `CAI_FACTION_BDI_POOL`.
66const LAST_POOL_SLOT: u32 = 38;
67
68/// Seed the game mixes into AI component ids to hash them.
69const COMPONENT_HASH_SEED: u32 = 0x4a54_5eed;
70
71/// Desires refering to the faction's AI faction record.
72const CAI_FACTION_USERS: [u32; 3] = [114, 141, 144];
73
74/// Desire types.
75const TARGET_LIST: u32 = 25;
76const FINANCE_MANAGER: u32 = 82;
77const RESEARCH: u32 = 88;
78const TAXATION: u32 = 90;
79const FINANCIAL_ANALYSER: u32 = 94;
80const TMS_COORDINATION: u32 = 140;
81const STANCE_MANAGER: u32 = 144;
82const STRATEGIC_ANALYSER: u32 = 150;
83const DIPLOMACY_MANAGER: u32 = 152;
84const TASK_GENERATOR: u32 = 114;
85const CONSTRUCTION: u32 = 159;
86const FORCE_SKILLS: u32 = 171;
87const CHARACTER_SKILLS: u32 = 173;
88const FINANCE_SAVINGS: u32 = 84;
89
90/// Belief types.
91const FINANCIAL_ANALYSIS: u32 = 95;
92const STRATEGIC_CONTEXT_ANALYSIS: u32 = 151;
93
94/// Intention types.
95const FACTION_INTENTION: u32 = 79;
96const FORCE_SKILLS_INTENTION: u32 = 172;
97const CHARACTER_SKILLS_INTENTION: u32 = 174;
98
99/// Values of an ownership entry linking an object to one it works for (analysers, intentions).
100pub(crate) const OWNER_LINK: (f32, f32, u32) = (0.0, 0.0, 1);
101
102/// Values of an ownership entry linking a skill desire to one of its intentions.
103const INTENTION_LINK: (f32, f32, u32) = (0.0, 1.0, 0);
104
105/// Update flags of the components of objects that haven't been updated yet.
106const FRESH_FLAGS: u32 = 97;
107
108/// Update flags of the diplomacy manager with and without war entries.
109const DIPLOMACY_FLAGS: (u32, u32) = (96, 369);
110
111/// Value of the diplomacy manager's first field without war entries.
112const NO_WARS_VALUE: i32 = -100;
113
114/// Pairs of values of each diplomacy manager entry after its first ones.
115const DIPLOMACY_ENTRY_PAIRS: u32 = 41;
116
117/// State the diplomacy manager keeps after its war entries, when it has any (the game writes 3 or 5).
118const DIPLOMACY_WAR_STATE: u32 = 3;
119
120/// Strategic context of the factions (0 to 4; most factions are at 4 in the game's startpos). The constructor's 5 is
121/// out of range: the finance manager's load fixup uses it as a row of the personality's parameter table and crashes.
122const STRATEGIC_CONTEXT: u32 = 4;
123
124/// Stances whose scores each stance re-evaluation mark can adjust.
125const STANCE_SCORES: usize = 7;
126
127/// Budget categories of the finance manager.
128const BUDGET_CATEGORIES: usize = 6;
129
130//---------------------------------------------------------------------------//
131//                              Types
132//---------------------------------------------------------------------------//
133
134/// Kinds of AI faction managers, by the AI level the game gives them.
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
136pub enum ManagerKind {
137
138    /// Level 7: a faction the AI plays.
139    #[default]
140    Ai,
141
142    /// Level 6: a faction players can pick. When a campaign starts, the game turns the ones nobody picked into AI
143    /// managers (it never turns AI managers into human ones in its own startposes).
144    Human,
145
146    /// Level 2: the rebels.
147    Rebels,
148}
149
150/// What the managers are written from.
151#[derive(Debug, Clone, Copy)]
152pub struct ManagerInputs<'a> {
153
154    /// Record layouts.
155    pub layout: &'a StartposLayout,
156
157    /// Component ids.
158    pub plan: &'a CaiPlan,
159
160    /// Starting wars and stance re-evaluation marks, by faction position.
161    pub stances: &'a StartingStances,
162
163    /// Components of each faction's characters, by faction position.
164    pub characters: &'a [Vec<u32>],
165
166    /// Components of each faction's mobiles, by faction position.
167    pub mobiles: &'a [Vec<u32>],
168
169    /// Year the campaign starts in.
170    pub start_year: u32,
171}
172
173/// Fields of a pool object's component that differ from its type's defaults.
174#[derive(Debug, Clone, Default)]
175pub(crate) struct ComponentFields {
176
177    /// Update flags.
178    pub(crate) flags: Option<u32>,
179
180    /// Priority (`^+2`).
181    pub(crate) priority: Option<f32>,
182
183    /// Ids of the objects the component lists (`^+5`).
184    pub(crate) users: Option<Vec<u32>>,
185
186    /// Ownership entries: the linked object, with the entry's values.
187    pub(crate) links: Vec<(u32, (f32, f32, u32))>,
188
189    /// Ids of the objects it refers to (`CAI_BDI_COMPONENT_BLOCK_OWNS+1`).
190    pub(crate) references: Vec<u32>,
191}
192
193/// A manager being written: its ids and paths.
194struct ManagerContext<'a> {
195    inputs: &'a ManagerInputs<'a>,
196    ids: &'a ManagerIds,
197    kind: ManagerKind,
198
199    /// The faction's component and AI faction record.
200    faction: u32,
201    cai_faction: u32,
202
203    /// Desire ids by type.
204    desires: HashMap<u32, u32>,
205}
206
207//---------------------------------------------------------------------------//
208//                              Implementations
209//---------------------------------------------------------------------------//
210
211impl ManagerKind {
212
213    /// Returns the type codes of the manager's desires, in creation order.
214    pub fn desires(self) -> &'static [u32] {
215        match self {
216            Self::Ai => &FACTION_DESIRES,
217            Self::Human => &HUMAN_DESIRES,
218            Self::Rebels => &FACTION_DESIRES[..REBEL_DESIRES],
219        }
220    }
221
222    /// Returns the AI level of the manager.
223    pub fn level(self) -> u32 {
224        match self {
225            Self::Ai => FACTION_LEVEL,
226            Self::Human => HUMAN_LEVEL,
227            Self::Rebels => REBEL_LEVEL,
228        }
229    }
230}
231
232/// Writes the AI faction managers.
233///
234/// # Arguments
235///
236/// * `inputs` - What the managers are written from.
237///
238/// # Errors
239///
240/// Returns an error if the layout lacks one of the records or object types.
241pub fn write_managers(inputs: &ManagerInputs) -> Result<RecordNode> {
242    let path = managers_path();
243    let mut list = inputs.layout.builder(&path)?;
244    for ids in inputs.plan.managers() {
245        let faction = &inputs.plan.factions()[ids.faction()];
246        let context = ManagerContext {
247            inputs,
248            ids,
249            faction: faction.faction(),
250            cai_faction: faction.cai_faction(),
251            kind: inputs.plan.manager_kinds()[ids.faction()],
252            desires: inputs.plan.manager_kinds()[ids.faction()].desires().iter().copied().zip(ids.desires().iter().copied()).collect(),
253        };
254        list.push_group()?
255            .set_record("CAI_BDI_POOL", Some(context.write_pool()?))?
256            .set_record("CAI_FACTION_BDI_POOL", Some(context.write_pool_slots()?))?
257            .set_record("CAI_FACTION_MANAGER", Some(context.write_faction_manager()?))?;
258    }
259    list.build()
260}
261
262/// Writes the component of a pool object from the defaults of its type.
263///
264/// # Arguments
265///
266/// * `layout` - Record layouts.
267/// * `variant` - Path of the object's type.
268/// * `id` - Component id.
269/// * `fields` - Fields that differ from the type's defaults.
270///
271/// # Errors
272///
273/// Returns an error if the layout lacks the component.
274pub(crate) fn pool_component(layout: &StartposLayout, variant: &str, id: u32, fields: ComponentFields) -> Result<RecordNode> {
275    let path = child_path(variant, "CAI_BDI_COMPONENT");
276    let mut ownership = layout.builder(&child_path(&path, "CAI_BDI_COMPONENT_BLOCK_OWNS"))?;
277    for (linked, (first, second, flag)) in &fields.links {
278        ownership.push_group()?
279            .set_u32("^+1", *linked)?
280            .set_u32("^+2", id)?
281            .set_f32("^+3", *first)?
282            .set_f32("^+4", *second)?
283            .set_u32("^+5", *flag)?;
284    }
285
286    let mut component = layout.builder(&path)?;
287    let group = component.group()?;
288    group.set_u32("^+1", id)?
289        .set_record("CAI_BDI_COMPONENT_BLOCK_OWNS", Some(ownership.build()?))?
290        .set_u32s("CAI_BDI_COMPONENT_BLOCK_OWNS+1", fields.references)?;
291    if let Some(users) = fields.users {
292        group.set_u32s("^+5", users)?;
293    }
294    if let Some(flags) = fields.flags {
295        group.set_u32("^+4", flags)?;
296    }
297    if let Some(priority) = fields.priority {
298        group.set_f32("^+2", priority)?;
299    }
300    component.build()
301}
302
303/// Writes the analyser of a pool object type with its maps empty, if the type has one. The type's defaults can hold
304/// the maps of the game's objects, whose ids mean other objects in a generated startpos.
305///
306/// # Arguments
307///
308/// * `layout` - Record layouts.
309/// * `variant` - Path of the object's type.
310///
311/// # Errors
312///
313/// Returns an error if the analyser's layout lacks its maps.
314pub(crate) fn empty_analyser(layout: &StartposLayout, variant: &str) -> Result<Option<RecordNode>> {
315    let path = child_path(variant, "CAI_ANALYSER");
316    if layout.record(&path).is_none() {
317        return Ok(None);
318    }
319    let mut analyser = layout.builder(&path)?;
320    analyser.group()?
321        .set_u32s("^+1", vec![])?
322        .set("^+2", NodeType::U64Array(vec![]))?;
323    analyser.build().map(Some)
324}
325
326/// Returns the path of the managers list.
327fn managers_path() -> String {
328    child_path(CAI_PATH, "CAI_INTERFACE_MANAGERS")
329}
330
331/// Returns the order the game lists a map of objects keyed by id in (AI components, regions...), from their ids in
332/// insertion order.
333pub(crate) fn component_map_order(components: &[u32]) -> Vec<usize> {
334    hash_map_order(&components.iter().map(|component| component ^ COMPONENT_HASH_SEED).collect::<Vec<_>>())
335}
336
337/// Returns an empty unsigned array leaf, as the game writes them.
338fn empty_u32s() -> NodeType {
339    NodeType::U32Array(VecU32Node::default())
340}
341
342impl ManagerContext<'_> {
343
344
345    /// Returns the id of a desire of the manager.
346    fn desire(&self, code: u32) -> u32 {
347        self.desires.get(&code).copied().unwrap_or_default()
348    }
349
350    /// Writes the pool of desires, beliefs and intentions.
351    fn write_pool(&self) -> Result<RecordNode> {
352        let layout = self.inputs.layout;
353        let path = child_path(&managers_path(), "CAI_BDI_POOL");
354
355        let mut desires = layout.builder(&child_path(&path, "CAI_BDI_POOL_DESIRES"))?;
356        let ids = self.ids.desires();
357        for index in component_map_order(ids) {
358            self.write_desire(&mut desires, self.kind.desires()[index], ids[index])?;
359        }
360
361        let mut beliefs = layout.builder(&child_path(&path, "CAI_BDI_POOL_BELIEFS"))?;
362        if let Some((financial, strategic)) = self.ids.beliefs() {
363            let list = [(FINANCIAL_ANALYSIS, financial), (STRATEGIC_CONTEXT_ANALYSIS, strategic)];
364            for index in component_map_order(&[financial, strategic]) {
365                self.write_belief(&mut beliefs, list[index].0, list[index].1)?;
366            }
367        }
368
369        let mut intentions = layout.builder(&child_path(&path, "CAI_BDI_POOL_INTENTIONS"))?;
370        let position = self.ids.faction();
371        let characters = self.inputs.characters.get(position).map(Vec::as_slice).unwrap_or_default();
372        let mobiles = self.inputs.mobiles.get(position).map(Vec::as_slice).unwrap_or_default();
373        let list = std::iter::once((FACTION_INTENTION, self.ids.intention(), None))
374            .chain(self.ids.character_intentions().iter().zip(characters).map(|(id, target)| (CHARACTER_SKILLS_INTENTION, *id, Some(*target))))
375            .chain(self.ids.mobile_intentions().iter().zip(mobiles).map(|(id, target)| (FORCE_SKILLS_INTENTION, *id, Some(*target))))
376            .collect::<Vec<_>>();
377        for index in component_map_order(&list.iter().map(|(_, id, _)| *id).collect::<Vec<_>>()) {
378            let (code, id, target) = list[index];
379            self.write_intention(&mut intentions, code, id, target)?;
380        }
381
382        let mut pool = layout.builder(&path)?;
383        let group = pool.group()?;
384        group.set_record("CAI_BDI_POOL_BELIEFS", Some(beliefs.build()?))?
385            .set_record("CAI_BDI_POOL_DESIRES", Some(desires.build()?))?
386            .set_record("CAI_BDI_POOL_INTENTIONS", Some(intentions.build()?))?;
387        if self.kind != ManagerKind::Ai {
388            group.set_u32("CAI_BDI_POOL_FAILED+1", NON_AI_POOL_VALUE)?;
389        }
390        pool.build()
391    }
392
393    /// Writes a desire, with the references of its type.
394    fn write_desire(&self, desires: &mut RecordBuilder, code: u32, id: u32) -> Result<()> {
395        let layout = self.inputs.layout;
396        let variant = variant_path(&child_path(&managers_path(), "CAI_BDI_POOL/CAI_BDI_POOL_DESIRES"), code);
397        let analysed_belief = match code {
398            FINANCIAL_ANALYSER => self.ids.beliefs().map(|(financial, _)| financial),
399            STRATEGIC_ANALYSER => self.ids.beliefs().map(|(_, strategic)| strategic),
400            _ => None,
401        };
402        let links = match code {
403            TARGET_LIST => vec![(self.desire(TMS_COORDINATION), OWNER_LINK)],
404            FORCE_SKILLS => self.ids.mobile_intentions().iter().map(|intention| (*intention, INTENTION_LINK)).collect(),
405            CHARACTER_SKILLS => self.ids.character_intentions().iter().map(|intention| (*intention, INTENTION_LINK)).collect(),
406            _ => analysed_belief.map(|belief| (belief, OWNER_LINK)).into_iter().collect(),
407        };
408        let wars = self.wars();
409        let flags = match code {
410            FINANCE_MANAGER => Some(FRESH_FLAGS),
411            DIPLOMACY_MANAGER => Some(if wars.is_empty() { DIPLOMACY_FLAGS.1 } else { DIPLOMACY_FLAGS.0 }),
412            _ => None,
413        };
414        let users = CAI_FACTION_USERS.contains(&code).then(|| vec![self.cai_faction]);
415        let human = self.kind == ManagerKind::Human;
416        let priority = HUMAN_PRIORITIES.iter().find(|(human_code, _)| human && *human_code == code).map(|(_, priority)| *priority);
417        let component = pool_component(layout, &variant, id, ComponentFields { flags, priority, users, links, references: vec![] })?;
418
419        let group = desires.push_variant(code)?;
420        group.set_record("CAI_BDI_COMPONENT", Some(component))?;
421        match code {
422            TARGET_LIST => self.set_single(group, &variant, "CAI_MANOEUVRE_AND_TARGET_LIST", "^+1", self.desire(TMS_COORDINATION))?,
423            FINANCE_MANAGER => { group.set_record("CAI_FACTION_BDI_POOL_FINANCE_MANAGER_BASIC", Some(self.write_finance_manager(&variant)?))?; },
424            RESEARCH => self.set_single(group, &variant, "CAI_BDI_DESIRE_RESEARCH_TECHNOLOGY", "^+1", self.faction)?,
425            TAXATION => self.set_single(group, &variant, "CAI_BDI_DESIRE_TAXATION", "^+1", self.faction)?,
426            FINANCE_SAVINGS => {
427                let savings_path = child_path(&variant, "CAI_BDI_DESIRE_FINANCE_SAVINGS");
428                let mut date = layout.builder(&child_path(&savings_path, "DATE"))?;
429                date.group()?.set_u32("^+1", self.inputs.start_year)?;
430                let mut savings = layout.builder(&savings_path)?;
431                savings.group()?.set_record("DATE", Some(date.build()?))?;
432                group.set_record("CAI_BDI_DESIRE_FINANCE_SAVINGS", Some(savings.build()?))?;
433            },
434            FINANCIAL_ANALYSER | STRATEGIC_ANALYSER => {
435                let mut analyser = layout.builder(&child_path(&variant, "CAI_ANALYSER"))?;
436                analyser.group()?
437                    .set_u32s("^+1", analysed_belief.map(|belief| vec![self.faction, belief]).unwrap_or_default())?
438                    .set("^+2", NodeType::U64Array(vec![]))?;
439                group.set_record("CAI_ANALYSER", Some(analyser.build()?))?;
440            },
441            TMS_COORDINATION => {
442                let mut properties = layout.builder(&child_path(&variant, "CAI_BDI_COMPONENT_PROPERTY_SET"))?;
443                properties.group()?.set_u32("^+12", self.faction)?;
444                let mut coordination = layout.builder(&child_path(&variant, "CAI_TMS_COORDINATION_MANAGER"))?;
445                coordination.group()?
446                    .set_u32("^+1", self.faction)?
447                    .set_u32("^+2", self.desire(TARGET_LIST))?;
448                group.set_record("CAI_BDI_COMPONENT_PROPERTY_SET", Some(properties.build()?))?
449                    .set_record("CAI_TMS_COORDINATION_MANAGER", Some(coordination.build()?))?;
450            },
451            STANCE_MANAGER => { group.set_record("CAI_STRATEGIC_STANCE_MANAGER", Some(self.write_stance_manager(&variant)?))?; },
452            TASK_GENERATOR => {
453                let mut generator = layout.builder(&child_path(&variant, "CAI_TASK_GENERATOR_MANAGER"))?;
454                generator.group()?.set_bool("^+4", human)?;
455                group.set_record("CAI_TASK_GENERATOR_MANAGER", Some(generator.build()?))?;
456            },
457            DIPLOMACY_MANAGER => { group.set_record("CAI_DIPLOMACY_MANAGER", Some(self.write_diplomacy_manager(&variant, &wars)?))?; },
458            _ => if let Some(analyser) = empty_analyser(layout, &variant)? {
459                group.set_record("CAI_ANALYSER", Some(analyser))?;
460            },
461        }
462        Ok(())
463    }
464
465    /// Writes a belief about the faction: the financial one in its constructor state, the strategic context one in
466    /// the most common state of the game's.
467    fn write_belief(&self, beliefs: &mut RecordBuilder, code: u32, id: u32) -> Result<()> {
468        let layout = self.inputs.layout;
469        let variant = variant_path(&child_path(&managers_path(), "CAI_BDI_POOL/CAI_BDI_POOL_BELIEFS"), code);
470        let key = if code == FINANCIAL_ANALYSIS { "CAI_FINANCIAL_ANALYSIS" } else { "CAI_STRATEGIC_CONTEXT_ANALYSIS" };
471        let references = if code == FINANCIAL_ANALYSIS { vec![self.desire(FINANCE_MANAGER)] } else { vec![] };
472        let component = pool_component(layout, &variant, id, ComponentFields { flags: Some(FRESH_FLAGS), references, ..Default::default() })?;
473
474        let mut properties = layout.builder(&child_path(&variant, "CAI_BDI_COMPONENT_PROPERTY_SET"))?;
475        properties.group()?.set_u32("^+9", self.faction)?;
476        let mut analysis = layout.builder(&child_path(&variant, key))?;
477        let group = analysis.group()?;
478        group.set_u32("^+1", self.faction)?;
479        if code == FINANCIAL_ANALYSIS {
480            group.set_f32("^+2", 0.0)?.set_f32("^+3", 0.0)?;
481        } else {
482            group.set_u32("^+2", STRATEGIC_CONTEXT)?.set_u32("^+3", 0)?;
483        }
484
485        beliefs.push_variant(code)?
486            .set_record("CAI_BDI_COMPONENT_PROPERTY_SET", Some(properties.build()?))?
487            .set_record("CAI_BDI_COMPONENT", Some(component))?
488            .set_record(key, Some(analysis.build()?))?;
489        Ok(())
490    }
491
492    /// Writes an intention: the faction's one (no target), or the skill management of a character or mobile.
493    fn write_intention(&self, intentions: &mut RecordBuilder, code: u32, id: u32, target: Option<u32>) -> Result<()> {
494        let layout = self.inputs.layout;
495        let variant = variant_path(&child_path(&managers_path(), "CAI_BDI_POOL/CAI_BDI_POOL_INTENTIONS"), code);
496        let links = target.map(|target| vec![(target, OWNER_LINK)]).unwrap_or_default();
497        let component = pool_component(layout, &variant, id, ComponentFields { links, ..Default::default() })?;
498        let group = intentions.push_variant(code)?;
499        group.set_record("CAI_BDI_COMPONENT", Some(component))?;
500        if let Some(target) = target {
501            let mut properties = layout.builder(&child_path(&variant, "CAI_BDI_COMPONENT_PROPERTY_SET"))?;
502            properties.group()?.set_u32("^+12", target)?;
503            group.set_record("CAI_BDI_COMPONENT_PROPERTY_SET", Some(properties.build()?))?;
504
505            let key = if code == CHARACTER_SKILLS_INTENTION { "CAI_BDI_CHARACTER_SKILL_MANAGEMENT_FOR_CHARACTER" } else { "CAI_BDI_FORCE_LEGACY_SKILL_MANAGEMENT_FOR_FORCE" };
506            self.set_single(group, &variant, key, "^+1", target)?;
507        }
508        Ok(())
509    }
510
511    /// Sets a child record of a pool object holding one reference.
512    fn set_single(&self, group: &mut GroupBuilder, variant: &str, key: &str, field: &str, value: u32) -> Result<()> {
513        let mut record = self.inputs.layout.builder(&child_path(variant, key))?;
514        record.group()?.set_u32(field, value)?;
515        group.set_record(key, Some(record.build()?))?;
516        Ok(())
517    }
518
519    /// Returns the components of the factions the manager's faction is at war with, sorted.
520    fn wars(&self) -> Vec<u32> {
521        let position = self.ids.faction();
522        self.inputs.plan.factions().iter().enumerate()
523            .filter(|(other, _)| self.inputs.stances.at_war(position, *other))
524            .map(|(_, ids)| ids.faction())
525            .collect()
526    }
527
528    /// Writes the finance manager in its constructor state: the first update recomputes it from the faction's
529    /// personality and treasury.
530    fn write_finance_manager(&self, variant: &str) -> Result<RecordNode> {
531        let mut record = self.inputs.layout.builder(&child_path(variant, "CAI_FACTION_BDI_POOL_FINANCE_MANAGER_BASIC"))?;
532        let mut repeated = VecI32Node::default();
533        repeated.set_value(vec![2; BUDGET_CATEGORIES]).set_optimized(true);
534        let mut never = VecU32Node::default();
535        never.set_value(vec![0; BUDGET_CATEGORIES]).set_optimized(true);
536        record.group()?
537            .set_u32s("^+1", vec![])?
538            .set_u32("^+2", u32::MAX)?
539            .set_u32s("^+3", vec![])?
540            .set_f32("^+4", 0.0)?
541            .set_i32("^+5", -1)?
542            .set_f32("^+6", 0.0)?
543            .set_bool("^+7", false)?
544            .set_f32("^+8", -1.0)?
545            .set_f32("^+9", -1.0)?
546            .set_f32("^+10", 0.0)?
547            .set_f32("^+11", 0.0)?
548            .set_f32("^+12", 0.0)?
549            .set("^+13", NodeType::F32Array(vec![1.0 / BUDGET_CATEGORIES as f32; BUDGET_CATEGORIES]))?
550            .set("^+14", NodeType::F32Array(vec![0.0; BUDGET_CATEGORIES]))?
551            .set("^+15", NodeType::BoolArray(vec![false; BUDGET_CATEGORIES]))?
552            .set("^+16", NodeType::U32Array(never))?
553            .set("^+17", NodeType::F32Array(vec![1000.0; BUDGET_CATEGORIES]))?
554            .set("^+18", NodeType::F32Array(vec![0.0; BUDGET_CATEGORIES]))?
555            .set("^+19", NodeType::F32Array(vec![0.0; BUDGET_CATEGORIES]))?
556            .set("^+20", NodeType::I32Array(repeated))?
557            .set_u32("^+21", 5)?
558            .set_u32("^+22", 5)?;
559        record.build()
560    }
561
562    /// Writes the strategic stance manager: the factions marked for re-evaluation, and the empire data in its
563    /// constructor state (the game computes it after loading).
564    fn write_stance_manager(&self, variant: &str) -> Result<RecordNode> {
565        let layout = self.inputs.layout;
566        let path = child_path(variant, "CAI_STRATEGIC_STANCE_MANAGER");
567        let factions = self.inputs.plan.factions();
568        let marks = self.inputs.stances.marks(self.ids.faction()).iter().map(|position| factions[*position].faction()).collect::<Vec<_>>();
569        let mut blocking = layout.builder(&child_path(&path, "CAI_FACTION_STANCE_BLOCKING_AND_PROMOTION"))?;
570        let adjustments_path = child_path(&child_path(&path, "CAI_FACTION_STANCE_BLOCKING_AND_PROMOTION"), "CAI_FACTION_STANCE_BLOCKING_AND_PROMOTION_SCORE_ADJUSTMENT");
571        for index in component_map_order(&marks) {
572            let mut adjustments = layout.builder(&adjustments_path)?;
573            for _ in 0..STANCE_SCORES {
574                adjustments.push_group()?;
575            }
576            blocking.push_group()?
577                .set_u32("^+1", marks[index])?
578                .set_record("CAI_FACTION_STANCE_BLOCKING_AND_PROMOTION_SCORE_ADJUSTMENT", Some(adjustments.build()?))?;
579        }
580
581        let mut empire = layout.builder(&child_path(&path, "CAI_STRATEGIC_STANCE_MANAGER_EMPIRE_DATA"))?;
582        let group = empire.group()?;
583        for field in 1..=6 {
584            group.set_u32(&format!("^+{field}"), 0)?;
585        }
586        for field in 7..=12 {
587            group.set_f32(&format!("^+{field}"), 0.5)?;
588        }
589
590        let mut manager = layout.builder(&path)?;
591        manager.group()?
592            .set_u32("^+1", self.faction)?
593            .set_record("CAI_FACTION_STANCE_BLOCKING_AND_PROMOTION", Some(blocking.build()?))?
594            .set_record("CAI_STRATEGIC_STANCE_MANAGER_EMPIRE_DATA", Some(empire.build()?))?
595            .set_u32("CAI_STRATEGIC_STANCE_MANAGER_EMPIRE_DATA+1", u32::MAX)?;
596        manager.build()
597    }
598
599    /// Writes the diplomacy manager: an entry per faction at war with the manager's one.
600    fn write_diplomacy_manager(&self, variant: &str, wars: &[u32]) -> Result<RecordNode> {
601        let mut manager = self.inputs.layout.builder(&child_path(variant, "CAI_DIPLOMACY_MANAGER"))?;
602        let group = manager.group()?;
603        group.push_child(i32_node(if wars.is_empty() { NO_WARS_VALUE } else { 0 }))?
604            .push_child(u32_node(wars.len() as u32))?
605            .push_child(u32_node(DIPLOMACY_ENTRY_PAIRS + 1))?;
606        for enemy in wars {
607            group.push_child(u32_node(*enemy))?
608                .push_child(i32_node(3))?
609                .push_child(i32_node(0))?;
610            for _ in 0..DIPLOMACY_ENTRY_PAIRS {
611                group.push_child(i32_node(-1))?.push_child(i32_node(i32::MAX))?;
612            }
613        }
614        group.push_child(u32_node(if wars.is_empty() { 0 } else { DIPLOMACY_WAR_STATE }))?
615            .push_child(bool_node(false))?
616            .push_child(empty_u32s())?
617            .push_child(empty_u32s())?
618            .push_child(u32_node(0))?;
619        manager.build()
620    }
621
622    /// Writes the slots of the pool holding its main desires.
623    fn write_pool_slots(&self) -> Result<RecordNode> {
624        let mut record = self.inputs.layout.builder(&child_path(&managers_path(), "CAI_FACTION_BDI_POOL"))?;
625        let group = record.group()?;
626        for slot in 3..=LAST_POOL_SLOT {
627            let desire = POOL_SLOTS.iter().find(|(pool_slot, _)| *pool_slot == slot).map(|(_, code)| self.desire(*code)).unwrap_or_default();
628            group.set_u32(&format!("^+{slot}"), desire)?;
629        }
630        record.build()
631    }
632
633    /// Writes the manager's faction and AI level.
634    fn write_faction_manager(&self) -> Result<RecordNode> {
635        let mut record = self.inputs.layout.builder(&child_path(&managers_path(), "CAI_FACTION_MANAGER"))?;
636        record.group()?
637            .set_u32("^+1", self.faction)?
638            .set_u32("^+2", self.kind.level())?
639            .set_bool("CAI_FACTION_LEARNT_PARAMETERS+1", self.kind != ManagerKind::Rebels)?
640            .set_u32("CAI_FACTION_LEARNT_PARAMETERS+2", 0)?
641            .set_u32("CAI_FACTION_LEARNT_PARAMETERS+3", 0)?;
642        record.build()
643    }
644}