ck3_history_extractor/structures/
dynasty.rs

1use std::path::Path;
2
3use serde::Serialize;
4
5use super::{
6    super::{
7        display::{Grapher, ProceduralPath, Renderable},
8        game_data::{GameData, Localizable, LocalizationError, Localize},
9        jinja_env::DYN_TEMPLATE_NAME,
10        parser::{GameObjectMap, GameObjectMapping, GameState, ParsingError, SaveFileValue},
11        types::{GameString, HashMap, Wrapper},
12    },
13    Character, EntityRef, FromGameObject, GameObjectDerived, GameObjectEntity, GameRef, House,
14};
15
16#[derive(Serialize)]
17pub struct Dynasty {
18    name: Option<GameString>,
19    prestige_tot: f32,
20    prestige: f32,
21    perks: HashMap<GameString, u8>,
22    leader: Option<GameRef<Character>>,
23    houses: Vec<GameRef<House>>,
24}
25
26impl FromGameObject for Dynasty {
27    fn from_game_object(
28        base: &GameObjectMap,
29        game_state: &mut GameState,
30    ) -> Result<Self, ParsingError> {
31        let mut val = Self {
32            // name can also be stored as key, where it's either a string, or an ID pointing to an object declared in game files 00_dynasties.txt
33            name: base
34                .get("name")
35                .or(base.get("dynasty_name"))
36                .or(base.get("localized_name"))
37                .and_then(|n| n.as_string().ok()),
38            prestige_tot: 0.0,
39            prestige: 0.0,
40            perks: HashMap::new(),
41            leader: None,
42            houses: Vec::new(),
43        };
44        if let Some(leader) = base.get("dynasty_head") {
45            val.leader = Some(game_state.get_character(&leader.as_id()?).clone());
46        }
47        if let Some(perks_obj) = base.get("perk") {
48            for p in perks_obj.as_object()?.as_array()? {
49                let perk = p.as_string()?;
50                //get the split perk by the second underscore
51                let mut i: u8 = 0;
52                let mut key: Option<&str> = None;
53                let mut level: u8 = 0;
54                for el in perk.rsplitn(2, '_') {
55                    if i == 0 {
56                        level = el.parse::<u8>().unwrap();
57                    } else {
58                        key = Some(el);
59                    }
60                    i += 1;
61                }
62                if let Some(key) = key {
63                    let key = GameString::from(key);
64                    if *val.perks.entry(key.clone()).or_default() < level {
65                        val.perks.insert(key, level);
66                    }
67                }
68            }
69        }
70        if let Some(currency) = base.get("prestige") {
71            let o = currency.as_object()?.as_map()?;
72            if let Some(acc) = o.get("accumulated") {
73                if let SaveFileValue::Object(o) = acc {
74                    val.prestige_tot = o.as_map()?.get_real("value")? as f32;
75                } else {
76                    val.prestige_tot = acc.as_real()? as f32;
77                }
78            }
79            if let Some(c) = o.get("currency") {
80                if let SaveFileValue::Object(o) = c {
81                    val.prestige = o.as_map()?.get_real("value")? as f32;
82                } else {
83                    val.prestige = c.as_real()? as f32;
84                }
85            }
86        }
87        return Ok(val);
88    }
89
90    fn finalize(&mut self, _reference: &GameRef<Self>) {
91        self.houses.sort_by(|a, b| {
92            a.get_internal()
93                .inner()
94                .unwrap()
95                .get_found_date()
96                .cmp(&b.get_internal().inner().unwrap().get_found_date())
97        });
98        // instead of resolving game files we can just get the name from the first house
99        if self.name.is_none() {
100            self.name = Some(
101                self.houses
102                    .first()
103                    .unwrap()
104                    .clone()
105                    .get_internal()
106                    .inner()
107                    .unwrap()
108                    .get_name()
109                    .clone(),
110            );
111        }
112    }
113}
114
115impl Dynasty {
116    pub fn register_house(&mut self, house: GameRef<House>) {
117        self.houses.push(house);
118    }
119
120    pub fn get_founder(&self) -> GameRef<Character> {
121        self.houses
122            .first()
123            .unwrap()
124            .clone()
125            .get_internal()
126            .inner()
127            .unwrap()
128            .get_founder()
129    }
130
131    pub fn get_leader(&self) -> Option<GameRef<Character>> {
132        self.leader.clone()
133    }
134}
135
136impl GameObjectDerived for Dynasty {
137    fn get_name(&self) -> GameString {
138        self.name.as_ref().unwrap().clone()
139    }
140
141    fn get_references<E: From<EntityRef>, C: Extend<E>>(&self, collection: &mut C) {
142        if let Some(leader) = &self.leader {
143            collection.extend([E::from(leader.clone().into())]);
144        }
145        for house in self.houses.iter() {
146            collection.extend([E::from(house.clone().into())]);
147        }
148    }
149}
150
151impl ProceduralPath for Dynasty {
152    fn get_subdir() -> &'static str {
153        "dynasties"
154    }
155}
156
157impl Renderable for GameObjectEntity<Dynasty> {
158    fn get_template() -> &'static str {
159        DYN_TEMPLATE_NAME
160    }
161
162    fn render(&self, path: &Path, _: &GameState, grapher: Option<&Grapher>, _: &GameData) {
163        if let Some(grapher) = grapher {
164            if let Some(dynasty) = self.inner() {
165                let mut buf = path.join(Dynasty::get_subdir());
166                buf.push(self.id.to_string() + ".svg");
167                grapher.create_dynasty_graph(dynasty, &buf);
168            }
169        }
170    }
171}
172
173impl Localizable for Dynasty {
174    fn localize(&mut self, localization: &GameData) -> Result<(), LocalizationError> {
175        if let Some(name) = &self.name {
176            self.name = Some(localization.localize(name)?);
177        }
178        let drained_perks: Vec<_> = self.perks.drain().collect();
179        for (perk, level) in drained_perks {
180            self.perks.insert(
181                localization.localize(perk.to_string() + "_track_name")?,
182                level,
183            );
184        }
185        Ok(())
186    }
187}