eu4save/
query.rs

1use crate::{
2    models::{
3        Country, CountryEvent, Eu4Save, LedgerData, LedgerDatum, Province, ProvinceEvent,
4        ProvinceEventValue, WarEvent,
5    },
6    ProvinceId, TagResolver,
7};
8use crate::{CountryTag, Eu4Date, PdsDate};
9use once_cell::sync::OnceCell;
10use serde::{Deserialize, Serialize};
11use std::{
12    collections::{HashMap, HashSet},
13    num::NonZeroU16,
14};
15
16#[derive(Debug)]
17pub struct AnnualLedgers {
18    pub score: Vec<LedgerDatum>,
19    pub inflation: Vec<LedgerDatum>,
20    pub size: Vec<LedgerDatum>,
21    pub income: Vec<LedgerDatum>,
22}
23
24#[derive(Debug, Serialize, Deserialize)]
25pub struct CountryIncomeLedger {
26    pub taxation: f32,
27    pub production: f32,
28    pub trade: f32,
29    pub gold: f32,
30    pub tariffs: f32,
31    pub vassals: f32,
32    pub harbor_fees: f32,
33    pub subsidies: f32,
34    pub war_reparations: f32,
35    pub interest: f32,
36    pub gifts: f32,
37    pub events: f32,
38    pub spoils_of_war: f32,
39    pub treasure_fleet: f32,
40    pub siphoning_income: f32,
41    pub condottieri: f32,
42    pub knowledge_sharing: f32,
43    pub blockading_foreign_ports: f32,
44    pub looting_foreign_cities: f32,
45    pub other: f32,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
49pub struct CountryExpenseLedger {
50    pub advisor_maintenance: f32,
51    pub interest: f32,
52    pub state_maintenance: f32,
53    pub subsidies: f32,
54    pub war_reparations: f32,
55    pub army_maintenance: f32,
56    pub fleet_maintenance: f32,
57    pub fort_maintenance: f32,
58    pub colonists: f32,
59    pub missionaries: f32,
60    pub raising_armies: f32,
61    pub building_fleets: f32,
62    pub building_fortresses: f32,
63    pub buildings: f32,
64    pub repaid_loans: f32,
65    pub gifts: f32,
66    pub advisors: f32,
67    pub events: f32,
68    pub peace: f32,
69    pub vassal_fee: f32,
70    pub tariffs: f32,
71    pub support_loyalists: f32,
72    pub condottieri: f32,
73    pub root_out_corruption: f32,
74    pub embrace_institution: f32,
75    pub knowledge_sharing: f32,
76    pub trade_company_investments: f32,
77    pub other: f32,
78    pub ports_blockaded: f32,
79    pub cities_looted: f32,
80    pub monuments: f32,
81    pub cot_upgrades: f32,
82    pub colony_changes: f32,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct CountryManaUsage {
87    pub adm: CountryManaSpend,
88    pub dip: CountryManaSpend,
89    pub mil: CountryManaSpend,
90}
91
92#[derive(Debug, Serialize, Deserialize)]
93pub struct CountryManaSpend {
94    pub buy_idea: i32,
95    pub advance_tech: i32,
96    pub boost_stab: i32,
97    pub buy_general: i32,
98    pub buy_admiral: i32,
99    pub buy_conq: i32,
100    pub buy_explorer: i32,
101    pub develop_prov: i32,
102    pub force_march: i32,
103    pub assault: i32,
104    pub seize_colony: i32,
105    pub burn_colony: i32,
106    pub attack_natives: i32,
107    pub scorch_earth: i32,
108    pub demand_non_wargoal_prov: i32,
109    pub reduce_inflation: i32,
110    pub move_capital: i32,
111    pub make_province_core: i32,
112    pub replace_rival: i32,
113    pub change_gov: i32,
114    pub change_culture: i32,
115    pub harsh_treatment: i32,
116    pub reduce_we: i32,
117    pub boost_faction: i32,
118    pub raise_war_taxes: i32,
119    pub buy_native_advancement: i32,
120    pub increse_tariffs: i32,
121    pub promote_merc: i32,
122    pub decrease_tariffs: i32,
123    pub move_trade_port: i32,
124    pub create_trade_post: i32,
125    pub siege_sorties: i32,
126    pub buy_religious_reform: i32,
127    pub set_primary_culture: i32,
128    pub add_accepted_culture: i32,
129    pub remove_accepted_culture: i32,
130    pub strengthen_government: i32,
131    pub boost_militarization: i32,
132    pub artillery_barrage: i32,
133    pub establish_siberian_frontier: i32,
134    pub government_interaction: i32,
135    pub naval_barrage: i32,
136    pub create_leader: i32,
137    pub enforce_culture: i32,
138    pub effect: i32,
139    pub minority_expulsion: i32,
140    pub other: i32,
141    pub add_tribal_land: i32,
142}
143
144#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
145pub enum BuildingConstruction {
146    Constructed,
147    Destroyed,
148}
149
150#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
151pub struct BuildingEvent<'a> {
152    pub building: &'a str,
153    pub date: Eu4Date,
154    pub action: BuildingConstruction,
155}
156
157#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
158pub struct PlayerHistory {
159    pub history: NationEvents,
160
161    /// Whether the player is currently in the session
162    pub is_human: bool,
163
164    /// Names of the players (may be empty)
165    pub player_names: Vec<String>,
166}
167
168pub struct ProvinceOwners {
169    /// Initial owners of provinces, index using province id
170    pub initial: Vec<Option<CountryTag>>,
171
172    /// Sorted by date and then province id    
173    pub changes: Vec<ProvinceOwnerChange>,
174}
175
176#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
177pub struct ProvinceOwnerChange {
178    pub province: ProvinceId,
179    pub tag: CountryTag,
180    pub date: Eu4Date,
181}
182
183pub struct ProvinceReligions {
184    pub initial: Vec<Option<ReligionIndex>>,
185    pub changes: Vec<ProvinceReligionChange>,
186}
187
188#[derive(Debug, PartialEq, Eq, Clone)]
189pub struct ProvinceReligionChange {
190    pub province: ProvinceId,
191    pub religion: ReligionIndex,
192    pub date: Eu4Date,
193}
194
195pub struct ReligionLookup {
196    religions: Vec<String>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
200pub struct ReligionIndex(NonZeroU16);
201
202#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
203pub struct Player {
204    pub name: String,
205    pub tag: CountryTag,
206}
207
208#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
209pub struct NationEvents {
210    /// The initial starting tag for a country. In a TYR -> IRE -> GBR run,
211    /// this would be TYR
212    pub initial: CountryTag,
213
214    /// The latest tag that a country achieved. If DMS -> IRE but then
215    /// IRE is annexed by SCO which culture shifts to form IRE then both
216    /// initial tags of SCO and DMS will report a latest tag of IRE
217    pub latest: CountryTag,
218
219    /// The tag which the history of this country is stored under. For
220    /// instance if ULM forms byzantium then the initial byzantium operator's
221    /// history is stored under ULM
222    pub stored: CountryTag,
223
224    /// An ordered (by date) recounting of how the initial tag became the
225    /// the latest tag. May be empty for nations that did not tag switch,
226    /// get annexed, etc.
227    pub events: Vec<NationEvent>,
228}
229
230#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
231pub struct NationEvent {
232    pub date: Eu4Date,
233    pub kind: NationEventKind,
234}
235
236impl NationEvent {
237    pub fn as_tag_switch(&self) -> Option<(Eu4Date, CountryTag)> {
238        if let NationEventKind::TagSwitch(to) = self.kind {
239            Some((self.date, to))
240        } else {
241            None
242        }
243    }
244}
245
246#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
247pub enum NationEventKind {
248    TagSwitch(CountryTag),
249    Appeared,
250    Annexed,
251}
252
253#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
254pub struct LedgerPoint {
255    pub tag: CountryTag,
256    pub year: u16,
257    pub value: i32,
258}
259
260#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
261pub struct ResolvedWarParticipant {
262    /// The tag as it appears in the war history
263    pub tag: CountryTag,
264
265    /// The actual location of the tag in country history
266    pub stored: CountryTag,
267}
268
269#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
270pub struct ResolvedWarParticipants {
271    pub war: String,
272    pub participants: Vec<ResolvedWarParticipant>,
273}
274
275#[derive(Debug, Clone)]
276#[cfg_attr(feature = "serialize", derive(Serialize))]
277pub struct SaveCountry<'a> {
278    pub id: usize,
279    pub tag: CountryTag,
280    pub country: &'a Country,
281}
282
283#[derive(Debug, Clone, Copy)]
284struct TagId {
285    id: usize,
286    tag: CountryTag,
287}
288
289#[derive(Debug, Clone, Serialize)]
290pub struct Inheritance {
291    pub start_t0_year: i16,
292    pub end_t0_year: i16,
293    pub start_t1_year: i16,
294    pub end_t1_year: i16,
295    pub start_t2_year: i16,
296    pub end_t2_year: i16,
297    pub inheritance_value: u8,
298    pub subtotal: i64,
299    pub calculations: Vec<Calculation>,
300}
301
302#[derive(Debug, Clone, Copy, Serialize)]
303pub enum TagDependency {
304    Independent,
305    Dependent(CountryTag),
306}
307
308#[derive(Debug, Clone, Serialize)]
309pub struct Calculation {
310    pub name: String,
311    pub value: i64,
312    pub dependency: TagDependency,
313}
314
315impl Calculation {
316    pub fn new(name: &str, value: i64, dependency: TagDependency) -> Self {
317        Self {
318            name: String::from(name),
319            value,
320            dependency,
321        }
322    }
323}
324
325#[derive(Debug)]
326pub struct Query {
327    save: Eu4Save,
328    tag_ids: Vec<TagId>,
329    tag_lookup: HashMap<CountryTag, TagId>,
330    buildings: OnceCell<HashSet<String>>,
331}
332
333impl Query {
334    pub fn from_save(save: Eu4Save) -> Self {
335        let tag_ids: Vec<_> = save
336            .game
337            .countries
338            .iter()
339            .enumerate()
340            .map(|(i, (tag, _))| TagId { id: i, tag: *tag })
341            .collect();
342
343        let tag_lookup = tag_ids.iter().map(|id| (id.tag, *id)).collect();
344
345        Query {
346            save,
347            tag_ids,
348            tag_lookup,
349            buildings: OnceCell::default(),
350        }
351    }
352
353    pub fn save(&self) -> &Eu4Save {
354        &self.save
355    }
356
357    pub fn countries(&self) -> impl Iterator<Item = SaveCountry> + '_ {
358        self.save
359            .game
360            .countries
361            .iter()
362            .zip(self.tag_ids.iter())
363            .map(|((_, country), tag_id)| SaveCountry {
364                id: tag_id.id,
365                tag: tag_id.tag,
366                country,
367            })
368    }
369
370    pub fn save_country(&self, tag: &CountryTag) -> Option<SaveCountry> {
371        self.tag_lookup.get(tag).and_then(|tag_id| {
372            self.save
373                .game
374                .countries
375                .get(tag_id.id)
376                .map(|(_, country)| SaveCountry {
377                    id: tag_id.id,
378                    tag: tag_id.tag,
379                    country,
380                })
381        })
382    }
383
384    pub fn country(&self, tag: &CountryTag) -> Option<&Country> {
385        self.save_country(tag).map(|x| x.country)
386    }
387
388    /// Returns a set of the names of the players who participated in a playthrough.
389    /// May be blank for single player run through where the player is detected as
390    /// "Player". No guarantees are given as to the state of each player's country.
391    /// Annexed countries may still show the original player. It is undefined what
392    /// happens to players when a formable nation is annexed and then reformed by
393    /// another player.
394    pub fn players(&self) -> Vec<Player> {
395        players(&self.save)
396    }
397
398    /// Provides a rich structure that contains the following:
399    ///
400    /// - A list countries that had a player at one point or another
401    /// - What country the player is playing
402    /// - Whether that player is currently in the session
403    /// - The names of the players in the session
404    /// - A list of all prior tags that country had been
405    pub fn player_histories(&self, nation_events: &[NationEvents]) -> Vec<PlayerHistory> {
406        let save = self.save();
407        let mut result = Vec::with_capacity(save.game.players_countries.len());
408        let players = players(save);
409        let mut leftovers = Vec::new();
410        for (tag, country) in save.game.countries.iter().filter(|(_, c)| c.was_player) {
411            let tag = *tag;
412            let tag_players: Vec<_> = players
413                .iter()
414                .filter(|x| x.tag == tag)
415                .map(|x| x.name.clone())
416                .collect();
417            let history = nation_events
418                .iter()
419                .find(|x| x.stored == tag)
420                .cloned()
421                .unwrap_or_else(|| NationEvents {
422                    initial: tag,
423                    latest: tag,
424                    stored: tag,
425                    events: Vec::new(),
426                });
427
428            let no_players = tag_players.is_empty();
429            let history = PlayerHistory {
430                history,
431                is_human: country.human,
432                player_names: tag_players,
433            };
434
435            if country.was_player && no_players {
436                leftovers.push(history);
437            } else {
438                result.push(history);
439            }
440        }
441
442        // Only for ironman will we try and resolve "release and play as" as the
443        // save does not often paint an accurate picture of these transitions. And
444        // we need to track these for achievements like spaghetti western.
445        if result.len() == 1 && save.meta.is_ironman {
446            for x in &mut result {
447                if x.is_human && x.player_names.len() == 1 && x.history.stored == x.history.latest {
448                    let country = self.country(&x.history.stored);
449                    let rpa = country.map_or(false, |x| x.has_switched_nation);
450                    let alive = country.map_or(false, |x| x.num_of_cities > 0);
451                    if rpa && alive {
452                        if let Some(end) = leftovers.pop() {
453                            x.history.initial = end.history.initial;
454                        }
455                    }
456                }
457            }
458        }
459
460        result.append(&mut leftovers);
461
462        result
463    }
464
465    /// Calculates the major events that befell countries (annexations, appearances, and tag switches)
466    pub fn nation_events(&self, province_owners: &ProvinceOwners) -> Vec<NationEvents> {
467        nation_events(&self.save, province_owners)
468    }
469
470    /// Aggregate when lands changed hands
471    pub fn province_owners(&self) -> ProvinceOwners {
472        province_owners(&self.save)
473    }
474
475    pub fn province_religions(&self, lookup: &ReligionLookup) -> ProvinceReligions {
476        province_religions(&self.save, lookup)
477    }
478
479    pub fn religion_lookup(&self) -> ReligionLookup {
480        let mut religions = self
481            .save
482            .game
483            .religions
484            .iter()
485            .map(|(key, _religion)| key.clone())
486            .collect::<Vec<_>>();
487        religions.sort_unstable();
488        ReligionLookup { religions }
489    }
490
491    /// Return the starting country in single player playthroughs. If playing in multiplayer or if
492    /// the starting country can't be determined then none is returned.
493    pub fn starting_country(&self, histories: &[PlayerHistory]) -> Option<CountryTag> {
494        let mut preexisting = histories.iter().filter(|x| {
495            !x.history
496                .events
497                .first()
498                .map_or(false, |x| x.kind == NationEventKind::Appeared)
499        });
500        let first = preexisting.next();
501        let second = preexisting.next();
502
503        if second.is_some() {
504            return None;
505        }
506
507        first.map(|x| x.history.initial).or(match histories {
508            [player] => Some(player.history.initial),
509            _ => None,
510        })
511    }
512
513    pub fn tag_resolver(&self, nation_events: &[NationEvents]) -> TagResolver {
514        TagResolver::create(nation_events)
515    }
516
517    fn inherit_subtotal(&self, country: &SaveCountry) -> (i64, Vec<Calculation>) {
518        let hre_ruler = self
519            .save()
520            .game
521            .empire
522            .as_ref()
523            .and_then(|x| x.emperor)
524            .and_then(|x| self.country(&x))
525            .and_then(|x| x.monarch.as_ref())
526            .map(|x| i64::from(x.id))
527            .unwrap_or_default();
528
529        let papacy_controller = self
530            .save()
531            .game
532            .religion_instance_data
533            .get("catholic")
534            .and_then(|x| x.papacy.as_ref())
535            .and_then(|x| self.save_country(&x.controller));
536
537        let papacy_tag = papacy_controller
538            .as_ref()
539            .map(|x| x.tag)
540            .unwrap_or_else(|| "---".parse().unwrap());
541        let papacy_id = papacy_controller
542            .as_ref()
543            .map(|x| x.id as i64)
544            .unwrap_or_default();
545
546        let ruler = country
547            .country
548            .monarch
549            .as_ref()
550            .map(|x| i64::from(x.id))
551            .unwrap_or_default();
552
553        let previous_rulers = country
554            .country
555            .previous_monarchs
556            .iter()
557            .map(|x| i64::from(x.id))
558            .sum::<i64>();
559
560        let capital_province = i64::from(country.country.capital.as_u16());
561
562        let provinces = i64::from(country.country.num_of_cities);
563
564        #[rustfmt::skip]
565        let calculations = vec![
566            Calculation::new("Nation ID", country.id as i64, TagDependency::Dependent(country.tag)),
567            Calculation::new("HRE Ruler ID", hre_ruler, TagDependency::Independent),
568            Calculation::new("Curia Controller Nation ID", papacy_id, TagDependency::Dependent(papacy_tag)),
569            Calculation::new("Ruler ID", ruler, TagDependency::Dependent(country.tag)),
570            Calculation::new("Previous Ruler IDs", previous_rulers, TagDependency::Dependent(country.tag)),
571            Calculation::new("Capital Province", capital_province, TagDependency::Dependent(country.tag)),
572            Calculation::new("Owned Provinces", provinces, TagDependency::Dependent(country.tag)),
573        ];
574
575        let raw = hre_ruler
576            + papacy_id
577            + ruler
578            + previous_rulers
579            + capital_province
580            + provinces
581            + country.id as i64;
582
583        (raw, calculations)
584    }
585
586    pub fn inherit(&self, country: &SaveCountry) -> Inheritance {
587        let (subtotal, calculations) = self.inherit_subtotal(country);
588
589        let year = i64::from(self.save().meta.date.year());
590        let inheritance_value = (subtotal + year) % 100;
591
592        let t0_mod = (0 - inheritance_value) % 100;
593        let t1_mod = (75 - inheritance_value) % 100;
594        let t2_mod = (80 - inheritance_value) % 100;
595
596        // end date < year => +100
597        // end date > year + 100 => -100
598        let t0_offset = if year + t0_mod + 74 < year { 100 } else { 0 };
599
600        let t1_offset = if year + t1_mod + 4 < year { 100 } else { 0 };
601
602        let t2_offset = if year + t2_mod + 19 < year { 100 } else { 0 };
603
604        // dbg!((century, t0_offset, t0_mod));
605        let start_t0_year = year + t0_mod + t0_offset;
606        let end_t0_year = year + t0_mod + t0_offset + 74;
607        let start_t1_year = year + t1_mod + t1_offset;
608        let end_t1_year = year + t1_mod + t1_offset + 4;
609        let start_t2_year = year + t2_mod + t2_offset;
610        let end_t2_year = year + t2_mod + t2_offset + 19;
611
612        Inheritance {
613            start_t0_year: start_t0_year as i16,
614            end_t0_year: end_t0_year as i16,
615            start_t1_year: start_t1_year as i16,
616            end_t1_year: end_t1_year as i16,
617            start_t2_year: start_t2_year as i16,
618            end_t2_year: end_t2_year as i16,
619            inheritance_value: inheritance_value as u8,
620            subtotal,
621            calculations,
622        }
623    }
624
625    pub fn resolved_war_participants(
626        &self,
627        tag_resolver: &TagResolver,
628    ) -> Vec<ResolvedWarParticipants> {
629        war_participants(&self.save, tag_resolver)
630    }
631
632    pub fn income_statistics_ledger(&self, nation: &NationEvents) -> Vec<LedgerPoint> {
633        self.nation_ledger(nation, &self.save.game.income_statistics, |x| x / 12)
634    }
635
636    pub fn inflation_statistics_ledger(&self, nation: &NationEvents) -> Vec<LedgerPoint> {
637        self.nation_ledger(nation, &self.save.game.inflation_statistics, |x| x)
638    }
639
640    pub fn score_statistics_ledger(&self, nation: &NationEvents) -> Vec<LedgerPoint> {
641        self.nation_ledger(nation, &self.save.game.score_statistics, |x| x)
642    }
643
644    pub fn nation_size_statistics_ledger(&self, nation: &NationEvents) -> Vec<LedgerPoint> {
645        self.nation_ledger(nation, &self.save.game.nation_size_statistics, |x| x)
646    }
647
648    fn nation_ledger<F: Fn(i32) -> i32>(
649        &self,
650        nation: &NationEvents,
651        ledger: &LedgerData,
652        f: F,
653    ) -> Vec<LedgerPoint> {
654        let time_range = self.save.game.start_date.days_until(&self.save.meta.date) / 365 + 1;
655        let mut result = Vec::with_capacity(time_range as usize);
656
657        #[derive(Debug)]
658        struct NationChain {
659            tag: CountryTag,
660            start: Eu4Date,
661            end: Eu4Date,
662        }
663
664        let mut chains: Vec<NationChain> = Vec::new();
665        let mut current_tag = nation.initial;
666        let mut start = self.save.game.start_date;
667        let mut annexed = false;
668        for event in &nation.events {
669            match event.kind {
670                NationEventKind::Annexed => {
671                    chains.push(NationChain {
672                        tag: current_tag,
673                        start,
674                        end: event.date,
675                    });
676                    annexed = true;
677                }
678                NationEventKind::Appeared => {
679                    start = event.date;
680                    annexed = false;
681                }
682                NationEventKind::TagSwitch(c) => {
683                    chains.push(NationChain {
684                        tag: current_tag,
685                        start,
686                        end: event.date,
687                    });
688                    annexed = false;
689                    start = event.date;
690                    current_tag = c;
691                }
692            }
693        }
694
695        if !annexed {
696            chains.push(NationChain {
697                tag: current_tag,
698                start,
699                end: self.save.meta.date,
700            })
701        }
702
703        let ledger_chain = chains.iter().filter_map(|chain| {
704            ledger
705                .ledger
706                .iter()
707                .find(|datum| datum.name == chain.tag)
708                .map(|ledger| (chain, ledger))
709        });
710
711        for (chain, ledger) in ledger_chain {
712            let data = ledger
713                .data
714                .iter()
715                .skip_while(|(year, _)| (*year as i16) < chain.start.year())
716                .take_while(|(year, _)| (*year as i16) <= chain.end.year());
717
718            let mut current = (chain.start.year() + 1) as u16;
719            for &(x, y) in data {
720                for year in current..x {
721                    result.push(LedgerPoint {
722                        tag: chain.tag,
723                        year,
724                        value: 0,
725                    });
726                }
727
728                result.push(LedgerPoint {
729                    tag: chain.tag,
730                    year: x,
731                    value: f(y),
732                });
733                current = x + 1;
734            }
735
736            for year in current..(chain.end.year() as u16) + 1 {
737                result.push(LedgerPoint {
738                    tag: chain.tag,
739                    year,
740                    value: 0,
741                });
742            }
743        }
744
745        result
746    }
747
748    pub fn country_tag_hex_color(&self, country_tag: &CountryTag) -> Option<String> {
749        self.country(country_tag)
750            .map(|x| self.country_color_to_hex(x))
751    }
752
753    pub fn country_color_to_hex(&self, country: &Country) -> String {
754        let colors = &country.colors.country_color;
755        format!("#{:02x}{:02x}{:02x}", colors[0], colors[1], colors[2])
756    }
757
758    pub fn country_income_breakdown(&self, country: &Country) -> CountryIncomeLedger {
759        let ledger = &country.ledger.lastmonthincometable;
760        CountryIncomeLedger {
761            taxation: *ledger.first().unwrap_or(&0.0),
762            production: *ledger.get(1).unwrap_or(&0.0),
763            trade: *ledger.get(2).unwrap_or(&0.0),
764            gold: *ledger.get(3).unwrap_or(&0.0),
765            tariffs: *ledger.get(4).unwrap_or(&0.0),
766            vassals: *ledger.get(5).unwrap_or(&0.0),
767            harbor_fees: *ledger.get(6).unwrap_or(&0.0),
768            subsidies: *ledger.get(7).unwrap_or(&0.0),
769            war_reparations: *ledger.get(8).unwrap_or(&0.0),
770            interest: *ledger.get(9).unwrap_or(&0.0),
771            gifts: *ledger.get(10).unwrap_or(&0.0),
772            events: *ledger.get(11).unwrap_or(&0.0),
773            spoils_of_war: *ledger.get(12).unwrap_or(&0.0),
774            treasure_fleet: *ledger.get(13).unwrap_or(&0.0),
775            siphoning_income: *ledger.get(14).unwrap_or(&0.0),
776            condottieri: *ledger.get(15).unwrap_or(&0.0),
777            knowledge_sharing: *ledger.get(16).unwrap_or(&0.0),
778            blockading_foreign_ports: *ledger.get(17).unwrap_or(&0.0),
779            looting_foreign_cities: *ledger.get(18).unwrap_or(&0.0),
780            other: ledger.get(19..).iter().flat_map(|x| x.iter()).sum(),
781        }
782    }
783
784    pub fn countries_income_breakdown(&self) -> HashMap<CountryTag, CountryIncomeLedger> {
785        self.save
786            .game
787            .countries
788            .iter()
789            .filter(|(_, country)| country.num_of_cities > 0)
790            .map(|(tag, country)| (*tag, self.country_income_breakdown(country)))
791            .collect()
792    }
793
794    pub fn countries_expense_breakdown(&self) -> HashMap<CountryTag, CountryExpenseLedger> {
795        self.save
796            .game
797            .countries
798            .iter()
799            .filter(|(_, country)| country.num_of_cities > 0)
800            .map(|(tag, country)| (*tag, self.country_expense_breakdown(country)))
801            .collect()
802    }
803
804    pub fn countries_total_expense_breakdown(&self) -> HashMap<CountryTag, CountryExpenseLedger> {
805        self.save
806            .game
807            .countries
808            .iter()
809            .filter(|(_, c)| c.ledger.totalexpensetable.iter().any(|&x| x > 0.0))
810            .map(|(tag, country)| (*tag, self.country_total_expense_breakdown(country)))
811            .collect()
812    }
813
814    fn expense_ledger_breakdown(&self, ledger: &[f32]) -> CountryExpenseLedger {
815        CountryExpenseLedger {
816            advisor_maintenance: *ledger.first().unwrap_or(&0.0),
817            interest: *ledger.get(1).unwrap_or(&0.0),
818            state_maintenance: *ledger.get(2).unwrap_or(&0.0),
819            subsidies: *ledger.get(4).unwrap_or(&0.0),
820            war_reparations: *ledger.get(5).unwrap_or(&0.0),
821            army_maintenance: *ledger.get(6).unwrap_or(&0.0),
822            fleet_maintenance: *ledger.get(7).unwrap_or(&0.0),
823            fort_maintenance: *ledger.get(8).unwrap_or(&0.0),
824            colonists: *ledger.get(9).unwrap_or(&0.0),
825            missionaries: *ledger.get(10).unwrap_or(&0.0),
826            raising_armies: *ledger.get(11).unwrap_or(&0.0),
827            building_fleets: *ledger.get(12).unwrap_or(&0.0),
828            building_fortresses: *ledger.get(13).unwrap_or(&0.0),
829            buildings: *ledger.get(14).unwrap_or(&0.0),
830            repaid_loans: *ledger.get(16).unwrap_or(&0.0),
831            gifts: *ledger.get(17).unwrap_or(&0.0),
832            advisors: *ledger.get(18).unwrap_or(&0.0),
833            events: *ledger.get(19).unwrap_or(&0.0),
834            peace: *ledger.get(20).unwrap_or(&0.0),
835            vassal_fee: *ledger.get(21).unwrap_or(&0.0),
836            tariffs: *ledger.get(22).unwrap_or(&0.0),
837            support_loyalists: *ledger.get(23).unwrap_or(&0.0),
838            condottieri: *ledger.get(26).unwrap_or(&0.0),
839            root_out_corruption: *ledger.get(27).unwrap_or(&0.0),
840            embrace_institution: *ledger.get(28).unwrap_or(&0.0),
841            knowledge_sharing: *ledger.get(30).unwrap_or(&0.0),
842            trade_company_investments: *ledger.get(31).unwrap_or(&0.0),
843            ports_blockaded: *ledger.get(33).unwrap_or(&0.0),
844            cities_looted: *ledger.get(34).unwrap_or(&0.0),
845            monuments: *ledger.get(35).unwrap_or(&0.0),
846            cot_upgrades: *ledger.get(36).unwrap_or(&0.0),
847            colony_changes: *ledger.get(37).unwrap_or(&0.0),
848            other: *ledger.get(3).unwrap_or(&0.0)
849                + *ledger.get(15).unwrap_or(&0.0)
850                + *ledger.get(24).unwrap_or(&0.0)
851                + *ledger.get(25).unwrap_or(&0.0)
852                + *ledger.get(29).unwrap_or(&0.0)
853                + *ledger.get(32).unwrap_or(&0.0)
854                + ledger.get(38..).iter().flat_map(|x| x.iter()).sum::<f32>(),
855        }
856    }
857
858    pub fn country_expense_breakdown(&self, country: &Country) -> CountryExpenseLedger {
859        self.expense_ledger_breakdown(&country.ledger.lastmonthexpensetable)
860    }
861
862    pub fn country_total_expense_breakdown(&self, country: &Country) -> CountryExpenseLedger {
863        self.expense_ledger_breakdown(&country.ledger.totalexpensetable)
864    }
865
866    fn mana_spent_indexed(&self, data: &[(i32, i32)]) -> CountryManaSpend {
867        let offset = if self.save().meta.savegame_version.second >= 31 {
868            1
869        } else {
870            0
871        };
872
873        let force_march = find_index(8, data) + find_index(45, data);
874        CountryManaSpend {
875            buy_idea: find_index(0, data),
876            advance_tech: find_index(1, data),
877            boost_stab: find_index(2, data),
878            buy_general: find_index(3, data),
879            buy_admiral: find_index(4, data),
880            buy_conq: find_index(5, data),
881            buy_explorer: find_index(6, data),
882            develop_prov: find_index(7, data),
883            force_march,
884            assault: find_index(9, data),
885            seize_colony: find_index(10, data),
886            burn_colony: find_index(11, data),
887            attack_natives: find_index(12, data),
888            scorch_earth: find_index(13, data),
889            demand_non_wargoal_prov: find_index(14, data),
890            reduce_inflation: find_index(15, data),
891            move_capital: find_index(16, data),
892            make_province_core: find_index(17, data),
893            replace_rival: find_index(18, data),
894            change_gov: find_index(19, data),
895            change_culture: find_index(20, data),
896            harsh_treatment: find_index(21, data),
897            reduce_we: find_index(22, data),
898            boost_faction: find_index(23, data),
899            raise_war_taxes: find_index(24, data),
900            buy_native_advancement: if offset != 0 { 0 } else { find_index(25, data) },
901            increse_tariffs: find_index(26 - offset, data),
902            promote_merc: find_index(27 - offset, data),
903            decrease_tariffs: find_index(28 - offset, data),
904            move_trade_port: find_index(29 - offset, data),
905            create_trade_post: find_index(30 - offset, data),
906            siege_sorties: find_index(31 - offset, data),
907            buy_religious_reform: find_index(32 - offset, data),
908            set_primary_culture: find_index(33 - offset, data),
909            add_accepted_culture: find_index(34 - offset, data),
910            remove_accepted_culture: find_index(35 - offset, data),
911            strengthen_government: find_index(36 - offset, data),
912            boost_militarization: find_index(37 - offset, data),
913            artillery_barrage: find_index(39 - offset, data),
914            establish_siberian_frontier: find_index(40 - offset, data),
915            government_interaction: find_index(41 - offset, data),
916            naval_barrage: find_index(43 - offset, data),
917            add_tribal_land: if offset != 1 {
918                0
919            } else {
920                find_index(44 - offset, data)
921            },
922            create_leader: find_index(46, data),
923            enforce_culture: find_index(47, data),
924            effect: find_index(48, data),
925            minority_expulsion: find_index(49, data),
926            other: find_index(38 - offset, data)
927                + find_index(42 - offset, data)
928                + find_index(44, data)
929                + data
930                    .iter()
931                    .filter(|(ind, _)| *ind > 49)
932                    .map(|(_, val)| val)
933                    .sum::<i32>(),
934        }
935    }
936
937    pub fn country_mana_breakdown(&self, country: &Country) -> CountryManaUsage {
938        CountryManaUsage {
939            adm: self.mana_spent_indexed(&country.adm_spent_indexed),
940            dip: self.mana_spent_indexed(&country.dip_spent_indexed),
941            mil: self.mana_spent_indexed(&country.mil_spent_indexed),
942        }
943    }
944
945    /// Return all unique buildings in the world that are built
946    pub fn built_buildings(&self) -> &HashSet<String> {
947        self.buildings.get_or_init(|| {
948            self.save
949                .game
950                .provinces
951                .values()
952                .flat_map(|x| x.buildings.keys())
953                .cloned()
954                .collect()
955        })
956    }
957
958    pub fn province_building_history<'a>(&'a self, province: &'a Province) -> Vec<BuildingEvent> {
959        let buildings = self.built_buildings();
960        let initial_buildings = province.history.other.iter().filter_map(|(key, _event)| {
961            if buildings.contains(key) {
962                Some(BuildingEvent {
963                    building: key.as_str(),
964                    date: self.save.game.start_date,
965                    action: BuildingConstruction::Constructed,
966                })
967            } else {
968                None
969            }
970        });
971
972        let over_time = province.history.events.iter().flat_map(|(date, events)| {
973            events.0.iter().filter_map(move |event| match event {
974                ProvinceEvent::KV((key, value)) => {
975                    let constructed = if let ProvinceEventValue::Bool(x) = value {
976                        if buildings.contains(key) {
977                            if *x {
978                                BuildingConstruction::Constructed
979                            } else {
980                                BuildingConstruction::Destroyed
981                            }
982                        } else {
983                            return None;
984                        }
985                    } else {
986                        return None;
987                    };
988
989                    Some(BuildingEvent {
990                        building: key.as_str(),
991                        date: *date,
992                        action: constructed,
993                    })
994                }
995                _ => None,
996            })
997        });
998
999        initial_buildings.chain(over_time).collect()
1000    }
1001}
1002
1003fn find_index(index: i32, data: &[(i32, i32)]) -> i32 {
1004    data.iter()
1005        .find(|&(ind, _)| *ind == index)
1006        .map(|(_, val)| *val)
1007        .unwrap_or(0)
1008}
1009
1010fn nation_events(save: &Eu4Save, province_owners: &ProvinceOwners) -> Vec<NationEvents> {
1011    struct CountryTagSwitchFrom {
1012        date: Eu4Date,
1013        from: CountryTag,
1014    }
1015
1016    #[derive(Debug, PartialEq, Clone, Serialize)]
1017    struct CountryTagSwitch {
1018        date: Eu4Date,
1019        from: CountryTag,
1020        to: CountryTag,
1021        stored: CountryTag,
1022    }
1023
1024    let mut nation_events = HashMap::with_capacity(save.game.countries.len());
1025    let mut all_switches = Vec::with_capacity(save.game.countries.len());
1026    let mut initial_to_stored = HashMap::with_capacity(save.game.countries.len());
1027    for (tag, country) in &save.game.countries {
1028        let tag = *tag;
1029        let mut country_tag_switches = Vec::new();
1030
1031        for (date, events) in &country.history.events {
1032            for event in &events.0 {
1033                if let CountryEvent::ChangedTagFrom(from) = *event {
1034                    country_tag_switches.push(CountryTagSwitchFrom { date: *date, from });
1035                }
1036            }
1037        }
1038
1039        let initial_tag = country_tag_switches
1040            .first()
1041            .map(|x| x.from)
1042            .or_else(|| country.previous_country_tags.first().cloned())
1043            .unwrap_or(tag);
1044
1045        let latest = country
1046            .previous_country_tags
1047            .get(country_tag_switches.len())
1048            .cloned()
1049            .unwrap_or(tag);
1050
1051        let tos = country_tag_switches
1052            .iter()
1053            .map(|x| x.from)
1054            .skip(1)
1055            .chain(std::iter::once(latest));
1056
1057        let switches = country_tag_switches
1058            .iter()
1059            .zip(tos)
1060            .map(|(from, to)| CountryTagSwitch {
1061                date: from.date,
1062                stored: tag,
1063                from: from.from,
1064                to,
1065            })
1066            .collect::<Vec<_>>();
1067
1068        all_switches.extend(switches.clone());
1069
1070        if !switches.is_empty() || tag != latest || latest != initial_tag {
1071            let sws = nation_events.entry(tag).or_insert_with(Vec::new);
1072            let a = switches.iter().map(|x| NationEvent {
1073                date: x.date,
1074                kind: NationEventKind::TagSwitch(x.to),
1075            });
1076            sws.extend(a);
1077        }
1078
1079        initial_to_stored.insert(initial_tag, tag);
1080    }
1081
1082    let mut counts: HashMap<CountryTag, i32> = HashMap::with_capacity(save.game.countries.len());
1083    let mut owners: Vec<Option<CountryTag>> = vec![None; save.game.provinces.len() + 1];
1084    let initial_owners = province_owners
1085        .initial
1086        .iter()
1087        .enumerate()
1088        .filter_map(|(i, x)| x.map(|y| (i, y)));
1089
1090    for (id, tag) in initial_owners {
1091        if let Some(&stored) = initial_to_stored.get(&tag) {
1092            owners[id] = Some(stored);
1093            *counts.entry(stored).or_insert(0) += 1;
1094        } else {
1095            debug_assert!(false)
1096        }
1097    }
1098
1099    let mut tag_dater: HashMap<_, Vec<_>> = HashMap::new();
1100    for x in all_switches {
1101        tag_dater.entry(x.from).or_insert_with(Vec::new).push(x);
1102    }
1103    for x in tag_dater.values_mut() {
1104        x.sort_by_key(|x| x.date);
1105    }
1106
1107    for change in &province_owners.changes {
1108        let store = tag_dater
1109            .get(&change.tag)
1110            .and_then(|x| x.iter().find(|x| x.date >= change.date))
1111            .map(|x| x.stored)
1112            .unwrap_or(change.tag);
1113
1114        let prov_id = usize::from(change.province.as_u16());
1115        if let Some(old) = owners[prov_id].replace(store) {
1116            if let Some(count) = counts.get_mut(&old) {
1117                // There is a mod where the count dips below 0. I haven't seen it
1118                // dip below zero for a vanilla save, but we don't want to underflow
1119                // on any input so we bottom it out at zero.
1120                *count = std::cmp::max(0, *count - 1);
1121                if *count == 0 {
1122                    nation_events
1123                        .entry(old)
1124                        .or_insert_with(Vec::new)
1125                        .push(NationEvent {
1126                            date: change.date,
1127                            kind: NationEventKind::Annexed,
1128                        })
1129                }
1130            } else {
1131                debug_assert!(false, "tag of {} is not counted for", old);
1132            }
1133        }
1134
1135        let new_count = counts.entry(store).or_insert(0);
1136        if *new_count == 0 && change.date > save.game.start_date {
1137            nation_events
1138                .entry(store)
1139                .or_insert_with(Vec::new)
1140                .push(NationEvent {
1141                    date: change.date,
1142                    kind: NationEventKind::Appeared,
1143                })
1144        }
1145        *new_count += 1;
1146    }
1147
1148    for events in nation_events.values_mut() {
1149        events.sort_by_key(|x| x.date);
1150    }
1151
1152    let mut result = Vec::with_capacity(save.game.countries.len());
1153    for (initial, stored) in initial_to_stored {
1154        let events = nation_events.remove(&stored).unwrap_or_default();
1155        let latest = events
1156            .iter()
1157            .filter_map(|x| match x.kind {
1158                NationEventKind::TagSwitch(t) => Some(t),
1159                _ => None,
1160            })
1161            .last()
1162            .unwrap_or(initial);
1163
1164        // If a stored tag never owned a province in the entire playthrough, exclude it
1165        if counts.contains_key(&stored) {
1166            result.push(NationEvents {
1167                initial,
1168                latest,
1169                stored,
1170                events,
1171            });
1172        }
1173    }
1174
1175    result
1176}
1177
1178fn war_participants(save: &Eu4Save, tag_resolver: &TagResolver) -> Vec<ResolvedWarParticipants> {
1179    let active = save.game.active_wars.iter().map(|x| (&x.name, &x.history));
1180    let previous = save
1181        .game
1182        .previous_wars
1183        .iter()
1184        .map(|x| (&x.name, &x.history));
1185    let wars = active.chain(previous);
1186
1187    let mut war_participants =
1188        Vec::with_capacity(save.game.active_wars.len() + save.game.previous_wars.len());
1189    for (name, war) in wars {
1190        let mut tags = Vec::new();
1191        for (date, events) in &war.events {
1192            for event in &events.0 {
1193                match event {
1194                    WarEvent::AddAttacker(x) | WarEvent::AddDefender(x) => {
1195                        let stored_tag = tag_resolver.resolve(*x, *date);
1196                        tags.push(ResolvedWarParticipant {
1197                            tag: *x,
1198                            stored: stored_tag.map(|x| x.stored).unwrap_or(*x),
1199                        });
1200                    }
1201                    _ => {}
1202                }
1203            }
1204        }
1205
1206        war_participants.push(ResolvedWarParticipants {
1207            war: name.clone(),
1208            participants: tags,
1209        })
1210    }
1211
1212    war_participants
1213}
1214
1215fn province_owners(save: &Eu4Save) -> ProvinceOwners {
1216    let mut initial = vec![None; save.game.provinces.len() + 1];
1217    let mut owners = vec![None; save.game.provinces.len() + 1];
1218    let mut changes = Vec::with_capacity(save.game.provinces.len() * 2);
1219    for (&id, province) in &save.game.provinces {
1220        initial[usize::from(id.as_u16())] = province.history.owner;
1221        owners[usize::from(id.as_u16())] = province.history.owner;
1222
1223        for (date, events) in &province.history.events {
1224            for event in &events.0 {
1225                if let ProvinceEvent::Owner(new_owner) = *event {
1226                    // Check to make sure the province really changed hands. Exclude
1227                    // change owner events if the owner didn't change hands.
1228                    // In the trycone save, Leinster is listed as the new owner of
1229                    // Laighin in 1444.11.12 even though they already owned it
1230                    let prov_id = usize::from(id.as_u16());
1231                    let old_owner = owners[prov_id].replace(new_owner);
1232                    if old_owner.map_or(true, |x| new_owner != x) {
1233                        changes.push(ProvinceOwnerChange {
1234                            date: *date,
1235                            province: id,
1236                            tag: new_owner,
1237                        });
1238                    }
1239                }
1240            }
1241        }
1242    }
1243
1244    // Keep the sort stable so that when we come across a province history like
1245    // below, we keep "BUL" after "NAP", which could signify NAP conquering a
1246    // province and then immediately releasing bulgaria.
1247    // ```
1248    // 1477.2.27={
1249    //   owner="NAP"
1250    // }
1251    // 1477.2.27={
1252    //   owner="BUL"
1253    // }
1254    // ```
1255    changes.sort_by_key(|x| (x.date, x.province));
1256
1257    ProvinceOwners { initial, changes }
1258}
1259
1260fn players(save: &Eu4Save) -> Vec<Player> {
1261    let mut players = Vec::new();
1262    for entry in save.game.players_countries.chunks_exact(2) {
1263        let player_name = &entry[0];
1264        if player_name == "Player" && !save.meta.multiplayer {
1265            continue;
1266        }
1267
1268        let country_tag = match entry[1].parse::<CountryTag>() {
1269            Ok(x) => x,
1270            _ => continue,
1271        };
1272
1273        players.push(Player {
1274            name: player_name.clone(),
1275            tag: country_tag,
1276        })
1277    }
1278
1279    players
1280}
1281
1282fn province_religions(save: &Eu4Save, lookup: &ReligionLookup) -> ProvinceReligions {
1283    let mut initial = vec![None; save.game.provinces.len() + 1];
1284    let mut religions = vec![None; save.game.provinces.len() + 1];
1285    let mut changes = Vec::with_capacity(save.game.provinces.len());
1286    for (&id, province) in &save.game.provinces {
1287        let prov_id = usize::from(id.as_u16());
1288        let init = province
1289            .history
1290            .religion
1291            .as_ref()
1292            .and_then(|x| lookup.index(x));
1293
1294        initial[prov_id] = init;
1295        religions[prov_id] = init;
1296
1297        for (date, events) in &province.history.events {
1298            for event in &events.0 {
1299                if let ProvinceEvent::Religion(new_religion) = event {
1300                    if let Some(new_religion_index) = lookup.index(new_religion) {
1301                        let old_religion = religions[prov_id].replace(new_religion_index);
1302                        if old_religion.map_or(true, |x| new_religion_index != x) {
1303                            changes.push(ProvinceReligionChange {
1304                                date: *date,
1305                                province: id,
1306                                religion: new_religion_index,
1307                            });
1308                        }
1309                    }
1310                }
1311            }
1312        }
1313    }
1314
1315    changes.sort_by_key(|x| (x.date, x.province));
1316
1317    ProvinceReligions { initial, changes }
1318}
1319
1320impl ReligionLookup {
1321    pub fn index(&self, religion: &String) -> Option<ReligionIndex> {
1322        self.religions
1323            .binary_search(religion)
1324            .ok()
1325            .and_then(|x| u16::try_from(x).ok())
1326            .map(|x| x + 1)
1327            .and_then(NonZeroU16::new)
1328            .map(ReligionIndex)
1329    }
1330
1331    pub fn resolve(&self, index: ReligionIndex) -> &str {
1332        self.religions[usize::from(index.0.get() - 1)].as_str()
1333    }
1334}