1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::ck3::modif::ModifKinds;
5use crate::ck3::validate::validate_traits;
6use crate::context::ScopeContext;
7use crate::desc::validate_desc;
8use crate::everything::Everything;
9use crate::fileset::{FileEntry, FileHandler};
10use crate::helpers::{TigerHashMap, TigerHashSet, dup_error};
11use crate::item::Item;
12use crate::modif::validate_modifs;
13use crate::parse::ParserMemory;
14use crate::pdxfile::PdxFile;
15use crate::scopes::Scopes;
16use crate::token::Token;
17use crate::tooltipped::Tooltipped;
18use crate::validator::Validator;
19use crate::variables::Variables;
20
21#[derive(Clone, Debug, Default)]
22#[allow(clippy::struct_field_names)]
23pub struct Doctrines {
24 categories: TigerHashMap<&'static str, DoctrineCategory>,
25 doctrines: TigerHashMap<&'static str, Doctrine>,
26 parameters: TigerHashSet<Token>, boolean_parameters: TigerHashSet<Token>, }
29
30impl Doctrines {
31 fn load_item(&mut self, key: Token, block: Block) {
32 if let Some(other) = self.categories.get(key.as_str()) {
33 if other.key.loc.kind >= key.loc.kind {
34 dup_error(&key, &other.key, "doctrine category");
35 }
36 }
37 self.categories.insert(key.as_str(), DoctrineCategory::new(key, block));
38 }
39
40 pub fn scan_variables(&self, registry: &mut Variables) {
41 for item in self.categories.values() {
42 registry.scan(&item.block);
43 }
44 for item in self.doctrines.values() {
45 registry.scan(&item.block);
46 }
47 }
48
49 pub fn validate(&self, data: &Everything) {
50 for category in self.categories.values() {
51 category.validate(data);
52 }
53 for doctrine in self.doctrines.values() {
54 doctrine.validate(data);
55 }
56 }
57
58 pub fn exists(&self, key: &str) -> bool {
59 self.doctrines.contains_key(key)
60 }
61
62 pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
63 self.doctrines.values().map(|item| &item.key)
64 }
65
66 pub fn category_exists(&self, key: &str) -> bool {
67 self.categories.contains_key(key)
68 }
69
70 pub fn category(&self, key: &str) -> Option<&Token> {
71 self.doctrines.get(key).map(|d| &d.category)
72 }
73
74 pub fn number_of_picks(&self, category: &str) -> Option<&Token> {
75 self.categories.get(category).and_then(|c| c.picks.as_ref())
76 }
77
78 pub fn iter_category_keys(&self) -> impl Iterator<Item = &Token> {
79 self.categories.values().map(|item| &item.key)
80 }
81
82 pub fn parameter_exists(&self, key: &str) -> bool {
83 self.parameters.contains(key)
84 }
85
86 pub fn boolean_parameter_exists(&self, key: &str) -> bool {
87 self.boolean_parameters.contains(key)
88 }
89
90 pub fn iter_parameter_keys(&self) -> impl Iterator<Item = &Token> {
91 self.parameters.iter()
92 }
93
94 pub fn iter_boolean_parameter_keys(&self) -> impl Iterator<Item = &Token> {
95 self.boolean_parameters.iter()
96 }
97
98 pub fn unreformed(&self, key: &str) -> bool {
99 self.doctrines.get(key).is_some_and(Doctrine::unreformed)
100 }
101}
102
103impl FileHandler<Block> for Doctrines {
104 fn subpath(&self) -> PathBuf {
105 PathBuf::from("common/religion/doctrines")
106 }
107
108 fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
109 if !entry.filename().to_string_lossy().ends_with(".txt") {
110 return None;
111 }
112
113 PdxFile::read(entry, parser)
114 }
115
116 fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
117 for (key, block) in block.drain_definitions_warn() {
118 self.load_item(key, block);
119 }
120 }
121
122 fn finalize(&mut self) {
123 for category in self.categories.values() {
124 for (doctrine, block) in category.block.iter_definitions() {
125 if doctrine.is("is_available_on_create") || doctrine.is("name") {
127 continue;
128 }
129
130 if let Some(other) = self.doctrines.get(doctrine.as_str()) {
131 if other.key.loc.kind >= doctrine.loc.kind {
132 dup_error(doctrine, &other.key, "doctrine");
133 }
134 }
135
136 if let Some(b) = block.get_field_block("parameters") {
137 for (k, v) in b.iter_assignments() {
138 self.parameters.insert(k.clone());
139 if v.is("yes") || v.is("no") {
140 self.boolean_parameters.insert(k.clone());
141 }
142 }
143 }
144 self.doctrines.insert(
145 doctrine.as_str(),
146 Doctrine::new(doctrine.clone(), block.clone(), category.key.clone()),
147 );
148 }
149 }
150 }
151}
152
153#[derive(Clone, Debug)]
154pub struct DoctrineCategory {
155 key: Token,
156 block: Block,
157 picks: Option<Token>,
158}
159
160impl DoctrineCategory {
161 pub fn new(key: Token, block: Block) -> Self {
162 let picks = block.get_field_value("number_of_picks").cloned();
163 Self { key, block, picks }
164 }
165
166 pub fn needs_icon(&self, data: &Everything) -> bool {
167 if let Some(group) = self.block.get_field_value("group") {
168 if group.is("special") || group.is("not_creatable") {
169 return false;
170 }
171 }
172
173 if let Some(icon_path) =
174 data.get_defined_string_warn(&self.key, "NGameIcons|FAITH_DOCTRINE_GROUP_ICON_PATH")
175 {
176 let path = format!("{icon_path}/{}.dds", &self.key);
177 data.mark_used(Item::File, &path);
178 return !data.fileset.exists(&path);
179 }
180 true
181 }
182
183 pub fn validate(&self, data: &Everything) {
184 let mut vd = Validator::new(&self.block, data);
185 let mut sc = ScopeContext::new(Scopes::Faith, &self.key);
186
187 if !vd.field_validated_sc("name", &mut sc, validate_desc) {
188 let loca = format!("{}_name", self.key);
189 data.verify_exists_implied(Item::Localization, &loca, &self.key);
190 }
191
192 vd.field_value("group");
194
195 vd.field_integer("number_of_picks");
196 vd.field_trigger("is_available_on_create", Tooltipped::No, &mut sc);
197
198 vd.unknown_block_fields(|_, _| ());
201 }
202}
203
204#[derive(Clone, Debug)]
205pub struct Doctrine {
206 key: Token,
207 block: Block,
208 category: Token,
209}
210
211impl Doctrine {
212 pub fn new(key: Token, block: Block, category: Token) -> Self {
213 Self { key, block, category }
214 }
215
216 pub fn validate(&self, data: &Everything) {
217 let mut vd = Validator::new(&self.block, data);
218 let mut sc = ScopeContext::new(Scopes::Faith, &self.key);
219 sc.define_list("selected_doctrines", Scopes::Doctrine, &self.key);
220
221 if let Some(icon) = vd.field_value("icon") {
222 data.verify_icon("NGameIcons|FAITH_DOCTRINE_ICON_PATH", icon, ".dds");
223 } else if data.doctrines.categories[self.category.as_str()].needs_icon(data) {
224 data.verify_icon("NGameIcons|FAITH_DOCTRINE_ICON_PATH", &self.key, ".dds");
225 }
226
227 if !vd.field_validated_sc("name", &mut sc, validate_desc) {
228 let loca = format!("{}_name", self.key);
229 data.verify_exists_implied(Item::Localization, &loca, &self.key);
230 }
231
232 if !vd.field_validated_sc("desc", &mut sc, validate_desc) {
233 let loca = format!("{}_desc", self.key);
234 data.verify_exists_implied(Item::Localization, &loca, &self.key);
235 }
236
237 vd.field_bool("visible");
238 vd.field_validated_block("parameters", validate_parameters);
239 vd.field_script_value("piety_cost", &mut sc);
240 vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
241 vd.field_trigger("can_pick", Tooltipped::Yes, &mut sc);
242
243 vd.field_validated_block("character_modifier", |block, data| {
244 let mut vd = Validator::new(block, data);
245 vd.field_item("name", Item::Localization);
246 validate_modifs(block, data, ModifKinds::Character, vd);
247 });
248
249 vd.field_validated_block("clergy_modifier", |block, data| {
251 let vd = Validator::new(block, data);
252 validate_modifs(block, data, ModifKinds::Character, vd);
253 });
254
255 vd.field_validated_block("doctrine_character_modifier", |block, data| {
257 let mut vd = Validator::new(block, data);
258 vd.field_item("doctrine", Item::Doctrine);
259 validate_modifs(block, data, ModifKinds::Character, vd);
260 });
261
262 vd.field_validated_block("traits", validate_traits);
263 }
264
265 fn unreformed(&self) -> bool {
266 if let Some(block) = self.block.get_field_block("parameters") {
267 return block.field_value_is("unreformed", "yes");
268 }
269 false
270 }
271}
272
273fn validate_parameters(block: &Block, data: &Everything) {
274 let mut vd = Validator::new(block, data);
275
276 vd.unknown_value_fields(|_, value| {
277 if value.is("yes") || value.is("no") {
278 return;
279 }
280 value.expect_number();
281 });
282}