1mod decode;
4mod effect_lookup;
5mod game_data;
6mod live_modifier;
7mod passives;
8mod semantics;
9mod spell_props;
10#[cfg(any(test, feature = "test-support"))]
11pub mod test_support;
12
13mod weapon;
14
15use wowlab_engine_ports::WeaponEnchantStat;
16#[cfg(test)]
17use wowlab_types::data::SpellResources;
18use wowlab_types::data::{SpellDataFlat, SpellEffect};
19
20#[rustfmt::skip]
21pub use decode::{
22 HealthCosts, PowerModifiers, cost_entry_index, health_cost, primary_cost, primary_cost_pct,
23 primary_max_cost_pct, primary_optional_cost, primary_optional_cost_pct,
24 primary_power_burn_cost, primary_resource_gain, scale_power_amount, secondary_cost,
25 secondary_optional_cost, secondary_resource_gain,
26};
27pub use effect_lookup::{EffectLookup, ResolvedGameDataEffectExt};
28#[rustfmt::skip]
29pub use game_data::{
30 CR_MULTIPLIER_BASE_ILVL, CooldownMutation, EXPANSION_LEVEL_SCALING,
31 SCALING_CLASS_ARMOR_MULTIPLIED, SCALING_CLASS_DAMAGE_REPLACE_STAT,
32 SCALING_CLASS_DAMAGE_SECONDARY, SCALING_CLASS_PRIMARY_STAT, TriggerCooldownTarget,
33 TriggerOperation, TriggerProgram,
34 TriggerResolveError, TriggeredDamage, damage_def, damage_school, enchantment_stat_value,
35 equipped_weapon_matches, is_physical, item_effect_scaling_budget, item_scaled_effect_value,
36 resolve_effect_program, resolve_trigger_program, resolve_triggered_damage,
37 spell_scaling_budget,
38};
39pub use live_modifier::{LiveDamageModifierKind, live_damage_modifier};
40pub use spell_props::spell_props_from;
41#[rustfmt::skip]
42pub use passives::{
43 CritChanceScaledAxis, CritChanceScaledCritDamage,
44 ModifiedPowerCoefficients, POWER_REGEN_PERIOD_SECONDS, PassiveDamageMods,
45 PassiveModifierComponents, PassivePetStatMods, PassiveProcChanceMods, PassiveQuery,
46 PassiveStatMods, RankedPassive, ResourcePassive,
47 apply_passive_cooldowns, class_passive_spell_ids, crit_chance_scaled_crit_damage,
48 crit_chance_scaled_spell_percent, is_damage_modifier_passive,
49 is_stat_modifier_passive, is_target_damage_taken_debuff, modified_effect_amplitude,
50 modified_effect_base_points,
51 modified_effect_coefficient, modified_power_coefficients, passive_auto_attack_mult,
52 passive_cast_time_ms, passive_chain_multiplier, passive_chain_target_range,
53 passive_chain_targets, passive_cooldown_ms, passive_damage_mods, passive_doses,
54 passive_duration_ms, passive_gcd_ms, passive_guardian_damage_mult, passive_hasted_cooldown,
55 passive_hasted_gcd, passive_hostile_max_range, passive_mana_regen_multiplier,
56 passive_max_charges, passive_max_stacks, passive_max_targets, passive_period_ms,
57 passive_modifier_components, passive_pet_damage_mult, passive_pet_stat_mods,
58 passive_proc_chance_mods, passive_radius, passive_resource_cost, passive_stat_mods,
59 passive_target_armor_mult, talent_resource_passives,
60};
61#[rustfmt::skip]
62pub use semantics::{
63 AURA_SUBTYPE_SEMANTICS, AttributeMask, AuraState, AuraSubtypeKind, AuraSubtypeSemantic,
64 CombatRating,
65 CombatRatingMask,
66 DefenseType,
67 CrowdControlKind, CrowdControlSemantic, DiminishingGroup, crowd_control_semantic,
68 DifficultyId, EffectSemanticCoverage, EnchantEffectKind, EnchantTriggerSemantic, IMPLICIT_TARGET_SEMANTICS, ImplicitTargetCheck,
69 ImplicitTargetDirection, ImplicitTargetObject, ImplicitTargetReference,
70 ImplicitTargetSelection, ImplicitTargetSemantic, ImplicitTargetSelector, InventoryType, ItemClass, ItemEffectTrigger,
71 GemSubclass, DungeonDifficultyFlags, ItemBonusListGroupEntryFlags, ItemBonusType, ItemContext,
72 RaidDifficultyFlags,
73 MODIFIER_PROPERTY_SEMANTICS, ModifierFilter, ModifierOperation, ModifierOperations,
74 DispelType, Mechanic, MechanicMask, ModifierPropertyKind, ModifierPropertySemantic, PetStatKind, SPELL_ATTRIBUTE_SEMANTICS,
75 SPELL_CAST_TARGET_FLAG_SEMANTICS,
76 SPELL_EFFECT_SEMANTICS, CONSUMED_PROC_ATTRIBUTES, PROC_ATTRIBUTE_SEMANTICS, ProcAttributeKind,
77 PowerType, ProcAttributeSemantic, ProcAttributes, ProcTypeMask, RatingMultiplierMask,
78 ResolvedGameDataSemanticExt, SemanticSupport, ShapeshiftForm, SpellAttributeKind,
79 SpellAttributeSemantic,
80 SpellAuraInterruptFlags, SpellCastTargetFlagSemantic, SpellInterruptFlags,
81 SpellCastTargetFlags, SpellEffectAttributes, SpellEffectImplicitTarget, SpellEffectKind,
82 SpellEffectSemantic, SpellEffectSemanticExt, SpellEffectTargetSemantic, SpellSchoolMask,
83 SpellShapeshiftFormFlags, SpellTargetMasks, StatModifierKind, TraitConditionType, TraitCurrencyFlags,
84 TraitEffectOperation, TraitNodeType, TraitTreeIndex, WeaponSubclass, ArmorSubclass,
85 ConsumableSubclass,
86 aura_subtype_is, aura_subtype_is_any, aura_subtype_kind, aura_subtype_modifier,
87 aura_subtype_raw, aura_subtype_semantic, effect_semantic_coverage,
88 enchant_effect_is, enchant_effect_kind, enchant_trigger_semantic, implicit_target_semantic, is_primary_stat_misc,
89 modifier_combination_supported, modifier_property_for_effect,
90 modifier_property_is, modifier_property_is_any,
91 modifier_property_kind, modifier_property_raw, modifier_property_semantic, pet_stat_kind,
92 proc_attribute_semantic,
93 item_matches_consumable_query, resolve_item_budget, rppm_crit_scaled, rppm_haste_scaled, spell_attribute_is, spell_attribute_is_any,
94 spell_attribute_semantic, spell_cast_target_flag_semantic, spell_effect_is,
95 spell_effect_is_any, spell_effect_kind, spell_effect_raw, spell_effect_semantic,
96 spell_effect_target_semantic, spell_target_masks, stance_mask_allows,
97};
98#[rustfmt::skip]
99pub use weapon::{WeaponClass, classify_weapon, normalized_speed_s, resolve_weapon_stats};
100
101#[derive(Clone, Copy, Debug, Default)]
103pub struct TriggerPayload {
104 pub sp_coef: f64,
105 pub ap_coef: f64,
106 pub base_points: f64,
107 pub amplitude: f64,
108 pub coefficient: f64,
109 pub power_coefficient_mastery_add: f64,
110}
111
112impl TriggerPayload {
113 #[must_use]
114 pub fn from_effect(effect: &SpellEffect) -> Self {
115 Self {
116 sp_coef: effect.bonus_coefficient,
117 ap_coef: effect.bonus_coefficient_from_ap,
118 base_points: effect.base_points,
119 amplitude: f64::from(effect.amplitude),
120 coefficient: f64::from(effect.coefficient),
121 power_coefficient_mastery_add: 0.0,
122 }
123 }
124
125 #[must_use]
127 pub fn has_no_coef(&self) -> bool {
128 self.sp_coef == 0.0 && self.ap_coef == 0.0
130 }
131}
132
133#[must_use]
135pub fn effect_redirects_to_trigger_spell(effect: &SpellEffect) -> bool {
136 if effect.trigger_spell == 0 {
137 return false;
138 }
139
140 match spell_effect_kind(effect.effect) {
141 Some(
142 SpellEffectKind::TriggerMissile
143 | SpellEffectKind::TriggerSpell
144 | SpellEffectKind::TriggerSpellWithValue
145 | SpellEffectKind::TriggerSpell2,
146 ) => true,
147 Some(SpellEffectKind::ApplyAura) => effect.aura_is(AuraSubtypeKind::PeriodicTriggerSpell),
148 _ => false,
149 }
150}
151
152#[must_use]
154pub fn extract_damage_payload(spell: &SpellDataFlat) -> Option<TriggerPayload> {
155 let mut periodic: Option<TriggerPayload> = None;
156
157 for e in &spell.effects {
158 let has_coef = e.bonus_coefficient != 0.0 || e.bonus_coefficient_from_ap != 0.0;
160
161 if !has_coef {
162 continue;
163 }
164
165 let payload = TriggerPayload::from_effect(e);
166 let effect_kind = spell_effect_kind(e.effect);
167
168 if matches!(
169 effect_kind,
170 Some(SpellEffectKind::SchoolDamage | SpellEffectKind::HealthLeech)
171 ) {
172 return Some(payload);
173 }
174
175 if effect_kind == Some(SpellEffectKind::ApplyAura)
176 && e.aura_is_any(&[
177 AuraSubtypeKind::PeriodicDamage,
178 AuraSubtypeKind::PeriodicLeech,
179 ])
180 && periodic.is_none()
181 {
182 periodic = Some(payload);
183 }
184 }
185
186 periodic
187}
188
189#[must_use]
191pub fn classify_enchant_buff_effect(
192 aura_kind: i32,
193 misc_value_0: i32,
194 base_points: f64,
195) -> (WeaponEnchantStat, f64) {
196 match aura_subtype_kind(aura_kind) {
197 Some(AuraSubtypeKind::ModStat) if is_primary_stat_misc(misc_value_0) => {
198 (WeaponEnchantStat::PrimaryStat, base_points)
199 }
200 Some(AuraSubtypeKind::ModTotalStatPercentage) => {
201 (WeaponEnchantStat::PrimaryStatPercent, base_points)
202 }
203 Some(AuraSubtypeKind::ModRating) => {
204 match CombatRatingMask::from_dbc(misc_value_0).primary_rating() {
205 Some(CombatRating::Crit) => (WeaponEnchantStat::Crit, base_points),
206 Some(CombatRating::Haste) => (WeaponEnchantStat::Haste, base_points),
207 Some(CombatRating::Versatility) => (WeaponEnchantStat::Versatility, base_points),
208 Some(CombatRating::Mastery) => (WeaponEnchantStat::Mastery, base_points),
209 None => (WeaponEnchantStat::None, 0.0),
210 }
211 }
212 _ => (WeaponEnchantStat::None, 0.0),
213 }
214}
215
216#[must_use]
218pub fn cast_time_ms(spell: &SpellDataFlat) -> i32 {
219 if is_channeled_spell(spell) {
220 spell.duration
221 } else {
222 spell.cast_time
223 }
224}
225
226#[must_use]
228pub fn is_channeled_spell(spell: &SpellDataFlat) -> bool {
229 spell_attribute_is_any(
230 &spell.attributes,
231 &[
232 SpellAttributeKind::Channeled,
233 SpellAttributeKind::SelfChanneled,
234 ],
235 )
236}
237
238#[cfg(test)]
239#[path = "dbc_tests.rs"]
240mod tests;