tiger_lib/ck3/data/
gameconcepts.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::everything::Everything;
5use crate::fileset::{FileEntry, FileHandler};
6use crate::helpers::{TigerHashMap, dup_error};
7use crate::item::Item;
8use crate::parse::ParserMemory;
9use crate::pdxfile::PdxFile;
10use crate::report::{ErrorKey, warn};
11use crate::token::Token;
12use crate::validator::Validator;
13
14#[derive(Clone, Debug, Default)]
15pub struct GameConcepts {
16    concepts: TigerHashMap<&'static str, Concept>,
17    aliases: TigerHashMap<&'static str, &'static str>,
18}
19
20impl GameConcepts {
21    pub fn load_item(&mut self, key: Token, block: Block) {
22        if let Some(other) = self.concepts.get(key.as_str()) {
23            if other.key.loc.kind >= key.loc.kind {
24                dup_error(&key, &other.key, "game concept");
25            }
26        }
27        self.concepts.insert(key.as_str(), Concept::new(key, block));
28    }
29
30    pub fn exists(&self, key: &str) -> bool {
31        self.concepts.contains_key(key) || self.aliases.contains_key(key)
32    }
33
34    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
35        self.concepts.values().map(|item| &item.key)
36    }
37
38    pub fn validate(&self, data: &Everything) {
39        for item in self.concepts.values() {
40            item.validate(data);
41        }
42    }
43}
44
45impl FileHandler<Block> for GameConcepts {
46    fn subpath(&self) -> PathBuf {
47        PathBuf::from("common/game_concepts")
48    }
49
50    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
51        if !entry.filename().to_string_lossy().ends_with(".txt") {
52            return None;
53        }
54
55        PdxFile::read(entry, parser)
56    }
57
58    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
59        for (key, block) in block.drain_definitions_warn() {
60            self.load_item(key, block);
61        }
62    }
63
64    fn finalize(&mut self) {
65        for (key, concept) in &self.concepts {
66            for token in concept.block.get_multi_field_list("alias") {
67                self.aliases.insert(token.as_str(), key);
68            }
69        }
70    }
71}
72
73#[derive(Clone, Debug)]
74pub struct Concept {
75    key: Token,
76    block: Block,
77}
78
79impl Concept {
80    pub fn new(key: Token, block: Block) -> Self {
81        Self { key, block }
82    }
83
84    pub fn validate(&self, data: &Everything) {
85        fn validate_framesize(block: &Block, data: &Everything) {
86            let mut vd = Validator::new(block, data);
87            vd.req_tokens_integers_exactly(2);
88        }
89
90        let loca = format!("game_concept_{}", self.key);
91        data.verify_exists_implied(Item::Localization, &loca, &self.key);
92        let loca = format!("game_concept_{}_desc", self.key);
93        data.verify_exists_implied(Item::Localization, &loca, &self.key);
94
95        let mut vd = Validator::new(&self.block, data);
96        vd.multi_field_validated_list("alias", |alias, data| {
97            let loca = format!("game_concept_{alias}");
98            data.verify_exists_implied(Item::Localization, &loca, alias);
99        });
100
101        vd.field_item("parent", Item::GameConcept);
102        if let Some(token) = vd.field_value("texture") {
103            // TODO: check the file's resolution and check it against framesize and frame keys
104            if !token.is("piety") {
105                data.fileset.verify_exists(token);
106            }
107        }
108        if let Some(texture) = self.block.get_field_value("texture") {
109            vd.field_validated_block("framesize", validate_framesize);
110            vd.field_integer("frame");
111            if self.block.has_key("framesize") != self.block.has_key("frame") {
112                let msg = "`framesize` and `frame` should be specified together";
113                warn(ErrorKey::Validation).msg(msg).loc(&self.key).push();
114            }
115            if let Some(frame) = self.block.get_field_integer("frame") {
116                if let Some(b) = self.block.get_field_block("framesize") {
117                    let tokens: Vec<&Token> = b.iter_values().collect();
118                    if tokens.len() == 2 {
119                        if let Ok(width) = tokens[0].as_str().parse::<u32>() {
120                            if let Ok(height) = tokens[1].as_str().parse::<u32>() {
121                                #[allow(clippy::cast_possible_truncation)] // TODO
122                                #[allow(clippy::cast_sign_loss)]
123                                data.dds.validate_frame(texture, width, height, frame as u32);
124                            }
125                        }
126                    }
127                }
128            }
129        } else {
130            vd.advice_field("framesize", "not needed without texture");
131            vd.advice_field("frame", "not needed without texture");
132        }
133        vd.field_item("requires_dlc_flag", Item::DlcFeature);
134        vd.field_bool("shown_in_encyclopedia");
135    }
136}