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 pub is_human: bool,
163
164 pub player_names: Vec<String>,
166}
167
168pub struct ProvinceOwners {
169 pub initial: Vec<Option<CountryTag>>,
171
172 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 pub initial: CountryTag,
213
214 pub latest: CountryTag,
218
219 pub stored: CountryTag,
223
224 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 pub tag: CountryTag,
264
265 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 pub fn players(&self) -> Vec<Player> {
395 players(&self.save)
396 }
397
398 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 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 pub fn nation_events(&self, province_owners: &ProvinceOwners) -> Vec<NationEvents> {
467 nation_events(&self.save, province_owners)
468 }
469
470 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 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 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 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 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 *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 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 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 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}