Skip to main content

wowlab_engine_content/hooks/beast_mastery_hunter/
hooks.rs

1use wowlab_engine_combat::{
2    AuraOps as _, CooldownOps as _, DamageFlags, DamageOps as _, HookCtx, ImpactEvent, MasteryCtx,
3    ResourceOps as _, SwingEvent, SwingHand,
4};
5use wowlab_engine_rng::proc_chance;
6use wowlab_types::constants::HUNDRED;
7
8use super::config::BeastMasteryConfig;
9use crate::{
10    generated::specs::beast_mastery_hunter::{
11        AURA, AURA_BARBED_SHOT_REGEN, AURA_BEAR_ACTIVE, AURA_BEAR_READY, AURA_BEAST_CLEAVE,
12        AURA_BLOODSHED_DOT, AURA_BLOODY_FRENZY, AURA_BOAR_READY, AURA_COBRA_FANG,
13        AURA_DIRE_BEAST_ACTIVE, AURA_HOWL_COOLDOWN, AURA_HUNTMASTERS_CALL, AURA_NATURES_ALLY,
14        AURA_NATURES_ALLY_KILL_COMMAND, AURA_REND_FLESH, AURA_WYVERN_READY, AURA_WYVERNS_CRY,
15        DAMAGE_EFFECT as MID2_DAMAGE_EFFECT, DAMAGE_REPORTED as MID2_DAMAGE_REPORTED, HERO,
16        SPELL_BARBED_SHOT, SPELL_KILL_COMMAND,
17    },
18    hooks::shared::hunter::{PackLeaderHowl, apply_next_howl_ready, consume_howl},
19};
20
21/// Stable pets inherit 0.6 owner AP.
22const STABLE_PET_AP: f64 = 0.6;
23
24/// Dire beasts inherit 1.0 owner AP.
25const DIRE_BEAST_AP: f64 = 1.0;
26
27/// Fenryr and Hati inherit 2.0 owner AP.
28const CALL_PET_AP: f64 = 2.0;
29const PET_SWING_MS: u32 = 2_000;
30const BEAR_SWING_MS: u32 = 1_500;
31
32/// Spell 267116 supplies two permanent pets without a damage penalty.
33const PERMANENT_PETS: usize = 2;
34
35/// War Orders 393933 e3 reduces Kill Command cooldown by three seconds.
36const WAR_ORDERS_KC_CDR_MS: u32 = 3_000;
37
38/// Fenryr/Hati melee swings over their 10s summon.
39const CALL_PET_SWINGS: f64 = 5.0;
40
41/// Fenryr's Ravenous Leap: 8 x 1s ticks (459753).
42const RAVENOUS_LEAP_TICKS: f64 = 8.0;
43
44/// Huntmaster's Call 459731 requires three stacks.
45const HUNTMASTERS_CALL_CAP: i32 = 3;
46
47/// Huntmaster's Call selects Fenryr or Hati with equal probability.
48const FENRYR_CHANCE: f64 = 0.5;
49
50/// Stampede 201594 deals eight ticks of three hits.
51const STAMPEDE_HITS: f64 = 24.0;
52
53/// Bear summons deal ten seconds of 1.5-second swings at a 7x multiplier.
54const BEAR_SWINGS: f64 = 6.7;
55const BEAR_AUTO_MULT: f64 = 7.0;
56
57/// Ursine Fury: bear summon spawns 2 dire beasts (472476 e1).
58const URSINE_FURY_DIRE_BEASTS: i64 = 2;
59
60/// Master of Beasts 76657: the "shots and traps" direct and periodic effects.
61const MASTERY_SHOTS_DIRECT: u8 = 5;
62const MASTERY_SHOTS_PERIODIC: u8 = 6;
63
64/// Returns Master of Beasts mastery damage: 76657 e1 for pets, e5/e6 for the hunter's own shots.
65#[must_use]
66pub(crate) fn mastery(ctx: &MasteryCtx<'_>) -> f64 {
67    if ctx.is_pet
68        || ctx.spell_id == HERO::BM_PETS::SPELL::BLOODSHED
69        || ctx.spell_id == AURA::REND_FLESH
70    {
71        return ctx.bonus(1);
72    }
73
74    // "and the damage of your shots and traps by $s5.1%" — e5/e6 carry their own
75    // affect list at half the pet coefficient, and base points of 0, so the static
76    // fold resolves them to nothing and only the live mastery value supplies them.
77
78    if ctx.is_periodic {
79        ctx.affected_spell_bonus(MASTERY_SHOTS_PERIODIC)
80    } else {
81        ctx.affected_spell_bonus(MASTERY_SHOTS_DIRECT)
82    }
83}
84
85fn pet_mult(ctx: &HookCtx<'_>) -> f64 {
86    let cry = f64::from(ctx.aura_stacks(AURA_WYVERNS_CRY));
87
88    1.0 + cry
89        * ctx.effect_percent(
90            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WYVERNS_CRY_PCT,
91        )
92}
93
94fn stable_pets(ctx: &HookCtx<'_>) -> usize {
95    if ctx.is_aura_active(AURA_NATURES_ALLY) {
96        PERMANENT_PETS + 1
97    } else {
98        PERMANENT_PETS
99    }
100}
101
102fn advance_howl_cycle(ctx: &mut HookCtx<'_>) {
103    if ctx.is_aura_active(AURA_HOWL_COOLDOWN) {
104        return;
105    }
106
107    let ready_up = ctx.is_aura_active(AURA_WYVERN_READY)
108        || ctx.is_aura_active(AURA_BOAR_READY)
109        || ctx.is_aura_active(AURA_BEAR_READY);
110
111    if ready_up {
112        return;
113    }
114
115    apply_next_howl_ready(
116        ctx,
117        wowlab_engine_combat::generated_identities::proc_counter::BEAST_MASTERY_HOWL,
118        [AURA_WYVERN_READY, AURA_BOAR_READY, AURA_BEAR_READY],
119    );
120    ctx.apply_aura(AURA_HOWL_COOLDOWN);
121}
122
123/// Whole melee swings a temporary beast lands over `duration_ms`, counting the one it opens with.
124///
125/// A summon lands one opening swing plus every whole hasted swing period that fits in its window.
126/// Guardian autos inherit the owner's haste.
127fn temporary_pet_swings(ctx: &HookCtx<'_>, duration_ms: u32, swing_ms: u32) -> f64 {
128    let hasted_swing_ms = f64::from(swing_ms) / (1.0 + ctx.buf().player().haste / HUNDRED);
129
130    if hasted_swing_ms <= 0.0 {
131        return 1.0;
132    }
133
134    1.0 + (f64::from(duration_ms) / hasted_swing_ms).floor()
135}
136
137fn summon_dire_beast(ctx: &mut HookCtx<'_>) {
138    ctx.apply_aura(AURA_DIRE_BEAST_ACTIVE);
139
140    let duration_ms = ctx.action_aura_duration_ms(
141        crate::generated::specs::beast_mastery_hunter::DAMAGE_AURA::DIRE_BEAST_ACTIVE,
142    );
143    // Temporary-pet melee is collapsed to summon time.
144    let melee = DIRE_BEAST_AP
145        * wowlab_engine_domain::damage::pet_swing_ap(PET_SWING_MS)
146        * temporary_pet_swings(ctx, duration_ms, PET_SWING_MS)
147        * pet_mult(ctx);
148
149    ctx.deal_physical_pet_damage_ap(
150        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::MELEE,
151        melee,
152    );
153    trigger_huntmasters_call(ctx);
154}
155
156pub(crate) fn dire_beast_proc(ctx: &mut HookCtx<'_>, _impact: ImpactEvent) {
157    summon_dire_beast(ctx);
158}
159
160// Temporary-pet melee and Ravenous Leap are collapsed to summon time.
161fn trigger_huntmasters_call(ctx: &mut HookCtx<'_>) {
162    ctx.apply_aura(AURA_HUNTMASTERS_CALL);
163
164    if ctx.aura_stacks(AURA_HUNTMASTERS_CALL) < HUNTMASTERS_CALL_CAP {
165        return;
166    }
167
168    ctx.expire_aura(AURA_HUNTMASTERS_CALL);
169    let mult = pet_mult(ctx);
170    let melee = CALL_PET_AP
171        * wowlab_engine_domain::damage::pet_swing_ap(PET_SWING_MS)
172        * CALL_PET_SWINGS
173        * mult;
174
175    ctx.deal_physical_pet_damage_ap(
176        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::MELEE,
177        melee,
178    );
179
180    if proc_chance(ctx.rng(), FENRYR_CHANCE) {
181        let leap = ctx.effect_ap_coefficient(
182            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::RAVENOUS_LEAP,
183        ) * CALL_PET_AP
184            * RAVENOUS_LEAP_TICKS
185            * mult;
186
187        ctx.deal_physical_pet_damage_ap(
188            crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::RAVENOUS_LEAP,
189            leap,
190        );
191    }
192}
193
194fn howl_wyvern(ctx: &mut HookCtx<'_>) {
195    let stacks = ctx.effect_base_points(
196        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::HOWL_WYVERN_STACKS,
197    );
198
199    ctx.apply_aura_n(
200        AURA_WYVERNS_CRY,
201        wowlab_types::numeric::f64_to_i32_saturating_trunc(stacks),
202    );
203}
204
205fn howl_boar(ctx: &mut HookCtx<'_>) {
206    let mult = pet_mult(ctx);
207
208    ctx.deal_damage_ap(
209        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::BOAR_CHARGE,
210        ctx.effect_ap_coefficient(
211            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::BOAR_CHARGE,
212        ) * mult,
213        DamageFlags::PHYSICAL,
214    );
215    ctx.deal_damage_ap(
216        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::BOAR_CHARGE_CLEAVE,
217        ctx.effect_ap_coefficient(
218            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::BOAR_CHARGE_CLEAVE,
219        ) * mult,
220        DamageFlags::PHYSICAL,
221    );
222}
223
224fn howl_bear(ctx: &mut HookCtx<'_>) {
225    ctx.apply_aura(AURA_BEAR_ACTIVE);
226    ctx.apply_aura(AURA_REND_FLESH);
227    let bear_melee = STABLE_PET_AP
228        * wowlab_engine_domain::damage::pet_swing_ap(BEAR_SWING_MS)
229        * BEAR_AUTO_MULT
230        * BEAR_SWINGS
231        * pet_mult(ctx);
232
233    ctx.deal_physical_pet_damage_ap(
234        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::MELEE,
235        bear_melee,
236    );
237
238    for _ in 0..URSINE_FURY_DIRE_BEASTS {
239        summon_dire_beast(ctx);
240    }
241}
242
243fn howl_consumed(ctx: &mut HookCtx<'_>) {
244    let cdr_ms = ctx.effect_base_points(
245        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::PACK_MENTALITY_BARBED_CDR,
246    );
247
248    ctx.reduce_cooldown(
249        SPELL_BARBED_SHOT,
250        wowlab_types::numeric::f64_to_u32_saturating_trunc(cdr_ms),
251    );
252}
253
254pub(crate) fn kill_command_hook(ctx: &mut HookCtx<'_>) {
255    advance_howl_cycle(ctx);
256
257    let howl_ready = ctx.is_aura_active(AURA_WYVERN_READY)
258        || ctx.is_aura_active(AURA_BOAR_READY)
259        || ctx.is_aura_active(AURA_BEAR_READY);
260
261    let mut bonus = ctx.effect_percent(
262        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::ALPHA_PREDATOR_KC_PCT,
263    );
264
265    if howl_ready {
266        bonus += ctx.effect_percent(
267            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::PACK_MENTALITY_KC_PCT,
268        );
269    }
270
271    let kc_coef = ctx.effect_ap_coefficient(
272        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::KC_PET_DAMAGE,
273    ) * STABLE_PET_AP
274        * (1.0 + bonus)
275        * pet_mult(ctx);
276
277    for _ in 0..stable_pets(ctx) {
278        ctx.deal_physical_pet_damage_ap(
279            crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::KILL_COMMAND_PET,
280            kc_coef,
281        );
282    }
283
284    let ws_coef = ctx.effect_ap_coefficient(
285        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILDSPEAKER_KC,
286    ) * DIRE_BEAST_AP
287        * pet_mult(ctx);
288
289    for _ in 0..ctx.aura_stacks(AURA_DIRE_BEAST_ACTIVE) {
290        ctx.deal_physical_pet_damage_ap(
291            crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::WILDSPEAKER_KILL_COMMAND,
292            ws_coef,
293        );
294    }
295
296    consume_howl(
297        ctx,
298        PackLeaderHowl {
299            wyvern_ready: AURA_WYVERN_READY,
300            boar_ready: AURA_BOAR_READY,
301            bear_ready: AURA_BEAR_READY,
302            on_wyvern: howl_wyvern,
303            on_boar: howl_boar,
304            on_bear: howl_bear,
305            on_consumed: howl_consumed,
306            consume_all: false,
307        },
308    );
309
310    let dire_command_chance = ctx.effect_percent(
311        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::DIRE_COMMAND_PCT,
312    );
313
314    if proc_chance(ctx.rng(), dire_command_chance) {
315        summon_dire_beast(ctx);
316    }
317
318    ctx.expire_aura(AURA_NATURES_ALLY_KILL_COMMAND);
319}
320
321pub(crate) fn barbed_shot_hook(ctx: &mut HookCtx<'_>) {
322    ctx.apply_aura(AURA_BARBED_SHOT_REGEN);
323    ctx.apply_aura(AURA_NATURES_ALLY_KILL_COMMAND);
324    let pack_tactics_focus = ctx.effect_base_points(
325        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::PACK_TACTICS_FOCUS,
326    );
327
328    ctx.gain_resource(pack_tactics_focus);
329    let stomp = ctx.effect_ap_coefficient(
330        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::STOMP_DAMAGE,
331    ) * STABLE_PET_AP
332        * pet_mult(ctx);
333
334    let cfg = ctx
335        .spec_config::<BeastMasteryConfig>()
336        .copied()
337        .unwrap_or_default();
338
339    for _ in 0..stable_pets(ctx) {
340        trigger_stomp(ctx, stomp, cfg.mid2_cobra_fang);
341
342        if cfg.mid2_extra_stomp_mult > 0.0 {
343            trigger_stomp(ctx, stomp * cfg.mid2_extra_stomp_mult, cfg.mid2_cobra_fang);
344        }
345    }
346
347    ctx.reduce_cooldown(SPELL_KILL_COMMAND, WAR_ORDERS_KC_CDR_MS);
348}
349
350pub(crate) fn bestial_wrath_hook(ctx: &mut HookCtx<'_>) {
351    ctx.apply_aura(AURA_NATURES_ALLY);
352
353    if ctx.talent_selected(
354        crate::generated::specs::beast_mastery_hunter::DAMAGE_TALENT::BLOODY_FRENZY,
355    ) {
356        ctx.apply_aura(AURA_BLOODY_FRENZY);
357    }
358
359    let mult = pet_mult(ctx);
360    let bw_hit = ctx.effect_ap_coefficient(
361        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::PET_BW_DAMAGE,
362    ) * STABLE_PET_AP
363        * mult;
364    let hooves = ctx.effect_ap_coefficient(
365        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::STOMP_DAMAGE,
366    ) * (1.0
367        + ctx.effect_percent(
368            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::THUNDERING_HOOVES_PCT,
369        ))
370        * STABLE_PET_AP
371        * mult;
372
373    for _ in 0..stable_pets(ctx) {
374        ctx.deal_physical_pet_damage_ap(
375            crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::PET_BESTIAL_WRATH,
376            bw_hit,
377        );
378
379        let cobra_fang = ctx
380            .spec_config::<BeastMasteryConfig>()
381            .is_some_and(|cfg| cfg.mid2_cobra_fang);
382
383        trigger_stomp(ctx, hooves, cobra_fang);
384    }
385
386    ctx.apply_aura(AURA_BLOODSHED_DOT);
387    ctx.grant_charges(SPELL_BARBED_SHOT, 1);
388
389    // Stampede is collapsed to the Bestial Wrath cast.
390    ctx.deal_effect_damage_ap_with_geometry(
391        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::STAMPEDE_DAMAGE,
392        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::STAMPEDE_GEOMETRY,
393        STAMPEDE_HITS,
394        DamageFlags::PHYSICAL,
395    );
396}
397
398/// Executes each stable pet's Wild Thrash and enables Beast Cleave.
399pub(crate) fn wild_thrash_hook(ctx: &mut HookCtx<'_>) {
400    let target_count = ctx.with_resolved_spell_effect_targets(
401        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILD_THRASH_DAMAGE,
402        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILD_THRASH_DAMAGE.action(),
403        None,
404        |_ctx, targets| targets.len(),
405    );
406    let damage_mult = wild_thrash_target_multiplier(
407        target_count,
408        ctx.effect_base_points(
409            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILD_THRASH_THRESHOLD,
410        ),
411        ctx.effect_percent(
412            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILD_THRASH_BONUS,
413        ),
414    );
415
416    for _ in 0..stable_pets(ctx) {
417        ctx.deal_effect_damage_ap(
418            crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::WILD_THRASH_DAMAGE,
419            STABLE_PET_AP * pet_mult(ctx) * damage_mult,
420            DamageFlags::PHYSICAL | DamageFlags::PET,
421        );
422    }
423
424    let duration_ms = wowlab_types::numeric::f64_to_u32_saturating_trunc(ctx.effect_base_points(
425        crate::generated::specs::beast_mastery_hunter::DAMAGE_EFFECT::BEAST_CLEAVE_DURATION,
426    ));
427
428    ctx.apply_aura_with_duration(AURA_BEAST_CLEAVE, duration_ms);
429}
430
431fn wild_thrash_target_multiplier(target_count: usize, threshold: f64, bonus: f64) -> f64 {
432    let target_count = f64::from(u32::try_from(target_count).unwrap_or(u32::MAX));
433
434    if target_count >= threshold {
435        1.0 + bonus
436    } else {
437        1.0
438    }
439}
440
441pub(crate) fn cobra_shot_hook(ctx: &mut HookCtx<'_>) {
442    const BARBED_SCALES_CDR_MS: u32 = 2_000;
443    ctx.reduce_cooldown(SPELL_BARBED_SHOT, BARBED_SCALES_CDR_MS);
444    ctx.apply_aura(AURA_NATURES_ALLY_KILL_COMMAND);
445
446    let cfg = ctx
447        .spec_config::<BeastMasteryConfig>()
448        .copied()
449        .unwrap_or_default();
450    let coefficient = ctx.effect_ap_coefficient(MID2_DAMAGE_EFFECT::COBRA_SHOT_DAMAGE);
451    let stacks = ctx.aura_stacks(AURA_COBRA_FANG);
452
453    ctx.deal_damage_ap(
454        MID2_DAMAGE_EFFECT::COBRA_SHOT_DAMAGE.action(),
455        if stacks > 0 && !ctx.is_aura_active(AURA_BEAST_CLEAVE) {
456            coefficient * (1.0 + f64::from(stacks) * cfg.cobra_fang_primary_per_stack)
457        } else {
458            coefficient
459        },
460        DamageFlags::empty(),
461    );
462
463    if stacks <= 0 {
464        return;
465    }
466
467    let stacks = f64::from(stacks);
468
469    if ctx.is_aura_active(AURA_BEAST_CLEAVE) {
470        let primary = ctx.target();
471
472        ctx.with_resolved_spell_effect_targets(
473            MID2_DAMAGE_EFFECT::COBRA_CLEAVE_GEOMETRY,
474            MID2_DAMAGE_REPORTED::COBRA_CLEAVE,
475            None,
476            |ctx, targets| {
477                for target in targets
478                    .iter()
479                    .copied()
480                    .filter(|target| Some(*target) != primary)
481                {
482                    ctx.deal_damage_ap_to_target(
483                        target,
484                        MID2_DAMAGE_REPORTED::COBRA_CLEAVE,
485                        coefficient * stacks * cfg.cobra_fang_cleave_per_stack,
486                        DamageFlags::empty(),
487                    );
488                }
489            },
490        );
491    }
492
493    ctx.expire_aura(AURA_COBRA_FANG);
494}
495
496fn trigger_stomp(ctx: &mut HookCtx<'_>, coefficient: f64, cobra_fang: bool) {
497    ctx.deal_physical_pet_damage_ap(
498        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::STOMP,
499        coefficient,
500    );
501
502    if cobra_fang {
503        ctx.apply_aura(AURA_COBRA_FANG);
504    }
505}
506
507/// Claw 16827 uses 0.75 pet AP at 0.88 casts per two-second swing window.
508const CLAW_COEF: f64 = 0.75;
509const CLAWS_PER_SWING: f64 = 0.88;
510
511pub(crate) fn pet_swing(ctx: &mut HookCtx<'_>, event: SwingEvent) {
512    let hand = event.hand;
513
514    if hand == SwingHand::OffHand {
515        return;
516    }
517
518    let mult = pet_mult(ctx);
519    let extra_pets = stable_pets(ctx) - 1;
520    let swing = STABLE_PET_AP
521        * wowlab_engine_domain::damage::pet_swing_ap(PET_SWING_MS)
522        * mult
523        * wowlab_types::numeric::usize_to_f64(extra_pets);
524
525    ctx.deal_physical_pet_damage_ap(
526        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::MELEE,
527        swing,
528    );
529
530    let claw = STABLE_PET_AP
531        * CLAW_COEF
532        * CLAWS_PER_SWING
533        * mult
534        * wowlab_types::numeric::usize_to_f64(stable_pets(ctx));
535
536    ctx.deal_physical_pet_damage_ap(
537        crate::generated::specs::beast_mastery_hunter::DAMAGE_SPELL::CLAW,
538        claw,
539    );
540}
541
542#[cfg(test)]
543mod tests {
544    use googletest::prelude::*;
545
546    use super::wild_thrash_target_multiplier;
547
548    #[gtest]
549    #[rstest::rstest]
550    #[case::below_threshold(1, 2.0, 2.0, 1.0)]
551    #[case::at_threshold(2, 2.0, 2.0, 3.0)]
552    #[case::above_threshold(5, 2.0, 2.0, 3.0)]
553    fn wild_thrash_bonus_uses_resolved_target_count(
554        #[case] target_count: usize,
555        #[case] threshold: f64,
556        #[case] bonus: f64,
557        #[case] expected: f64,
558    ) -> Result<()> {
559        verify_that!(
560            wild_thrash_target_multiplier(target_count, threshold, bonus),
561            eq(expected)
562        )
563    }
564}