tiger_lib/ck3/data/
council.rs

1use crate::block::{BV, Block};
2use crate::ck3::modif::ModifKinds;
3use crate::context::ScopeContext;
4use crate::db::{Db, DbKind};
5use crate::desc::validate_desc;
6use crate::everything::Everything;
7use crate::game::GameFlags;
8use crate::item::{Item, ItemLoader};
9use crate::modif::validate_modifs;
10use crate::report::{ErrorKey, err, warn};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::trigger::validate_trigger;
15use crate::validator::Validator;
16
17#[derive(Clone, Debug)]
18pub struct CouncilPosition {}
19
20inventory::submit! {
21    ItemLoader::Normal(GameFlags::Ck3, Item::CouncilPosition, CouncilPosition::add)
22}
23
24impl CouncilPosition {
25    pub fn add(db: &mut Db, key: Token, block: Block) {
26        db.add(Item::CouncilPosition, key, block, Box::new(Self {}));
27    }
28}
29
30impl DbKind for CouncilPosition {
31    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
32        let mut vd = Validator::new(block, data);
33        let mut sc = ScopeContext::new(Scopes::Character, key);
34        sc.define_name("councillor", Scopes::Character, key);
35        sc.define_name("councillor_liege", Scopes::Character, key);
36
37        // The base key has to exist even if name = a triggered desc
38        data.verify_exists(Item::Localization, key);
39        let loca = format!("{key}_possessive");
40        data.verify_exists_implied(Item::Localization, &loca, key);
41
42        let mut sc_name = ScopeContext::new(Scopes::Character, key);
43        sc_name.define_name("councillor", Scopes::Character, key);
44        vd.field_validated_sc("name", &mut sc_name, validate_desc);
45        vd.field_validated_sc("tooltip", &mut sc, validate_desc);
46
47        vd.field_item("skill", Item::Skill);
48        vd.field_validated_sc("auto_fill", &mut sc, validate_yes_no_trigger);
49        vd.field_bool("fill_from_pool");
50        vd.field_bool("is_clergy_position");
51        vd.field_validated_sc("inherit", &mut sc, validate_yes_no_trigger);
52        vd.field_validated_sc("can_fire", &mut sc, validate_yes_no_trigger);
53        vd.field_validated_sc("can_reassign", &mut sc, validate_yes_no_trigger);
54        vd.field_validated_sc("can_change_once", &mut sc, validate_yes_no_trigger);
55
56        let mut count = 0;
57        vd.multi_field_validated_block_rooted("modifier", Scopes::Character, |block, data, sc| {
58            let mut vd = Validator::new(block, data);
59            vd.field_item("name", Item::Localization);
60            vd.field_script_value("scale", sc);
61            validate_modifs(block, data, ModifKinds::Character, vd);
62            count += 1;
63            if count > 5 {
64                let msg = "no more than 5 modifier blocks can be specified here";
65                warn(ErrorKey::Validation).msg(msg).loc(block).push();
66            }
67        });
68        count = 0;
69        vd.multi_field_validated_block_rooted(
70            "council_owner_modifier",
71            Scopes::Character,
72            |block, data, sc| {
73                let mut vd = Validator::new(block, data);
74                vd.field_item("name", Item::Localization);
75                vd.field_script_value("scale", sc);
76                validate_modifs(block, data, ModifKinds::Character, vd);
77                count += 1;
78                if count > 5 {
79                    let msg = "no more than 5 council_owner_modifier blocks can be specified here";
80                    warn(ErrorKey::Validation).msg(msg).loc(block).push();
81                }
82            },
83        );
84
85        vd.field_trigger_rooted("valid_position", Tooltipped::No, Scopes::Character);
86        vd.field_trigger("valid_character", Tooltipped::No, &mut sc);
87
88        vd.field_effect_builder("on_get_position", Tooltipped::No, |key| {
89            let mut sc = ScopeContext::new(Scopes::Character, key);
90            sc.define_name("councillor_liege", Scopes::Character, key);
91            // TODO: figure out scope type here
92            sc.define_name("swapped_position", Scopes::all(), key);
93            sc
94        });
95        vd.field_effect_builder("on_lose_position", Tooltipped::No, |key| {
96            let mut sc = ScopeContext::new(Scopes::Character, key);
97            sc.define_name("councillor_liege", Scopes::Character, key);
98            sc
99        });
100        vd.field_effect_builder("on_fired_from_position", Tooltipped::No, |key| {
101            let mut sc = ScopeContext::new(Scopes::Character, key);
102            sc.define_name("councillor_liege", Scopes::Character, key);
103            sc.define_name("new_councillor", Scopes::Character, key);
104            sc
105        });
106
107        vd.replaced_field("use_for_scheme_power", "use_for_scheme_phase_duration");
108        vd.field_bool("use_for_scheme_phase_duration");
109        vd.field_bool("use_for_scheme_resistance");
110
111        vd.field_item("portrait_animation", Item::PortraitAnimation);
112        vd.field_validated_block("barbershop_data", |block, data| {
113            let mut vd = Validator::new(block, data);
114            vd.field_list_numeric_exactly("position", 2);
115            vd.field_bool("click_to_front");
116        });
117
118        // undocumented
119
120        vd.field_script_value("councillor_cooldown_days", &mut sc);
121        vd.field_item("pool_character_config", Item::PoolSelector);
122    }
123}
124
125#[derive(Clone, Debug)]
126pub struct CouncilTask {}
127
128inventory::submit! {
129    ItemLoader::Normal(GameFlags::Ck3, Item::CouncilTask, CouncilTask::add)
130}
131
132impl CouncilTask {
133    pub fn add(db: &mut Db, key: Token, block: Block) {
134        db.add(Item::CouncilTask, key, block, Box::new(Self {}));
135    }
136}
137
138impl DbKind for CouncilTask {
139    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
140        let mut vd = Validator::new(block, data);
141        let mut sc = ScopeContext::new(Scopes::Character, key);
142        sc.define_name("councillor", Scopes::Character, key);
143        sc.define_name("councillor_liege", Scopes::Character, key);
144        if let Some(token) = block.get_field_value("task_type") {
145            if token.is("task_type_county") {
146                sc.define_name("province", Scopes::Province, token);
147                sc.define_name("county", Scopes::LandedTitle, token);
148            } else if token.is("task_type_court") {
149                sc.define_name("target_character", Scopes::Character, token);
150            }
151        }
152
153        vd.field_validated_value("default_task", |key, mut vd| {
154            vd.bool();
155            if vd.value().is("yes") {
156                if !block.field_value_is("task_type", "task_type_general") {
157                    let msg = "`default_task` is only available for `task_type_general` tasks";
158                    err(ErrorKey::Validation).msg(msg).loc(key).push();
159                }
160                if !block.field_value_is("task_progress", "task_progress_infinite") {
161                    let msg = "`default_task` is only available for `task_progress_infinite` tasks";
162                    err(ErrorKey::Validation).msg(msg).loc(key).push();
163                }
164            }
165        });
166        vd.field_item("position", Item::CouncilPosition);
167        vd.field_choice("task_type", &["task_type_general", "task_type_county", "task_type_court"]);
168        if block.field_value_is("task_type", "task_type_county") {
169            vd.field_choice(
170                "county_target",
171                &["all", "realm", "domain", "neighbor_land", "neighbor_land_or_water"],
172            );
173            vd.field_choice(
174                "ai_county_target",
175                &["all", "realm", "domain", "neighbor_land", "neighbor_land_or_water"],
176            );
177            if let Some(token) = block.get_field_value("county_target") {
178                if token.is("neighbor_land_or_water") {
179                    let msg = "`neighbor_land_or_water` is only for `ai_county_target`";
180                    warn(ErrorKey::Validation).msg(msg).loc(token).push();
181                }
182            }
183            vd.field_script_value("ai_target_score", &mut sc);
184        } else {
185            vd.ban_field("county_target", || "task_type_county");
186            vd.ban_field("ai_county_target", || "task_type_county");
187            vd.ban_field("ai_target_score", || "task_type_county");
188        }
189
190        vd.field_choice(
191            "task_progress",
192            &["task_progress_infinite", "task_progress_percentage", "task_progress_value"],
193        );
194        if block.field_value_is("task_progress", "task_progress_value") {
195            vd.field_script_value("task_current_value", &mut sc);
196            vd.field_script_value("task_max_value", &mut sc);
197        } else {
198            vd.ban_field("task_current_value", || "task_progress_value");
199            vd.ban_field("task_max_value", || "task_progress_value");
200        }
201        vd.field_bool("restart_on_finish");
202        vd.field_bool("highlight_own_realm");
203
204        vd.multi_field_validated_block_sc("asset", &mut sc, validate_asset);
205
206        vd.multi_field_validated_block_rooted(
207            "councillor_modifier",
208            Scopes::Character,
209            |block, data, sc| {
210                let mut vd = Validator::new(block, data);
211                vd.field_item("name", Item::Localization);
212                vd.field_script_value("scale", sc);
213                validate_modifs(block, data, ModifKinds::Character, vd);
214            },
215        );
216        vd.multi_field_validated_block_rooted(
217            "council_owner_modifier",
218            Scopes::Character,
219            |block, data, sc| {
220                let mut vd = Validator::new(block, data);
221                vd.field_item("name", Item::Localization);
222                vd.field_script_value("scale", sc);
223                validate_modifs(block, data, ModifKinds::Character, vd);
224            },
225        );
226        if block.field_value_is("task_type", "task_type_county") {
227            vd.multi_field_validated_block_rooted(
228                "county_modifier",
229                Scopes::Character,
230                |block, data, sc| {
231                    let mut vd = Validator::new(block, data);
232                    vd.field_item("name", Item::Localization);
233                    vd.field_script_value("scale", sc);
234                    validate_modifs(block, data, ModifKinds::County, vd);
235                },
236            );
237        } else {
238            vd.ban_field("county_modifier", || "task_type_county");
239        }
240
241        vd.field_trigger("is_shown", Tooltipped::No, &mut sc);
242        vd.field_trigger("is_valid_showing_failures_only", Tooltipped::FailuresOnly, &mut sc);
243        vd.field_effect("on_start_task", Tooltipped::Yes, &mut sc);
244        vd.field_effect("on_finish_task", Tooltipped::Yes, &mut sc);
245        vd.field_effect("on_cancel_task", Tooltipped::No, &mut sc);
246        vd.field_effect("on_monthly", Tooltipped::No, &mut sc);
247        vd.field_action("monthly_on_action", &sc);
248
249        if let Some(token) = block.get_field_value("task_type") {
250            if token.is("task_type_county") {
251                vd.field_trigger("potential_county", Tooltipped::Yes, &mut sc);
252                vd.field_trigger("valid_county", Tooltipped::Yes, &mut sc);
253                vd.field_effect("on_start_task_county", Tooltipped::Yes, &mut sc);
254                vd.field_effect("on_finish_task_county", Tooltipped::Yes, &mut sc);
255                vd.field_effect("on_cancel_task_county", Tooltipped::Yes, &mut sc);
256                vd.field_effect("on_monthly_county", Tooltipped::No, &mut sc);
257            } else {
258                vd.ban_field("potential_county", || "task_type_county");
259                vd.ban_field("valid_county", || "task_type_county");
260                vd.ban_field("on_start_task_county", || "task_type_county");
261                vd.ban_field("on_finish_task_county", || "task_type_county");
262                vd.ban_field("on_cancel_task_county", || "task_type_county");
263                vd.ban_field("on_monthly_county", || "task_type_county");
264            }
265            if token.is("task_type_court") {
266                vd.field_trigger("potential_target_court", Tooltipped::Yes, &mut sc);
267                vd.field_trigger("valid_target_court", Tooltipped::Yes, &mut sc);
268                vd.field_effect("on_start_task_court", Tooltipped::Yes, &mut sc);
269                vd.field_effect("on_finish_task_court", Tooltipped::Yes, &mut sc);
270                vd.field_effect("on_cancel_task_court", Tooltipped::No, &mut sc);
271                vd.field_effect("on_monthly_court", Tooltipped::No, &mut sc);
272            } else {
273                vd.ban_field("potential_court", || "task_type_court");
274                vd.ban_field("valid_court", || "task_type_court");
275                vd.ban_field("on_start_task_court", || "task_type_court");
276                vd.ban_field("on_finish_task_court", || "task_type_court");
277                vd.ban_field("on_cancel_task_court", || "task_type_court");
278                vd.ban_field("on_monthly_court", || "task_type_court");
279            }
280        }
281
282        // task_accept_culture is a hardcoded exception
283        if !block.field_value_is("task_progress", "task_progress_infinite")
284            || key.is("task_accept_culture")
285        {
286            vd.field_script_value("progress", &mut sc); // documented as a mtth though
287            vd.field_script_value("full_progress", &mut sc);
288        } else {
289            vd.ban_field("progress", || "task_progress_percent or task_progress_value");
290            vd.ban_field("full_progress", || "task_progress_percent or task_progress_value");
291        }
292        vd.field_item("custom_other_loc", Item::Localization);
293        vd.field_validated_sc("effect_desc", &mut sc, validate_desc);
294        // TODO: the cloned task must be filled out (you cannot build clone chains where A clones B and B clones C)
295        // TODO: you cannot redefine anything else than the court position, and that field MUST be redefined.
296        vd.field_item("clone", Item::CouncilTask);
297
298        // undocumented
299        vd.field_script_value("ai_will_do", &mut sc);
300        vd.field_item("skill", Item::Skill);
301    }
302}
303
304fn validate_yes_no_trigger(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
305    match bv {
306        BV::Value(token) => {
307            if !token.is("yes") && !token.is("no") {
308                err(ErrorKey::Validation).msg("expected yes or no or trigger").loc(token).push();
309            }
310        }
311        BV::Block(block) => {
312            validate_trigger(block, data, sc, Tooltipped::No);
313        }
314    }
315}
316
317fn validate_asset(block: &Block, data: &Everything, sc: &mut ScopeContext) {
318    let mut vd = Validator::new(block, data);
319    vd.field_trigger("trigger", Tooltipped::No, sc);
320    vd.field_item("icon", Item::File);
321    vd.field_item("background", Item::File);
322    vd.field_item("frame", Item::File);
323    vd.field_item("glow", Item::File);
324}