wowlab_engine_application/game_data/
passives.rs1mod crit;
4mod masking;
5mod sources;
6mod stats;
7
8use wowlab_engine_domain::dbc::PowerModifiers;
9use wowlab_engine_gamedata::ResolvedGameDataBuilder;
10use wowlab_engine_ports::{
11 DataResolver, DynDataResolver, EngineError, SpecDescriptor, SpellId, TalentSelection,
12};
13use wowlab_types::{
14 game::RaceId,
15 sim::{FastSet, SpellIdx},
16};
17
18use self::{
19 crit::apply_crit_chance_scaled_player_axis,
20 masking::mask_declared_passive_effects,
21 sources::{
22 resolve_baseline_passives, resolve_race_passives, resolve_set_bonus_passives,
23 resolve_spell_overrides, resolve_talent_passives,
24 },
25 stats::{apply_passive_globals, rescale_folded_effect_precision},
26};
27
28#[rustfmt::skip]
29pub(super) use crit::resolve_crit_chance_scaled_crit_damage;
30#[rustfmt::skip]
31#[cfg(test)]
32pub(super) use masking::should_fold_sheet_stat_passive;
33#[rustfmt::skip]
34pub(super) use masking::{
35 EquippedWeapons, apply_declared_passive_effect_overrides,
36 mask_spec_conditional_passive_effects, retain_statically_folded_passives,
37};
38#[rustfmt::skip]
39#[cfg(test)]
40pub(super) use sources::apply_spell_overrides;
41#[rustfmt::skip]
42pub(crate) use sources::resolve_race_passive_spells;
43#[rustfmt::skip]
44#[cfg(test)]
45pub(super) use stats::apply_effect_precision_scales;
46#[rustfmt::skip]
47pub(super) use stats::apply_talent_effect_overrides;
48
49#[rustfmt::skip]
50pub(crate) use stats::resolve_stat_passives;
51
52#[cfg(test)]
53#[path = "passives/sources/tests.rs"]
54mod learned_passive_tests;
55
56pub(super) struct PassiveData {
57 pub passives: Vec<wowlab_engine_domain::dbc::RankedPassive>,
58 pub effect_overrides: Vec<((u32, u8), f64)>,
60 pub crit_chance_scaled: Vec<wowlab_engine_domain::dbc::CritChanceScaledCritDamage>,
62 pub active_aura_spell_ids: FastSet<i32>,
63 pub overrides: Vec<(SpellId, SpellId)>,
64 pub resolvable_spec_spells: Vec<u32>,
65 pub class_target_debuffs: Vec<u32>,
66}
67
68pub(super) struct PassiveInputs<'a> {
70 pub descriptor: &'a SpecDescriptor,
71 pub talents: &'a [TalentSelection],
72 pub set_bonus_auras: &'a [u32],
73 pub weapons: EquippedWeapons<'a>,
74 pub race: Option<RaceId>,
75}
76
77pub(super) async fn resolve_passives(
79 builder: &mut ResolvedGameDataBuilder,
80 resolver: &DynDataResolver<'_>,
81 inputs: PassiveInputs<'_>,
82 power_modifiers: &PowerModifiers,
83) -> Result<PassiveData, EngineError> {
84 let PassiveInputs {
85 descriptor,
86 talents,
87 set_bonus_auras,
88 weapons,
89 race,
90 } = inputs;
91 let mut passive_data = PassiveData {
92 passives: Vec::new(),
93 effect_overrides: Vec::new(),
94 crit_chance_scaled: Vec::new(),
95 active_aura_spell_ids: FastSet::default(),
96 overrides: Vec::new(),
97 resolvable_spec_spells: Vec::new(),
98 class_target_debuffs: Vec::new(),
99 };
100 let specialization_order = resolver
101 .get_spec(wowlab_types::numeric::u32_to_i32_saturating(
102 descriptor.spec_id.wow_spec_id(),
103 ))
104 .await
105 .map_err(|error| {
106 EngineError::spec_construction(format!(
107 "failed to resolve specialization metadata: {error}"
108 ))
109 })?
110 .order_index;
111
112 resolve_talent_passives(
113 builder,
114 resolver,
115 talents,
116 power_modifiers,
117 specialization_order,
118 &mut passive_data,
119 )
120 .await?;
121 resolve_set_bonus_passives(
122 resolver,
123 set_bonus_auras,
124 &mut passive_data.passives,
125 &mut passive_data.active_aura_spell_ids,
126 )
127 .await;
128 resolve_race_passives(
129 resolver,
130 race,
131 descriptor.spec_id.class(),
132 &mut passive_data,
133 )
134 .await;
135 passive_data.overrides = resolve_spell_overrides(builder, descriptor, resolver).await?;
136 passive_data.resolvable_spec_spells = resolve_baseline_passives(
137 builder,
138 descriptor,
139 resolver,
140 power_modifiers,
141 specialization_order,
142 &mut passive_data,
143 )
144 .await?;
145
146 rescale_folded_effect_precision(&mut passive_data.passives);
147 retain_statically_folded_passives(&mut passive_data.passives, weapons);
148 mask_spec_conditional_passive_effects(&mut passive_data.passives, specialization_order);
149 mask_declared_passive_effects(&mut passive_data.passives, descriptor);
150 passive_data.effect_overrides =
151 apply_declared_passive_effect_overrides(&mut passive_data.passives, descriptor);
152 passive_data.crit_chance_scaled =
153 resolve_crit_chance_scaled_crit_damage(&passive_data.passives);
154 apply_crit_chance_scaled_player_axis(builder, &passive_data.crit_chance_scaled);
155 apply_passive_globals(
156 builder,
157 &passive_data.passives,
158 &passive_data.resolvable_spec_spells,
159 );
160 builder.set_folded_passive_spells(
161 passive_data
162 .passives
163 .iter()
164 .map(|(spell, _)| {
165 SpellIdx::from_raw(wowlab_types::numeric::i32_to_u32_nonnegative(spell.id))
166 })
167 .collect::<Vec<_>>(),
168 );
169
170 Ok(passive_data)
171}