Skip to main content

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