1use crate::block::{BV, Block, Comparator, Eq::Single};
4use crate::context::{ScopeContext, Temporary};
5use crate::desc::validate_desc;
6use crate::effect::{validate_effect, validate_effect_control};
7use crate::everything::Everything;
8#[cfg(feature = "jomini")]
9use crate::game::Game;
10use crate::item::Item;
11use crate::lowercase::Lowercase;
12use crate::report::{ErrorKey, Severity, err, warn};
13use crate::scopes::Scopes;
14#[cfg(feature = "jomini")]
15use crate::script_value::validate_script_value;
16use crate::special_tokens::SpecialTokens;
17use crate::token::Token;
18use crate::tooltipped::Tooltipped;
19use crate::trigger::{validate_target_ok_this, validate_trigger_key_bv};
20use crate::validate::{validate_identifier, validate_optional_duration};
21use crate::validator::{Validator, ValueValidator};
22
23#[allow(dead_code)]
24#[cfg(feature = "imperator")]
25pub fn validate_add_to_list_imperator(
26 key: &Token,
27 mut vd: ValueValidator,
28 sc: &mut ScopeContext,
29 _tooltipped: Tooltipped,
30) {
31 let temp = if key.as_str().contains("_temporary_") { Temporary::Yes } else { Temporary::No };
32 vd.identifier("list name");
33 sc.define_or_expect_list_this(vd.value(), vd.data(), temp);
34 vd.accept();
35}
36
37#[allow(dead_code)]
38#[cfg(any(feature = "ck3", feature = "vic3"))]
39pub fn validate_add_to_list(
40 key: &Token,
41 bv: &BV,
42 data: &Everything,
43 sc: &mut ScopeContext,
44 _tooltipped: Tooltipped,
45) {
46 let temp = if key.as_str().contains("_temporary_") { Temporary::Yes } else { Temporary::No };
47 match bv {
48 BV::Value(name) => {
49 validate_identifier(name, "list name", Severity::Error);
50 sc.define_or_expect_list_this(name, data, temp);
51 }
52 BV::Block(block) => {
53 let mut vd = Validator::new(block, data);
54 vd.req_field("name");
55 vd.req_field("value");
56 if let Some(target) = vd.field_value("value").cloned() {
57 if let Some(name) = vd.field_value("name") {
58 validate_identifier(name, "list name", Severity::Error);
59 let target_scopes = sc.local_list_scopes(name.as_str(), data);
60 let outscopes = validate_target_ok_this(&target, data, sc, target_scopes);
61 sc.define_or_expect_list(name, outscopes, data, temp);
62 }
63 }
64 }
65 }
66}
67
68#[cfg(feature = "jomini")]
71pub fn validate_add_to_variable_list(
72 key: &Token,
73 _block: &Block,
74 data: &Everything,
75 sc: &mut ScopeContext,
76 mut vd: Validator,
77 _tooltipped: Tooltipped,
78) {
79 vd.req_field("name");
80 vd.req_field("target");
81 if let Some(target) = vd.field_value("target").cloned() {
82 if let Some(name) = vd.field_value("name") {
83 validate_identifier(name, "list name", Severity::Error);
84 if key.as_str().contains("_local_") {
88 let target_scopes = sc.local_list_scopes(name.as_str(), data);
89 let outscopes = validate_target_ok_this(&target, data, sc, target_scopes);
90 sc.define_or_expect_local_list(name, outscopes, data);
91 } else if key.as_str().contains("_global_") {
92 let target_scopes = data.global_list_scopes.scopes(name.as_str());
93 let outscopes = validate_target_ok_this(&target, data, sc, target_scopes);
94 data.global_list_scopes.expect(name.as_str(), name, outscopes);
95 } else {
96 let target_scopes = data.variable_list_scopes.scopes(name.as_str());
97 let outscopes = validate_target_ok_this(&target, data, sc, target_scopes);
98 data.variable_list_scopes.expect(name.as_str(), name, outscopes);
99 }
100 }
101 }
102 if key.starts_with("add_") && (Game::is_ck3() || Game::is_vic3()) {
103 validate_optional_duration(&mut vd, sc);
104 }
105}
106
107#[cfg(feature = "jomini")]
109pub fn validate_change_variable(
110 key: &Token,
111 _block: &Block,
112 data: &Everything,
113 sc: &mut ScopeContext,
114 mut vd: Validator,
115 _tooltipped: Tooltipped,
116) {
117 vd.req_field("name");
118 if let Some(name) = vd.field_value("name") {
119 validate_identifier(name, "variable name", Severity::Error);
120 if key.as_str().contains("_local_") {
121 sc.expect_local(name, Scopes::Value, data);
122 } else if key.as_str().contains("_global_") {
123 data.global_scopes.expect(name.as_str(), name, Scopes::Value);
124 } else {
125 data.variable_scopes.expect(name.as_str(), name, Scopes::Value);
126 }
127 }
128 vd.field_script_value("add", sc);
129 vd.field_script_value("subtract", sc);
130 vd.field_script_value("multiply", sc);
131 vd.field_script_value("divide", sc);
132 vd.field_script_value("modulo", sc);
133 vd.field_script_value("min", sc);
134 vd.field_script_value("max", sc);
135}
136
137#[cfg(feature = "jomini")]
139pub fn validate_clamp_variable(
140 key: &Token,
141 _block: &Block,
142 data: &Everything,
143 sc: &mut ScopeContext,
144 mut vd: Validator,
145 _tooltipped: Tooltipped,
146) {
147 vd.req_field("name");
148 if let Some(name) = vd.field_value("name") {
149 validate_identifier(name, "variable name", Severity::Error);
150 if key.as_str().contains("_local_") {
151 sc.expect_local(name, Scopes::Value, data);
152 } else if key.as_str().contains("_global_") {
153 data.global_scopes.expect(name.as_str(), name, Scopes::Value);
154 } else {
155 data.variable_scopes.expect(name.as_str(), name, Scopes::Value);
156 }
157 }
158 vd.field_script_value("min", sc);
159 vd.field_script_value("max", sc);
160}
161
162#[cfg(feature = "jomini")]
164pub fn validate_random_list(
165 key: &Token,
166 _block: &Block,
167 data: &Everything,
168 sc: &mut ScopeContext,
169 mut vd: Validator,
170 tooltipped: Tooltipped,
171 special_tokens: &mut SpecialTokens,
172) -> bool {
173 let caller = Lowercase::new(key.as_str());
174 vd.field_integer("pick");
175 vd.field_bool("unique"); vd.field_validated_sc("desc", sc, validate_desc);
177 let mut has_tooltip = false;
178 vd.unknown_block_fields(|key, block| {
179 if let Some(n) = key.expect_number() {
180 if n < 0.0 {
181 let msg = "negative weights make the whole `random_list` fail";
182 err(ErrorKey::Range).strong().msg(msg).loc(key).push();
183 } else if n > 0.0 && n < 1.0 {
184 let msg = "fractional weights are treated as just 0 in `random_list`";
185 err(ErrorKey::Range).strong().msg(msg).loc(key).push();
186 } else if n.fract() != 0.0 {
187 let msg = "fractions are discarded in `random_list` weights";
188 warn(ErrorKey::Range).strong().msg(msg).loc(key).push();
189 }
190 has_tooltip |=
191 validate_effect_control(&caller, block, data, sc, tooltipped, special_tokens);
192 }
193 });
194 if has_tooltip && key.is("random_list") {
195 special_tokens.insert(key);
196 }
197 has_tooltip
198}
199
200#[cfg(feature = "jomini")]
201pub fn validate_remove_from_list(
202 _key: &Token,
203 mut vd: ValueValidator,
204 sc: &mut ScopeContext,
205 _tooltipped: Tooltipped,
206) {
207 vd.identifier("list name");
208 sc.expect_list(vd.value(), vd.data());
209 vd.accept();
210}
211
212#[cfg(feature = "jomini")]
214pub fn validate_round_variable(
215 key: &Token,
216 _block: &Block,
217 data: &Everything,
218 sc: &mut ScopeContext,
219 mut vd: Validator,
220 _tooltipped: Tooltipped,
221) {
222 vd.req_field("name");
223 vd.req_field("nearest");
224 if let Some(name) = vd.field_value("name") {
225 validate_identifier(name, "variable name", Severity::Error);
226 if key.as_str().contains("_local_") {
227 sc.expect_local(name, Scopes::Value, data);
228 } else if key.as_str().contains("_global_") {
229 data.global_scopes.expect(name.as_str(), name, Scopes::Value);
230 } else {
231 data.variable_scopes.expect(name.as_str(), name, Scopes::Value);
232 }
233 }
234 vd.field_script_value("nearest", sc);
235}
236
237#[cfg(feature = "jomini")]
238pub fn validate_save_scope(
239 key: &Token,
240 mut vd: ValueValidator,
241 sc: &mut ScopeContext,
242 _tooltipped: Tooltipped,
243) {
244 let temp = if key.as_str().contains("_temporary_") { Temporary::Yes } else { Temporary::No };
245 vd.identifier("scope name");
246 sc.save_current_scope(vd.value().as_str(), temp);
247 vd.accept();
248}
249
250#[cfg(feature = "jomini")]
252pub fn validate_save_scope_value(
253 key: &Token,
254 _block: &Block,
255 _data: &Everything,
256 sc: &mut ScopeContext,
257 mut vd: Validator,
258 _tooltipped: Tooltipped,
259) {
260 let temp = if key.as_str().contains("_temporary_") { Temporary::Yes } else { Temporary::No };
261 vd.req_field("name");
262 vd.req_field("value");
263 if let Some(name) = vd.field_identifier_or_flag("name", sc) {
264 sc.define_name_token(name.as_str(), Scopes::primitive(), name, temp);
266 }
267 vd.field_script_value_or_flag("value", sc);
268}
269
270#[cfg(feature = "jomini")]
272pub fn validate_set_variable(
273 key: &Token,
274 bv: &BV,
275 data: &Everything,
276 sc: &mut ScopeContext,
277 _tooltipped: Tooltipped,
278) {
279 match bv {
280 BV::Value(token) => {
281 validate_identifier(token, "variable name", Severity::Error);
282 if key.as_str().contains("_local_") {
283 sc.set_local_variable(token, Scopes::Bool);
284 } else if key.as_str().contains("_global_") {
285 data.global_scopes.expect(token.as_str(), token, Scopes::Bool);
286 } else {
287 data.variable_scopes.expect(token.as_str(), token, Scopes::Bool);
288 }
289 }
290 BV::Block(block) => {
291 let mut vd = Validator::new(block, data);
292 vd.set_case_sensitive(false);
293 vd.req_field("name");
294 let name = vd.field_identifier("name", "variable name").cloned();
295 vd.field_validated("value", |bv, data| match bv {
296 BV::Value(token) => {
297 if let Some(name) = &name {
298 if key.as_str().contains("_local_") {
299 let target_scopes = sc.local_variable_scopes(name.as_str(), data);
300 let outscopes = validate_target_ok_this(token, data, sc, target_scopes);
301 sc.set_local_variable(name, outscopes);
302 } else if key.as_str().contains("_global_") {
303 let target_scopes = data.global_scopes.scopes(name.as_str());
304 let outscopes = validate_target_ok_this(token, data, sc, target_scopes);
305 data.global_scopes.expect(name.as_str(), name, outscopes);
306 } else {
307 let target_scopes = data.variable_scopes.scopes(name.as_str());
308 let outscopes = validate_target_ok_this(token, data, sc, target_scopes);
309 data.variable_scopes.expect(name.as_str(), name, outscopes);
310 }
311 }
312 }
313 BV::Block(_) => {
314 #[cfg(feature = "jomini")]
315 if Game::is_jomini() {
316 validate_script_value(bv, data, sc);
317 if let Some(name) = &name {
318 if key.as_str().contains("_local_") {
319 sc.set_local_variable(name, Scopes::Value);
320 } else if key.as_str().contains("_global_") {
321 data.global_scopes.expect(name.as_str(), name, Scopes::Value);
322 } else {
323 data.variable_scopes.expect(name.as_str(), name, Scopes::Value);
324 }
325 }
326 }
327 }
329 });
330 validate_optional_duration(&mut vd, sc);
331 }
332 }
333}
334
335#[cfg(feature = "jomini")]
337pub fn validate_switch(
338 key: &Token,
339 _block: &Block,
340 data: &Everything,
341 sc: &mut ScopeContext,
342 mut vd: Validator,
343 tooltipped: Tooltipped,
344) {
345 vd.set_case_sensitive(true);
346 vd.req_field("trigger");
347 if let Some(target) = vd.field_value("trigger").cloned() {
348 let mut count = 0;
349 vd.set_allow_questionmark_equals(true);
350 vd.unknown_block_fields(|key, block| {
351 count += 1;
352 if !key.is("fallback") {
353 let synthetic_bv = BV::Value(key.clone());
355 validate_trigger_key_bv(
356 &target,
357 Comparator::Equals(Single),
358 &synthetic_bv,
359 data,
360 sc,
361 tooltipped,
362 false,
363 Severity::Error,
364 );
365 }
366
367 validate_effect(block, data, sc, tooltipped);
368 });
369 if count == 0 {
370 let msg = "switch with no branches";
371 err(ErrorKey::Logic).msg(msg).loc(key).push();
372 }
373 }
374}
375
376#[cfg(feature = "jomini")]
377pub fn validate_trigger_event(
378 _key: &Token,
379 bv: &BV,
380 data: &Everything,
381 sc: &mut ScopeContext,
382 _tooltipped: Tooltipped,
383) {
384 match bv {
385 BV::Value(token) => {
386 data.verify_exists(Item::Event, token);
387 data.event_check_scope(token, sc);
388 if let Some(mut event_sc) = sc.root_for_event(token, data) {
389 data.event_validate_call(token, &mut event_sc);
390 }
391 }
392 BV::Block(block) => {
393 let mut vd = Validator::new(block, data);
394 vd.set_case_sensitive(false);
395 vd.field_event("id", sc);
396 vd.field_action("on_action", sc);
397 #[cfg(feature = "ck3")]
398 if Game::is_ck3() {
399 vd.field_target("saved_event_id", sc, Scopes::Flag);
400 vd.field_date("trigger_on_next_date");
401 vd.field_bool("delayed");
402 }
403 #[cfg(feature = "vic3")]
404 if Game::is_vic3() {
405 vd.field_bool("popup");
406 }
407 validate_optional_duration(&mut vd, sc);
408 }
409 }
410}