wowlab_engine_domain/dbc/
spell_props.rs1use wowlab_engine_gamedata::SpellProps;
2use wowlab_types::data::{SpellDataFlat, SpellResources};
3
4use super::{
5 DefenseType, SpellAttributeKind, cast_time_ms, rppm_crit_scaled, rppm_haste_scaled,
6 spell_attribute_is, spell_attribute_is_any, spell_target_masks,
7};
8
9const MELEE_CHAIN_TARGET_RANGE: f64 = 5.0;
10const RANGED_CHAIN_TARGET_RANGE: f64 = 7.5;
11const MAGIC_CHAIN_TARGET_RANGE: f64 = 10.0;
12const FULL_ANGLE_TO_HALF: f64 = 2.0;
13
14fn combine_dbc_mask_words(low: i32, high: i32) -> u64 {
15 let low = u64::from(u32::from_ne_bytes(low.to_ne_bytes()));
16 let high = u64::from(u32::from_ne_bytes(high.to_ne_bytes()));
17
18 low | (high << u32::BITS)
19}
20
21#[must_use]
23pub fn spell_props_from(spell: &SpellDataFlat, resources: SpellResources) -> SpellProps {
24 let mut props = SpellProps::default();
25
26 populate_identity(&mut props, spell);
27 populate_resources_and_timing(&mut props, spell, resources);
28 populate_targeting_and_requirements(&mut props, spell);
29 populate_periodic_and_proc(&mut props, spell);
30
31 props
32}
33
34fn populate_identity(props: &mut SpellProps, spell: &SpellDataFlat) {
35 props.attributes.clone_from(&spell.attributes);
36 props.defense_type = spell.defense_type;
37 props.dispel_type = spell.dispel_type;
38 props.mechanic = spell.mechanic;
39 props.can_empower = spell.can_empower;
40 props.empower_stages.clone_from(&spell.empower_stages);
41 props.equipped_item_requirement = spell.equipped_item_requirement;
42 props.shapeshift_required_mask =
43 combine_dbc_mask_words(spell.shapeshift_mask_0, spell.shapeshift_mask_1);
44 props.shapeshift_excluded_mask =
45 combine_dbc_mask_words(spell.shapeshift_exclude_0, spell.shapeshift_exclude_1);
46 props.caster_aura_spell = spell.caster_aura_spell;
47 props.caster_aura_state = spell.caster_aura_state;
48 props.exclude_caster_aura_spell = spell.exclude_caster_aura_spell;
49 props.exclude_caster_aura_state = spell.exclude_caster_aura_state;
50 props.target_aura_spell = spell.target_aura_spell;
51 props.target_aura_state = spell.target_aura_state;
52 props.exclude_target_aura_spell = spell.exclude_target_aura_spell;
53 props.exclude_target_aura_state = spell.exclude_target_aura_state;
54 props.aura_interrupt_flags = u32::from_ne_bytes(spell.interrupt_aura_0.to_ne_bytes());
55 props.spell_interrupt_flags = u32::from_ne_bytes(spell.interrupt_flags.to_ne_bytes());
56 props.channel_interrupt_flags = u32::from_ne_bytes(spell.interrupt_channel_0.to_ne_bytes());
57}
58
59fn populate_resources_and_timing(
60 props: &mut SpellProps,
61 spell: &SpellDataFlat,
62 resources: SpellResources,
63) {
64 props.cost = resources.primary_cost.max(0.0);
65 props.optional_cost = resources.primary_optional_cost;
66 props.cost_pct = resources.primary_cost_pct;
67 props.max_cost_pct = resources.primary_max_cost_pct;
68 props.optional_cost_pct = resources.primary_optional_cost_pct;
69 props.gain = resources.primary_gain + (-resources.primary_cost).max(0.0);
70 props.health_cost = resources.health_cost;
71 props.health_cost_pct = resources.health_cost_pct;
72 props.health_max_cost_pct = resources.health_max_cost_pct;
73 props.health_optional_cost = resources.health_optional_cost;
74 props.health_optional_cost_pct = resources.health_optional_cost_pct;
75 props.primary_cost_entry = resources.primary_cost_entry;
76 props.secondary_cost_entry = resources.secondary_cost_entry;
77 props.secondary_cost = resources.secondary_cost.max(0.0);
78 props.secondary_optional_cost = resources.secondary_optional_cost.max(0.0);
79 props.secondary_gain = resources.secondary_gain + (-resources.secondary_cost).max(0.0);
80 props.cooldown_ms = spell.recovery_time.max(0);
81 props.cooldown_category = wowlab_types::data::CooldownCategoryId::from_raw(spell.category_id);
82 props.category_cooldown_ms = spell.category_recovery_time.max(0);
83 props.charge_category =
84 wowlab_types::data::CooldownCategoryId::from_raw(spell.charge_category_id);
85 props.cast_time_ms = cast_time_ms(spell);
86 props.duration_ms = spell.duration;
87 props.max_stacks = spell.max_stacks;
88 props.max_charges = spell.max_charges;
89 props.charge_cd_ms = spell.charge_recovery_time;
90 props.start_recovery_category =
91 wowlab_types::data::CooldownCategoryId::from_raw(spell.start_recovery_category);
92 props.gcd_ms = spell.start_recovery_time;
93}
94
95fn populate_targeting_and_requirements(props: &mut SpellProps, spell: &SpellDataFlat) {
96 let target_masks = spell_target_masks(spell);
97
98 props.projectile_speed = f64::from(spell.speed);
99 props.launch_delay_s = f64::from(spell.launch_delay);
100 props.hostile_min_range = f64::from(spell.range_min_0);
101 props.hostile_max_range = f64::from(spell.range_max_0);
102 props.radius = f64::from(spell.radius_max.max(spell.radius_min));
103 props.chain_target_range = match DefenseType::try_from(spell.defense_type).ok() {
104 Some(DefenseType::Melee) => MELEE_CHAIN_TARGET_RANGE,
105 Some(DefenseType::Ranged) => RANGED_CHAIN_TARGET_RANGE,
106 Some(DefenseType::None | DefenseType::Magic) | None => MAGIC_CHAIN_TARGET_RANGE,
107 };
108 props.max_affected_targets =
109 u8::try_from(spell.max_targets.clamp(0, i32::from(u8::MAX))).unwrap_or(u8::MAX);
110 props.cone_half_angle = f64::from(spell.cone_degrees).to_radians() / FULL_ANGLE_TO_HALF;
111 props.fixed_travel_time =
112 spell_attribute_is(&spell.attributes, SpellAttributeKind::FixedTravelTime);
113 props.ignores_line_of_sight =
114 spell_attribute_is(&spell.attributes, SpellAttributeKind::IgnoreLineOfSight);
115 props.explicit_target_mask = target_masks.explicit.bits();
116 props.required_explicit_target_mask = target_masks.required.bits();
117 props.gcd_haste_type = wowlab_types::combat::GcdHasteType::None;
118 props.hasted_ticks = spell.hasted_ticks;
119 props.duration_hasted = spell.duration_hasted;
120 props.mastery_affects_points =
121 spell_attribute_is(&spell.attributes, SpellAttributeKind::MasteryAffectsPoints);
122 props.cannot_crit = spell_attribute_is(&spell.attributes, SpellAttributeKind::CannotCrit);
123 props.requires_unshifted =
124 spell_attribute_is(&spell.attributes, SpellAttributeKind::NotShapeshifted);
125 props.allow_while_unshifted = spell_attribute_is(
126 &spell.attributes,
127 SpellAttributeKind::AllowWhileNotShapeshifted,
128 );
129 props.requires_stealth =
130 spell_attribute_is(&spell.attributes, SpellAttributeKind::RequiresStealth);
131 props.treat_as_periodic =
132 spell_attribute_is(&spell.attributes, SpellAttributeKind::TreatAsPeriodic);
133 props.treat_as_area_effect =
134 spell_attribute_is(&spell.attributes, SpellAttributeKind::TreatAsAreaEffect);
135 props.does_not_break_stealth =
136 spell_attribute_is(&spell.attributes, SpellAttributeKind::AllowWhileStealthed);
137 props.usable_while_casting = spell_attribute_is_any(
138 &spell.attributes,
139 &[
140 SpellAttributeKind::AllowCastWhileCasting,
141 SpellAttributeKind::AllowCastWhileChanneling,
142 ],
143 );
144 props.asynchronous_stacks = spell_attribute_is(
145 &spell.attributes,
146 SpellAttributeKind::AsynchronousStackingBuff,
147 );
148 props.disable_player_damage_multiplier = spell_attribute_is(
149 &spell.attributes,
150 SpellAttributeKind::DisablePlayerMultiplier,
151 );
152 props.disable_target_damage_multiplier = spell_attribute_is(
153 &spell.attributes,
154 SpellAttributeKind::DisableTargetMultiplier,
155 );
156 props.disable_positive_target_damage_multiplier = spell_attribute_is(
157 &spell.attributes,
158 SpellAttributeKind::DisableTargetPositiveMultiplier,
159 );
160 props.requires_main_hand =
161 spell_attribute_is(&spell.attributes, SpellAttributeKind::RequiresMainHand);
162 props.requires_off_hand =
163 spell_attribute_is(&spell.attributes, SpellAttributeKind::RequiresOffHand);
164}
165
166fn populate_periodic_and_proc(props: &mut SpellProps, spell: &SpellDataFlat) {
167 props.refresh_behavior = spell.refresh_behavior;
168 props.rolling_periodic = spell.rolling_periodic;
169 props.tick_may_crit = spell.tick_may_crit;
170 props.tick_on_application = spell.tick_on_application;
171 props.proc_chance_pct = spell.proc_chance;
172 props.proc_charges = spell.proc_charges.max(0);
173 props.proc_type_mask = u64::try_from(spell.proc_type_mask).unwrap_or(0);
174 props.proc_category_recovery_ms = spell.proc_category_recovery_ms.max(0);
175 props.rppm_base_rate = f64::from(spell.rppm_base_rate);
176 props.rppm_flags = spell.rppm_flags;
177 props.rppm_haste_scales = rppm_haste_scaled(&spell.rppm_mods);
178 props.rppm_crit_scales = rppm_crit_scaled(&spell.rppm_mods);
179 props.hit_chance_multiplier = 1.0;
180 props.target_resistance_multiplier = 1.0;
181 props.dispel_resistance_multiplier = 1.0;
182}