tiger_lib/
on_action.rs

1//! Track scope context for the builtin on-actions in the various games.
2//!
3//! On-actions are script items that are called by the game engine, either at scheduled intervals
4//! or when certain things happen.
5
6use std::sync::LazyLock;
7
8use crate::block::BV;
9use crate::context::ScopeContext;
10use crate::everything::Everything;
11use crate::game::Game;
12use crate::helpers::TigerHashMap;
13#[cfg(feature = "hoi4")]
14use crate::hoi4::tables::on_action::on_action_scopecontext_hoi4;
15#[cfg(feature = "ck3")]
16use crate::item::Item;
17use crate::parse::pdxfile::parse_pdx_internal;
18use crate::scopes::Scopes;
19use crate::token::Token;
20
21#[derive(Debug, Clone)]
22struct OnActionScopeContext {
23    root: Scopes,
24    names: Vec<(String, Scopes)>,
25    lists: Vec<(String, Scopes)>,
26}
27
28static ON_ACTION_SCOPES_MAP: LazyLock<TigerHashMap<String, OnActionScopeContext>> =
29    LazyLock::new(|| {
30        build_on_action_hashmap(match Game::game() {
31            #[cfg(feature = "ck3")]
32            Game::Ck3 => crate::ck3::tables::on_action::ON_ACTION_SCOPES,
33            #[cfg(feature = "vic3")]
34            Game::Vic3 => crate::vic3::tables::on_action::ON_ACTION_SCOPES,
35            #[cfg(feature = "imperator")]
36            Game::Imperator => crate::imperator::tables::on_action::ON_ACTION_SCOPES,
37            #[cfg(feature = "eu5")]
38            Game::Eu5 => crate::eu5::tables::on_action::ON_ACTION_SCOPES,
39            #[cfg(feature = "hoi4")]
40            Game::Hoi4 => crate::hoi4::tables::on_action::ON_ACTION_SCOPES,
41        })
42    });
43
44#[allow(unused_variables)] // only ck3 and hoi4 use `data`
45pub fn on_action_scopecontext(key: &Token, data: &Everything) -> Option<ScopeContext> {
46    if let Some(oa_sc) = ON_ACTION_SCOPES_MAP.get(key.as_str()) {
47        let mut sc = ScopeContext::new(oa_sc.root, key);
48        for (name, s) in &oa_sc.names {
49            sc.define_name(name, *s, key);
50        }
51        for (list, s) in &oa_sc.lists {
52            sc.define_list(list, *s, key);
53        }
54        return Some(sc);
55    }
56
57    #[cfg(feature = "ck3")]
58    if Game::is_ck3() {
59        if let Some(relation) = key.as_str().strip_suffix("_quarterly_pulse") {
60            if data.item_exists(Item::Relation, relation) {
61                let mut sc = ScopeContext::new(Scopes::Character, key);
62                sc.define_name("quarter", Scopes::Value, key); // undocumented
63                return Some(sc);
64            }
65        } else {
66            for pfx in &["on_set_relation_", "on_remove_relation_", "on_death_relation_"] {
67                if let Some(relation) = key.as_str().strip_prefix(pfx) {
68                    if data.item_exists(Item::Relation, relation) {
69                        let mut sc = ScopeContext::new(Scopes::Character, key);
70                        sc.define_name("target", Scopes::Character, key); // undocumented
71                        return Some(sc);
72                    }
73                }
74            }
75        }
76    }
77
78    #[cfg(feature = "hoi4")]
79    if Game::is_hoi4() {
80        if let Some(sc) = on_action_scopecontext_hoi4(key, data) {
81            return Some(sc);
82        }
83    }
84    None
85}
86
87fn build_on_action_hashmap(
88    description: &'static str,
89) -> TigerHashMap<String, OnActionScopeContext> {
90    let mut hash: TigerHashMap<String, OnActionScopeContext> = TigerHashMap::default();
91
92    let mut block = parse_pdx_internal(description, "on action builtin scopes");
93    for item in block.drain() {
94        let field = item.get_field().expect("internal error");
95        match field.bv() {
96            BV::Value(token) => {
97                // key1 = key2 means copy from key2
98                let value = hash.get(token.as_str()).expect("internal error");
99                hash.insert(field.key().to_string(), value.clone());
100            }
101            BV::Block(block) => {
102                let root = block.get_field_value("root").expect("internal error");
103                let root = Scopes::from_snake_case_multi(root.as_str()).expect("internal error");
104                let mut value = OnActionScopeContext { root, names: Vec::new(), lists: Vec::new() };
105                for (key, token) in block.iter_assignments() {
106                    if key.is("root") {
107                        continue;
108                    }
109                    let s = Scopes::from_snake_case_multi(token.as_str()).expect("internal error");
110                    value.names.push((key.to_string(), s));
111                }
112                for (key, block) in block.iter_definitions() {
113                    if key.is("list") {
114                        for (key, token) in block.iter_assignments() {
115                            let s = Scopes::from_snake_case_multi(token.as_str())
116                                .expect("internal error");
117                            value.lists.push((key.to_string(), s));
118                        }
119                    }
120                }
121                hash.insert(field.key().to_string(), value);
122            }
123        }
124    }
125
126    hash
127}