Skip to main content

wowlab_engine_content/
racials.rs

1//! Race-specific DPS abilities (Blood Fury, Berserking, Ancestral Call) registered post-build at sim start.
2
3use wowlab_engine_combat::{BuffEffect, MAX_AURA_BUFF_EFFECTS};
4use wowlab_engine_domain::dbc::EffectLookup;
5use wowlab_engine_gamedata::ResolvedGameData;
6use wowlab_engine_ports::EngineError;
7use wowlab_types::{
8    constants::{ANCESTRAL_CALL_SPELL_ID, BERSERKING_SPELL_ID, BLOOD_FURY_SPELL_ID},
9    game::RaceId,
10    sim::{EffectRef, SpellIdx},
11};
12
13use crate::raid_buffs::PrecombatBuffSpec;
14
15const BLOOD_FURY_DURATION_MS: u32 = 15_000;
16const BLOOD_FURY_EFFECT: u8 = 1;
17
18const BERSERKING_DURATION_MS: u32 = 12_000;
19const BERSERKING_EFFECT: u8 = 1;
20
21const ANCESTRAL_CALL_DURATION_MS: u32 = 15_000;
22const ANCESTRAL_CALL_EFFECT: u8 = 1;
23
24fn primary_stat_effects(
25    lookup: EffectLookup<'_>,
26) -> Result<[Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS], EngineError> {
27    let data = lookup.data;
28    let level = data.level();
29    let coef = lookup.coefficient();
30    let budget = wowlab_engine_domain::dbc::spell_scaling_budget(
31        data.game_tables(),
32        wowlab_engine_domain::dbc::SCALING_CLASS_PRIMARY_STAT,
33        level,
34    )
35    .ok_or_else(|| {
36        EngineError::spec_construction(format!(
37            "spell scaling budget (class {}) missing for level {level}",
38            wowlab_engine_domain::dbc::SCALING_CLASS_PRIMARY_STAT
39        ))
40    })?;
41    let mut effects = [None; MAX_AURA_BUFF_EFFECTS];
42
43    effects[0] = Some(BuffEffect::PrimaryStat(coef * budget));
44
45    Ok(effects)
46}
47
48/// The racial precombat-buff spec for `race`, or `None` for races with no modelled DPS racial.
49#[must_use]
50pub(crate) fn racial_spec(race: RaceId) -> Option<PrecombatBuffSpec> {
51    match race {
52        RaceId::Orc => Some(blood_fury_spec()),
53        RaceId::Troll => Some(berserking_spec()),
54        RaceId::MagharOrc => Some(ancestral_call_spec()),
55        _ => None,
56    }
57}
58
59fn blood_fury_effects(
60    data: &ResolvedGameData,
61) -> Result<[Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS], EngineError> {
62    primary_stat_effects(EffectLookup::new(
63        data,
64        EffectRef::new(SpellIdx::from_raw(BLOOD_FURY_SPELL_ID), BLOOD_FURY_EFFECT),
65    ))
66}
67
68/// Orc Blood Fury (20572) precombat-buff spec.
69pub(crate) const fn blood_fury_spec() -> PrecombatBuffSpec {
70    PrecombatBuffSpec::fixed(
71        BLOOD_FURY_SPELL_ID,
72        "Blood Fury",
73        BLOOD_FURY_DURATION_MS,
74        blood_fury_effects,
75    )
76}
77
78#[expect(
79    clippy::unnecessary_wraps,
80    reason = "precombat effect factories share a fallible callback signature"
81)]
82fn berserking_effects(
83    data: &ResolvedGameData,
84) -> Result<[Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS], EngineError> {
85    let haste = data.base_points(SpellIdx::from_raw(BERSERKING_SPELL_ID), BERSERKING_EFFECT);
86    let mut effects = [None; MAX_AURA_BUFF_EFFECTS];
87
88    effects[0] = Some(BuffEffect::HasteMult(haste));
89
90    Ok(effects)
91}
92
93/// Troll Berserking (26297) precombat-buff spec.
94pub(crate) const fn berserking_spec() -> PrecombatBuffSpec {
95    PrecombatBuffSpec::fixed(
96        BERSERKING_SPELL_ID,
97        "Berserking",
98        BERSERKING_DURATION_MS,
99        berserking_effects,
100    )
101}
102
103fn ancestral_call_effects(
104    data: &ResolvedGameData,
105) -> Result<[Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS], EngineError> {
106    primary_stat_effects(EffectLookup::new(
107        data,
108        EffectRef::new(
109            SpellIdx::from_raw(ANCESTRAL_CALL_SPELL_ID),
110            ANCESTRAL_CALL_EFFECT,
111        ),
112    ))
113}
114
115/// Mag'har Orc Ancestral Call (274738) precombat-buff spec.
116pub(crate) const fn ancestral_call_spec() -> PrecombatBuffSpec {
117    PrecombatBuffSpec::fixed(
118        ANCESTRAL_CALL_SPELL_ID,
119        "Ancestral Call",
120        ANCESTRAL_CALL_DURATION_MS,
121        ancestral_call_effects,
122    )
123}
124
125#[cfg(test)]
126mod tests;