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 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 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 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 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}