tiger_lib/ck3/data/
courtpos.rs

1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::ck3::validate::validate_cost;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::game::GameFlags;
8use crate::item::{Item, ItemLoader};
9use crate::modif::validate_modifs;
10use crate::report::{ErrorKey, err};
11use crate::scopes::Scopes;
12use crate::script_value::validate_script_value;
13use crate::token::Token;
14use crate::tooltipped::Tooltipped;
15use crate::validator::Validator;
16
17#[derive(Clone, Debug)]
18pub struct CourtPosition {}
19
20inventory::submit! {
21    ItemLoader::Normal(GameFlags::Ck3, Item::CourtPosition, CourtPosition::add)
22}
23
24impl CourtPosition {
25    pub fn add(db: &mut Db, key: Token, block: Block) {
26        db.add(Item::CourtPosition, key, block, Box::new(Self {}));
27    }
28}
29
30impl DbKind for CourtPosition {
31    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
32        data.verify_exists(Item::Localization, key);
33        let loca = format!("{key}_desc");
34        data.verify_exists_implied(Item::Localization, &loca, key);
35
36        let mut vd = Validator::new(block, data);
37        vd.field_item("skill", Item::Skill);
38        vd.field_integer("max_available_positions");
39        vd.advice_field("category", "removed in 1.15");
40        vd.field_choice("minimum_rank", &["county", "duchy", "kingdom", "empire", "hegemony"]);
41        vd.field_bool("is_travel_related");
42
43        vd.multi_field_validated_block("court_position_asset", |block, data| {
44            let mut vd = Validator::new(block, data);
45            vd.field_trigger_rooted("trigger", Tooltipped::Yes, Scopes::Character);
46            vd.field_item("animation", Item::PortraitAnimation);
47            vd.field_item("background", Item::File);
48            vd.field_item("localization_key", Item::Localization);
49        });
50
51        let mut sc = ScopeContext::new(Scopes::None, key);
52        sc.define_name("liege", Scopes::Character, key);
53        sc.define_name("employee", Scopes::Character, key);
54        vd.field_script_value("opinion", &mut sc);
55
56        vd.field_validated_block("aptitude_level_breakpoints", validate_breakpoints);
57        vd.field_script_value_rooted("aptitude", Scopes::Character);
58        vd.field_trigger_rooted("is_shown", Tooltipped::No, Scopes::Character);
59        vd.field_trigger_rooted("valid_position", Tooltipped::No, Scopes::Character);
60        vd.field_trigger("is_shown_character", Tooltipped::No, &mut sc);
61        vd.field_trigger("valid_character", Tooltipped::Yes, &mut sc);
62
63        // guessing that root is the liege here
64        vd.field_validated_block_rooted("revoke_cost", Scopes::Character, |block, data, sc| {
65            validate_cost(block, data, sc);
66        });
67
68        for field in &["salary", "received_salary"] {
69            vd.field_validated_key_block(field, |key, block, data| {
70                let mut sc = ScopeContext::new(Scopes::None, key);
71                sc.define_name("liege", Scopes::Character, key);
72                validate_cost(block, data, &mut sc);
73            });
74        }
75
76        vd.field_validated_block("base_employer_modifier", |block, data| {
77            let vd = Validator::new(block, data);
78            validate_modifs(block, data, ModifKinds::Character, vd);
79        });
80        vd.field_validated_block("culture_modifier", |block, data| {
81            let mut vd = Validator::new(block, data);
82            vd.field_item("parameter", Item::CultureParameter);
83            validate_modifs(block, data, ModifKinds::Character, vd);
84        });
85        vd.field_validated_block("faith_modifier", |block, data| {
86            let mut vd = Validator::new(block, data);
87            vd.field_item("parameter", Item::DoctrineParameter);
88            validate_modifs(block, data, ModifKinds::Character, vd);
89        });
90
91        vd.field_validated_block("scaling_employer_modifiers", |block, data| {
92            validate_scaling_employer_modifiers(block, data);
93        });
94        vd.field_validated_block("custom_scaling_employer_modifier_description", |block, data| {
95            let mut vd = Validator::new(block, data);
96            for field in ["terrible", "poor", "average", "good", "excellent", "range"] {
97                vd.field_localization(field, &mut sc);
98            }
99        });
100
101        vd.field_validated_block("base_employer_court_modifier", |block, data| {
102            let vd = Validator::new(block, data);
103            validate_modifs(block, data, ModifKinds::Character, vd);
104        });
105
106        vd.field_validated_block("scaling_employer_court_modifiers", |block, data| {
107            validate_scaling_employer_modifiers(block, data);
108        });
109
110        vd.field_validated_block("modifier", |block, data| {
111            let vd = Validator::new(block, data);
112            validate_modifs(block, data, ModifKinds::Character, vd);
113        });
114
115        vd.field_item("custom_employer_modifier_description", Item::Localization);
116        vd.field_item("custom_employee_modifier_description", Item::Localization);
117
118        vd.field_effect_rooted("search_for_courtier", Tooltipped::No, Scopes::Character);
119
120        for field in &[
121            "on_court_position_received",
122            "on_court_position_revoked",
123            "on_court_position_invalidated",
124            "on_court_position_vacated",
125        ] {
126            vd.field_effect(field, Tooltipped::No, &mut sc);
127        }
128
129        vd.field_script_value_no_breakdown_builder("ai_position_score", |key| {
130            let mut sc = ScopeContext::new(Scopes::None, key);
131            sc.define_name("liege", Scopes::Character, key);
132            sc.define_name("monthly_character_expenses", Scopes::Value, key);
133            sc
134        });
135
136        vd.replaced_field("candidate_score", "ai_candidate_score");
137        vd.field_validated_key("ai_candidate_score", |key, bv, data| {
138            let mut sc = ScopeContext::new(Scopes::None, key);
139            sc.define_name("liege", Scopes::Character, key);
140            sc.define_name("employee", Scopes::Character, key);
141            sc.define_name("firing_court_position", Scopes::Bool, key);
142            sc.define_name("percent_of_monthly_gold_income", Scopes::Value, key);
143            sc.define_name("percent_of_positive_monthly_prestige_balance", Scopes::Value, key);
144            sc.define_name("percent_of_positive_monthly_piety_balance", Scopes::Value, key);
145            sc.define_name("percent_of_monthly_gold_income_all_positions", Scopes::Value, key);
146            sc.define_name(
147                "percent_of_positive_monthly_prestige_balance_all_positions",
148                Scopes::Value,
149                key,
150            );
151            sc.define_name(
152                "percent_of_positive_monthly_piety_balance_all_positions",
153                Scopes::Value,
154                key,
155            );
156
157            // undocumented
158
159            sc.define_name("base_value", Scopes::Value, key);
160            sc.define_name("highest_available_aptitude", Scopes::Value, key);
161            sc.define_name("employee_aptitude", Scopes::Value, key);
162
163            validate_script_value(bv, data, &mut sc);
164        });
165
166        vd.field_script_value("sort_order", &mut sc);
167
168        // undocumented
169
170        vd.field_bool("is_powerful_agent");
171    }
172}
173
174fn validate_breakpoints(block: &Block, data: &Everything) {
175    let mut vd = Validator::new(block, data);
176    vd.req_tokens_integers_exactly(4);
177}
178
179fn validate_scaling_employer_modifiers(block: &Block, data: &Everything) {
180    let mut vd = Validator::new(block, data);
181
182    for field in ["terrible", "poor", "average", "good", "excellent"] {
183        vd.field_validated_block(field, |block, data| {
184            let vd = Validator::new(block, data);
185            validate_modifs(block, data, ModifKinds::Character, vd);
186        });
187    }
188
189    for field in [
190        "aptitude_level_1",
191        "aptitude_level_2",
192        "aptitude_level_3",
193        "aptitude_level_4",
194        "aptitude_level_5",
195    ] {
196        if let Some(key) = block.get_key(field) {
197            err(ErrorKey::Removed).msg("the aptitude_level_N fields have been replaced in 1.11 with terrible, poor, average, good, and excellent").loc(key).push();
198        }
199    }
200}
201
202#[derive(Clone, Debug)]
203pub struct CourtPositionTask {}
204
205inventory::submit! {
206    ItemLoader::Normal(GameFlags::Ck3, Item::CourtPositionTask, CourtPositionTask::add)
207}
208
209impl CourtPositionTask {
210    pub fn add(db: &mut Db, key: Token, block: Block) {
211        db.add(Item::CourtPositionTask, key, block, Box::new(Self {}));
212    }
213}
214
215impl DbKind for CourtPositionTask {
216    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
217        fn sc_ai_will_do(key: &Token) -> ScopeContext {
218            let mut sc = ScopeContext::new(Scopes::Character, key);
219            sc.define_name("liege", Scopes::Character, key);
220            sc.define_name("employee", Scopes::Character, key);
221            sc.define_name("monthly_character_expenses", Scopes::Value, key);
222            sc
223        }
224
225        data.verify_exists(Item::Localization, key);
226        let loca = format!("{key}_desc");
227        data.verify_exists_implied(Item::Localization, &loca, key);
228
229        let mut vd = Validator::new(block, data);
230        vd.advice_field("position_types", "docs say position_types but it's court_position_types");
231        vd.field_list_items("court_position_types", Item::CourtPosition);
232
233        vd.multi_field_validated_block("court_position_asset", |block, data| {
234            let mut vd = Validator::new(block, data);
235            vd.field_trigger_rooted("trigger", Tooltipped::Yes, Scopes::Character);
236            vd.field_item("animation", Item::PortraitAnimation);
237            vd.field_item("background", Item::File);
238        });
239
240        for field in &["cost", "received_cost"] {
241            vd.field_validated_key_block(field, |key, block, data| {
242                let mut sc = ScopeContext::new(Scopes::None, key);
243                sc.define_name("liege", Scopes::Character, key);
244                validate_cost(block, data, &mut sc);
245            });
246        }
247
248        vd.field_validated_block("employee_modifier", |block, data| {
249            let vd = Validator::new(block, data);
250            validate_modifs(block, data, ModifKinds::Character, vd);
251        });
252        vd.field_validated_block("base_employer_modifier", |block, data| {
253            let vd = Validator::new(block, data);
254            validate_modifs(block, data, ModifKinds::Character, vd);
255        });
256        vd.field_validated_block("scaling_employer_modifiers", |block, data| {
257            validate_scaling_employer_modifiers(block, data);
258        });
259        vd.field_validated_block("base_employer_court_modifier", |block, data| {
260            let vd = Validator::new(block, data);
261            validate_modifs(block, data, ModifKinds::Character, vd);
262        });
263        vd.field_validated_block("scaling_employer_court_modifiers", |block, data| {
264            validate_scaling_employer_modifiers(block, data);
265        });
266
267        vd.field_trigger_rooted("is_shown", Tooltipped::No, Scopes::Character);
268        vd.field_trigger_builder(
269            "is_valid_showing_failures_only",
270            Tooltipped::FailuresOnly,
271            |key| {
272                let mut sc = ScopeContext::new(Scopes::Character, key);
273                sc.define_name("liege", Scopes::Character, key);
274                sc
275            },
276        );
277
278        for field in &["on_start", "on_end", "on_monthly", "on_yearly"] {
279            // TODO: see if on_start is tooltipped
280            vd.field_effect_builder(field, Tooltipped::No, |key| {
281                let mut sc = ScopeContext::new(Scopes::Character, key);
282                sc.define_name("liege", Scopes::Character, key);
283                sc
284            });
285        }
286
287        vd.field_script_value_no_breakdown_builder("ai_will_do", sc_ai_will_do);
288    }
289}