tiger_lib/data/
defines.rs1use std::path::PathBuf;
2
3use crate::block::{BV, Block};
4use crate::everything::Everything;
5use crate::fileset::{FileEntry, FileHandler};
6use crate::game::Game;
7use crate::helpers::{TigerHashMap, dup_error};
8#[cfg(feature = "ck3")]
9use crate::item::Item;
10use crate::parse::ParserMemory;
11use crate::pdxfile::PdxFile;
12#[cfg(feature = "ck3")]
13use crate::report::Severity;
14use crate::report::{ErrorKey, err};
15use crate::token::Token;
16
17#[derive(Clone, Debug, Default)]
18pub struct Defines {
19 defines: TigerHashMap<String, Define>,
20}
21
22impl Defines {
23 pub fn load_item(&mut self, group: Token, name: Token, bv: &BV) {
24 let key = format!("{}|{}", &group, &name);
25 if let Some(other) = self.defines.get(&key)
26 && other.name.loc.kind >= name.loc.kind
27 && !bv.equivalent(&other.bv)
28 {
29 dup_error(&name, &other.name, "define");
30 }
31 self.defines.insert(key, Define::new(group, name, bv.clone()));
32 }
33
34 pub fn exists(&self, key: &str) -> bool {
35 self.defines.contains_key(key)
36 }
37
38 pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
40 self.defines.values().map(|item| &item.name)
41 }
42
43 pub fn validate(&self, data: &Everything) {
44 for item in self.defines.values() {
45 item.validate(data);
46 }
47 }
48
49 #[cfg(feature = "jomini")]
50 pub fn get_bv(&self, key: &str) -> Option<&BV> {
51 self.defines.get(key).map(|d| &d.bv)
52 }
53}
54
55impl FileHandler<Block> for Defines {
56 fn subpath(&self) -> PathBuf {
57 PathBuf::from("common/defines")
58 }
59
60 fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
61 if !entry.filename().to_string_lossy().ends_with(".txt") {
62 return None;
63 }
64
65 PdxFile::read(entry, parser)
66 }
67
68 fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
69 for (group, block) in block.drain_definitions_warn() {
71 for (name, bv) in block.iter_assignments_and_definitions_warn() {
72 self.load_item(group.clone(), name.clone(), bv);
73 }
74 }
75 }
76}
77
78#[derive(Clone, Debug)]
79pub struct Define {
80 #[allow(dead_code)] group: Token,
82 name: Token,
83 bv: BV,
84}
85
86impl Define {
87 pub fn new(group: Token, name: Token, bv: BV) -> Self {
88 Self { group, name, bv }
89 }
90
91 #[allow(clippy::unused_self)]
92 #[allow(unused_variables)] pub fn validate(&self, data: &Everything) {
94 let defines_map = match Game::game() {
95 #[cfg(feature = "ck3")]
96 Game::Ck3 => &crate::ck3::tables::defines::DEFINES_MAP,
97 #[cfg(feature = "vic3")]
98 Game::Vic3 => &crate::vic3::tables::defines::DEFINES_MAP,
99 #[cfg(feature = "imperator")]
100 Game::Imperator => &crate::imperator::tables::defines::DEFINES_MAP,
101 #[cfg(feature = "eu5")]
102 Game::Eu5 => &crate::eu5::tables::defines::DEFINES_MAP,
103 #[cfg(feature = "hoi4")]
104 Game::Hoi4 => &crate::hoi4::tables::defines::DEFINES_MAP,
105 };
106
107 let key = format!("{}|{}", &self.group, &self.name);
109 if let Some(dt) = defines_map.get(&*key) {
110 dt.validate(&self.bv, data);
111 } else {
112 let msg = format!("unknown define {key}");
113 err(ErrorKey::UnknownField).msg(msg).loc(&self.name).push();
114 }
115
116 if std::env::var("TIGER_DUMP_DEFINES").is_ok() {
117 let define_type = match &self.bv {
118 BV::Value(token) => {
119 if token.is_number() {
120 "DefineType::Number"
121 } else if token.is("yes") || token.is("no") {
122 "DefineType::Boolean"
123 } else {
124 "DefineType::String"
125 }
126 }
127 BV::Block(block) => {
128 if block.num_items() == 0 {
129 "DefineType::UnknownList"
130 } else {
131 let first = block.iter_items().next().unwrap();
132 if first.get_value().is_some_and(Token::is_number) {
133 if block.num_items() == 4 {
134 "DefineType::Color"
135 } else {
136 "DefineType::NumberList"
137 }
138 } else if first.get_value().is_some_and(|t| !t.is_number()) {
139 "DefineType::StringList"
140 } else {
141 "DefineType::UnknownList"
142 }
143 }
144 }
145 };
146 if let Some(define_type) = defines_map.get(&*key) {
147 eprintln!(" (\"{}|{}\", DefineType::{define_type:?}),", &self.group, &self.name);
148 } else {
149 eprintln!(" (\"{}|{}\", {define_type}),", &self.group, &self.name);
150 }
151 }
152
153 #[cfg(feature = "ck3")]
154 if self.group.is("NGameIcons")
155 && self.name.is("PIETY_GROUPS")
156 && let Some(icon_path) =
157 data.get_defined_string_warn(&self.name, "NGameIcons|PIETY_LEVEL_PATH")
158 && let Some(groups) = self.bv.expect_block()
159 {
160 for icon_group in groups.iter_values_warn() {
161 for nr in &["00", "01", "02", "03", "04", "05"] {
162 let pathname = format!("{icon_path}/icon_piety_{icon_group}_{nr}.dds");
163 data.verify_exists_implied_max_sev(
164 Item::File,
165 &pathname,
166 icon_group,
167 Severity::Warning,
168 );
169 }
170 }
171 }
172 }
173}