tiger_lib/data/
events.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3use std::sync::Mutex;
4
5use rayon::prelude::*;
6
7use crate::block::{Block, BlockItem, Field};
8use crate::context::{Reason, ScopeContext, Signature};
9use crate::data::scripted_effects::Effect;
10use crate::data::scripted_triggers::Trigger;
11use crate::everything::Everything;
12use crate::fileset::{FileEntry, FileHandler};
13use crate::game::Game;
14use crate::helpers::{TigerHashMap, TigerHashSet, dup_error};
15use crate::item::Item;
16use crate::parse::ParserMemory;
17use crate::pathtable::PathTableIndex;
18use crate::pdxfile::PdxFile;
19use crate::report::{ErrorKey, err, warn};
20use crate::scopes::Scopes;
21use crate::token::Token;
22use crate::variables::Variables;
23
24#[derive(Debug, Default)]
25#[allow(clippy::struct_field_names)]
26pub struct Events {
27    events: TigerHashMap<(&'static str, u16), Event>,
28    namespaces: TigerHashSet<Token>,
29    triggers: TigerHashMap<(PathTableIndex, &'static str), Trigger>,
30    effects: TigerHashMap<(PathTableIndex, &'static str), Effect>,
31}
32
33impl Events {
34    fn load_event(&mut self, key: Token, block: Block) {
35        if let Some((key_a, key_b)) = key.as_str().split_once('.') {
36            if let Ok(id) = u16::from_str(key_b) {
37                if let Some(other) = self.get_event(key.as_str()) {
38                    #[allow(clippy::redundant_else)]
39                    if Game::is_vic3() {
40                        // Earlier events override later ones in vic3.
41                        // The game will complain but it does work, so don't warn unless warranted.
42                        if other.key.loc.kind <= key.loc.kind {
43                            dup_error(&other.key, &key, "event");
44                        }
45                        return;
46                    } else {
47                        // In the other games, overriding events is always an error.
48                        dup_error(&key, &other.key, "event");
49                    }
50                }
51                self.events.insert((key_a, id), Event::new(key, block));
52                return;
53            }
54        }
55        let msg = "Event names should be in the form NAMESPACE.NUMBER";
56        let info = "where NAMESPACE is the namespace declared at the top of the file, and NUMBER is a series of up to 4 digits.";
57        warn(ErrorKey::EventNamespace).msg(msg).info(info).loc(key).push();
58    }
59
60    fn load_scripted_trigger(&mut self, key: Token, block: Block) {
61        let index = (key.loc.idx, key.as_str());
62        if let Some(other) = self.triggers.get(&index) {
63            dup_error(&key, &other.key, "scripted trigger");
64        }
65        self.triggers.insert(index, Trigger::new(key, block, None));
66    }
67
68    fn load_scripted_effect(&mut self, key: Token, block: Block) {
69        let index = (key.loc.idx, key.as_str());
70        if let Some(other) = self.effects.get(&index) {
71            dup_error(&key, &other.key, "scripted effect");
72        }
73        self.effects.insert(index, Effect::new(key, block, None));
74    }
75
76    pub fn scan_variables(&self, registry: &mut Variables) {
77        for item in self.events.values() {
78            registry.scan(&item.block);
79        }
80        for item in self.triggers.values() {
81            registry.scan(&item.block);
82        }
83        for item in self.effects.values() {
84            registry.scan(&item.block);
85        }
86    }
87
88    #[cfg(any(feature = "ck3", feature = "eu5"))]
89    pub fn get_trigger(&self, key: &Token) -> Option<&Trigger> {
90        let index = (key.loc.idx, key.as_str());
91        self.triggers.get(&index)
92    }
93
94    #[cfg(any(feature = "ck3", feature = "eu5"))]
95    pub fn get_effect(&self, key: &Token) -> Option<&Effect> {
96        let index = (key.loc.idx, key.as_str());
97        self.effects.get(&index)
98    }
99
100    fn get_event<'a>(&'a self, key: &'a str) -> Option<&'a Event> {
101        if let Some((namespace, id)) = key.split_once('.') {
102            if let Ok(id) = u16::from_str(id) {
103                return self.events.get(&(namespace, id));
104            }
105        }
106        None
107    }
108
109    pub fn check_scope(&self, token: &Token, sc: &mut ScopeContext, data: &Everything) {
110        if let Some(event) = self.get_event(token.as_str()) {
111            sc.expect(event.expects_scope, &Reason::Token(token.clone()), data);
112        }
113    }
114
115    pub fn namespace_exists(&self, key: &str) -> bool {
116        self.namespaces.contains(key)
117    }
118
119    pub fn iter_namespace_keys(&self) -> impl Iterator<Item = &Token> {
120        self.namespaces.iter()
121    }
122
123    pub fn exists(&self, key: &str) -> bool {
124        if let Some((namespace, id)) = key.split_once('.') {
125            if let Ok(id) = u16::from_str(id) {
126                if self.events.contains_key(&(namespace, id)) {
127                    return true;
128                }
129            }
130        }
131        false
132    }
133
134    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
135        self.events.values().map(|item| &item.key)
136    }
137
138    pub fn validate(&self, data: &Everything) {
139        for item in self.effects.values() {
140            item.validate(data);
141        }
142
143        for item in self.triggers.values() {
144            item.validate(data);
145        }
146
147        self.events.par_iter().for_each(|(_, item)| {
148            item.validate(data);
149        });
150    }
151
152    pub fn validate_call(&self, key: &Token, data: &Everything, sc: &mut ScopeContext) {
153        if let Some(event) = self.get_event(key.as_str()) {
154            event.validate_call(data, sc);
155        }
156    }
157}
158
159impl FileHandler<Block> for Events {
160    fn subpath(&self) -> PathBuf {
161        PathBuf::from("events")
162    }
163
164    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
165        if !entry.filename().to_string_lossy().ends_with(".txt") {
166            return None;
167        }
168
169        PdxFile::read(entry, parser)
170    }
171
172    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
173        #[derive(Copy, Clone)]
174        enum Expecting {
175            Event,
176            ScriptedTrigger,
177            ScriptedEffect,
178        }
179
180        let mut expecting = Expecting::Event;
181
182        for item in block.drain() {
183            if let BlockItem::Field(Field(key, _, bv)) = item {
184                if key.is("namespace") {
185                    if let Some(value) = bv.expect_into_value() {
186                        self.namespaces.insert(value);
187                    }
188                } else if key.is("scripted_trigger") || key.is("scripted_effect") {
189                    let msg = format!("`{key}` should be used without `=`");
190                    err(ErrorKey::ParseError).msg(msg).loc(key).push();
191                } else if let Some(block) = bv.into_block() {
192                    match expecting {
193                        Expecting::ScriptedTrigger => {
194                            self.load_scripted_trigger(key, block);
195                            expecting = Expecting::Event;
196                        }
197                        Expecting::ScriptedEffect => {
198                            self.load_scripted_effect(key, block);
199                            expecting = Expecting::Event;
200                        }
201                        Expecting::Event => {
202                            self.load_event(key, block);
203                        }
204                    }
205                } else {
206                    let msg = "unknown setting in event file";
207                    err(ErrorKey::UnknownField).msg(msg).loc(key).push();
208                }
209            } else if let Some(key) = item.expect_value() {
210                if matches!(expecting, Expecting::Event) && key.is("scripted_trigger") {
211                    if !Game::is_ck3() && !Game::is_eu5() {
212                        let msg = "scripted triggers in event files are only for CK3 and EU5";
213                        err(ErrorKey::WrongGame).msg(msg).loc(key).push();
214                    }
215                    expecting = Expecting::ScriptedTrigger;
216                } else if matches!(expecting, Expecting::Event) && key.is("scripted_effect") {
217                    if !Game::is_ck3() && !Game::is_eu5() {
218                        let msg = "scripted effects in event files are only for CK3 and EU5";
219                        err(ErrorKey::WrongGame).msg(msg).loc(key).push();
220                    }
221                    expecting = Expecting::ScriptedEffect;
222                } else {
223                    err(ErrorKey::Validation)
224                        .msg("unexpected token")
225                        .info("Did you forget an = ?")
226                        .loc(key)
227                        .push();
228                }
229            }
230        }
231    }
232}
233
234#[derive(Debug)]
235pub struct Event {
236    pub key: Token,
237    pub block: Block,
238    expects_scope: Scopes,
239    expects_from_token: Token,
240    visited: Mutex<TigerHashSet<Signature>>,
241}
242
243impl Event {
244    pub fn new(key: Token, block: Block) -> Self {
245        let (expects_scope, expects_from_token) = match Game::game() {
246            #[cfg(feature = "ck3")]
247            Game::Ck3 => crate::ck3::events::get_event_scope(&key, &block),
248            #[cfg(feature = "vic3")]
249            Game::Vic3 => crate::vic3::events::get_event_scope(&key, &block),
250            #[cfg(feature = "imperator")]
251            Game::Imperator => crate::imperator::events::get_event_scope(&key, &block),
252            #[cfg(feature = "eu5")]
253            Game::Eu5 => crate::eu5::events::get_event_scope(&key, &block),
254            #[cfg(feature = "hoi4")]
255            Game::Hoi4 => unimplemented!(),
256        };
257        let visited = Mutex::new(TigerHashSet::default());
258        Self { key, block, expects_scope, expects_from_token, visited }
259    }
260
261    pub fn validate(&self, data: &Everything) {
262        if let Some((namespace, _)) = self.key.as_str().split_once('.') {
263            if !data.item_exists(Item::EventNamespace, namespace) {
264                let msg = format!("event file should start with `namespace = {namespace}`");
265                let info = "otherwise the event won't be found in-game";
266                err(ErrorKey::EventNamespace).msg(msg).info(info).loc(&self.key).push();
267            }
268        }
269
270        let mut sc = ScopeContext::new(self.expects_scope, &self.expects_from_token);
271        sc.set_strict_scopes(false);
272        sc.set_source(&self.key);
273
274        match Game::game() {
275            #[cfg(feature = "ck3")]
276            Game::Ck3 => crate::ck3::events::validate_event(self, data, &mut sc),
277            #[cfg(feature = "vic3")]
278            Game::Vic3 => crate::vic3::events::validate_event(self, data, &mut sc),
279            #[cfg(feature = "imperator")]
280            Game::Imperator => crate::imperator::events::validate_event(self, data, &mut sc),
281            #[cfg(feature = "eu5")]
282            Game::Eu5 => crate::eu5::events::validate_event(self, data, &mut sc),
283            #[cfg(feature = "hoi4")]
284            Game::Hoi4 => unimplemented!(),
285        }
286    }
287
288    pub fn validate_call(&self, data: &Everything, sc: &mut ScopeContext) {
289        if !self.visited.lock().unwrap().insert(sc.signature(data)) {
290            // The event was already visited with an equivalent sc
291            return;
292        }
293        match Game::game() {
294            #[cfg(feature = "ck3")]
295            Game::Ck3 => crate::ck3::events::validate_event(self, data, sc),
296            #[cfg(feature = "vic3")]
297            Game::Vic3 => crate::vic3::events::validate_event(self, data, sc),
298            #[cfg(feature = "imperator")]
299            Game::Imperator => crate::imperator::events::validate_event(self, data, sc),
300            #[cfg(feature = "eu5")]
301            Game::Eu5 => crate::eu5::events::validate_event(self, data, sc),
302            #[cfg(feature = "hoi4")]
303            Game::Hoi4 => unimplemented!(),
304        }
305    }
306}