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 if other.key.loc.kind <= key.loc.kind {
43 dup_error(&other.key, &key, "event");
44 }
45 return;
46 } else {
47 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 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}