tiger_lib/vic3/tables/
modifs.rs

1#![allow(non_upper_case_globals)]
2
3use std::borrow::Cow;
4use std::sync::LazyLock;
5
6use crate::everything::Everything;
7use crate::helpers::{TigerHashMap, TigerHashMapExt};
8use crate::item::Item;
9use crate::lowercase::Lowercase;
10use crate::report::{ErrorKey, Severity, report, untidy};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::vic3::modif::ModifKinds;
14
15pub fn lookup_modif(name: &Token, data: &Everything, warn: Option<Severity>) -> Option<ModifKinds> {
16    let name_lc = Lowercase::new(name.as_str());
17
18    let kind = lookup_engine_modif(name, &name_lc, data, warn);
19
20    // The Item::ModifierType must exist
21    if data.item_exists_lc(Item::ModifierTypeDefinition, &name_lc) {
22        return kind.or_else(|| Some(lookup_modif_prefix(name)));
23    } else if let Some(sev) = warn {
24        let info = kind
25            .and(Some("You can create it and the game engine will apply the modifier"))
26            .unwrap_or("You can create it and the modifier will appear in game, but the engine will not apply any effect");
27        report(ErrorKey::MissingItem, sev)
28            .msg("modifier type definition does not exist")
29            .info(info)
30            .wiki("https://vic3.paradoxwikis.com/Modifier_types#Defining_modifier_types")
31            .loc(name)
32            .push();
33    }
34
35    None
36}
37
38fn lookup_modif_prefix(name: &Token) -> ModifKinds {
39    for (prefix, kind) in MODIF_PREFIX_TABLE {
40        if name.as_str().starts_with(prefix) {
41            return kind;
42        }
43    }
44    // A modifier that doesn't use one of the prefixes will appear in the scope it was added.
45    // The game will log an error for this, but it otherwise works.
46    untidy(ErrorKey::Modifiers)
47        .msg("script only modifier does not use a valid prefix")
48        .info("consider using a prefix to ensure the modifier flows to the intended scope")
49        .wiki("https://vic3.paradoxwikis.com/Modifier_types#Modifier_type_flow")
50        .loc(name)
51        .push();
52    ModifKinds::all()
53}
54
55/// Returns Some(kinds) if the token is a valid modif or *could* be a valid modif if the appropriate item existed.
56/// Returns None otherwise.
57pub fn lookup_engine_modif(
58    name: &Token,
59    name_lc: &Lowercase,
60    data: &Everything,
61    warn: Option<Severity>,
62) -> Option<ModifKinds> {
63    if let result @ Some(_) = MODIF_MAP.get(name_lc).copied() {
64        return result;
65    }
66
67    if let Some(info) = MODIF_REMOVED_MAP.get(name_lc).copied() {
68        if let Some(sev) = warn {
69            let msg = format!("{name} has been removed");
70            report(ErrorKey::Removed, sev).msg(msg).info(info).loc(name).push();
71        }
72        return None;
73    }
74
75    // Look up generated modifs, in a careful order because of possibly overlapping suffixes.
76
77    // building_employment_$PopType$_add
78    // building_employment_$PopType$_mult
79    if let Some(part) = name_lc.strip_prefix_unchecked("building_employment_") {
80        for &sfx in &["_add", "_mult"] {
81            if let Some(part) = part.strip_suffix_unchecked(sfx) {
82                maybe_warn(Item::PopType, &part, name, data, warn);
83                return Some(ModifKinds::Building);
84            }
85        }
86    }
87    // building_group_$BuildingGroup$_$PopType$_fertility_mult
88    // building_group_$BuildingGroup$_$PopType$_mortality_mult
89    // building_group_$BuildingGroup$_$PopType$_standard_of_living_add
90    // building_group_$BuildingGroup$_allowed_collectivization_add
91    // building_group_$BuildingGroup$_employee_mult
92    // building_group_$BuildingGroup$_fertility_mult
93    // building_group_$BuildingGroup$_infrastructure_usage_mult
94    // building_group_$BuildingGroup$_mortality_mult
95    // building_group_$BuildingGroup$_self_investment_chance_add
96    // building_group_$BuildingGroup$_standard_of_living_add
97    // building_group_$BuildingGroup$_throughput_mult (obsolete)
98    // building_group_$BuildingGroup$_unincorporated_throughput_add
99    // building_group_$BuildingGroup$_throughput_add
100    // building_group_$BuildingGroup$_tax_mult
101    if let Some(part) = name_lc.strip_prefix_unchecked("building_group_") {
102        for &sfx in &["_fertility_mult", "_mortality_mult", "_standard_of_living_add"] {
103            if let Some(part) = part.strip_suffix_unchecked(sfx) {
104                // This is tricky because both BuildingGroup and PopType can have `_` in them.
105                for (i, _) in part.rmatch_indices_unchecked('_') {
106                    if data.item_exists_lc(Item::PopType, &part.slice(i + 1..)) {
107                        maybe_warn(Item::BuildingGroup, &part.slice(..i), name, data, warn);
108                        return Some(ModifKinds::Building);
109                    }
110                }
111                // Check if it's the kind without $PopType$
112                maybe_warn(Item::BuildingGroup, &part, name, data, warn);
113                return Some(ModifKinds::Building);
114            }
115        }
116        for &sfx in &[
117            "_allowed_collectivization_add",
118            "_infrastructure_usage_mult",
119            "_employee_mult",
120            "_tax_mult",
121            "_unincorporated_throughput_add",
122            "_throughput_add",
123            "_self_investment_chance_add",
124            "_construction_efficiency_add",
125        ] {
126            if let Some(part) = part.strip_suffix_unchecked(sfx) {
127                maybe_warn(Item::BuildingGroup, &part, name, data, warn);
128                return Some(ModifKinds::Building);
129            }
130        }
131        if let Some(part) = part.strip_suffix_unchecked("_throughput_mult") {
132            maybe_warn(Item::BuildingGroup, &part, name, data, warn);
133            if let Some(sev) = warn {
134                let msg = format!("`{name}` was removed in 1.5");
135                let info = "it was replaced with `_add`";
136                report(ErrorKey::Removed, sev).msg(msg).info(info).loc(name).push();
137            }
138            return Some(ModifKinds::Building);
139        }
140    }
141
142    // $BuildingType$_throughput_mult (obsolete)
143    if let Some(part) = name_lc.strip_suffix_unchecked("_throughput_mult") {
144        maybe_warn(Item::BuildingType, &part, name, data, warn);
145        if let Some(sev) = warn {
146            let msg = format!("`{name}` was removed in 1.5");
147            let info = "it was replaced with `_add`";
148            report(ErrorKey::Removed, sev).msg(msg).info(info).loc(name).push();
149        }
150        return Some(ModifKinds::Building);
151    }
152    // $BuildingType$_throughput_add
153    if let Some(part) = name_lc.strip_suffix_unchecked("_throughput_add") {
154        maybe_warn(Item::BuildingType, &part, name, data, warn);
155        return Some(ModifKinds::Building);
156    }
157
158    // building_$PopType$_fertility_mult
159    // building_$PopType$_job_attractiveness_mult
160    // building_$PopType$_mortality_mult
161    // building_$PopType$_shares_add
162    // building_$PopType$_shares_mult
163    // building_$PopType$_standard_of_living_add
164    if let Some(part) = name_lc.strip_prefix_unchecked("building_") {
165        for &sfx in &[
166            "_fertility_mult",
167            "_job_attractiveness_mult",
168            "_mortality_mult",
169            "_shares_add",
170            "_shares_mult",
171            "_standard_of_living_add",
172        ] {
173            if let Some(part) = part.strip_suffix_unchecked(sfx) {
174                maybe_warn(Item::PopType, &part, name, data, warn);
175                return Some(ModifKinds::Building);
176            }
177        }
178    }
179
180    // building_input_$Goods$_add (obsolete)
181    if let Some(part) = name_lc.strip_prefix_unchecked("building_input_") {
182        if let Some(part) = part.strip_suffix_unchecked("_add") {
183            maybe_warn(Item::Goods, &part, name, data, warn);
184            if let Some(sev) = warn {
185                let msg = format!("`{name}` was removed in 1.5");
186                let info = format!("replaced with `goods_input_{part}_add`");
187                report(ErrorKey::Removed, sev).msg(msg).info(info).loc(name).push();
188            }
189            return Some(ModifKinds::Building);
190        }
191    }
192
193    // goods_input_$Goods$_add
194    // goods_input_$Goods$_mult
195    if let Some(part) = name_lc.strip_prefix_unchecked("goods_input_") {
196        for &sfx in &["_add", "_mult"] {
197            if let Some(part) = part.strip_suffix_unchecked(sfx) {
198                maybe_warn(Item::Goods, &part, name, data, warn);
199                return Some(ModifKinds::Goods);
200            }
201        }
202    }
203
204    // goods_trade_advantage_$Goods$_mult
205    if let Some(part) = name_lc.strip_prefix_unchecked("goods_trade_advantage_") {
206        if let Some(part) = part.strip_suffix_unchecked("_mult") {
207            maybe_warn(Item::Goods, &part, name, data, warn);
208            return Some(ModifKinds::Goods);
209        }
210    }
211
212    // building_output_$Goods$_add (obsolete)
213    // building_output_$Goods$_mult (obsolete)
214    if let Some(part) = name_lc.strip_prefix_unchecked("building_output_") {
215        for &sfx in &["_add", "_mult"] {
216            if let Some(part) = part.strip_suffix_unchecked(sfx) {
217                maybe_warn(Item::Goods, &part, name, data, warn);
218                if let Some(sev) = warn {
219                    let msg = format!("`{name}` was removed in 1.5");
220                    let info = format!("it was replaced with `goods_output_{part}{sfx}`");
221                    report(ErrorKey::Removed, sev).msg(msg).info(info).loc(name).push();
222                }
223                return Some(ModifKinds::Building);
224            }
225        }
226    }
227    // goods_output_$Goods$_add
228    // goods_output_$Goods$_mult
229    if let Some(part) = name_lc.strip_prefix_unchecked("goods_output_") {
230        for &sfx in &["_add", "_mult"] {
231            if let Some(part) = part.strip_suffix_unchecked(sfx) {
232                maybe_warn(Item::Goods, &part, name, data, warn);
233                return Some(ModifKinds::Goods);
234            }
235        }
236    }
237
238    // character_$BattleCondition$_mult
239    if let Some(part) = name_lc.strip_prefix_unchecked("character_") {
240        if let Some(part) = part.strip_suffix_unchecked("_mult") {
241            maybe_warn(Item::BattleCondition, &part, name, data, warn);
242            return Some(ModifKinds::Character);
243        }
244    }
245
246    // country_institution_cost_$Institution$_add
247    // country_institution_cost_$Institution$_mult
248    if let Some(part) = name_lc.strip_prefix_unchecked("country_institution_cost_") {
249        for &sfx in &["_add", "_mult"] {
250            if let Some(part) = part.strip_suffix_unchecked(sfx) {
251                maybe_warn(Item::Institution, &part, name, data, warn);
252                return Some(ModifKinds::Country);
253            }
254        }
255    }
256
257    // country_institution_impact_$Institution$_mult
258    if let Some(part) = name_lc.strip_prefix_unchecked("country_institution_impact_") {
259        if let Some(part) = part.strip_suffix_unchecked("_mult") {
260            maybe_warn(Item::Institution, &part, name, data, warn);
261            return Some(ModifKinds::Country);
262        }
263    }
264
265    // country_institution_size_change_speed_$Institution$_mult
266    if let Some(part) = name_lc.strip_prefix_unchecked("country_institution_size_change_speed_") {
267        if let Some(part) = part.strip_suffix_unchecked("_mult") {
268            maybe_warn(Item::Institution, &part, name, data, warn);
269            return Some(ModifKinds::Country);
270        }
271    }
272
273    // country_can_impose_same_$LawGroup$_in_power_bloc_bool
274    if let Some(part) = name_lc.strip_prefix_unchecked("country_can_impose_same_") {
275        if let Some(part) = part.strip_suffix_unchecked("_in_power_bloc_bool") {
276            maybe_warn(Item::LawGroup, &part, name, data, warn);
277            return Some(ModifKinds::Country);
278        }
279    }
280
281    // country_$PopType$_pol_str_mult
282    // country_$PopType$_voting_power_add
283    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
284        for &sfx in &["_pol_str_mult", "_voting_power_add"] {
285            if let Some(part) = part.strip_suffix_unchecked(sfx) {
286                maybe_warn(Item::PopType, &part, name, data, warn);
287                return Some(ModifKinds::Country);
288            }
289        }
290    }
291
292    // country_$Institution$_max_investment_add
293    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
294        if let Some(part) = part.strip_suffix_unchecked("_max_investment_add") {
295            maybe_warn(Item::Institution, &part, name, data, warn);
296            return Some(ModifKinds::Country);
297        }
298    }
299
300    // country_subsidies_$BuildingGroup$
301    if let Some(part) = name_lc.strip_prefix_unchecked("country_subsidies_") {
302        maybe_warn(Item::BuildingGroup, &part, name, data, warn);
303        if let Some(sev) = warn {
304            let msg = format!("`{name}` was removed in 1.7");
305            report(ErrorKey::Removed, sev).msg(msg).loc(name).push();
306        }
307        return Some(ModifKinds::Country);
308    }
309
310    // country_enactment_success_chance_$Law$_add
311    if let Some(part) = name_lc.strip_prefix_unchecked("country_enactment_success_chance_") {
312        if let Some(part) = part.strip_suffix_unchecked("_add") {
313            maybe_warn(Item::LawType, &part, name, data, warn);
314            return Some(ModifKinds::Country);
315        }
316    }
317
318    // country_allow_assimilation_$AcceptanceStatus$_bool
319    // country_allow_conversion_$AcceptanceStatus$_bool
320    // country_allow_voting_$AcceptanceStatus$_bool
321    // country_disallow_government_work_$AcceptanceStatus$_bool
322    // country_disallow_military_work_$AcceptanceStatus$_bool
323    for &pfx in &[
324        "country_allow_assimilation_",
325        "country_allow_conversion_",
326        "country_allow_voting_",
327        "country_disallow_government_work_",
328        "country_disallow_military_work_",
329    ] {
330        if let Some(part) = name_lc.strip_prefix_unchecked(pfx) {
331            if let Some(part) = part.strip_suffix_unchecked("_bool") {
332                maybe_warn(Item::AcceptanceStatus, &part, name, data, warn);
333                return Some(ModifKinds::Country);
334            }
335        }
336    }
337
338    // country_disallow_$Law$_bool
339    if let Some(part) = name_lc.strip_prefix_unchecked("country_disallow_") {
340        if let Some(part) = part.strip_suffix_unchecked("_bool") {
341            maybe_warn(Item::LawType, &part, name, data, warn);
342            return Some(ModifKinds::Country);
343        }
344    }
345
346    // country_assimilation_$AcceptanceStatus$_mult
347    // country_loyalism_increases_$AcceptanceStatus$_mult
348    // country_political_strength_$AcceptanceStatus$_mult
349    // country_qualification_growth_$AcceptanceStatus$_mult
350    // country_radicalism_increases_$AcceptanceStatus$_mult
351    // country_voting_power_$AcceptanceStatus$_mult
352    // country_wage_$AcceptanceStatus$_mult
353    for &pfx in &[
354        "country_assimilation_",
355        "country_loyalism_increases_",
356        "country_political_strength_",
357        "country_qualification_growth_",
358        "country_radicalism_increases_",
359        "country_voting_power_",
360        "country_wage_",
361    ] {
362        if let Some(part) = name_lc.strip_prefix_unchecked(pfx) {
363            if let Some(part) = part.strip_suffix_unchecked("_mult") {
364                maybe_warn(Item::AcceptanceStatus, &part, name, data, warn);
365                return Some(ModifKinds::Country);
366            }
367        }
368    }
369
370    // country_standard_of_living_$AcceptanceStatus$_add
371    if let Some(part) = name_lc.strip_prefix_unchecked("country_standard_of_living_") {
372        if let Some(part) = part.strip_suffix_unchecked("_add") {
373            maybe_warn(Item::AcceptanceStatus, &part, name, data, warn);
374            return Some(ModifKinds::Country);
375        }
376    }
377
378    // country_fervor_target_$Culture$_add
379    if let Some(part) = name_lc.strip_prefix_unchecked("country_fervor_target_") {
380        if let Some(part) = part.strip_suffix_unchecked("_add") {
381            maybe_warn(Item::Culture, &part, name, data, warn);
382            return Some(ModifKinds::Country);
383        }
384    }
385
386    // country_$SocialClass$_cultural_acceptance_add
387    // country_$Culture$_cultural_acceptance_add
388    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
389        if let Some(part) = part.strip_suffix_unchecked("_cultural_acceptance_add") {
390            if let Some(sev) = warn {
391                if !data.item_exists_lc(Item::SocialClass, &part)
392                    && !data.item_exists_lc(Item::Culture, &part)
393                {
394                    let msg = format!("{part} not found as culture or social class");
395                    let info = format!("so the modifier {name} will have no effect");
396                    report(ErrorKey::MissingItem, sev).msg(msg).info(info).loc(name).push();
397                }
398            }
399            return Some(ModifKinds::Country);
400        }
401    }
402
403    // country_$SocialClass$_acceptance_max_add
404    // country_$SocialClass$_acceptance_min_add
405    // country_$SocialClass$_cultural_acceptance_mult
406    // country_$SocialClass$_education_access_add
407    // country_$SocialClass$_education_access_mult
408    // country_$SocialClass$_qualification_growth_add
409    // country_$SocialClass$_qualification_growth_mult
410    // country_$SocialClass$_qualification_growth_other_class
411    // country_$SocialClass$_qualification_growth_same_class
412    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
413        for &sfx in &[
414            "_acceptance_max_add",
415            "_acceptance_min_add",
416            "_cultural_acceptance_add",
417            "_cultural_acceptance_mult",
418            "_education_access_add",
419            "_education_access_mult",
420            "_qualification_growth_add",
421            "_qualification_growth_mult",
422            "_qualification_growth_other_class",
423            "_qualification_growth_same_class",
424        ] {
425            if let Some(part) = part.strip_suffix_unchecked(sfx) {
426                maybe_warn(Item::SocialClass, &part, name, data, warn);
427                return Some(ModifKinds::Country);
428            }
429        }
430    }
431
432    // country_enactment_time_$Law$_mult
433    if let Some(part) = name_lc.strip_prefix_unchecked("country_enactment_time_") {
434        if let Some(part) = part.strip_suffix_unchecked("_mult") {
435            maybe_warn(Item::LawType, &part, name, data, warn);
436            return Some(ModifKinds::Country);
437        }
438    }
439
440    // country_$BuildingGroup$_require_subsidies_bool
441    // country_$Building$_require_subsidies_bool
442    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
443        if let Some(part) = part.strip_suffix_unchecked("_require_subsidies_bool") {
444            if let Some(sev) = warn {
445                if !data.item_exists_lc(Item::BuildingGroup, &part)
446                    && !data.item_exists_lc(Item::BuildingType, &part)
447                {
448                    let msg = format!("{part} not found as building type or building group");
449                    let info = format!("so the modifier {name} will have no effect");
450                    report(ErrorKey::MissingItem, sev).msg(msg).info(info).loc(name).push();
451                }
452            }
453            return Some(ModifKinds::Country);
454        }
455    }
456
457    // country_$Goods$_export_tariffs_rate_add
458    // country_$Goods$_import_tariffs_rate_add
459    // country_$Goods$_max_export_tariffs_level_add
460    // country_$Goods$_max_import_tariffs_level_add
461    // country_$Goods$_min_export_tariffs_level_add
462    // country_$Goods$_min_import_tariffs_level_add
463    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
464        for &sfx in &[
465            "_export_tariffs_rate_add",
466            "_import_tariffs_rate_add",
467            "_max_export_tariffs_level_add",
468            "_max_import_tariffs_level_add",
469            "_min_export_tariffs_level_add",
470            "_min_import_tariffs_level_add",
471        ] {
472            if let Some(part) = part.strip_suffix_unchecked(sfx) {
473                maybe_warn(Item::Goods, &part, name, data, warn);
474                return Some(ModifKinds::Country);
475            }
476        }
477    }
478
479    // interest_group_$InterestGroup$_approval_add
480    // interest_group_$InterestGroup$_pol_str_mult
481    // interest_group_$InterestGroup$_pop_attraction_mult
482    if let Some(part) = name_lc.strip_prefix_unchecked("interest_group_") {
483        for &sfx in &["_approval_add", "_pol_str_mult", "_pop_attraction_mult"] {
484            if let Some(part) = part.strip_suffix_unchecked(sfx) {
485                maybe_warn(Item::InterestGroup, &part, name, data, warn);
486                return Some(ModifKinds::InterestGroup);
487            }
488        }
489    }
490
491    // state_$Culture$_standard_of_living_add
492    // state_$Religion$_standard_of_living_add
493    if let Some(part) = name_lc.strip_prefix_unchecked("state_") {
494        if let Some(part) = part.strip_suffix_unchecked("_standard_of_living_add") {
495            if let Some(sev) = warn {
496                if !data.item_exists_lc(Item::Religion, &part)
497                    && !data.item_exists_lc(Item::Culture, &part)
498                {
499                    let msg = format!("{part} not found as culture or religion");
500                    let info = format!("so the modifier {name} will have no effect");
501                    report(ErrorKey::MissingItem, sev).msg(msg).info(info).loc(name).push();
502                }
503            }
504            return Some(ModifKinds::State);
505        }
506    }
507
508    // state_$PopType$_consumption_multiplier_add
509    // state_$PopType$_dependent_wage_mult
510    // state_$PopType$_internal_migration_disallowed_bool
511    // state_$PopType$_investment_pool_contribution_add
512    // state_$PopType$_investment_pool_efficiency_mult
513    // state_$PopType$_mass_migration_disallowed_bool
514    // state_$PopType$_mortality_mult
515    if let Some(part) = name_lc.strip_prefix_unchecked("state_") {
516        for &sfx in &[
517            "_consumption_multiplier_add",
518            "_dependent_wage_mult",
519            "_internal_migration_disallowed_bool",
520            "_investment_pool_contribution_add",
521            "_investment_pool_efficiency_mult",
522            "_mass_migration_disallowed_bool",
523            "_mortality_mult",
524        ] {
525            if let Some(part) = part.strip_suffix_unchecked(sfx) {
526                maybe_warn(Item::PopType, &part, name, data, warn);
527                return Some(ModifKinds::State);
528            }
529        }
530    }
531
532    // state_pop_support_$PoliticalMovement$_add
533    // state_pop_support_$PoliticalMovement$_mult
534    if let Some(part) = name_lc.strip_prefix_unchecked("state_pop_support_") {
535        for &sfx in &["_add", "_mult"] {
536            if let Some(part) = part.strip_suffix_unchecked(sfx) {
537                if data.item_exists_lc(Item::LawType, &part) {
538                    if let Some(sev) = warn {
539                        let msg = "support for law types has been replaced by support for political movements";
540                        report(ErrorKey::Removed, sev).msg(msg).loc(name).push();
541                    }
542                } else {
543                    maybe_warn(Item::PoliticalMovement, &part, name, data, warn);
544                }
545                return Some(ModifKinds::State);
546            }
547        }
548    }
549
550    // state_sell_orders_$Goods$_add
551    if let Some(part) = name_lc.strip_prefix_unchecked("state_sell_orders_") {
552        if let Some(part) = part.strip_suffix_unchecked("_add") {
553            maybe_warn(Item::Goods, &part, name, data, warn);
554            return Some(ModifKinds::State);
555        }
556    }
557    // state_buy_orders_$Goods$_add
558    if let Some(part) = name_lc.strip_prefix_unchecked("state_buy_orders_") {
559        if let Some(part) = part.strip_suffix_unchecked("_add") {
560            maybe_warn(Item::Goods, &part, name, data, warn);
561            return Some(ModifKinds::State);
562        }
563    }
564
565    // state_$Building$_max_level_add
566    if let Some(part) = name_lc.strip_prefix_unchecked("state_") {
567        if let Some(part) = part.strip_suffix_unchecked("_max_level_add") {
568            maybe_warn(Item::BuildingType, &part, name, data, warn);
569
570            if let Some(sev) = warn {
571                if data.item_exists(Item::BuildingType, part.as_str())
572                    && !data.item_has_property(Item::BuildingType, part.as_str(), "max_level")
573                {
574                    let msg = format!("building {part} does not have `has_max_level = yes`");
575                    let info = format!("so the modifier {name} will have no effect");
576                    report(ErrorKey::MissingItem, sev)
577                        .strong()
578                        .msg(msg)
579                        .info(info)
580                        .loc(name)
581                        .push();
582                }
583            }
584            return Some(ModifKinds::State);
585        }
586    }
587
588    // state_harvest_condition_$HarvestConditionType$_duration_mult
589    // state_harvest_condition_$HarvestConditionType$_impact_mult
590    if let Some(part) = name_lc.strip_prefix_unchecked("state_harvest_condition_") {
591        for &sfx in &["_duration_mult", "_impact_mult"] {
592            if let Some(part) = part.strip_suffix_unchecked(sfx) {
593                maybe_warn(Item::HarvestConditionType, &part, name, data, warn);
594                return Some(ModifKinds::State);
595            }
596        }
597    }
598
599    // state_loyalism_increases_$AcceptanceStatus$_mult
600    // state_radicalism_increases_$AcceptanceStatus$_mult
601    for &pfx in &["state_loyalism_increases_", "state_radicalism_increases_"] {
602        if let Some(part) = name_lc.strip_prefix_unchecked(pfx) {
603            if let Some(part) = part.strip_suffix_unchecked("_mult") {
604                maybe_warn(Item::AcceptanceStatus, &part, name, data, warn);
605                return Some(ModifKinds::State);
606            }
607        }
608    }
609
610    // unit_defense_$TerrainKey$_add
611    // unit_defense_$TerrainKey$_mult
612    // unit_offense_$TerrainKey$_mult
613    // unit_offense_$TerrainKey$_mult
614    for &pfx in &["unit_defense_", "unit_offense_"] {
615        if let Some(part) = name_lc.strip_prefix_unchecked(pfx) {
616            for &sfx in &["_add", "_mult"] {
617                if let Some(part) = part.strip_suffix_unchecked(sfx) {
618                    maybe_warn(Item::TerrainKey, &part, name, data, warn);
619                    return Some(ModifKinds::UnitCombat);
620                }
621            }
622        }
623    }
624
625    // unit_$CombatUnit$_offense_mult
626    // unit_$CombatUnit$_offense_add
627    if let Some(part) = name_lc.strip_prefix_unchecked("unit_") {
628        for &sfx in &["_offense_add", "_offense_mult"] {
629            if let Some(part) = part.strip_suffix_unchecked(sfx) {
630                maybe_warn(Item::CombatUnit, &part, name, data, warn);
631                return Some(ModifKinds::UnitCombat);
632            }
633        }
634    }
635
636    // power_bloc_invite_acceptance_$CountryRank$_add
637    if let Some(part) = name_lc.strip_prefix_unchecked("power_bloc_invite_acceptance_") {
638        if let Some(part) = part.strip_suffix_unchecked("_add") {
639            maybe_warn(Item::CountryRank, &part, name, data, warn);
640            return Some(ModifKinds::PowerBloc);
641        }
642    }
643
644    // power_bloc_mandate_progress_per_$CountryRank$_member_add
645    // power_bloc_mandate_progress_per_$CountryRank$_member_mult
646    if let Some(part) = name_lc.strip_prefix_unchecked("power_bloc_mandate_progress_per_") {
647        for &sfx in &["_member_add", "_member_mult"] {
648            if let Some(part) = part.strip_suffix_unchecked(sfx) {
649                maybe_warn(Item::CountryRank, &part, name, data, warn);
650                return Some(ModifKinds::PowerBloc);
651            }
652        }
653    }
654
655    // TODO: modifiers from terrain labels
656
657    None
658}
659
660fn maybe_warn(itype: Item, s: &Lowercase, name: &Token, data: &Everything, warn: Option<Severity>) {
661    if let Some(sev) = warn {
662        if !data.item_exists_lc(itype, s) {
663            let msg = format!("could not find {itype} {s}");
664            let info = format!("so the modifier {name} will have no effect");
665            report(ErrorKey::MissingItem, sev).strong().msg(msg).info(info).loc(name).push();
666        }
667    }
668}
669
670pub fn maybe_warn_modifiable_capitalization(name: &Token) {
671    if !name.is_lowercase() {
672        untidy(ErrorKey::DefinitionName)
673            .msg("item name contains capital letters, but modifier type definition names must be lowercase")
674            .info("so dynamic modifiers for this item will not be available")
675            .loc(name)
676            .push();
677    }
678}
679
680/// Return the modifier localization keys.
681/// It's usually just the name, but there are known exceptions.
682pub fn modif_loc_vic3(name: &Token, data: &Everything) -> (Cow<'static, str>, Cow<'static, str>) {
683    let name_lc = Lowercase::new(name.as_str());
684
685    if MODIF_MAP.contains_key(&name_lc) {
686        let desc_loc = format!("{name_lc}_desc");
687        return (name_lc.into_cow(), Cow::Owned(desc_loc));
688    }
689
690    if let Some(part) = name_lc.strip_prefix_unchecked("state_") {
691        if let Some(part) = part.strip_suffix_unchecked("_standard_of_living_add") {
692            if data.item_exists_lc(Item::Religion, &part) {
693                return (
694                    Cow::Borrowed("STATE_RELIGION_SOL_MODIFIER"),
695                    Cow::Borrowed("STATE_RELIGION_SOL_MODIFIER_DESC"),
696                );
697            } else if data.item_exists_lc(Item::Culture, &part) {
698                return (
699                    Cow::Borrowed("STATE_CULTURE_SOL_MODIFIER"),
700                    Cow::Borrowed("STATE_CULTURE_SOL_MODIFIER_DESC"),
701                );
702            }
703            // We need some kind of default for missing items, and cultures are more common.
704            return (
705                Cow::Borrowed("STATE_RELIGION_SOL_MODIFIER"),
706                Cow::Borrowed("STATE_RELIGION_SOL_MODIFIER_DESC"),
707            );
708        }
709    }
710
711    if let Some(part) = name_lc.strip_prefix_unchecked("country_fervor_target_") {
712        if part.strip_suffix_unchecked("_add").is_some() {
713            return (
714                Cow::Borrowed("COUNTRY_FERVOR_TARGET_CULTURE_MODIFIER"),
715                Cow::Borrowed("COUNTRY_FERVOR_TARGET_CULTURE_MODIFIER_DESC"),
716            );
717        }
718    }
719
720    if let Some(part) = name_lc.strip_prefix_unchecked("country_") {
721        if part.strip_suffix_unchecked("_cultural_acceptance_add").is_some() {
722            return (
723                Cow::Borrowed("COUNTRY_CULTURE_CULTURAL_ACCEPTANCE_MODIFIER"),
724                Cow::Borrowed("COUNTRY_CULTURE_CULTURAL_ACCEPTANCE_MODIFIER_DESC"),
725            );
726        }
727    }
728
729    // TODO: should the loca key be lowercased?
730    let desc_loc = format!("{name}_desc");
731    (Cow::Borrowed(name.as_str()), Cow::Owned(desc_loc))
732}
733
734const MODIF_PREFIX_TABLE: [(&str, ModifKinds); 13] = [
735    ("character_", ModifKinds::Character),
736    ("country_", ModifKinds::Country),
737    ("state_", ModifKinds::State),
738    ("unit_", ModifKinds::Unit),
739    ("battle_", ModifKinds::Battle),
740    ("building_", ModifKinds::Building),
741    ("interest_group_", ModifKinds::InterestGroup),
742    ("market_", ModifKinds::Market),
743    ("political_movement_", ModifKinds::PoliticalMovement),
744    ("tax_", ModifKinds::Tax),
745    ("goods_", ModifKinds::Goods),
746    ("military_formation_", ModifKinds::MilitaryFormation),
747    ("power_bloc_", ModifKinds::PowerBloc),
748];
749
750static MODIF_MAP: LazyLock<TigerHashMap<Lowercase<'static>, ModifKinds>> = LazyLock::new(|| {
751    let mut hash = TigerHashMap::default();
752    for (s, kind) in MODIF_TABLE.iter().copied() {
753        hash.insert(Lowercase::new_unchecked(s), kind);
754    }
755    hash
756});
757
758/// LAST UPDATED VIC3 VERSION 1.8.4
759/// See `modifiers.log` from the game data dumps.
760/// A `modif` is my name for the things that modifiers modify.
761const MODIF_TABLE: &[(&str, ModifKinds)] = &[
762    ("battle_casualties_mult", ModifKinds::Battle),
763    ("battle_combat_width_mult", ModifKinds::Battle),
764    ("battle_defense_owned_province_mult", ModifKinds::Battle),
765    ("battle_offense_owned_province_mult", ModifKinds::Battle),
766    ("battle_total_combat_width_mult", ModifKinds::Battle),
767    ("building_cash_reserves_mult", ModifKinds::Building),
768    ("building_company_government_dividends_add", ModifKinds::Building),
769    ("building_company_worker_dividends_add", ModifKinds::Building),
770    ("building_economy_of_scale_level_cap_add", ModifKinds::Building),
771    ("building_goods_input_mult", ModifKinds::Building),
772    ("building_job_attractiveness_mult", ModifKinds::Building),
773    ("building_level_bureaucracy_cost_add", ModifKinds::Building),
774    ("building_minimum_incorporated_subsistence_employment_add", ModifKinds::Building),
775    ("building_minimum_wage_mult", ModifKinds::Building),
776    ("building_mobilization_cost_mult", ModifKinds::Building),
777    ("building_nationalization_cost_mult", ModifKinds::Building),
778    ("building_nationalization_investment_return_add", ModifKinds::Building),
779    ("building_nationalization_radicals_mult", ModifKinds::Building),
780    ("building_self_investment_chance_add", ModifKinds::Building),
781    ("building_subsistence_output_add", ModifKinds::Building),
782    ("building_subsistence_output_mult", ModifKinds::Building),
783    ("building_throughput_add", ModifKinds::Building),
784    ("building_training_rate_add", ModifKinds::Building),
785    ("building_training_rate_mult", ModifKinds::Building),
786    ("building_unincorporated_subsistence_output_mult", ModifKinds::Building),
787    ("building_unincorporated_throughput_add", ModifKinds::Building),
788    ("building_working_conditions_mult", ModifKinds::Building),
789    // Character modifiers flow to military formation and character from other scopes
790    // They do *not* flow between characters and military formations
791    // The kind is set based on which scope actually applies an effect
792    ("character_advancement_speed_add", ModifKinds::Character),
793    ("character_advancement_speed_mult", ModifKinds::Character),
794    ("character_blockade_mult", ModifKinds::Character),
795    ("character_command_limit_add", ModifKinds::Character),
796    ("character_command_limit_mult", ModifKinds::Character),
797    ("character_convoy_protection_mult", ModifKinds::Character),
798    ("character_convoy_raiding_mult", ModifKinds::Character),
799    ("character_coup_strength_add", ModifKinds::Character),
800    ("character_coup_strength_mult", ModifKinds::Character),
801    ("character_expedition_events_explorer_mult", ModifKinds::Character),
802    ("character_health_add", ModifKinds::Character),
803    ("character_interception_add", ModifKinds::Character),
804    ("character_popularity_add", ModifKinds::Character),
805    ("character_supply_route_cost_mult", ModifKinds::MilitaryFormation),
806    ("country_acceptance_homeland_add", ModifKinds::Country),
807    ("country_acceptance_no_shared_heritage_trait_add", ModifKinds::Country),
808    ("country_acceptance_no_shared_language_trait_add", ModifKinds::Country),
809    ("country_acceptance_no_shared_religious_trait_add", ModifKinds::Country),
810    ("country_acceptance_no_shared_tradition_trait_add", ModifKinds::Country),
811    ("country_acceptance_not_homeland_add", ModifKinds::Country),
812    ("country_acceptance_primary_culture_add", ModifKinds::Country),
813    ("country_acceptance_shared_heritage_trait_add", ModifKinds::Country),
814    ("country_acceptance_shared_heritage_trait_group_add", ModifKinds::Country),
815    ("country_acceptance_shared_language_trait_add", ModifKinds::Country),
816    ("country_acceptance_shared_language_trait_group_add", ModifKinds::Country),
817    ("country_acceptance_shared_religious_trait_add", ModifKinds::Country),
818    ("country_acceptance_shared_religious_trait_group_add", ModifKinds::Country),
819    ("country_acceptance_shared_tradition_trait_add", ModifKinds::Country),
820    ("country_acceptance_state_religion_add", ModifKinds::Country),
821    ("country_agitator_slots_add", ModifKinds::Country),
822    ("country_ahead_of_time_research_penalty_mult", ModifKinds::Country),
823    ("country_all_buildings_protected_bool", ModifKinds::Country),
824    ("country_allow_enacting_decrees_in_subject_bool", ModifKinds::Country),
825    ("country_allow_multiple_alliances_bool", ModifKinds::Country),
826    ("country_amenability_add", ModifKinds::Country),
827    ("country_assimilation_delta_threshold_add", ModifKinds::Country),
828    ("country_authority_add", ModifKinds::Country),
829    ("country_authority_mult", ModifKinds::Country),
830    ("country_authority_per_subject_add", ModifKinds::Country),
831    ("country_block_government_reform_bool", ModifKinds::Country),
832    ("country_bolster_attraction_factor", ModifKinds::Country),
833    ("country_bolster_cost_mult", ModifKinds::Country),
834    ("country_bureaucracy_add", ModifKinds::Country),
835    ("country_bureaucracy_investment_cost_factor_mult", ModifKinds::Country),
836    ("country_bureaucracy_mult", ModifKinds::Country),
837    ("country_can_form_construction_company_bool", ModifKinds::Country),
838    ("country_can_only_conscript_peasants_bool", ModifKinds::Country),
839    ("country_cannot_be_target_for_law_imposition_bool", ModifKinds::Country),
840    ("country_cannot_cancel_law_enactment_bool", ModifKinds::Country),
841    ("country_cannot_enact_laws_bool", ModifKinds::Country),
842    ("country_cannot_start_law_enactment_bool", ModifKinds::Country),
843    ("country_company_construction_efficiency_bonus_add", ModifKinds::Country),
844    ("country_company_throughput_bonus_add", ModifKinds::Country),
845    ("country_construction_add", ModifKinds::Country),
846    ("country_construction_goods_cost_mult", ModifKinds::Country),
847    ("country_consumption_tax_cost_mult", ModifKinds::Country),
848    ("country_conversion_delta_threshold_add", ModifKinds::Country),
849    ("country_convoy_contribution_to_market_owner_add", ModifKinds::Country),
850    ("country_convoy_damage_taken_mult", ModifKinds::Country),
851    ("country_convoys_capacity_add", ModifKinds::Country),
852    ("country_convoys_capacity_mult", ModifKinds::Country),
853    ("country_coup_resistance_add", ModifKinds::Country),
854    ("country_coup_resistance_mult", ModifKinds::Country),
855    ("country_damage_relations_speed_mult", ModifKinds::Country),
856    ("country_diplomatic_play_maneuvers_add", ModifKinds::Country),
857    ("country_diplomatic_reputation_add", ModifKinds::Country),
858    ("country_disable_investment_pool_bool", ModifKinds::Country),
859    ("country_disable_nationalization_bool", ModifKinds::Country),
860    ("country_disable_nationalization_without_compensation_bool", ModifKinds::Country),
861    ("country_disable_non_company_privatization_bool", ModifKinds::Country),
862    ("country_disallow_aggressive_plays_bool", ModifKinds::Country),
863    ("country_disallow_agitator_invites_bool", ModifKinds::Country),
864    ("country_disallow_trade_bool", ModifKinds::Country),
865    ("country_disallow_trade_outside_canton_bool", ModifKinds::Country),
866    ("country_economic_dependence_on_overlord_add", ModifKinds::Country),
867    ("country_education_fervor_add", ModifKinds::Country),
868    ("country_electoral_confidence_impact_mult", ModifKinds::Country),
869    ("country_expedition_events_explorer_mult", ModifKinds::Country),
870    ("country_expenses_add", ModifKinds::Country),
871    ("country_financial_districts_buy_farms_likelyhood", ModifKinds::Country),
872    ("country_free_charters_add", ModifKinds::Country),
873    ("country_forbid_electoral_fraud_bool", ModifKinds::Country),
874    ("country_forbid_monopoly_bool", ModifKinds::Country),
875    ("country_force_privatization_bool", ModifKinds::Country),
876    ("country_foreign_collectivization_bool", ModifKinds::Country),
877    ("country_gold_reserve_limit_mult", ModifKinds::Country),
878    ("country_government_buildings_protected_bool", ModifKinds::Country),
879    ("country_government_dividends_efficiency_add", ModifKinds::Country),
880    ("country_government_dividends_reinvestment_add", ModifKinds::Country),
881    ("country_government_dividends_waste_add", ModifKinds::Country),
882    ("country_government_wages_mult", ModifKinds::Country),
883    ("country_higher_diplomatic_acceptance_same_religion_bool", ModifKinds::Country),
884    ("country_higher_leverage_from_economic_dependence_bool", ModifKinds::Country),
885    ("country_ignores_landing_craft_penalty_bool", ModifKinds::Country),
886    ("country_improve_relations_speed_mult", ModifKinds::Country),
887    ("country_infamy_decay_mult", ModifKinds::Country),
888    ("country_infamy_generation_mult", ModifKinds::Country),
889    ("country_infamy_generation_against_unrecognized_mult", ModifKinds::Country),
890    ("country_influence_add", ModifKinds::Country),
891    ("country_influence_mult", ModifKinds::Country),
892    ("country_initiator_war_goal_maneuver_cost_mult", ModifKinds::Country),
893    ("country_institution_size_change_speed_mult", ModifKinds::Country),
894    ("country_join_power_bloc_member_in_defensive_plays_bool", ModifKinds::Country),
895    ("country_join_power_bloc_member_in_plays_bool", ModifKinds::Country),
896    ("country_law_enactment_imposition_success_add", ModifKinds::Country),
897    ("country_law_enactment_max_setbacks_add", ModifKinds::Country),
898    ("country_law_enactment_stall_add", ModifKinds::Country),
899    ("country_law_enactment_stall_mult", ModifKinds::Country),
900    ("country_law_enactment_success_add", ModifKinds::Country),
901    ("country_law_enactment_time_mult", ModifKinds::Country),
902    ("country_leader_has_law_enactment_success_mult", ModifKinds::Country),
903    ("country_legitimacy_base_add", ModifKinds::Country),
904    ("country_legitimacy_govt_leader_clout_add", ModifKinds::Country),
905    ("country_legitimacy_govt_size_add", ModifKinds::Country),
906    ("country_legitimacy_govt_total_clout_add", ModifKinds::Country),
907    ("country_legitimacy_govt_total_votes_add", ModifKinds::Country),
908    ("country_legitimacy_headofstate_add", ModifKinds::Country),
909    ("country_legitimacy_ideological_incoherence_mult", ModifKinds::Country),
910    ("country_legitimacy_min_add", ModifKinds::Country),
911    ("country_leverage_generation_add", ModifKinds::Country),
912    ("country_leverage_generation_mult", ModifKinds::Country),
913    ("country_leverage_resistance_add", ModifKinds::Country),
914    ("country_leverage_resistance_mult", ModifKinds::Country),
915    ("country_liberty_desire_add", ModifKinds::Country),
916    ("country_liberty_desire_decrease_mult", ModifKinds::Country),
917    ("country_liberty_desire_increase_mult", ModifKinds::Country),
918    ("country_liberty_desire_of_subjects_mult", ModifKinds::Country),
919    ("country_loan_interest_rate_add", ModifKinds::Country),
920    ("country_loan_interest_rate_mult", ModifKinds::Country),
921    ("country_lobby_leverage_generation_mult", ModifKinds::Country),
922    ("country_loyalists_from_legitimacy_mult", ModifKinds::Country),
923    ("country_mass_migration_attraction_mult", ModifKinds::Country),
924    ("country_max_companies_add", ModifKinds::Country),
925    ("country_max_declared_interests_add", ModifKinds::Country),
926    ("country_max_declared_interests_mult", ModifKinds::Country),
927    ("country_max_weekly_construction_progress_add", ModifKinds::Country),
928    ("country_migration_restrictiveness_add", ModifKinds::Country),
929    ("country_military_goods_cost_mult", ModifKinds::Country),
930    ("country_military_tech_research_speed_mult", ModifKinds::Country),
931    ("country_military_tech_spread_mult", ModifKinds::Country),
932    ("country_military_wages_mult", ModifKinds::Country),
933    ("country_min_cultural_acceptance_add", ModifKinds::Country),
934    ("country_minting_add", ModifKinds::Country),
935    ("country_minting_mult", ModifKinds::Country),
936    ("country_must_have_movement_to_enact_laws_bool", ModifKinds::Country),
937    ("country_nationalization_cost_non_members_mult", ModifKinds::Country),
938    ("country_no_advantage_loss_from_lack_of_interest_bool", ModifKinds::Country),
939    ("country_non_state_religion_wages_mult", ModifKinds::Country),
940    ("country_opposition_ig_approval_add", ModifKinds::Country),
941    ("country_overlord_income_transfer_mult", ModifKinds::Country),
942    ("country_pact_leverage_generation_add", ModifKinds::Country),
943    ("country_pact_leverage_generation_mult", ModifKinds::Country),
944    ("country_party_whip_impact_add", ModifKinds::Country),
945    ("country_port_connection_cost_mult", ModifKinds::Country),
946    ("country_prestige_add", ModifKinds::Country),
947    ("country_prestige_from_army_power_projection_mult", ModifKinds::Country),
948    ("country_prestige_from_navy_power_projection_mult", ModifKinds::Country),
949    ("country_prestige_mult", ModifKinds::Country),
950    ("country_primary_culture_fervor_from_laws_add", ModifKinds::Country),
951    ("country_private_construction_allocation_mult", ModifKinds::Country),
952    ("country_production_tech_research_speed_mult", ModifKinds::Country),
953    ("country_production_tech_spread_mult", ModifKinds::Country),
954    ("country_radicals_from_conquest_mult", ModifKinds::Country),
955    ("country_radicals_from_legitimacy_mult", ModifKinds::Country),
956    ("country_reduced_liberty_desire_same_religion_bool", ModifKinds::Country),
957    ("country_resource_depletion_chance_mult", ModifKinds::Country),
958    ("country_resource_discovery_chance_mult", ModifKinds::Country),
959    ("country_revolution_clock_time_add", ModifKinds::Country),
960    ("country_revolution_progress_add", ModifKinds::Country),
961    ("country_revolution_progress_mult", ModifKinds::Country),
962    ("country_secession_clock_time_add", ModifKinds::Country),
963    ("country_secession_progress_add", ModifKinds::Country),
964    ("country_secession_progress_mult", ModifKinds::Country),
965    ("country_society_tech_research_speed_mult", ModifKinds::Country),
966    ("country_society_tech_spread_mult", ModifKinds::Country),
967    ("country_state_religion_wages_mult", ModifKinds::Country),
968    ("country_subject_income_transfer_heathen_mult", ModifKinds::Country),
969    ("country_subject_income_transfer_mult", ModifKinds::Country),
970    ("country_support_independence_weekly_liberty_desire_add", ModifKinds::Country),
971    ("country_support_separatism_resistance_mult", ModifKinds::Country),
972    ("country_support_separatism_separatism_mult", ModifKinds::Country),
973    ("country_suppression_attraction_factor", ModifKinds::Country),
974    ("country_suppression_cost_mult", ModifKinds::Country),
975    ("country_tax_income_add", ModifKinds::Country),
976    ("country_tech_group_research_speed_mult", ModifKinds::Country),
977    ("country_tech_research_speed_mult", ModifKinds::Country),
978    ("country_tech_spread_add", ModifKinds::Country),
979    ("country_tech_spread_mult", ModifKinds::Country),
980    ("country_tension_decay_mult", ModifKinds::Country),
981    ("country_treaty_leverage_generation_add", ModifKinds::Country),
982    ("country_treaty_leverage_generation_mult", ModifKinds::Country),
983    ("country_two_spains_conservative_drift_add", ModifKinds::Country),
984    ("country_two_spains_liberal_drift_add", ModifKinds::Country),
985    ("country_voting_power_base_add", ModifKinds::Country),
986    ("country_voting_power_from_literacy_add", ModifKinds::Country),
987    ("country_voting_power_mult", ModifKinds::Country),
988    ("country_voting_power_wealth_threshold_add", ModifKinds::Country),
989    ("country_war_exhaustion_casualties_mult", ModifKinds::Country),
990    ("country_weekly_innovation_add", ModifKinds::Country),
991    ("country_weekly_innovation_max_add", ModifKinds::Country),
992    ("country_weekly_innovation_mult", ModifKinds::Country),
993    ("country_yankee_and_dixie_cultures_obsessed_with_guns", ModifKinds::Country),
994    ("interest_group_amenability_add", ModifKinds::InterestGroup),
995    ("interest_group_approval_add", ModifKinds::InterestGroup),
996    ("interest_group_in_government_approval_add", ModifKinds::InterestGroup),
997    ("interest_group_in_government_attraction_mult", ModifKinds::InterestGroup),
998    ("interest_group_in_opposition_agitator_popularity_add", ModifKinds::InterestGroup),
999    ("interest_group_in_opposition_approval_add", ModifKinds::InterestGroup),
1000    ("interest_group_pol_str_factor", ModifKinds::InterestGroup),
1001    ("interest_group_pol_str_mult", ModifKinds::InterestGroup),
1002    ("interest_group_pop_attraction_mult", ModifKinds::InterestGroup),
1003    ("military_formation_attrition_risk_add", ModifKinds::MilitaryFormation),
1004    ("military_formation_attrition_risk_mult", ModifKinds::MilitaryFormation),
1005    ("military_formation_mobilization_speed_add", ModifKinds::MilitaryFormation),
1006    ("military_formation_mobilization_speed_mult", ModifKinds::MilitaryFormation),
1007    ("military_formation_movement_speed_add", ModifKinds::MilitaryFormation),
1008    ("military_formation_movement_speed_mult", ModifKinds::MilitaryFormation),
1009    ("military_formation_organization_gain_add", ModifKinds::MilitaryFormation),
1010    ("military_formation_organization_gain_mult", ModifKinds::MilitaryFormation),
1011    ("political_movement_character_attraction_mult", ModifKinds::PoliticalMovement),
1012    ("political_movement_pop_attraction_mult", ModifKinds::PoliticalMovement),
1013    ("political_movement_radicalism_add", ModifKinds::PoliticalMovement),
1014    ("political_movement_radicalism_from_enactment_approval_mult", ModifKinds::PoliticalMovement),
1015    (
1016        "political_movement_radicalism_from_enactment_disapproval_mult",
1017        ModifKinds::PoliticalMovement,
1018    ),
1019    ("power_bloc_allow_foreign_investment_lower_rank_bool", ModifKinds::PowerBloc),
1020    ("power_bloc_allow_wider_migration_area_bool", ModifKinds::PowerBloc),
1021    ("power_bloc_cohesion_add", ModifKinds::PowerBloc),
1022    ("power_bloc_cohesion_mult", ModifKinds::PowerBloc),
1023    ("power_bloc_cohesion_per_member_add", ModifKinds::PowerBloc),
1024    ("power_bloc_customs_union_bool", ModifKinds::PowerBloc),
1025    ("power_bloc_disallow_embargo_bool", ModifKinds::PowerBloc),
1026    ("power_bloc_disallow_war_bool", ModifKinds::PowerBloc),
1027    ("power_bloc_income_transfer_to_leader_factor", ModifKinds::PowerBloc),
1028    ("power_bloc_invite_acceptance_add", ModifKinds::PowerBloc),
1029    ("power_bloc_leader_can_add_wargoal_bool", ModifKinds::PowerBloc),
1030    ("power_bloc_leader_can_force_state_religion_bool", ModifKinds::PowerBloc),
1031    ("power_bloc_leader_can_make_subjects_bool", ModifKinds::PowerBloc),
1032    ("power_bloc_leader_can_regime_change_bool", ModifKinds::PowerBloc),
1033    ("power_bloc_leader_can_spread_culture_to_pb_bool", ModifKinds::PowerBloc),
1034    ("power_bloc_leverage_generation_mult", ModifKinds::PowerBloc),
1035    ("power_bloc_mandate_progress_mult", ModifKinds::PowerBloc),
1036    ("power_bloc_target_sway_cost_mult", ModifKinds::PowerBloc),
1037    ("power_bloc_trade_advantage_add", ModifKinds::PowerBloc),
1038    ("state_allow_assimilation_in_homeland_bool", ModifKinds::State),
1039    ("state_assimilation_mult", ModifKinds::State),
1040    ("state_birth_rate_mult", ModifKinds::State),
1041    ("state_blockade_resistance_add", ModifKinds::State),
1042    ("state_bureaucracy_population_base_cost_factor_mult", ModifKinds::State),
1043    ("state_colony_growth_creation_factor", ModifKinds::State),
1044    ("state_colony_growth_speed_mult", ModifKinds::State),
1045    ("state_conscription_rate_add", ModifKinds::State),
1046    ("state_conscription_rate_mult", ModifKinds::State),
1047    ("state_construction_mult", ModifKinds::State),
1048    ("state_contiguous_incorporation_speed_mult", ModifKinds::State),
1049    ("state_conversion_mult", ModifKinds::State),
1050    ("state_decree_cost_mult", ModifKinds::State),
1051    ("state_dependent_political_participation_add", ModifKinds::State),
1052    ("state_dependent_wage_add", ModifKinds::State),
1053    ("state_dependent_wage_mult", ModifKinds::State),
1054    ("state_devastation_decay_mult", ModifKinds::State),
1055    ("state_disallow_incorporation_bool", ModifKinds::State),
1056    ("state_education_access_add", ModifKinds::State),
1057    ("state_education_access_wealth_add", ModifKinds::State),
1058    ("state_expected_sol_from_literacy", ModifKinds::State),
1059    ("state_expected_sol_mult", ModifKinds::State),
1060    ("state_export_advantage_mult", ModifKinds::State),
1061    ("state_free_state_pop_support_movement_anti_slavery_mult", ModifKinds::State),
1062    ("state_food_security_add", ModifKinds::State),
1063    ("state_import_advantage_mult", ModifKinds::State),
1064    ("state_incorporation_speed_mult", ModifKinds::State),
1065    ("state_infrastructure_add", ModifKinds::State),
1066    ("state_infrastructure_from_automobiles_consumption_add", ModifKinds::State),
1067    ("state_infrastructure_from_population_add", ModifKinds::State),
1068    ("state_infrastructure_from_population_max_add", ModifKinds::State),
1069    ("state_infrastructure_from_population_max_mult", ModifKinds::State),
1070    ("state_infrastructure_from_population_mult", ModifKinds::State),
1071    ("state_infrastructure_mult", ModifKinds::State),
1072    ("state_institution_impact_add", ModifKinds::State),
1073    ("state_lower_strata_expected_sol_add", ModifKinds::State),
1074    ("state_lower_strata_standard_of_living_add", ModifKinds::State),
1075    ("state_loyalists_from_political_movements_mult", ModifKinds::State),
1076    ("state_max_trade_advantage_from_capacity_add", ModifKinds::State),
1077    ("state_middle_strata_expected_sol_add", ModifKinds::State),
1078    ("state_middle_strata_standard_of_living_add", ModifKinds::State),
1079    ("state_migration_pull_add", ModifKinds::State),
1080    ("state_migration_pull_mult", ModifKinds::State),
1081    ("state_migration_pull_unincorporated_mult", ModifKinds::State),
1082    ("state_migration_quota_mult", ModifKinds::State),
1083    ("state_market_access_price_impact", ModifKinds::State),
1084    ("state_minimum_incorporated_subsistence_arable_land_add", ModifKinds::State),
1085    ("state_mortality_mult", ModifKinds::State),
1086    ("state_mortality_turmoil_mult", ModifKinds::State),
1087    ("state_mortality_wealth_mult", ModifKinds::State),
1088    ("state_non_contiguous_incorporation_speed_mult", ModifKinds::State),
1089    ("state_non_homeland_colony_growth_speed_mult", ModifKinds::State),
1090    ("state_non_homeland_mortality_mult", ModifKinds::State),
1091    ("state_peasants_education_access_add", ModifKinds::State),
1092    ("state_political_strength_from_wealth_mult", ModifKinds::State),
1093    ("state_political_strength_from_welfare_mult", ModifKinds::State),
1094    ("state_pollution_generation_add", ModifKinds::State),
1095    ("state_pollution_reduction_health_mult", ModifKinds::State),
1096    ("state_pop_pol_str_add", ModifKinds::State),
1097    ("state_pop_pol_str_mult", ModifKinds::State),
1098    ("state_pop_qualifications_mult", ModifKinds::State),
1099    ("state_radicals_and_loyalists_from_sol_change_mult", ModifKinds::State),
1100    ("state_radicals_from_political_movements_mult", ModifKinds::State),
1101    ("state_slave_import_mult", ModifKinds::State),
1102    ("state_standard_of_living_add", ModifKinds::State),
1103    ("state_subvention_export_add", ModifKinds::State),
1104    ("state_subvention_import_add", ModifKinds::State),
1105    ("state_tariff_export_add", ModifKinds::State),
1106    ("state_tariff_import_add", ModifKinds::State),
1107    ("state_tax_capacity_add", ModifKinds::State),
1108    ("state_tax_capacity_mult", ModifKinds::State),
1109    ("state_tax_collection_mult", ModifKinds::State),
1110    ("state_tax_waste_add", ModifKinds::State),
1111    ("state_trade_advantage_from_capacity_add", ModifKinds::State),
1112    ("state_trade_advantage_mult", ModifKinds::State),
1113    ("state_trade_advantage_same_religion_add", ModifKinds::State),
1114    ("state_trade_capacity_add", ModifKinds::State),
1115    ("state_trade_capacity_mult", ModifKinds::State),
1116    ("state_trade_quantity_mult", ModifKinds::State),
1117    ("state_turmoil_effects_mult", ModifKinds::State),
1118    ("state_unincorporated_starting_wages_mult", ModifKinds::State),
1119    ("state_upper_strata_expected_sol_add", ModifKinds::State),
1120    ("state_upper_strata_standard_of_living_add", ModifKinds::State),
1121    ("state_urbanization_per_level_add", ModifKinds::State),
1122    ("state_urbanization_per_level_mult", ModifKinds::State),
1123    ("state_weekly_trades_add", ModifKinds::State),
1124    ("state_welfare_payments_add", ModifKinds::State),
1125    ("state_welfare_payments_mult", ModifKinds::State),
1126    ("state_working_adult_ratio_add", ModifKinds::State),
1127    ("tax_consumption_add", ModifKinds::Tax),
1128    ("tax_dividends_add", ModifKinds::Tax),
1129    ("tax_heathen_add", ModifKinds::Tax),
1130    ("tax_income_add", ModifKinds::Tax),
1131    ("tax_land_add", ModifKinds::Tax),
1132    ("tax_per_capita_add", ModifKinds::Tax),
1133    // Unit modifiers flow through either Military Formation or Battle.
1134    // They get to units from either scope, but not all work when applied from Battle.
1135    // This appears to be based on whether the modifier applies affects to combat
1136    // (eg. offense, kill rate) or out of combat (eg. advancement speed, convoy raiding).
1137    //
1138    // Note that experience gain modifiers work while in combat, but experience is only
1139    // added on the weekly tick. If the battle starts and ends without crossing into a
1140    // new week, they won't do anything. This may not match the expected behaviour
1141    ("unit_army_defense_add", ModifKinds::UnitCombat),
1142    ("unit_army_defense_mult", ModifKinds::UnitCombat),
1143    ("unit_army_experience_gain_add", ModifKinds::Unit),
1144    ("unit_army_experience_gain_mult", ModifKinds::Unit),
1145    ("unit_army_offense_add", ModifKinds::UnitCombat),
1146    ("unit_army_offense_mult", ModifKinds::UnitCombat),
1147    ("unit_blockade_add", ModifKinds::UnitNonCombat),
1148    ("unit_blockade_mult", ModifKinds::UnitNonCombat),
1149    ("unit_convoy_defense_mult", ModifKinds::UnitNonCombat),
1150    ("unit_convoy_raiding_interception_mult", ModifKinds::UnitNonCombat),
1151    ("unit_convoy_raiding_mult", ModifKinds::UnitNonCombat),
1152    ("unit_defense_add", ModifKinds::UnitCombat),
1153    ("unit_defense_mult", ModifKinds::UnitCombat),
1154    ("unit_devastation_mult", ModifKinds::UnitCombat),
1155    ("unit_experience_gain_add", ModifKinds::Unit),
1156    ("unit_experience_gain_mult", ModifKinds::Unit),
1157    ("unit_kill_rate_add", ModifKinds::UnitCombat),
1158    ("unit_morale_damage_mult", ModifKinds::UnitCombat),
1159    ("unit_morale_loss_add", ModifKinds::UnitCombat),
1160    ("unit_morale_loss_mult", ModifKinds::UnitCombat),
1161    ("unit_morale_recovery_mult", ModifKinds::UnitNonCombat),
1162    ("unit_navy_defense_add", ModifKinds::UnitCombat),
1163    ("unit_navy_defense_mult", ModifKinds::UnitCombat),
1164    ("unit_navy_experience_gain_add", ModifKinds::Unit),
1165    ("unit_navy_experience_gain_mult", ModifKinds::Unit),
1166    ("unit_navy_offense_add", ModifKinds::UnitCombat),
1167    ("unit_navy_offense_mult", ModifKinds::UnitCombat),
1168    ("unit_occupation_mult", ModifKinds::UnitCombat),
1169    ("unit_offense_add", ModifKinds::UnitCombat),
1170    ("unit_offense_mult", ModifKinds::UnitCombat),
1171    ("unit_provinces_captured_mult", ModifKinds::UnitCombat),
1172    ("unit_provinces_lost_mult", ModifKinds::UnitCombat),
1173    ("unit_recovery_rate_add", ModifKinds::UnitCombat),
1174    ("unit_supply_consumption_mult", ModifKinds::UnitNonCombat), // This one doesn't seem to work in any context
1175];
1176
1177pub fn modif_scope_kind(scopes: Scopes) -> ModifKinds {
1178    let mut kinds = ModifKinds::empty();
1179
1180    for s in scopes {
1181        kinds.set(
1182            // List of scopes from add_modifier supported scopes in script docs
1183            #[allow(clippy::match_same_arms)]
1184            match s {
1185                Scopes::Country => ModifKinds::Country,
1186                Scopes::Character => ModifKinds::Character,
1187                Scopes::State => ModifKinds::State,
1188                Scopes::Building => ModifKinds::Building,
1189                Scopes::Institution => ModifKinds::Country,
1190                Scopes::InterestGroup => ModifKinds::InterestGroup,
1191                Scopes::JournalEntry => ModifKinds::Country,
1192                Scopes::PoliticalMovement => ModifKinds::PoliticalMovement,
1193                Scopes::PowerBloc => ModifKinds::PowerBloc,
1194                _ => ModifKinds::empty(),
1195            },
1196            true,
1197        );
1198    }
1199    kinds
1200}
1201
1202/// Game internally has a modifier graph which controls how modifiers flow
1203/// This is a representation of reachability on that graph
1204pub static MODIF_FLOW_MAP: LazyLock<TigerHashMap<ModifKinds, ModifKinds>> = LazyLock::new(|| {
1205    let mut map = TigerHashMap::with_capacity(MODIF_FLOW_TABLE.len());
1206    for (a, b) in MODIF_FLOW_TABLE.iter().copied() {
1207        map.insert(a, b);
1208    }
1209    map
1210});
1211
1212/// Oddities:
1213///  - Charater modifiers placed on countries flow to military formations and do nothing
1214///  - Interest group strength modifiers flow to all characters. I don't know what the path for that is
1215const MODIF_FLOW_TABLE: &[(ModifKinds, ModifKinds)] = &[
1216    (
1217        // Things don't seem to flow from characters directly
1218        // rather it's conditional based on where the modifier is being applied
1219        // with the exception of combat modifiers
1220        ModifKinds::Character,
1221        ModifKinds::Character.union(ModifKinds::Battle).union(ModifKinds::UnitCombat),
1222    ),
1223    (
1224        ModifKinds::Country,
1225        ModifKinds::Country
1226            // Direct nodes
1227            .union(ModifKinds::State)
1228            .union(ModifKinds::InterestGroup)
1229            .union(ModifKinds::MilitaryFormation)
1230            .union(ModifKinds::PoliticalMovement)
1231            // From State
1232            .union(ModifKinds::Building)
1233            .union(ModifKinds::Battle)
1234            .union(ModifKinds::Tax)
1235            // From Building
1236            .union(ModifKinds::Goods)
1237            // From MilitaryFormation
1238            .union(ModifKinds::Unit),
1239        // Confirmed, not Characters
1240    ),
1241    (
1242        ModifKinds::State,
1243        ModifKinds::State
1244            // Direct nodes
1245            .union(ModifKinds::Building)
1246            .union(ModifKinds::Battle)
1247            .union(ModifKinds::Tax)
1248            // From Building
1249            .union(ModifKinds::Goods)
1250            // From Battle
1251            .union(ModifKinds::UnitCombat),
1252        // Confirmed, not interest group
1253    ),
1254    (
1255        ModifKinds::Unit,
1256        ModifKinds::Unit, // Confirmed, not Goods
1257    ),
1258    (
1259        ModifKinds::Battle,
1260        ModifKinds::Battle
1261            // Direct nodes
1262            .union(ModifKinds::UnitCombat),
1263    ),
1264    (
1265        ModifKinds::Building,
1266        ModifKinds::Building
1267            // Direct nodes
1268            .union(ModifKinds::Goods),
1269        // Does not flow to Unit, despite usage in vanilla.
1270    ),
1271    (ModifKinds::InterestGroup, ModifKinds::InterestGroup),
1272    (ModifKinds::Market, ModifKinds::Market),
1273    (ModifKinds::PoliticalMovement, ModifKinds::PoliticalMovement),
1274    (ModifKinds::Tariff, ModifKinds::Tariff),
1275    (ModifKinds::Tax, ModifKinds::Tax),
1276    (ModifKinds::Goods, ModifKinds::Goods),
1277    (
1278        ModifKinds::MilitaryFormation,
1279        ModifKinds::MilitaryFormation
1280            // Direct nodes
1281            .union(ModifKinds::Unit)
1282            .union(ModifKinds::Character),
1283        // Confirmed, not Battle
1284    ),
1285    (ModifKinds::PowerBloc, ModifKinds::PowerBloc),
1286];
1287
1288pub fn modif_flow_suggest(name: &str, kind: ModifKinds) -> Option<&'static str> {
1289    MODIF_FLOW_SUGGEST
1290        .get(name)
1291        .and_then(|&(other, other_kind)| other_kind.intersects(kind).then_some(other))
1292}
1293
1294static MODIF_FLOW_SUGGEST: LazyLock<TigerHashMap<&str, (&str, ModifKinds)>> = LazyLock::new(|| {
1295    let mut map = TigerHashMap::with_capacity(MODIF_FLOW_SUGGEST_TABLE.len() * 2);
1296    for &(a, b) in MODIF_FLOW_SUGGEST_TABLE {
1297        map.insert(a, (b, *MODIF_MAP.get(b).unwrap()));
1298        map.insert(b, (a, *MODIF_MAP.get(a).unwrap()));
1299    }
1300    map
1301});
1302
1303const MODIF_FLOW_SUGGEST_TABLE: &[(&str, &str)] = &[
1304    ("unit_blockade_mult", "character_blockade_mult"),
1305    ("unit_convoy_defense_mult", "character_convoy_protection_mult"),
1306    ("unit_convoy_raiding_mult", "character_convoy_raiding_mult"),
1307    ("unit_convoy_raiding_interception_mult", "character_interception_add"),
1308    ("unit_supply_consumption_mult", "building_mobilization_cost_mult"),
1309];
1310
1311static MODIF_REMOVED_MAP: LazyLock<TigerHashMap<Lowercase<'static>, &'static str>> =
1312    LazyLock::new(|| {
1313        let mut hash = TigerHashMap::default();
1314        for (s, info) in MODIF_REMOVED_TABLE.iter().copied() {
1315            hash.insert(Lowercase::new_unchecked(s), info);
1316        }
1317        hash
1318    });
1319
1320const MODIF_REMOVED_TABLE: &[(&str, &str)] = &[
1321    ("building_input_mult", "replaced in 1.5 with building_goods_input_mult"),
1322    ("building_throughput_mult", "replaced in 1.5 with building_throughput_add"),
1323    ("technology_invention_cost_mult", "replaced in 1.4.0 with country_tech_research_speed_mult"),
1324    (
1325        "country_production_tech_cost_mult",
1326        "replaced in 1.4.0 with country_production_tech_research_speed_mult",
1327    ),
1328    (
1329        "country_production_weekly_innovation_mult",
1330        "replaced in 1.4.0 with country_production_tech_research_speed_mult",
1331    ),
1332    (
1333        "country_military_tech_cost_mult",
1334        "replaced in 1.4.0 with country_military_tech_research_speed_mult",
1335    ),
1336    (
1337        "country_military_weekly_innovation_mult",
1338        "replaced in 1.4.0 with country_military_tech_research_speed_mult",
1339    ),
1340    (
1341        "country_society_tech_cost_mult",
1342        "replaced in 1.4.0 with country_society_tech_research_speed_mult",
1343    ),
1344    (
1345        "country_society_weekly_innovation_mult",
1346        "replaced in 1.4.0 with country_society_tech_research_speed_mult",
1347    ),
1348    ("country_trade_route_exports_add", "removed in 1.5"),
1349    ("country_trade_route_imports_add", "removed in 1.5"),
1350    (
1351        "country_army_power_projection_add",
1352        "replaced in 1.5 with country_prestige_from_army_power_projection_mult",
1353    ),
1354    (
1355        "country_army_power_projection_mult",
1356        "replaced in 1.5 with country_prestige_from_army_power_projection_mult",
1357    ),
1358    (
1359        "country_navy_power_projection_add",
1360        "replaced in 1.5 with country_prestige_from_navy_power_projection_mult",
1361    ),
1362    (
1363        "country_navy_power_projection_mult",
1364        "replaced in 1.5 with country_prestige_from_navy_power_projection_mult",
1365    ),
1366    ("character_attrition_risk_add", "removed in 1.5"),
1367    ("character_attrition_risk_mult", "removed in 1.5"),
1368    ("character_convoy_protection_add", "replaced in 1.5 with character_country_protection_mult"),
1369    ("character_convoy_raiding_add", "replaced in 1.5 with character_country_raiding_mult"),
1370    ("front_advancement_speed_add", "removed in 1.5"),
1371    ("front_advancement_speed_mult", "removed in 1.5"),
1372    ("front_enemy_advancement_speed_add", "removed in 1.5"),
1373    ("front_enemy_advancement_speed_mult", "removed in 1.5"),
1374    ("character_command_limit_combat_unit_conscript_add", "removed in 1.6"),
1375    ("character_command_limit_combat_unit_flotilla_add", "removed in 1.6"),
1376    ("character_command_limit_combat_unit_regular_add", "removed in 1.6"),
1377    ("building_government_shares_add", "removed in 1.7"),
1378    ("building_production_mult", "removed in 1.7"),
1379    ("building_throughput_oil_mult", "removed in 1.7"),
1380    ("building_workforce_shares_add", "removed in 1.7"),
1381    ("country_all_buildings_protected", "renamed to country_all_buildings_protected_bool in 1.7"),
1382    ("country_allow_multiple_alliances", "renamed to country_allow_multiple_alliances_bool in 1.7"),
1383    ("country_cannot_enact_laws", "renamed to country_cannot_enact_laws_bool in 1.7"),
1384    ("country_decree_cost_mult", "removed in 1.7"),
1385    ("country_disable_investment_pool", "renamed to country_disable_investment_pool_bool in 1.7"),
1386    (
1387        "country_disallow_aggressive_plays",
1388        "renamed to country_disallow_aggressive_plays_bool in 1.7",
1389    ),
1390    (
1391        "country_disallow_agitator_invites",
1392        "renamed to country_disallow_agitator_invites_bool in 1.7",
1393    ),
1394    (
1395        "country_disallow_discriminated_migration",
1396        "renamed to country_disallow_discriminated_migration_bool in 1.7",
1397    ),
1398    ("country_disallow_migration", "renamed to country_disallow_migration_bool in 1.7"),
1399    (
1400        "country_government_buildings_protected",
1401        "renamed to country_government_buildings_protected_bool in 1.7",
1402    ),
1403    (
1404        "country_ignores_landing_craft_penalty",
1405        "renamed to country_ignores_landing_craft_penalty_bool in 1.7",
1406    ),
1407    ("country_mandate_subsidies", "removed in 1.7"),
1408    (
1409        "country_must_have_movement_to_enact_laws",
1410        "renamed to country_must_have_movement_to_enact_laws_bool in 1.7",
1411    ),
1412    ("country_private_buildings_protected", "removed in 1.7"),
1413    ("country_promotion_ig_attraction_mult", "removed in 1.7"),
1414    ("country_subsidies_all", "removed in 1.7"),
1415    ("market_disallow_trade_routes", "renamed to market_disallow_trade_routes_bool in 1.7"),
1416    ("state_disallow_incorporation", "renamed to state_disallow_incorporation_bool in 1.7"),
1417    ("state_port_range_add", "removed in 1.7"),
1418    ("state_unincorporated_standard_of_living_add", "removed in 1.7"),
1419    ("state_urbanization_add", "removed in 1.7"),
1420    ("state_urbanization_mult", "removed in 1.7"),
1421    ("unit_mobilization_speed_mult", "removed in 1.7"),
1422    ("country_leverage_resistance_per_population_add", "removed in 1.7.1"),
1423    ("character_morale_cap_add", "removed in 1.8"),
1424    ("country_bolster_ig_attraction_mult", "removed in 1.8"),
1425    ("country_disallow_discriminated_migration_bool", "removed in 1.8"),
1426    ("country_disallow_migration_bool", "removed in 1.8"),
1427    ("country_force_collectivization_bool", "removed in 1.8"),
1428    ("country_suppression_ig_attraction_mult", "removed in 1.8"),
1429    ("political_movement_enact_support_mult", "removed in 1.8"),
1430    ("political_movement_preserve_support_mult", "removed in 1.8"),
1431    ("political_movement_radicalism_mult", "removed in 1.8"),
1432    ("political_movement_restore_support_mult", "removed in 1.8"),
1433    ("political_movement_support_add", "removed in 1.8"),
1434    ("political_movement_support_mult", "removed in 1.8"),
1435    ("state_accepted_birth_rate_mult", "removed in 1.8"),
1436    (
1437        "state_colony_growth_creation_mult",
1438        "replaced with state_colony_growth_creation_factor in 1.8",
1439    ),
1440    ("state_loyalists_from_sol_change_accepted_culture_mult", "removed in 1.8"),
1441    ("state_loyalists_from_sol_change_accepted_religion_mult", "removed in 1.8"),
1442    ("state_loyalists_from_sol_change_mult", "removed in 1.8"),
1443    ("state_middle_expected_sol", "removed in 1.8"),
1444    ("state_middle_standard_of_living_add", "removed in 1.8"),
1445    ("state_minimum_wealth_add", "removed in 1.8"),
1446    ("state_political_strength_from_discrimination_mult", "removed in 1.8"),
1447    ("state_poor_expected_sol", "removed in 1.8"),
1448    ("state_poor_standard_of_living_add", "removed in 1.8"),
1449    ("state_radicals_from_discrimination_mult", "removed in 1.8"),
1450    ("state_radicals_from_sol_change_accepted_culture_mult", "removed in 1.8"),
1451    ("state_radicals_from_sol_change_accepted_religion_mult", "removed in 1.8"),
1452    ("state_radicals_from_sol_change_mult", "removed in 1.8"),
1453    ("state_rich_expected_sol", "removed in 1.8"),
1454    ("state_rich_standard_of_living_add", "removed in 1.8"),
1455    ("country_allow_national_collectivization_bool", "removed in 1.8.4"),
1456    ("country_allow_trade_routes_without_interest_bool", "removed in 1.9"),
1457    ("country_bolster_attraction_mult", "changed to country_bolster_attraction_factor in 1.9"),
1458    ("country_company_pay_to_establish_bool", "removed in 1.9"),
1459    ("country_free_trade_routes_add", "removed in 1.9"),
1460    (
1461        "country_suppression_attraction_mult",
1462        "changed to country_suppression_attraction_factor in 1.9",
1463    ),
1464    ("country_trade_route_competitiveness_mult", "removed in 1.9"),
1465    ("country_trade_route_cost_mult", "removed in 1.9"),
1466    ("country_trade_route_quantity_mult", "removed in 1.9"),
1467    ("market_disallow_trade_routes_bool", "removed in 1.9"),
1468    ("market_land_trade_capacity_add", "removed in 1.9"),
1469    ("market_max_exports_add", "removed in 1.9"),
1470    ("market_max_imports_add", "removed in 1.9"),
1471    ("power_bloc_religion_trade_route_competitiveness_mult", "removed in 1.9"),
1472    ("power_bloc_trade_route_cost_mult", "removed in 1.9"),
1473    ("tariff_export_add", "removed in 1.9"),
1474    ("tariff_export_outside_power_bloc_mult", "removed in 1.9"),
1475    ("tariff_import_add", "removed in 1.9"),
1476    ("tariff_import_outside_power_bloc_mult", "removed in 1.9"),
1477    ("country_acceptance_culture_base_add", "replaced with new acceptance system in 1.10"),
1478    (
1479        "country_company_government_dividends_add",
1480        "replaced with `building_company_government_dividends_add` in 1.10.4",
1481    ),
1482    (
1483        "country_company_worker_dividends_add",
1484        "replaced with `building_company_worker_dividends_add` in 1.10.4",
1485    ),
1486    (
1487        "country_disable_privatization_bool",
1488        "replaced with `country_disable_non_company_privatization_bool` in 1.10.4",
1489    ),
1490    ("country_party_whip_impact_mult", "replaced with `country_party_whip_impact_add` in 1.10.4"),
1491    ("state_migration_push_mult", "removed in 1.12"),
1492    ("unit_advancement_speed_mult", "removed in 1.12"),
1493    ("unit_convoy_requirements_mult", "removed in 1.12"),
1494];