Skip to main content

rpfm_extensions/startpos/cai/
plan.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//! Component ids of every AI object, in the order the game creates them.
12//!
13//! The game counts from [`ROOT_ID`]: the interface, the two components of its world mirror, the central pool's
14//! desires, the theatre, each map region followed by its HLCIs, the factions (each with its AI faction, technology
15//! tree and technologies; the rebels last), the settlements (each after its building slots), the trade route
16//! segments, the region boundaries, the characters (each followed by its force: the mobile, a horde's building slots
17//! and the units), the provinces (each followed by its factions' parts), the factions' views of the seas, the
18//! governorships and the government posts. Beliefs the game creates in between aren't written (the game rebuilds
19//! them), so their ids aren't taken.
20
21use getset::{CopyGetters, Getters};
22
23use std::collections::{HashMap, HashSet};
24
25use super::interface::{CENTRAL_DESIRES, DIRECTOR_BELIEFS, DIRECTOR_DESIRES};
26use super::managers::ManagerKind;
27use super::map::MapRegion;
28use super::world_view::WorldView;
29
30#[cfg(test)] #[path = "plan_test.rs"] mod plan_test;
31
32/// Component id of the AI interface itself.
33pub const ROOT_ID: u32 = 2;
34
35/// Components of the AI's world mirror (its own and its time's).
36const WORLD_COMPONENTS: u32 = 2;
37
38/// Agent types of the characters that only move with a force. Other agents move on their own, as mobiles.
39const FORCE_AGENTS: [&str; 2] = ["general", "colonel"];
40
41//---------------------------------------------------------------------------//
42//                              Types
43//---------------------------------------------------------------------------//
44
45/// Component ids of the AI objects.
46#[derive(Debug, Clone, Default, Getters, CopyGetters)]
47pub struct CaiPlan {
48
49    /// Next free id.
50    #[getset(get_copy = "pub")]
51    next_id: u32,
52
53    /// Id of the first central desire.
54    #[getset(get_copy = "pub")]
55    first_central_desire: u32,
56
57    /// Id of the theatre (the whole map).
58    #[getset(get_copy = "pub")]
59    theatre: u32,
60
61    /// Ids of each map region, in map order.
62    #[getset(get = "pub")]
63    regions: Vec<RegionIds>,
64
65    /// Ids of each faction, in world order, then the rebels.
66    #[getset(get = "pub")]
67    factions: Vec<FactionIds>,
68
69    /// Ids of each land region's settlement, by map region index.
70    #[getset(get = "pub")]
71    settlements: HashMap<usize, SettlementIds>,
72
73    /// Id of the first trade route segment (one per trade spline, in spline order).
74    #[getset(get_copy = "pub")]
75    first_trade_segment: u32,
76
77    /// Region boundaries, in creation order.
78    #[getset(get = "pub")]
79    boundaries: Vec<BoundaryIds>,
80
81    /// Ids of each character, by world faction position, in `CHARACTER_ARRAY` order.
82    #[getset(get = "pub")]
83    characters: Vec<Vec<CharacterIds>>,
84
85    /// Ids of each province, in world order.
86    #[getset(get = "pub")]
87    provinces: Vec<ProvinceIds>,
88
89    /// Ids of the factions' views of each sea, by world sea position, in manager order.
90    #[getset(get = "pub")]
91    faction_seas: Vec<Vec<u32>>,
92
93    /// Id of each faction's governorship, in world order.
94    #[getset(get = "pub")]
95    governorships: Vec<u32>,
96
97    /// Ids of each faction's government posts, in world order.
98    #[getset(get = "pub")]
99    posts: Vec<Vec<u32>>,
100
101    /// Ids of the AI managers of the factions owning regions or mobiles, in `factions` order (the rebels always have
102    /// one).
103    #[getset(get = "pub")]
104    managers: Vec<ManagerIds>,
105
106    /// Kind of AI manager of each faction, in `factions` order.
107    #[getset(get = "pub")]
108    manager_kinds: Vec<ManagerKind>,
109
110    /// Ids of the director pool's beliefs, then its desires, in creation order.
111    #[getset(get = "pub")]
112    director: Vec<u32>,
113
114    /// Id of the first international trade route (one per route, in route id order).
115    #[getset(get_copy = "pub")]
116    first_trade_route: u32,
117}
118
119/// What the id plan is built from.
120#[derive(Debug, Clone, Copy)]
121pub struct PlanInputs<'a> {
122
123    /// Regions of the map, in map order.
124    pub map_regions: &'a [MapRegion],
125
126    /// The world's objects.
127    pub world: &'a WorldView,
128
129    /// Number of technologies of each world faction's tree, in world order.
130    pub technology_counts: &'a [usize],
131
132    /// Number of trade splines of the map.
133    pub trade_splines: usize,
134
135    /// If players can pick each world faction, in world order.
136    pub playable: &'a [bool],
137
138    /// Number of international trade routes of the trade manager.
139    pub trade_routes: usize,
140}
141
142/// Ids of a faction's AI manager.
143#[derive(Debug, Clone, Default, Getters, CopyGetters)]
144pub struct ManagerIds {
145
146    /// Position of its faction in the plan's factions.
147    #[getset(get_copy = "pub")]
148    faction: usize,
149
150    /// Ids of its desires, in creation order (see [`ManagerKind::desires`]).
151    #[getset(get = "pub")]
152    desires: Vec<u32>,
153
154    /// Id of its faction-wide intention.
155    #[getset(get_copy = "pub")]
156    intention: u32,
157
158    /// Ids of its financial and strategic context beliefs (the rebels have none).
159    #[getset(get_copy = "pub")]
160    beliefs: Option<(u32, u32)>,
161
162    /// Ids of the skill management intentions of each of its characters, in character order.
163    #[getset(get = "pub")]
164    character_intentions: Vec<u32>,
165
166    /// Ids of the skill management intentions of each of its mobiles, in mobile order.
167    #[getset(get = "pub")]
168    mobile_intentions: Vec<u32>,
169}
170
171/// Ids of a faction.
172#[derive(Debug, Clone, Default, Getters, CopyGetters)]
173pub struct FactionIds {
174
175    /// The faction's id.
176    #[getset(get_copy = "pub")]
177    faction: u32,
178
179    /// Id of its AI faction record.
180    #[getset(get_copy = "pub")]
181    cai_faction: u32,
182
183    /// Id of its technology tree (the rebels have none).
184    #[getset(get_copy = "pub")]
185    tree: Option<u32>,
186
187    /// Ids of its technologies, in tree order.
188    #[getset(get = "pub")]
189    technologies: Vec<u32>,
190}
191
192/// Ids of a settlement and its building slots.
193#[derive(Debug, Clone, Default, Getters, CopyGetters)]
194pub struct SettlementIds {
195
196    /// Ids of the slots, in world order.
197    #[getset(get = "pub")]
198    slots: Vec<u32>,
199
200    /// The settlement's id.
201    #[getset(get_copy = "pub")]
202    settlement: u32,
203}
204
205/// A boundary between two map regions.
206#[derive(Debug, Clone, Default, CopyGetters)]
207#[getset(get_copy = "pub")]
208pub struct BoundaryIds {
209
210    /// The boundary's id.
211    id: u32,
212
213    /// Index of the first region, the one listing the connection first.
214    region: usize,
215
216    /// Index of the other region.
217    other: usize,
218
219    /// Sum of both regions' connection values.
220    value: u32,
221}
222
223/// Ids of a character and its force.
224#[derive(Debug, Clone, Default, Getters, CopyGetters)]
225pub struct CharacterIds {
226
227    /// The character's id.
228    #[getset(get_copy = "pub")]
229    character: u32,
230
231    /// Id of the mobile of its force (or of the agent itself), if it has one.
232    #[getset(get_copy = "pub")]
233    mobile: Option<u32>,
234
235    /// Ids of its horde's building slots.
236    #[getset(get = "pub")]
237    horde_slots: Vec<u32>,
238
239    /// Ids of its force's units, in world order.
240    #[getset(get = "pub")]
241    units: Vec<u32>,
242}
243
244/// Ids of a province and its factions' parts.
245#[derive(Debug, Clone, Default, Getters, CopyGetters)]
246pub struct ProvinceIds {
247
248    /// The province's id.
249    #[getset(get_copy = "pub")]
250    province: u32,
251
252    /// Ids of each faction's part, in world order.
253    #[getset(get = "pub")]
254    faction_provinces: Vec<u32>,
255}
256
257/// Ids of a map region and its HLCIs.
258#[derive(Debug, Clone, Default, Getters, CopyGetters)]
259pub struct RegionIds {
260
261    /// The region's id.
262    #[getset(get_copy = "pub")]
263    id: u32,
264
265    /// Its HLCIs' ids, in the region's HLCI order.
266    #[getset(get = "pub")]
267    hlcis: Vec<u32>,
268}
269
270//---------------------------------------------------------------------------//
271//                              Implementations
272//---------------------------------------------------------------------------//
273
274impl CaiPlan {
275
276    /// Plans the ids of a campaign's AI objects.
277    pub fn new(inputs: &PlanInputs) -> Self {
278        let world = inputs.world;
279        let mut plan = Self { next_id: ROOT_ID + 1 + WORLD_COMPONENTS, ..Default::default() };
280        plan.first_central_desire = plan.take(CENTRAL_DESIRES.len() as u32);
281        plan.theatre = plan.take(1);
282        plan.regions = inputs.map_regions.iter().map(|region| {
283            let id = plan.take(1);
284            let hlcis = (0..region.hlcis().len()).map(|_| plan.take(1)).collect();
285            RegionIds { id, hlcis }
286        }).collect();
287
288        for count in inputs.technology_counts.iter().take(world.factions().len()) {
289            let faction = plan.take(1);
290            let cai_faction = plan.take(1);
291            let tree = Some(plan.take(1));
292            let technologies = (0..*count).map(|_| plan.take(1)).collect();
293            plan.factions.push(FactionIds { faction, cai_faction, tree, technologies });
294        }
295        let faction = plan.take(1);
296        let cai_faction = plan.take(1);
297        plan.factions.push(FactionIds { faction, cai_faction, tree: None, technologies: vec![] });
298
299        for (index, map_region) in inputs.map_regions.iter().enumerate() {
300            if let Some(region) = world.regions().iter().find(|region| region.key() == map_region.key()) {
301                let slots = region.slots().iter().map(|_| plan.take(1)).collect();
302                let settlement = plan.take(1);
303                plan.settlements.insert(index, SettlementIds { slots, settlement });
304            }
305        }
306
307        plan.first_trade_segment = plan.take(inputs.trade_splines as u32);
308        plan.boundaries = boundaries(inputs.map_regions).into_iter()
309            .map(|(region, other, value)| BoundaryIds { id: plan.take(1), region, other, value })
310            .collect();
311
312        for faction in world.factions() {
313            let characters = faction.characters().iter().map(|character| {
314                let id = plan.take(1);
315                let force = faction.forces().iter().find(|force| character.force() != 0 && force.id() == character.force());
316                let (mobile, horde_slots, units) = match force {
317                    Some(force) => (
318                        Some(plan.take(1)),
319                        force.horde_slots().iter().map(|_| plan.take(1)).collect(),
320                        force.units().iter().map(|_| plan.take(1)).collect(),
321                    ),
322                    None if !FORCE_AGENTS.contains(&character.agent().as_str()) => (Some(plan.take(1)), vec![], vec![]),
323                    None => (None, vec![], vec![]),
324                };
325                CharacterIds { character: id, mobile, horde_slots, units }
326            }).collect();
327            plan.characters.push(characters);
328        }
329
330        plan.provinces = world.provinces().iter().map(|province| {
331            let id = plan.take(1);
332            let faction_provinces = province.managers().iter().map(|_| plan.take(1)).collect();
333            ProvinceIds { province: id, faction_provinces }
334        }).collect();
335        plan.faction_seas = world.seas().iter().map(|sea| sea.managers().iter().map(|_| plan.take(1)).collect()).collect();
336        plan.governorships = world.factions().iter().map(|_| plan.take(1)).collect();
337        plan.posts = world.factions().iter().map(|faction| faction.posts().iter().map(|_| plan.take(1)).collect()).collect();
338
339        plan.manager_kinds = (0..world.factions().len())
340            .map(|position| if inputs.playable.get(position).copied().unwrap_or_default() { ManagerKind::Human } else { ManagerKind::Ai })
341            .chain(std::iter::once(ManagerKind::Rebels))
342            .collect();
343
344        let owners = world.regions().iter().map(|region| region.owner()).collect::<HashSet<_>>();
345        let counts = plan.characters.iter()
346            .map(|characters| (characters.len(), characters.iter().filter(|character| character.mobile().is_some()).count()))
347            .collect::<Vec<_>>();
348        for (position, (faction, (characters, mobiles))) in world.factions().iter().zip(counts).enumerate() {
349            if !owners.contains(&faction.id()) && mobiles == 0 {
350                continue;
351            }
352
353            // Only AI managers have intentions for their characters and mobiles.
354            let kind = plan.manager_kinds[position];
355            let (characters, mobiles) = if kind == ManagerKind::Ai { (characters, mobiles) } else { (0, 0) };
356            let desires = kind.desires().iter().map(|_| plan.take(1)).collect();
357            let intention = plan.take(1);
358            let beliefs = Some((plan.take(1), plan.take(1)));
359            let character_intentions = (0..characters).map(|_| plan.take(1)).collect();
360            let mobile_intentions = (0..mobiles).map(|_| plan.take(1)).collect();
361            plan.managers.push(ManagerIds { faction: position, desires, intention, beliefs, character_intentions, mobile_intentions });
362        }
363        let desires = ManagerKind::Rebels.desires().iter().map(|_| plan.take(1)).collect();
364        let intention = plan.take(1);
365        plan.managers.push(ManagerIds { faction: plan.factions.len() - 1, desires, intention, ..Default::default() });
366
367        plan.director = (0..DIRECTOR_BELIEFS.len() + DIRECTOR_DESIRES.len()).map(|_| plan.take(1)).collect();
368        plan.first_trade_route = plan.take(inputs.trade_routes as u32);
369        plan
370    }
371
372    /// Takes the next ids.
373    ///
374    /// # Returns
375    ///
376    /// The first of them.
377    fn take(&mut self, count: u32) -> u32 {
378        let first = self.next_id;
379        self.next_id += count;
380        first
381    }
382}
383
384/// Returns the boundaries between map regions: walking the regions in map order and each one's connections in order,
385/// one boundary per pair of regions at its first appearance, with the sum of both directions' values.
386fn boundaries(map_regions: &[MapRegion]) -> Vec<(usize, usize, u32)> {
387    let mut values = HashMap::new();
388    for (index, region) in map_regions.iter().enumerate() {
389        for (other, value) in region.connections() {
390            *values.entry((index.min(*other), index.max(*other))).or_insert(0) += value;
391        }
392    }
393
394    let mut boundaries = vec![];
395    let mut seen = HashSet::new();
396    for (index, region) in map_regions.iter().enumerate() {
397        for (other, _) in region.connections() {
398            let pair = (index.min(*other), index.max(*other));
399            if seen.insert(pair) {
400                boundaries.push((index, *other, values[&pair]));
401            }
402        }
403    }
404    boundaries
405}