1use wowlab_engine_combat::{
2 BuffEffect, DriverSpellModifier, DriverSpellModifierResult, MasteryCtx, PeriodicDriver,
3 RuntimeDamageDef,
4};
5use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
6use wowlab_types::constants::HUNDRED;
7
8use crate::generated::specs::assassination_rogue::{
9 AURA_AMPLIFYING_POISON_DEBUFF, AURA_ATROPHIC_POISON, AURA_CRIPPLING_POISON,
10 AURA_DEADLY_POISON_DOT, AURA_DEATHMARK, AURA_FATEBOUND_COIN_HEADS, AURA_GARROTE,
11 AURA_INTERNAL_BLEEDING, AURA_KINGSBANE, AURA_RUPTURE, EFFECT, HERO, REPORTED_SPELL, SET_BONUS,
12 SPELL, SPELL_AMBUSH, SPELL_DEATHMARK, SPELL_ENVENOM, SPELL_GARROTE, SPELL_RUPTURE, TALENT,
13};
14#[rustfmt::skip]
15static LETHAL_DOSE_SPELLS: [u32; 10] = [
16 SPELL::GARROTE,
17 SPELL::RUPTURE,
18 REPORTED_SPELL::DEADLY_POISON_DOT,
19 SPELL::DEADLY_POISON_INSTANT,
20 SPELL::DEATHMARK,
21 SPELL::INTERNAL_BLEEDING_DRIVER,
22 REPORTED_SPELL::AMPLIFYING_POISON,
23 SPELL::KINGSBANE,
24 SPELL::MUTILATED_FLESH,
25 SPELL::IMPLACABLE_STRIKES_NATURE,
26];
27
28#[rustfmt::skip]
29static ZOLDYCK_RECIPE_SPELLS: [u32; 14] = [
30 SPELL::GARROTE,
31 SPELL::RUPTURE,
32 REPORTED_SPELL::DEADLY_POISON_DOT,
33 SPELL::AMBUSH,
34 SPELL::MUTILATE_MH,
35 SPELL::MUTILATE_OH,
36 SPELL::ENVENOM,
37 SPELL::DEADLY_POISON_INSTANT,
38 SPELL::DEATHMARK,
39 SPELL::INTERNAL_BLEEDING_DRIVER,
40 REPORTED_SPELL::AMPLIFYING_POISON,
41 SPELL::KINGSBANE,
42 SPELL::MUTILATED_FLESH,
43 SPELL::IMPLACABLE_STRIKES_NATURE,
44];
45
46static MID2_ENVENOM_SPELLS: [u32; 1] = [SPELL::ENVENOM];
47static MID2_BLEED_SPELLS: [u32; 3] = [
48 SPELL::GARROTE,
49 SPELL::RUPTURE,
50 SPELL::INTERNAL_BLEEDING_DRIVER,
51];
52
53crate::hooks::define_spec_config! {
54pub(super) struct AssassinationConfig {
56 seal_fate_chance: f64 { provenance: "Seal Fate chance from 14190 e1.", gate: params.talent_selected(TALENT::SEAL_FATE), source: params.require_effect_base_points(EFFECT::SEAL_FATE_CHANCE)?, transform: |value| value / HUNDRED, },
57 seal_fate_cp: f64 { provenance: "Seal Fate combo points from 14190 e2.", gate: params.talent_selected(TALENT::SEAL_FATE), source: params.require_effect_base_points(EFFECT::SEAL_FATE_CP)?, transform: |value| value, },
58 deal_fate_cp: f64 { provenance: "Deal Fate combo points from its manifest effect binding.", gate: params.talent_selected(HERO::FATEBOUND::SPELL::DEAL_FATE), source: params.require_effect_base_points(EFFECT::DEAL_FATE_CP)?, transform: |value| value, },
59 blindside_chance: f64 { provenance: "Blindside base chance 328085 e1 above the execute threshold.", gate: params.talent_selected(TALENT::BLINDSIDE), source: params.require_effect_base_points(EFFECT::BLINDSIDE_CHANCE)?, transform: |value| value / HUNDRED, },
60 blindside_execute_chance: f64 { provenance: "Blindside chance 328085 e2 below its execute threshold.", gate: params.talent_selected(TALENT::BLINDSIDE), source: params.require_effect_base_points(EFFECT::BLINDSIDE_EXECUTE_CHANCE)?, transform: |value| value / HUNDRED, },
61 blindside_execute_threshold: f64 { provenance: "Blindside execute threshold from 328085 e3.", gate: params.talent_selected(TALENT::BLINDSIDE), source: params.require_effect_base_points(EFFECT::BLINDSIDE_EXECUTE_THRESHOLD)?, transform: |value| value / HUNDRED, },
62 doomblade_pct: f64 { provenance: "Doomblade 1217954 damage fraction.", gate: params.talent_selected(TALENT::DOOMBLADE), source: params.require_effect_base_points(EFFECT::DOOMBLADE_PCT)?, transform: |value| value / HUNDRED, },
63 systemic_failure_mult: f64 { provenance: "Systemic Failure multiplicative damage modifier.", gate: params.talent_selected(TALENT::SYSTEMIC_FAILURE), source: params.require_effect_base_points(EFFECT::SYSTEMIC_FAILURE_PCT)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
64 lethal_dose_per_effect: f64 { provenance: "Lethal Dose damage per active bleed or poison; resolved talent data already includes selected ranks.", gate: params.talent_ranks(TALENT::LETHAL_DOSE) > 0, source: params.require_effect_base_points(EFFECT::LETHAL_DOSE_PER_EFFECT)?, transform: |value| value / HUNDRED, },
65 zoldyck_mult: f64 { provenance: "Zoldyck Recipe execute-range damage multiplier.", gate: params.talent_selected(TALENT::ZOLDYCK_RECIPE), source: params.require_effect_base_points(EFFECT::ZOLDYCK_DAMAGE)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
66 zoldyck_threshold: f64 { provenance: "Zoldyck Recipe target-health threshold.", gate: params.talent_selected(TALENT::ZOLDYCK_RECIPE), source: params.require_effect_base_points(EFFECT::ZOLDYCK_THRESHOLD)?, transform: |value| value / HUNDRED, },
67 delivered_doom_mult: f64 { provenance: "Delivered Doom Envenom damage multiplier.", gate: params.talent_selected(HERO::FATEBOUND::SPELL::DELIVERED_DOOM_TALENT), source: params.require_effect_base_points(EFFECT::DELIVERED_DOOM_DMG)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
68 delivered_doom_cp: f64 { provenance: "Delivered Doom combo-point threshold.", gate: params.talent_selected(HERO::FATEBOUND::SPELL::DELIVERED_DOOM_TALENT), source: params.require_effect_base_points(EFFECT::DELIVERED_DOOM_CP)?, transform: |value| value, },
69 inspiring_strike_mult: f64 { provenance: "Inspiring Strike damage multiplier while refreshed Envenom is active.", gate: params.talent_selected(TALENT::INSPIRING_STRIKE), source: params.require_effect_base_points(EFFECT::INSPIRING_STRIKE_DAMAGE)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
70 regicides_reward_divisor: i32 { provenance: "Kingsbane ramp stacks required per Regicide's Reward haste stack.", gate: params.talent_selected(TALENT::REGICIDES_REWARD), source: params.require_effect_base_points(EFFECT::REGICIDES_REWARD_DIVISOR)?, transform: wowlab_types::numeric::f64_to_i32_saturating_trunc, },
71 mid2_envenom_mult: f64 { provenance: "MID2 Assassination two-piece Envenom multiplier from 1296590 e1.", gate: has_2pc, source: params.require_effect_base_points(EFFECT::MID2_ASSASSINATION_2PC_ENVENOM)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
72 mid2_bleed_mult: f64 { provenance: "MID2 Assassination four-piece bleed multiplier from 1296591 e1.", gate: has_4pc, source: params.require_effect_base_points(EFFECT::MID2_ASSASSINATION_4PC_BLEED)?, transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
73 mid2_4pc: bool { provenance: "MID2 Assassination four-piece enables poison-specific riders.", gate: has_4pc, source: true, transform: |value| value },
74 has_internal_bleeding: bool { provenance: "Internal Bleeding selection gate.", gate: params.talent_selected(TALENT::INTERNAL_BLEEDING), source: true, transform: |value| value, },
75 has_deadly_poison: bool { provenance: "Deadly Poison selection gate.", gate: params.talent_selected(TALENT::DEADLY_POISON), source: true, transform: |value| value, },
76 has_amplifying_poison: bool { provenance: "Amplifying Poison selection gate.", gate: params.talent_selected(TALENT::AMPLIFYING_POISON), source: true, transform: |value| value, },
77 envenom_poison_bonus: f64 { provenance: "Envenom 32645 e2 poison chance bonus.", gate: true, source: params.require_effect_base_points(EFFECT::ENVENOM_POISON_BONUS)?, transform: |value| value / HUNDRED, },
78 rapid_injection_mult: f64 { provenance: "Rapid Injection per-rank damage multiplier.", gate: params.talent_ranks(TALENT::RAPID_INJECTION) > 0, source: params.require_effect_base_points(EFFECT::RAPID_INJECTION_PCT)?, transform: |value| 1.0 + value / HUNDRED * f64::from(params.talent_ranks(TALENT::RAPID_INJECTION)), fallback: 1.0, },
79 amplifying_consume_stacks: i32 { provenance: "Amplifying Poison stacks consumed by Envenom.", gate: params.talent_selected(TALENT::AMPLIFYING_POISON), source: params.require_effect_base_points(EFFECT::AMPLIFYING_CONSUME_STACKS)?, transform: wowlab_types::numeric::f64_to_i32_saturating_trunc, },
80 amplifying_bonus_mult: f64 { provenance: "Amplifying Poison Envenom bonus multiplier.", gate: params.talent_selected(TALENT::AMPLIFYING_POISON), source: params.require_effect_base_points(EFFECT::AMPLIFYING_ENVENOM_BONUS)?, transform: |value| 1.0 + value / HUNDRED, },
81 alacrity_chance_per_cp: f64 { provenance: "Alacrity chance per combo point.", gate: params.talent_selected(TALENT::ALACRITY), source: params.require_effect_base_points(EFFECT::ALACRITY_CHANCE_PER_CP)?, transform: |value| value / HUNDRED, },
82 dashing_scoundrel_energy: f64 { provenance: "Dashing Scoundrel energy gain.", gate: params.talent_selected(TALENT::DASHING_SCOUNDREL), source: params.require_effect_base_points(EFFECT::DASHING_SCOUNDREL_ENERGY)?, transform: |value| value, },
83 has_implacable_regen: bool { provenance: "Implacable rank-one regeneration gate.", gate: params.talent_selected(TALENT::IMPLACABLE_1), source: true, transform: |value| value, },
84 has_implacable_strikes: bool { provenance: "Implacable rank-three strike gate.", gate: params.talent_selected(TALENT::IMPLACABLE_3), source: true, transform: |value| value, },
85 implacable_strike_cp: f64 { provenance: "Implacable strike combo-point gain.", gate: params.talent_selected(TALENT::IMPLACABLE_3), source: params.require_effect_base_points(EFFECT::IMPLACABLE_STRIKE_CP)?, transform: |value| value, },
86 has_finish_the_job: bool { provenance: "Finish the Job selection gate.", gate: params.talent_selected(TALENT::FINISH_THE_JOB), source: true, transform: |value| value, },
87 hand_of_fate_cp: f64 { provenance: "Hand of Fate combo-point gain.", gate: hand_of_fate, source: params.require_effect_base_points(EFFECT::HAND_OF_FATE_CP)?, transform: |value| value, },
88 mean_streak_odds: f64 { provenance: "Mean Streak matching-face odds modifier.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::MEAN_STREAK), source: params.require_effect_base_points(EFFECT::MEAN_STREAK_ODDS)?, transform: |value| value / HUNDRED, },
89 lucky_coin_flips: i32 { provenance: "Lucky Coin extra flips.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::LUCKY_COIN), source: params.require_effect_base_points(EFFECT::LUCKY_COIN_FLIPS)?, transform: wowlab_types::numeric::f64_to_i32_saturating_trunc, },
90 lucky_coin_odds: f64 { provenance: "Lucky Coin proc odds.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::LUCKY_COIN), source: params.require_effect_base_points(EFFECT::LUCKY_COIN_ODDS)?, transform: |value| value / HUNDRED, },
91 has_edge_case: bool { provenance: "Edge Case selection gate.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::EDGE_CASE), source: true, transform: |value| value, },
92 rush_energy: f64 { provenance: "Rush to the Inevitable energy gain.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::RUSH_TO_THE_INEVITABLE), source: params.require_effect_base_points(EFFECT::RUSH_ENERGY)?, transform: |value| value, },
93 rush_energy_edge: f64 { provenance: "Rush to the Inevitable Edge Case energy gain.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::RUSH_TO_THE_INEVITABLE), source: params.require_effect_base_points(EFFECT::RUSH_ENERGY_EDGE)?, transform: |value| value, },
94 overflowing_extra_coins: u32 { provenance: "Overflowing Purse coin count beyond the baseline flip.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::OVERFLOWING_PURSE), source: params.require_effect_base_points(EFFECT::OVERFLOWING_PURSE_COINS)?, transform: |value| wowlab_types::numeric::f64_to_u32_saturating_trunc(value).saturating_sub(1), },
95 overflowing_chance: f64 { provenance: "Overflowing Purse proc chance.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::OVERFLOWING_PURSE), source: params.require_effect_base_points(EFFECT::OVERFLOWING_PURSE_CHANCE)?, transform: |value| value / HUNDRED, },
96 controlled_chaos_streak: i32 { provenance: "Controlled Chaos forced-alternation streak.", gate: hand_of_fate && params.talent_selected(HERO::FATEBOUND::SPELL::CONTROLLED_CHAOS), source: params.require_effect_base_points(EFFECT::CONTROLLED_CHAOS_STREAK)?, transform: wowlab_types::numeric::f64_to_i32_saturating_trunc, },
97 deathmark_energy_per_tick: f64 { provenance: "Deathmark periodic Energy gain.", gate: true, source: params.require_effect_base_points(EFFECT::DEATHMARK_ENERGY)?, transform: |value| value, },
98 deathmark_period_ms: u32 { provenance: "Deathmark periodic cadence.", gate: true, source: params.game_data.effect_lookup(EFFECT::DEATHMARK_PERIOD).period(), transform: wowlab_types::numeric::f64_to_u32_saturating_round, },
99 deathmark_duration_ms: u32 { provenance: "Deathmark duration from resolved aura data.", gate: true, source: params.game_data.require_aura_duration_ms(wowlab_types::sim::SpellIdx::from_raw(SPELL::DEATHMARK))?, transform: |value| value, },
100}
101accessor pub(super) fn sin_config(ctx);
102register pub(super) fn register_assassination(built, params) -> Result<(), wowlab_engine_ports::EngineError>;
103prepare {
104 let has_2pc = params
105 .set_bonus_auras
106 .contains(&SET_BONUS::MID2_ASSASSINATION_2PC);
107 let has_4pc = params
108 .set_bonus_auras
109 .contains(&SET_BONUS::MID2_ASSASSINATION_4PC);
110 let mid2_damage = if has_2pc {
111 params.require_effect_base_points(EFFECT::MID2_ASSASSINATION_2PC_DAMAGE)? / HUNDRED
112 } else {
113 0.0
114 };
115 let mid2_auto_attack = if has_4pc {
116 params.require_effect_base_points(EFFECT::MID2_ASSASSINATION_4PC_AUTO_ATTACK)?
117 } else {
118 0.0
119 };
120 let hand_of_fate = params.talent_selected(HERO::FATEBOUND::SPELL::HAND_OF_FATE);
121 let coin_heads_initial = (params.require_effect_base_points(EFFECT::COIN_HEADS_INITIAL)?
122 + if params.talent_selected(HERO::FATEBOUND::SPELL::RAVENHOLDT_MINT) {
123 params.require_effect_base_points(EFFECT::RAVENHOLDT_MINT_HEADS)?
124 } else {
125 0.0
126 }) / HUNDRED;
127 let coin_heads_per_stack = params.require_effect_base_points(EFFECT::COIN_HEADS_PER_STACK)? / HUNDRED;
128}
129auras {
130 { provenance: "MID2 two-piece grants three percent damage while Envenom is active.", gate: has_2pc, aura: crate::generated::specs::assassination_rogue::AURA_ENVENOM, effect: BuffEffect::DamageMult(mid2_damage) },
131 { provenance: "MID2 four-piece increases auto-attack damage per Amplifying Poison stack.", gate: has_4pc, aura: AURA_AMPLIFYING_POISON_DEBUFF, effect: BuffEffect::AutoAttackDamageTaken(mid2_auto_attack) },
132 {
133 provenance: "Fatebound Heads combines its base damage with Ravenholdt Mint before per-stack growth.",
134 gate: hand_of_fate,
135 aura: AURA_FATEBOUND_COIN_HEADS,
136 effect: BuffEffect::DamageMultStacking { initial: coin_heads_initial, per_stack: coin_heads_per_stack },
137 },
138}
139wiring {
140 { provenance: "MID2 two-piece increases Envenom damage.", gate: has_2pc, apply: {
141 built.register_driver_spell_modifier(DriverSpellModifier::new(
142 SET_BONUS::MID2_ASSASSINATION_2PC,
143 &MID2_ENVENOM_SPELLS,
144 mid2_envenom_modifier,
145 ));
146 } },
147 { provenance: "MID2 four-piece increases bleeds only while Deadly Poison is active on the victim.", gate: has_4pc, apply: {
148 built.register_driver_spell_modifier(DriverSpellModifier::new(
149 SET_BONUS::MID2_ASSASSINATION_4PC,
150 &MID2_BLEED_SPELLS,
151 mid2_bleed_modifier,
152 ));
153 } },
154 { provenance: "Fatebound Coin Heads carries its scripted initial-stack bonus and its per-stack bonus as one additive modifier, so the data-derived rows would double the per-stack half.", gate: hand_of_fate, apply: {
155 for effect in [EFFECT::COIN_HEADS_PER_STACK, EFFECT::COIN_HEADS_PER_STACK_PERIODIC] {
156 built.mask_aura_dbc_effect(AURA_FATEBOUND_COIN_HEADS, effect.0, effect.1);
157 }
158 } },
159 { provenance: "Lethal Dose evaluates active target bleeds and poisons for affected damage events.", gate: params.talent_selected(TALENT::LETHAL_DOSE), apply: {
160 built.register_driver_spell_modifier(DriverSpellModifier::new(
161 TALENT::LETHAL_DOSE,
162 &LETHAL_DOSE_SPELLS,
163 lethal_dose_modifier,
164 ));
165 } },
166 { provenance: "Zoldyck Recipe evaluates target health for mastery-affected damage events.", gate: params.talent_selected(TALENT::ZOLDYCK_RECIPE), apply: {
167 built.register_driver_spell_modifier(DriverSpellModifier::new(
168 TALENT::ZOLDYCK_RECIPE,
169 &ZOLDYCK_RECIPE_SPELLS,
170 zoldyck_recipe_modifier,
171 ));
172 } },
173 { provenance: "Inspiring Strike is a triggered Envenom-overlap buff, not a passive precombat aura.", gate: params.talent_selected(TALENT::INSPIRING_STRIKE), apply: {
174 built.remove_precombat_aura(crate::generated::specs::assassination_rogue::AURA_INSPIRING_STRIKE);
175 } },
176}
177
178finish |cfg| {
179 for local in [
181 SPELL_AMBUSH,
182 SPELL_ENVENOM,
183 SPELL_GARROTE,
184 SPELL_RUPTURE,
185 SPELL_DEATHMARK,
186 ] {
187 built.patch_spell(local, |spell| {
188 spell.damage = RuntimeDamageDef::None;
189 });
190 }
191
192 match (
193 std::num::NonZeroU32::new(cfg.deathmark_period_ms),
194 std::num::NonZeroU32::new(cfg.deathmark_duration_ms),
195 ) {
196 (Some(period), Some(duration)) => built.register_periodic_driver(
197 PeriodicDriver::new(
198 SPELL::DEATHMARK,
199 period.get(),
200 super::hooks::deathmark_energize_tick,
201 )
202 .hasted()
203 .triggered_for(duration.get()),
204 ),
205 _ if !params.game_data.is_empty() => {
206 return Err(wowlab_engine_ports::EngineError::spec_construction(
207 "Deathmark periodic driver resolved a zero duration or interval",
208 ));
209 }
210 _ => {}
211 }
212
213 built.set_spec_config(cfg);
214 Ok(())
215}
216}
217
218fn mid2_envenom_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
219 DriverSpellModifierResult::damage_mult(
220 view.program()
221 .spec_config::<AssassinationConfig>()
222 .map_or(1.0, |cfg| cfg.mid2_envenom_mult),
223 )
224}
225
226fn mid2_bleed_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
227 let Some(cfg) = view.program().spec_config::<AssassinationConfig>() else {
228 return DriverSpellModifierResult::default();
229 };
230
231 if wowlab_engine_combat::is_aura_active(
232 &view.combat(),
233 AURA_DEADLY_POISON_DOT,
234 wowlab_types::sim::ActorId::Player,
235 view.target,
236 ) {
237 DriverSpellModifierResult::damage_mult(cfg.mid2_bleed_mult)
238 } else {
239 DriverSpellModifierResult::default()
240 }
241}
242
243fn lethal_dose_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
244 let per_effect = view
245 .program()
246 .spec_config::<AssassinationConfig>()
247 .map_or(0.0, |config| config.lethal_dose_per_effect);
248 let active = [
249 AURA_ATROPHIC_POISON,
250 AURA_CRIPPLING_POISON,
251 AURA_DEADLY_POISON_DOT,
252 AURA_AMPLIFYING_POISON_DEBUFF,
253 AURA_GARROTE,
254 AURA_RUPTURE,
255 AURA_INTERNAL_BLEEDING,
256 AURA_DEATHMARK,
257 AURA_KINGSBANE,
258 ]
259 .into_iter()
260 .filter(|local| {
261 wowlab_engine_combat::is_aura_active(
262 &view.combat(),
263 *local,
264 wowlab_types::sim::ActorId::Player,
265 view.target,
266 )
267 })
268 .count();
269
270 DriverSpellModifierResult::damage_mult(
271 1.0 + per_effect * wowlab_types::numeric::usize_to_f64(active),
272 )
273}
274
275fn zoldyck_recipe_modifier(view: wowlab_engine_combat::HookView<'_>) -> DriverSpellModifierResult {
276 let Some(config) = view.program().spec_config::<AssassinationConfig>() else {
277 return DriverSpellModifierResult::default();
278 };
279 let health = view.target_health_fraction();
280
281 if health < config.zoldyck_threshold {
282 DriverSpellModifierResult::damage_mult(config.zoldyck_mult)
283 } else {
284 DriverSpellModifierResult::default()
285 }
286}
287
288#[must_use]
290pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
291 if ctx.is_periodic {
292 return if ctx.affects_spell(EFFECT::POTENT_ASSASSIN_PERIODIC.1) {
293 ctx.bonus(EFFECT::POTENT_ASSASSIN_PERIODIC.1)
294 } else {
295 1.0
296 };
297 }
298
299 [
300 EFFECT::POTENT_ASSASSIN_DIRECT.1,
301 EFFECT::POTENT_ASSASSIN_DIRECT_LABEL.1,
302 EFFECT::POTENT_ASSASSIN_SECONDARY_DIRECT_LABEL.1,
303 ]
304 .into_iter()
305 .find(|&effect| ctx.affects_spell(effect))
306 .map_or(1.0, |effect| ctx.bonus(effect))
307}