Skip to main content

wowlab_engine_application/game_data/passives/
masking.rs

1use wowlab_engine_ports::SpecDescriptor;
2use wowlab_types::data::{EquippedItemRequirement, SpellDataFlat, WeaponStats};
3
4pub(crate) fn should_fold_sheet_stat_passive(spell: &SpellDataFlat) -> bool {
5    wowlab_engine_domain::dbc::is_stat_modifier_passive(spell)
6        && !updates_passives_on_apply_remove(spell)
7}
8
9fn updates_passives_on_apply_remove(spell: &SpellDataFlat) -> bool {
10    wowlab_engine_domain::dbc::spell_attribute_is(
11        &spell.attributes,
12        wowlab_engine_domain::dbc::SpellAttributeKind::UpdatePassivesOnApplyRemove,
13    )
14}
15
16/// The weapons a passive's equipped-item requirement is tested against.
17#[derive(Clone, Copy, Debug)]
18pub(crate) struct EquippedWeapons<'a> {
19    pub main_hand: &'a WeaponStats,
20    pub off_hand: Option<&'a WeaponStats>,
21}
22
23impl EquippedWeapons<'_> {
24    fn satisfies(self, requirement: EquippedItemRequirement) -> bool {
25        std::iter::once(self.main_hand)
26            .chain(self.off_hand)
27            .any(|weapon| {
28                requirement.matches(weapon.item_class, weapon.subclass, weapon.inventory_type)
29            })
30    }
31}
32
33/// Keeps the passives whose contribution is fixed for the whole run.
34pub(crate) fn retain_statically_folded_passives(
35    passives: &mut Vec<wowlab_engine_domain::dbc::RankedPassive>,
36    weapons: EquippedWeapons<'_>,
37) {
38    passives.retain(|(spell, _)| {
39        if !updates_passives_on_apply_remove(spell) {
40            return true;
41        }
42
43        let satisfied = spell
44            .equipped_item_requirement
45            .is_some_and(|requirement| weapons.satisfies(requirement));
46
47        tracing::trace!(
48            spell_id = spell.id,
49            spell_name = spell.name.as_str(),
50            satisfied,
51            "apply/remove-updating passive tested against the equipped weapons"
52        );
53
54        satisfied
55    });
56}
57
58/// Drops passive effects a description assigns to another specialization.
59pub(crate) fn mask_spec_conditional_passive_effects(
60    passives: &mut [wowlab_engine_domain::dbc::RankedPassive],
61    specialization_order: i32,
62) {
63    let Ok(order_index) = u8::try_from(specialization_order) else {
64        return;
65    };
66
67    for (spell, _) in passives.iter_mut() {
68        for statement in wowlab_parsers::spell_desc_spec_conditional_effects(&spell.description) {
69            let inert = i32::from(statement.inert_effect(order_index)) - 1;
70
71            // #t(rust_log_in_loop) one line per masked effect is the only record of the selection
72            tracing::debug!(
73                spell_id = spell.id,
74                spell_name = spell.name.as_str(),
75                spec_index = statement.spec_index,
76                order_index,
77                inert_effect = statement.inert_effect(order_index),
78                "masked the passive effect the description assigns to another specialization"
79            );
80
81            spell.effects.retain(|effect| effect.index != inert);
82        }
83    }
84}
85
86/// Drops passive effects declared inert for this specialization.
87pub(super) fn mask_declared_passive_effects(
88    passives: &mut [wowlab_engine_domain::dbc::RankedPassive],
89    descriptor: &SpecDescriptor,
90) {
91    for (spell_id, effect_index) in descriptor.metadata.masked_passive_effects {
92        let spell_id = wowlab_types::numeric::u32_to_i32_saturating(*spell_id);
93        let dbc_index = i32::from(*effect_index) - 1;
94
95        for (spell, _) in passives
96            .iter_mut()
97            .filter(|(spell, _)| spell.id == spell_id)
98        {
99            let before = spell.effects.len();
100
101            spell.effects.retain(|effect| effect.index != dbc_index);
102
103            debug_assert!(
104                spell.effects.len() != before,
105                "declared passive effect mask matched no DBC effect"
106            );
107        }
108    }
109}
110
111/// Replaces every passive effect value explicitly overridden by the specialization.
112pub(crate) fn apply_declared_passive_effect_overrides(
113    passives: &mut [wowlab_engine_domain::dbc::RankedPassive],
114    descriptor: &SpecDescriptor,
115) -> Vec<((u32, u8), f64)> {
116    let mut resolved = Vec::with_capacity(descriptor.metadata.passive_effect_overrides.len());
117
118    for declared in descriptor.metadata.passive_effect_overrides {
119        let replacement = declared.multiplier
120            * match declared.source {
121                Some((source_id, source_effect)) => {
122                    match declared_effect_base_points(passives, source_id, source_effect) {
123                        Some(base_points) => base_points,
124                        None => continue,
125                    }
126                }
127                None => declared.value,
128            };
129
130        write_effect_base_points(passives, declared.spell_id, declared.effect, replacement);
131        resolved.push(((declared.spell_id, declared.effect), replacement));
132    }
133
134    resolved
135}
136
137fn declared_effect_base_points(
138    passives: &[wowlab_engine_domain::dbc::RankedPassive],
139    spell_id: u32,
140    effect: u8,
141) -> Option<f64> {
142    let spell_id = wowlab_types::numeric::u32_to_i32_saturating(spell_id);
143    let dbc_index = i32::from(effect) - 1;
144
145    passives
146        .iter()
147        .filter(|(spell, _)| spell.id == spell_id)
148        .find_map(|(spell, _)| {
149            spell
150                .effects
151                .iter()
152                .find(|effect| effect.index == dbc_index)
153                .map(|effect| effect.base_points)
154        })
155}
156
157fn write_effect_base_points(
158    passives: &mut [wowlab_engine_domain::dbc::RankedPassive],
159    spell_id: u32,
160    effect: u8,
161    base_points: f64,
162) {
163    let spell_id = wowlab_types::numeric::u32_to_i32_saturating(spell_id);
164    let dbc_index = i32::from(effect) - 1;
165
166    tracing::debug!(
167        spell_id,
168        effect_index = effect,
169        base_points,
170        "declared passive effect override applied"
171    );
172
173    for (spell, _) in passives
174        .iter_mut()
175        .filter(|(spell, _)| spell.id == spell_id)
176    {
177        for target in spell
178            .effects
179            .iter_mut()
180            .filter(|effect| effect.index == dbc_index)
181        {
182            target.base_points = base_points;
183        }
184    }
185}