ck3_history_extractor/structures/
player.rs

1use image::{
2    buffer::ConvertBuffer,
3    codecs::gif::{GifEncoder, Repeat},
4    Delay, Frame, Rgb,
5};
6
7use jomini::common::PdsDate;
8use serde::Serialize;
9
10use super::{
11    super::{
12        display::{GetPath, Grapher, Renderable},
13        game_data::{GameData, Localizable, LocalizationError, MapGenerator, MapImage},
14        jinja_env::H_TEMPLATE_NAME,
15        parser::{GameObjectMap, GameObjectMapping, GameState, ParsingError},
16        types::{GameString, Wrapper},
17    },
18    Character, EntityRef, FromGameObject, GameObjectDerived, GameRef, LineageNode,
19};
20
21use std::{
22    collections::HashSet,
23    fs::File,
24    ops::Deref,
25    path::{Path, PathBuf},
26};
27
28const TARGET_COLOR: Rgb<u8> = Rgb([70, 255, 70]);
29const SECONDARY_COLOR: Rgb<u8> = Rgb([255, 255, 70]);
30const BASE_COLOR: Rgb<u8> = Rgb([255, 255, 255]);
31
32/// A struct representing a player in the game
33#[derive(Serialize)]
34pub struct Player {
35    name: GameString,
36    character: Option<GameRef<Character>>,
37    lineage: Vec<LineageNode>,
38}
39
40impl FromGameObject for Player {
41    fn from_game_object(
42        base: &GameObjectMap,
43        game_state: &mut GameState,
44    ) -> Result<Self, ParsingError> {
45        let mut player = Self {
46            name: base.get_string("name")?,
47            character: Some(
48                game_state
49                    .get_character(&base.get_game_id("character")?)
50                    .clone(),
51            ),
52            lineage: Vec::new(),
53        };
54        for leg in base.get_object("legacy")?.as_array()? {
55            player.lineage.push(LineageNode::from_game_object(
56                leg.as_object()?.as_map()?,
57                game_state,
58            )?)
59        }
60        Ok(player)
61    }
62}
63
64impl GameObjectDerived for Player {
65    fn get_name(&self) -> GameString {
66        self.name.clone()
67    }
68
69    fn get_references<E: From<EntityRef>, C: Extend<E>>(&self, collection: &mut C) {
70        collection.extend([E::from(self.character.as_ref().unwrap().clone().into())]);
71        for node in self.lineage.iter() {
72            collection.extend([E::from(node.get_character().clone().into())]);
73        }
74    }
75}
76
77impl GetPath for Player {
78    fn get_path(&self, path: &Path) -> PathBuf {
79        path.join("index.html")
80    }
81}
82
83impl Renderable for Player {
84    fn get_template() -> &'static str {
85        H_TEMPLATE_NAME
86    }
87
88    fn render(
89        &self,
90        path: &Path,
91        game_state: &GameState,
92        grapher: Option<&Grapher>,
93        data: &GameData,
94    ) {
95        if let Some(map) = data.get_map() {
96            //timelapse rendering
97            let mut file = File::create(path.join("timelapse.gif")).unwrap();
98            let mut gif_encoder = GifEncoder::new(&mut file);
99            for char in self.lineage.iter() {
100                /* Note on timelapse:
101                Paradox doesn't save any data regarding top level liege changes.
102                Not even basic data that would allow us to reconstruct the map through implication.
103                We would need something as basic as adding liege changes to history, or even just storing dead character's vassal relations
104                I once had an idea that it could be possible to still have a timelapse by looking at dead vassals of the children of chars in lineage
105                But that idea got stuck at the recursive step of that algorithm, and even so the result would have NO accuracy
106                 */
107                if let Some(char) = char.get_character().get_internal().inner() {
108                    //we get the provinces held by the character and the vassals who died under their reign.
109                    //This is the closes approximation we can get of changes in the map that are 100% accurate
110                    let death_date = char.get_death_date();
111                    let date = if let Some(death_date) = &death_date {
112                        death_date.iso_8601()
113                    } else {
114                        game_state.get_current_date().unwrap().iso_8601()
115                    };
116                    let mut barony_map =
117                        map.create_map_flat(char.get_barony_keys(true), TARGET_COLOR);
118                    barony_map.draw_text(date.to_string());
119                    let fbytes = barony_map.convert();
120                    //these variables cuz fbytes is moved
121                    let width = fbytes.width();
122                    let height = fbytes.height();
123                    let frame = Frame::from_parts(
124                        fbytes,
125                        width,
126                        height,
127                        Delay::from_numer_denom_ms(3000, 1),
128                    );
129                    gif_encoder.encode_frame(frame).unwrap();
130                }
131            }
132            gif_encoder.set_repeat(Repeat::Infinite).unwrap();
133            let mut direct_titles = HashSet::new();
134            let mut descendant_title = HashSet::new();
135            let first = self.lineage.first().unwrap().get_character();
136            if let Some(first) = first.get_internal().inner() {
137                let dynasty = first.get_house();
138                let dynasty = dynasty.as_ref().unwrap().get_internal();
139                for desc in dynasty
140                    .inner()
141                    .unwrap()
142                    .get_founder()
143                    .get_internal()
144                    .inner()
145                    .unwrap()
146                    .get_descendants()
147                {
148                    if let Some(desc) = desc.get_internal().inner() {
149                        if desc.get_death_date().is_some() {
150                            continue;
151                        }
152                        let target = if desc.get_house().map_or(false, |d| {
153                            d.get_internal()
154                                .inner()
155                                .unwrap()
156                                .get_dynasty()
157                                .get_internal()
158                                .deref()
159                                == dynasty
160                                    .inner()
161                                    .unwrap()
162                                    .get_dynasty()
163                                    .get_internal()
164                                    .deref()
165                        }) {
166                            &mut direct_titles
167                        } else {
168                            &mut descendant_title
169                        };
170                        for title in desc.get_barony_keys(false) {
171                            target.insert(title.clone());
172                        }
173                    }
174                }
175            }
176            let mut dynasty_map = map.create_map::<_, _, Vec<GameString>>(
177                |key: &str| {
178                    if direct_titles.contains(key) {
179                        return TARGET_COLOR;
180                    } else if descendant_title.contains(key) {
181                        return SECONDARY_COLOR;
182                    } else {
183                        return BASE_COLOR;
184                    }
185                },
186                None,
187            );
188            dynasty_map.draw_legend([
189                ("Dynastic titles".to_string(), TARGET_COLOR),
190                ("Descendant titles".to_string(), SECONDARY_COLOR),
191            ]);
192            dynasty_map.save_in_thread(path.join("dynastyMap.png"));
193        }
194        if let Some(grapher) = grapher {
195            let last = self.lineage.last().unwrap().get_character();
196            grapher.create_tree_graph(last, true, &path.join("line.svg"));
197        }
198    }
199}
200
201impl Localizable for Player {
202    fn localize(&mut self, localization: &GameData) -> Result<(), LocalizationError> {
203        for node in self.lineage.iter_mut() {
204            node.localize(localization)?;
205        }
206        Ok(())
207    }
208}