tiger_lib/data/
scripted_rules.rs

1use std::sync::LazyLock;
2
3use crate::block::Block;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::game::{Game, GameFlags};
8use crate::helpers::TigerHashMap;
9use crate::item::{Item, ItemLoader};
10use crate::parse::pdxfile::parse_pdx_internal;
11use crate::report::{ErrorKey, err};
12use crate::scopes::Scopes;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::trigger::validate_trigger;
16
17#[derive(Clone, Debug)]
18pub struct ScriptedRule {}
19
20inventory::submit! {
21    ItemLoader::Normal(GameFlags::Ck3.union(GameFlags::Vic3), Item::ScriptedRule, ScriptedRule::add)
22}
23
24impl ScriptedRule {
25    pub fn add(db: &mut Db, key: Token, block: Block) {
26        db.add(Item::ScriptedRule, key, block, Box::new(Self {}));
27    }
28}
29
30impl DbKind for ScriptedRule {
31    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
32        if let Some(sr_sc) = SCRIPTED_RULE_SCOPES_MAP.get(key.as_str()) {
33            let mut sc = ScopeContext::new(sr_sc.root, key);
34            for (name, s) in &sr_sc.names {
35                sc.define_name(name, *s, key);
36            }
37            for (list, s) in &sr_sc.names {
38                sc.define_list(list, *s, key);
39            }
40            validate_trigger(block, data, &mut sc, sr_sc.tooltipped);
41        } else {
42            let msg = "unknown scripted rule";
43            err(ErrorKey::Validation).msg(msg).loc(key).push();
44            let mut sc = ScopeContext::new(Scopes::non_primitive(), key);
45            sc.set_strict_scopes(false);
46            validate_trigger(block, data, &mut sc, Tooltipped::No);
47        }
48    }
49}
50
51#[derive(Debug, Clone)]
52struct ScriptedRuleScopeContext {
53    tooltipped: Tooltipped,
54    root: Scopes,
55    names: Vec<(&'static str, Scopes)>,
56    lists: Vec<(&'static str, Scopes)>,
57}
58
59/// Processed version of game-specific `SCRIPTED_RULES`.
60static SCRIPTED_RULE_SCOPES_MAP: LazyLock<TigerHashMap<&'static str, ScriptedRuleScopeContext>> =
61    LazyLock::new(|| {
62        let rules = match Game::game() {
63            #[cfg(feature = "ck3")]
64            Game::Ck3 => crate::ck3::tables::rules::SCRIPTED_RULES,
65            #[cfg(feature = "vic3")]
66            Game::Vic3 => crate::vic3::tables::rules::SCRIPTED_RULES,
67            #[cfg(feature = "imperator")]
68            Game::Imperator => unimplemented!(),
69            #[cfg(feature = "hoi4")]
70            Game::Hoi4 => unimplemented!(),
71        };
72        build_scripted_rule_hashmap(rules)
73    });
74
75// Mostly copied from build_on_action_hashmap.
76// TODO: more generic facility for this?
77fn build_scripted_rule_hashmap(
78    description: &'static str,
79) -> TigerHashMap<&'static str, ScriptedRuleScopeContext> {
80    let mut hash: TigerHashMap<&'static str, ScriptedRuleScopeContext> = TigerHashMap::default();
81
82    let mut block = parse_pdx_internal(description, "scripted rule builtin scopes");
83    for (key, block) in block.drain_definitions_warn() {
84        let root = block.get_field_value("root").expect("internal error");
85        let root = if root.lowercase_is("all") {
86            Scopes::all()
87        } else {
88            Scopes::from_snake_case(root.as_str()).expect("internal error")
89        };
90        let tooltipped = if block.field_value_is("tooltipped", "yes") {
91            Tooltipped::Yes
92        } else {
93            Tooltipped::No
94        };
95        let mut value =
96            ScriptedRuleScopeContext { tooltipped, root, names: Vec::new(), lists: Vec::new() };
97        for (key, token) in block.iter_assignments() {
98            if key.is("root") || key.is("tooltipped") {
99                continue;
100            }
101            let s = Scopes::from_snake_case(token.as_str()).expect("internal error");
102            value.names.push((key.as_str(), s));
103        }
104        for (key, block) in block.iter_definitions() {
105            if key.is("list") {
106                for (key, token) in block.iter_assignments() {
107                    let s = Scopes::from_snake_case(token.as_str()).expect("internal error");
108                    value.lists.push((key.as_str(), s));
109                }
110            }
111        }
112        hash.insert(key.as_str(), value);
113    }
114
115    hash
116}