wowlab_engine_application/game_data/passives/
masking.rs1use 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#[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
33pub(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
58pub(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 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
86pub(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
111pub(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}