Skip to main content

rpfm_extensions/startpos/cai/
world.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's mirror of the world (`CAI_WORLD`), from what the AI writer works out of a campaign.
12
13use getset::Getters;
14
15use std::collections::HashMap;
16
17use rpfm_lib::error::Result;
18use rpfm_lib::files::esf::{RecordNode, ESF};
19
20use super::factions::{capitals, faction_links, victory_factions, write_factions, write_technologies, write_technology_trees};
21use super::lists::{write_boundaries, write_faction_seas, write_governorships, write_posts, write_provinces, write_trade_routes, write_trade_segments};
22use super::map::{pathfinder_nodes, theatre_key, AreaGroups, MapRegion, PathfinderNode};
23use super::mobiles::{mobile_cells, write_characters, write_mobiles, write_units, MobileInputs};
24use super::plan::{CaiPlan, PlanInputs};
25use super::regions::{region_links, region_positions, write_hlcis, write_regions, write_theatres, LinkInputs};
26use super::settlements::{port_cells, settlement_components, write_building_slots, write_settlements};
27use super::technologies::faction_technologies;
28use super::world_view::WorldView;
29use super::{component, CAI_WORLD_PATH};
30use crate::startpos::layout::StartposLayout;
31use crate::startpos::region_map::RegionMap;
32use crate::startpos::settlements::SettlementLayout;
33use crate::startpos::tables::StartposTables;
34use crate::startpos::world::CampaignIndex;
35
36#[cfg(test)] #[path = "world_test.rs"] mod world_test;
37
38/// Component id of the world mirror's own component.
39const WORLD_COMPONENT: u32 = 3;
40
41/// Component id of the world mirror's time.
42const TIME_COMPONENT: u32 = 4;
43
44//---------------------------------------------------------------------------//
45//                              Types
46//---------------------------------------------------------------------------//
47
48/// What the AI writer is built from.
49#[derive(Debug, Clone, Copy)]
50pub struct CaiInputs<'a> {
51
52    /// Tables of the game and the campaign.
53    pub tables: &'a StartposTables,
54
55    /// The campaign's startpos rows.
56    pub index: &'a CampaignIndex,
57
58    /// Record layouts of the game.
59    pub layout: &'a StartposLayout,
60
61    /// The written `WORLD` record.
62    pub world: &'a RecordNode,
63
64    /// Decoded `map_data.esf` of the campaign map.
65    pub map_data: &'a ESF,
66
67    /// Decoded `hlp_data.esf` of the campaign map.
68    pub hlp_data: &'a ESF,
69
70    /// Number of trade splines of the campaign map.
71    pub trade_splines: usize,
72
73    /// International trade routes of the written trade manager: owner faction (runtime id) and route id.
74    pub trade_routes: &'a [(u32, u32)],
75
76    /// Campaign key.
77    pub campaign: &'a str,
78}
79
80/// What the AI writer works out of a campaign before writing.
81#[derive(Debug, Clone, Getters)]
82#[getset(get = "pub")]
83pub struct CaiContext {
84
85    /// The world's objects.
86    world: WorldView,
87
88    /// Regions of the map, in map order.
89    map_regions: Vec<MapRegion>,
90
91    /// Pathfinder nodes, by region index.
92    nodes: HashMap<usize, Vec<PathfinderNode>>,
93
94    /// Region and area of each cell.
95    region_map: RegionMap,
96
97    /// Settlement layouts of the map.
98    settlement_layouts: Vec<SettlementLayout>,
99
100    /// Technology keys of each world faction's tree.
101    technologies: Vec<Vec<String>>,
102
103    /// Key of the map's theatre.
104    theatre_key: String,
105
106    /// Component ids.
107    plan: CaiPlan,
108}
109
110//---------------------------------------------------------------------------//
111//                              Implementations
112//---------------------------------------------------------------------------//
113
114impl CaiContext {
115
116    /// Works out what the AI writer needs from a campaign.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the world doesn't fit its layouts, a map file is malformed or a table is missing.
121    pub fn new(inputs: &CaiInputs) -> Result<Self> {
122        let world = WorldView::new(inputs.layout, inputs.world)?;
123        let map_regions = MapRegion::all_from_map_data(inputs.map_data)?;
124        let nodes = pathfinder_nodes(inputs.hlp_data)?;
125        let region_map = RegionMap::from_map_data(inputs.map_data)?;
126        let settlement_layouts = SettlementLayout::all_from_map_data(inputs.map_data, |_| false)?;
127        let theatre_key = theatre_key(inputs.map_data)?;
128        let technologies = world.factions().iter()
129            .map(|faction| match inputs.index.faction(faction.key()) {
130                Some(entry) => faction_technologies(inputs.tables, inputs.campaign, entry),
131                None => Ok(vec![]),
132            })
133            .collect::<Result<Vec<_>>>()?;
134        let technology_counts = technologies.iter().map(Vec::len).collect::<Vec<_>>();
135        let playable = world.factions().iter().map(|faction| inputs.index.faction(faction.key()).is_some_and(|entry| entry.playable())).collect::<Vec<_>>();
136        let plan = CaiPlan::new(&PlanInputs { map_regions: &map_regions, world: &world, technology_counts: &technology_counts, trade_splines: inputs.trade_splines, playable: &playable, trade_routes: inputs.trade_routes.len() });
137        Ok(Self { world, map_regions, nodes, region_map, settlement_layouts, technologies, theatre_key, plan })
138    }
139
140    /// Writes the world mirror (`CAI_WORLD`).
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if a table is missing or the layout lacks a record.
145    pub fn write_world(&self, inputs: &CaiInputs) -> Result<RecordNode> {
146        self.with_mobiles(inputs, |mobile_inputs| self.write_world_with(inputs, mobile_inputs))
147    }
148
149    /// Runs a writer with what the mobiles are written from: their settlements, ports and connectivity groups.
150    pub(crate) fn with_mobiles<T>(&self, inputs: &CaiInputs, write: impl FnOnce(&MobileInputs) -> Result<T>) -> Result<T> {
151        let (plan, world, map_regions) = (&self.plan, &self.world, &self.map_regions[..]);
152        let groups = AreaGroups::new(&self.region_map, map_regions);
153        let settlements = settlement_components(world, plan, map_regions);
154        let ports = port_cells(world, map_regions, &groups);
155        write(&MobileInputs { layout: inputs.layout, plan, world, region_map: &self.region_map, groups: &groups, settlements: &settlements, ports: &ports })
156    }
157
158    /// Writes the world mirror from what the mobiles are written from.
159    pub(crate) fn write_world_with(&self, inputs: &CaiInputs, mobile_inputs: &MobileInputs) -> Result<RecordNode> {
160        let layout = inputs.layout;
161        let (plan, world, map_regions) = (&self.plan, &self.world, &self.map_regions[..]);
162        let ports = mobile_inputs.ports;
163        let mobiles = mobile_cells(mobile_inputs);
164        let victory = victory_factions(inputs.tables, inputs.index, world, plan)?;
165        let links = region_links(&LinkInputs { plan, world, map_regions, region_map: &self.region_map, mobiles: &mobiles, ports, victory_factions: &victory });
166        let positions = region_positions(map_regions, &self.nodes);
167        let capitals = capitals(inputs.index, world, plan, map_regions);
168        let faction_links = faction_links(inputs.tables, inputs.index, world, plan, map_regions)?;
169        let (provinces, faction_provinces) = write_provinces(layout, plan, world, map_regions, inputs.index)?;
170        let mobile_ids = mobiles.iter().map(|(mobile, _)| *mobile).collect();
171
172        let mut record = layout.builder(CAI_WORLD_PATH)?;
173        record.group()?
174            .set_record("CAI_WORLD_THEATRES", Some(write_theatres(layout, plan, &self.theatre_key, mobile_ids)?))?
175            .set_record("CAI_WORLD_FACTIONS", Some(write_factions(layout, plan, &faction_links)?))?
176            .set_record("CAI_WORLD_REGIONS", Some(write_regions(layout, plan, map_regions, &self.nodes, &links)?))?
177            .set_record("CAI_WORLD_REGION_BOUNDARIES", Some(write_boundaries(layout, plan)?))?
178            .set_record("CAI_WORLD_SETTLEMENTS", Some(write_settlements(mobile_inputs, map_regions, &positions, &capitals)?))?
179            .set_record("CAI_WORLD_BUILDING_SLOTS", Some(write_building_slots(layout, plan, world, map_regions, &self.settlement_layouts)?))?
180            .set_record("CAI_WORLD_RESOURCE_MOBILES", Some(write_mobiles(mobile_inputs)?))?
181            .set_record("CAI_WORLD_UNITS", Some(write_units(mobile_inputs)?))?
182            .set_record("CAI_WORLD_CHARACTERS", Some(write_characters(mobile_inputs)?))?
183            .set_record("CAI_BDI_COMPONENT", Some(component(layout, CAI_WORLD_PATH, WORLD_COMPONENT, vec![])?))?
184            .set_record("CAI_BDI_COMPONENT@2", Some(component(layout, CAI_WORLD_PATH, TIME_COMPONENT, vec![])?))?
185            .set_record("CAI_WORLD_TRADE_ROUTE_SEGMENTS", Some(write_trade_segments(layout, plan, inputs.trade_splines)?))?
186            .set_record("CAI_WORLD_TRADE_ROUTES", Some(write_trade_routes(layout, plan, world, inputs.trade_routes)?))?
187            .set_record("CAI_WORLD_TECHNOLOGY_TREES", Some(write_technology_trees(layout, plan)?))?
188            .set_record("CAI_WORLD_TECHNOLOGIES", Some(write_technologies(layout, plan, &self.technologies)?))?
189            .set_record("CAI_WORLD_GOVERNORSHIPS", Some(write_governorships(layout, plan)?))?
190            .set_record("CAI_WORLD_REGION_HLCIS", Some(write_hlcis(layout, plan, map_regions, &self.nodes)?))?
191            .set_record("CAI_CHARACTER_POSTS", Some(write_posts(layout, plan, world)?))?
192            .set_record("CAI_PROVINCES", Some(provinces))?
193            .set_record("CAI_FACTION_PROVINCES", Some(faction_provinces))?
194            .set_record("CAI_FACTION_SEA_REGIONS", Some(write_faction_seas(layout, plan, world, map_regions)?))?;
195        record.build()
196    }
197}