Skip to main content

wowlab_engine_content/hooks/subtlety_rogue/
trickster.rs

1use wowlab_engine_combat::DamageOps as _;
2use wowlab_engine_combat::AuraOps as _;
3use wowlab_engine_combat::{DamageFlags, HookCtx};
4use wowlab_engine_rng::proc_chance;
5use wowlab_types::sim::SpellIdx;
6
7use crate::generated::specs::subtlety_rogue::{
8    AURA_COUP_DE_GRACE, AURA_DISORIENTING_STRIKES, AURA_ESCALATING_BLADE, AURA_FAZED,
9    AURA_FLAWLESS_FORM, AURA_UNSEEN_BLADE_CD, EFFECT, HERO, REPORTED_SPELL,
10};
11
12use super::config::SubtletyConfig;
13
14const ESCALATING_BLADE_MAX: i32 = 4;
15
16/// Unseen Blade proc off Backstab/Shadowstrike: once per 20s window, bypassed by Disorienting Strikes charges.
17pub(super) fn trigger_unseen_blade(
18    ctx: &mut HookCtx<'_>,
19    cfg: &SubtletyConfig,
20) {
21    if !cfg.unseen_blade {
22        return;
23    }
24
25    let disoriented = ctx.aura_stacks(AURA_DISORIENTING_STRIKES.raw());
26
27    if ctx.is_aura_active(AURA_UNSEEN_BLADE_CD.raw()) && disoriented == 0 {
28        return;
29    }
30
31    deal_unseen_blade(ctx, cfg);
32
33    if cfg.flashing_steel_chance > 0.0 && proc_chance(ctx.rng(), cfg.flashing_steel_chance) {
34        deal_unseen_blade(ctx, cfg);
35    }
36
37    if disoriented > 0 {
38        ctx.consume_aura_stack(AURA_DISORIENTING_STRIKES.raw());
39    } else {
40        ctx.apply_aura(AURA_UNSEEN_BLADE_CD.raw());
41    }
42}
43
44fn deal_unseen_blade(ctx: &mut HookCtx<'_>, cfg: &SubtletyConfig) {
45    ctx.deal_damage_ap(
46        HERO::TRICKSTER::SPELL::UNSEEN_BLADE,
47        cfg.unseen_blade_coef,
48        DamageFlags::empty(),
49    );
50    ctx.apply_aura(AURA_FAZED.raw());
51
52    if cfg.flawless_form {
53        ctx.apply_aura(AURA_FLAWLESS_FORM.raw());
54    }
55
56    if cfg.coup_de_grace {
57        ctx.apply_aura(AURA_ESCALATING_BLADE.raw());
58
59        if ctx.aura_stacks(AURA_ESCALATING_BLADE.raw()) >= ESCALATING_BLADE_MAX {
60            ctx.apply_aura(AURA_COUP_DE_GRACE.raw());
61        }
62    }
63}
64
65/// Coup de Grace: empowered Eviscerate's three hits plus Shadowed-Finisher bonus hits.
66pub(super) fn deal_coup_de_grace(
67    ctx: &mut HookCtx<'_>,
68    cfg: &SubtletyConfig,
69    cp: f64,
70    fw_active: bool,
71) {
72    let cdg_cp = cp + cfg.cdg_bonus_cp;
73    let hits = [
74        (
75            HERO::TRICKSTER::SPELL::COUP_DE_GRACE_DAMAGE_1,
76            EFFECT::CDG_DAMAGE_1,
77        ),
78        (
79            HERO::TRICKSTER::SPELL::COUP_DE_GRACE_DAMAGE_2,
80            EFFECT::CDG_DAMAGE_2,
81        ),
82        (
83            HERO::TRICKSTER::SPELL::COUP_DE_GRACE_DAMAGE_3,
84            EFFECT::CDG_DAMAGE_3,
85        ),
86    ];
87    let bonuses = [
88        (REPORTED_SPELL::CDG_BONUS_1, EFFECT::CDG_BONUS_1),
89        (REPORTED_SPELL::CDG_BONUS_2, EFFECT::CDG_BONUS_2),
90        (REPORTED_SPELL::CDG_BONUS_3, EFFECT::CDG_BONUS_3),
91    ];
92
93    for (i, (spell, eff)) in hits.into_iter().enumerate() {
94        let coef = ctx.game_data().ap_coef(SpellIdx::from_raw(eff.0), eff.1);
95
96        ctx.deal_damage_ap(spell, coef * cdg_cp, DamageFlags::PHYSICAL);
97
98        if fw_active && cfg.shadowed_finishers_pct > 0.0 {
99            // #t(rust_unchecked_indexing) bonuses and hits have identical lengths
100            let (bonus_id, bonus_effect) = bonuses[i];
101            let bonus_coef = ctx
102                .game_data()
103                .ap_coef(SpellIdx::from_raw(bonus_effect.0), bonus_effect.1);
104
105            ctx.deal_damage_ap(
106                bonus_id,
107                bonus_coef * cfg.shadowed_finishers_pct * cdg_cp,
108                DamageFlags::empty(),
109            );
110        }
111    }
112
113    ctx.expire_aura(AURA_ESCALATING_BLADE.raw());
114
115    if cfg.flawless_form && ctx.is_aura_active(AURA_FAZED.raw()) {
116        ctx.apply_aura_n(AURA_FLAWLESS_FORM.raw(), cfg.cdg_flawless_stacks);
117    }
118}