Skip to main content

wowlab_engine_content/hooks/shared/
rogue.rs

1use wowlab_engine_combat::{
2    AuraOps as _, DamageFlags, DamageOps as _, HookCtx, LocalAuraIdx, ResourceOps as _,
3};
4use wowlab_engine_rng::proc_chance;
5use wowlab_types::sim::SpellIdx;
6
7const MS_PER_COMBO_POINT: u32 = 3_000;
8pub(crate) const COIN_FAIR_ODDS: f64 = 0.5;
9
10pub(crate) fn cut_to_the_chase(ctx: &mut HookCtx<'_>, aura: LocalAuraIdx, combo_points: f64) {
11    if !ctx.is_aura_active(aura.raw()) {
12        ctx.apply_aura(aura.raw());
13    }
14
15    ctx.extend_aura(
16        aura.raw(),
17        wowlab_types::numeric::f64_to_u32_saturating_trunc(combo_points) * MS_PER_COMBO_POINT,
18    );
19}
20
21#[derive(Clone, Copy, Debug)]
22pub(crate) struct FateboundConfig {
23    pub hand_of_fate_cp: f64,
24    pub matching_odds: f64,
25    pub lucky_coin_odds: f64,
26    pub controlled_chaos_streak: i32,
27    pub rush_energy: f64,
28    pub rush_energy_edge: f64,
29    pub lucky_coin_flips: i32,
30    pub has_edge_case: bool,
31}
32
33#[derive(Clone, Copy, Debug)]
34pub(crate) struct FateboundIds {
35    pub heads: LocalAuraIdx,
36    pub tails: LocalAuraIdx,
37    pub lucky_coin: LocalAuraIdx,
38    pub lucky_coin_counter: LocalAuraIdx,
39    pub tails_damage_effect: (u32, u8),
40    pub tails_damage_spell: u32,
41}
42
43#[derive(Clone, Copy, Debug)]
44pub(crate) struct FateboundBinding {
45    pub config: fn(&HookCtx<'_>) -> FateboundConfig,
46    pub ids: FateboundIds,
47    pub on_tails: fn(&mut HookCtx<'_>),
48}
49
50#[derive(Clone, Copy, Debug)]
51pub(crate) struct ResolvedFatebound {
52    config: FateboundConfig,
53    ids: FateboundIds,
54    on_tails: fn(&mut HookCtx<'_>),
55}
56
57impl FateboundBinding {
58    pub(crate) fn resolve(self, ctx: &HookCtx<'_>) -> ResolvedFatebound {
59        ResolvedFatebound {
60            config: (self.config)(ctx),
61            ids: self.ids,
62            on_tails: self.on_tails,
63        }
64    }
65}
66
67pub(crate) fn bound_hand_of_fate(ctx: &mut HookCtx<'_>, biased: bool, binding: FateboundBinding) {
68    let fatebound = binding.resolve(ctx);
69
70    hand_of_fate(ctx, fatebound, biased);
71}
72
73pub(crate) fn bound_edge_case(ctx: &mut HookCtx<'_>, binding: FateboundBinding) {
74    let fatebound = binding.resolve(ctx);
75
76    edge_case(ctx, fatebound);
77}
78
79#[derive(Clone, Copy, PartialEq)]
80enum Flip {
81    Heads,
82    Tails,
83    Edge,
84}
85
86pub(crate) fn hand_of_fate(ctx: &mut HookCtx<'_>, fatebound: ResolvedFatebound, biased: bool) {
87    let config = fatebound.config;
88
89    if config.hand_of_fate_cp <= 0.0 || ctx.last_secondary_spent() < config.hand_of_fate_cp {
90        return;
91    }
92
93    flip_coin(ctx, fatebound, biased);
94}
95
96pub(crate) fn edge_case(ctx: &mut HookCtx<'_>, fatebound: ResolvedFatebound) {
97    let config = fatebound.config;
98
99    if config.has_edge_case && config.hand_of_fate_cp > 0.0 {
100        resolve_flip(ctx, fatebound, Flip::Edge);
101    }
102}
103
104pub(crate) fn flip_coin(ctx: &mut HookCtx<'_>, fatebound: ResolvedFatebound, biased: bool) {
105    let config = fatebound.config;
106    let ids = fatebound.ids;
107    let heads = ctx.aura_stacks(ids.heads.raw());
108    let tails = ctx.aura_stacks(ids.tails.raw());
109    let result = if heads == tails {
110        if proc_chance(ctx.rng(), COIN_FAIR_ODDS) {
111            Flip::Heads
112        } else {
113            Flip::Tails
114        }
115    } else {
116        let matching = if biased {
117            config.matching_odds
118                + if ctx.is_aura_active(ids.lucky_coin.raw()) {
119                    config.lucky_coin_odds
120                } else {
121                    0.0
122                }
123        } else {
124            COIN_FAIR_ODDS
125        };
126        let current_is_heads = heads > tails;
127
128        if proc_chance(ctx.rng(), matching) == current_is_heads {
129            Flip::Heads
130        } else {
131            Flip::Tails
132        }
133    };
134
135    resolve_flip(ctx, fatebound, result);
136
137    if config.controlled_chaos_streak > 0 {
138        let opposite_streak = match result {
139            Flip::Heads => tails >= config.controlled_chaos_streak,
140            Flip::Tails => heads >= config.controlled_chaos_streak,
141            Flip::Edge => false,
142        };
143
144        if opposite_streak {
145            resolve_flip(ctx, fatebound, result);
146        }
147    }
148}
149
150fn resolve_flip(ctx: &mut HookCtx<'_>, fatebound: ResolvedFatebound, result: Flip) {
151    let config = fatebound.config;
152    let ids = fatebound.ids;
153
154    if matches!(result, Flip::Heads | Flip::Edge) {
155        if result != Flip::Edge {
156            ctx.consume_aura(ids.tails.raw());
157        }
158
159        ctx.apply_aura(ids.heads.raw());
160    }
161
162    // Tails per-stack (452917 e1) and Lucky Coin (1248971 e3) are live-aura DBC modifiers on the
163    // coin damage; the generic aura-modifier path applies both, so the coefficient stays raw.
164
165    if matches!(result, Flip::Tails | Flip::Edge) {
166        if result != Flip::Edge {
167            ctx.consume_aura(ids.heads.raw());
168        }
169
170        let coefficient = ctx.game_data().ap_coef(
171            SpellIdx::from_raw(ids.tails_damage_effect.0),
172            ids.tails_damage_effect.1,
173        );
174
175        ctx.deal_damage_ap(ids.tails_damage_spell, coefficient, DamageFlags::empty());
176        ctx.apply_aura(ids.tails.raw());
177        (fatebound.on_tails)(ctx);
178    }
179
180    let energy = if result == Flip::Edge {
181        config.rush_energy_edge
182    } else {
183        config.rush_energy
184    };
185
186    if energy > 0.0 {
187        ctx.gain_resource(energy);
188    }
189
190    if config.lucky_coin_flips > 0 && !ctx.is_aura_active(ids.lucky_coin.raw()) {
191        ctx.apply_aura(ids.lucky_coin_counter.raw());
192
193        if ctx.aura_stacks(ids.lucky_coin_counter.raw()) >= config.lucky_coin_flips {
194            ctx.apply_aura(ids.lucky_coin.raw());
195            ctx.consume_aura(ids.lucky_coin_counter.raw());
196        }
197    }
198}