overlord_event_system/behaviors/combat/
cast_ability.rs

1//! Native ports for the `cast_ability` category — ability `script`s (the
2//! `CastAbility` event handler, run via `run_event` in
3//! `logic::fighting::handle_cast_ability`, returning
4//! `Vec<OverlordEvent>`).
5//!
6//! These are the combat effect of *casting* an ability: most call
7//! `ctx.on_cast(CasterEntity)` then either launch a projectile
8//! (`Result.push(OverlordEventStartCastProjectile(...))`) or apply combat
9//! primitives (`ctx.attack`, `ctx.spell_heal`, `ctx.apply_entity_effect`).
10//!
11//! ## RNG
12//! Ability `script`s consume the authoritative `Random` (via `on_cast`'s
13//! `bravery` throw, `attack`'s evasion/counterattack/deceit/crit/block throws,
14//! and `stat_throw`).
15//!
16//! ## Effect callbacks
17//! `on_cast` / `apply_entity_effect` dispatch the applied effect's `on_apply`
18//! reaction. For the shipped effects those reactions push `add_entity_stat_mod`
19//! (`empower`/`weakness`/`protection`/`vulnerability`) events; the native path
20//! must replicate them, so we pass [`OverlordEffectCb`] rather than
21//! `NoopEffectCb`.
22//!
23//! ## Scope (per the `handle_cast_ability` `run_event` call site)
24//! `CasterEntity`, `TargetEntity`, `Fight`, `Random`, `AbilityLevel`,
25//! `AbilitySlotLevel`, `CurrentTick`, `FightDurationTicks`, and the event.
26
27use configs::game_config::GameConfig;
28use essences::entity::Entity;
29use essences::fighting::ActiveFight;
30use event_system::script::random::GameRng;
31use uuid::Uuid;
32
33use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
34use crate::event::OverlordEvent;
35use crate::mechanics::content::ability_info;
36use crate::mechanics::content_lookups::ContentLookups;
37use crate::mechanics::effect_cb::OverlordEffectCb;
38use crate::mechanics::fight::{
39    self, AttackParams, NativeSink, SpellHealParams, attack, on_cast, spell_heal, stat_throw,
40};
41
42/// ability `script` scope the shipped scripts read.
43pub struct CastAbilityCtx<'a> {
44    /// `CasterEntity` const.
45    pub caster_entity: &'a Entity,
46    /// `TargetEntity` const.
47    pub target_entity: &'a Entity,
48    /// `Fight` const (used by the cone/AoE ability for `Fight.entities`).
49    pub fight: &'a ActiveFight,
50    /// RNG snapshot (clone at the same state) so combat primitives draw
51    pub rng: &'a GameRng,
52    /// `AbilityLevel` const (passed to `get_ability_info` and projectile launches).
53    pub ability_level: i64,
54    pub config: &'a GameConfig,
55    pub lookups: &'a ContentLookups,
56}
57
58/// Signature of a `cast_ability` native fn.
59pub type CastAbilityFn = fn(&CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>>;
60
61/// Run `ctx.on_cast(CasterEntity)` into a sink — the `on_cast` hook (bravery
62/// throw → maybe apply protection/empower, dispatching the effect `on_apply`).
63fn run_on_cast(ctx: &CastAbilityCtx, sink: &mut NativeSink) -> anyhow::Result<()> {
64    let mut effects = OverlordEffectCb;
65    on_cast(sink, ctx.rng, ctx.lookups, &mut effects, ctx.caster_entity)
66        .map_err(|e| anyhow::anyhow!("on_cast: {e}"))
67}
68
69/// Run `ctx.attack(caster, target, params)` into a sink, returning the floored
70fn run_attack(
71    ctx: &CastAbilityCtx,
72    sink: &mut NativeSink,
73    target: &Entity,
74    params: &AttackParams,
75) -> anyhow::Result<Option<i64>> {
76    let mut effects = OverlordEffectCb;
77    attack(
78        sink,
79        ctx.rng,
80        ctx.lookups,
81        &mut effects,
82        ctx.fight.player_id,
83        ctx.caster_entity,
84        target,
85        params,
86    )
87    .map_err(|e| anyhow::anyhow!("attack: {e}"))
88}
89
90/// `OverlordEventStartCastProjectile(caster.id, target.id, uuid(pid), level, delay)`).
91fn start_cast_projectile(ctx: &CastAbilityCtx, projectile_id: &str, delay: u64) -> OverlordEvent {
92    OverlordEvent::StartCastProjectile {
93        by_entity_id: ctx.caster_entity.id,
94        to_entity_id: ctx.target_entity.id,
95        projectile_id: Uuid::parse_str(projectile_id).expect("valid projectile uuid literal"),
96        level: ctx.ability_level,
97        delay,
98    }
99}
100
101/// `content::get_ability_info(id, AbilityLevel)` — resolves the per-ability info
102/// closure for the given ability id at the current level.
103fn info(
104    ctx: &CastAbilityCtx,
105    ability_id: &str,
106) -> anyhow::Result<crate::mechanics::content::AbilityInfo> {
107    let id = Uuid::parse_str(ability_id).expect("valid ability uuid literal");
108    ability_info(ctx.config, ctx.lookups, id, ctx.ability_level)
109}
110
111// ---------------------------------------------------------------------------
112// Shared parametrized bodies
113// ---------------------------------------------------------------------------
114
115/// `on_cast` + push a single `StartCastProjectile(projectile_id, delay=0)`.
116/// Covers every "on_cast then launch one projectile" ability.
117fn on_cast_then_projectile(
118    ctx: &CastAbilityCtx,
119    projectile_id: &str,
120) -> anyhow::Result<Vec<OverlordEvent>> {
121    let mut sink = NativeSink::default();
122    run_on_cast(ctx, &mut sink)?;
123    sink.events
124        .push(start_cast_projectile(ctx, projectile_id, 0));
125    Ok(sink.events)
126}
127
128/// Bare `Result.push(StartCastProjectile(projectile_id, delay=0))` — no
129/// `fight_context`, no `on_cast` (the four scripts that are a single push line).
130fn bare_projectile(
131    ctx: &CastAbilityCtx,
132    projectile_id: &str,
133) -> anyhow::Result<Vec<OverlordEvent>> {
134    Ok(vec![start_cast_projectile(ctx, projectile_id, 0)])
135}
136
137/// `on_cast` + `attack(caster, target, #{ power: ability_info.damage })`.
138fn on_cast_then_attack(
139    ctx: &CastAbilityCtx,
140    ability_id: &str,
141) -> anyhow::Result<Vec<OverlordEvent>> {
142    let mut sink = NativeSink::default();
143    run_on_cast(ctx, &mut sink)?;
144    let ability_info = info(ctx, ability_id)?;
145    let params = AttackParams {
146        power: ability_info.damage,
147        ..Default::default()
148    };
149    run_attack(ctx, &mut sink, ctx.target_entity, &params)?;
150    Ok(sink.events)
151}
152
153// ---------------------------------------------------------------------------
154// Distinct ability ports
155// ---------------------------------------------------------------------------
156
157/// `0194d64e-...` — on_cast + attack { power }.
158pub fn melee_strike(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
159    on_cast_then_attack(ctx, "0194d64e-20f2-75e5-89c8-4cb812672485")
160}
161
162/// `019bff40-...` — on_cast + attack { power }.
163pub fn sword_slash(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
164    on_cast_then_attack(ctx, "019bff40-af44-75b7-940c-6074097a2925")
165}
166
167/// `019cc464-...` — on_cast + attack { power }.
168pub fn mob_melee_quick(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
169    on_cast_then_attack(ctx, "019cc464-e752-71c1-a9dd-8fda9f212801")
170}
171
172/// `019cc465-14b8-...` — on_cast + attack { power }.
173pub fn mob_melee_average(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
174    on_cast_then_attack(ctx, "019cc465-14b8-7dbc-9799-4691b91805d3")
175}
176
177/// `019cc465-2f63-...` — on_cast + attack { power }.
178pub fn mob_melee_slow(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
179    on_cast_then_attack(ctx, "019cc465-2f63-7b54-8ddc-fcbcb483fe81")
180}
181
182/// `01955be6-...` — `ctx.apply_entity_effect(CasterEntity, "test_effect", 3)`.
183/// No `on_cast`. `test_effect`'s `on_apply` is `print`-only (no events).
184pub fn apply_test_effect(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
185    let mut sink = NativeSink::default();
186    let mut effects = OverlordEffectCb;
187    fight::apply_entity_effect(
188        &mut sink,
189        ctx.lookups,
190        &mut effects,
191        ctx.caster_entity,
192        "test_effect",
193        3.0,
194    )
195    .map_err(|e| anyhow::anyhow!("apply_entity_effect: {e}"))?;
196    Ok(sink.events)
197}
198
199/// `019584aa-...` — on_cast + attack { power, crit_chance_bonus * 10000.0 }.
200pub fn crit_strike(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
201    let mut sink = NativeSink::default();
202    run_on_cast(ctx, &mut sink)?;
203    let ability_info = info(ctx, "019584aa-5bde-7ac2-8850-076dafdc4603")?;
204    let params = AttackParams {
205        power: ability_info.damage,
206        crit_chance_bonus: ability_info.crit_chance_bonus.unwrap_or(0.0) * 10000.0,
207        ..Default::default()
208    };
209    run_attack(ctx, &mut sink, ctx.target_entity, &params)?;
210    Ok(sink.events)
211}
212
213/// `019584f4-...` — cone/AoE: on_cast, roll one shared crit, then attack every
214/// enemy in a forward 3-cell x-band with `{ power, is_crit }`.
215pub fn cone_strike(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
216    let mut sink = NativeSink::default();
217    run_on_cast(ctx, &mut sink)?;
218    let ability_info = info(ctx, "019584f4-2c99-72bf-bcd3-bfc02fb33977")?;
219    // `let is_crit = ctx.stat_throw(CasterEntity, "crit_chance");` — one draw.
220    let is_crit = stat_throw(ctx.rng, ctx.lookups, ctx.caster_entity, "crit_chance", 0.0);
221    let caster_x = ctx.caster_entity.coordinates.x;
222    let caster_team = &ctx.caster_entity.team;
223    // `CasterEntity.get_enemies(Fight.entities)` preserves `Fight.entities` order.
224    for target in ctx.fight.entities.iter().filter(|e| &e.team != caster_team) {
225        let tx = target.coordinates.x;
226        if tx > caster_x && tx <= caster_x + 3 {
227            let params = AttackParams {
228                power: ability_info.damage,
229                is_crit: Some(is_crit),
230                ..Default::default()
231            };
232            run_attack(ctx, &mut sink, target, &params)?;
233        }
234    }
235    Ok(sink.events)
236}
237
238/// `019589e6-...` — on_cast + launch `ability_info.projectiles` projectiles,
239/// each delayed `i * 150`.
240pub fn multi_projectile(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
241    const PROJECTILE_DELAY: i64 = 150;
242    let mut sink = NativeSink::default();
243    run_on_cast(ctx, &mut sink)?;
244    let ability_info = info(ctx, "019589e6-f9dd-7b22-8d39-5350e95aaf69")?;
245    let projectiles = ability_info.projectiles.unwrap_or(0);
246    for i in 0..projectiles {
247        let delay = (i * PROJECTILE_DELAY) as u64;
248        sink.events.push(start_cast_projectile(
249            ctx,
250            "0196a6b3-f885-7fdc-af8c-92a1ffb79ceb",
251            delay,
252        ));
253    }
254    Ok(sink.events)
255}
256
257/// `01958a30-...` — on_cast + attack { power }; if damage dealt (`!= ()`),
258/// apply `empower` for `ability_info.effect_duration`.
259pub fn strike_then_empower(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
260    let mut sink = NativeSink::default();
261    run_on_cast(ctx, &mut sink)?;
262    let ability_info = info(ctx, "01958a30-8f18-745f-928a-75028cb3ee99")?;
263    let params = AttackParams {
264        power: ability_info.damage,
265        ..Default::default()
266    };
267    let damage = run_attack(ctx, &mut sink, ctx.target_entity, &params)?;
268    if damage.is_some() {
269        let mut effects = OverlordEffectCb;
270        fight::apply_entity_effect(
271            &mut sink,
272            ctx.lookups,
273            &mut effects,
274            ctx.caster_entity,
275            "empower",
276            ability_info.effect_duration.unwrap_or(0.0),
277        )
278        .map_err(|e| anyhow::anyhow!("apply_entity_effect: {e}"))?;
279    }
280    Ok(sink.events)
281}
282
283/// `01958ef2-...` — on_cast + attack { power }; if damage dealt, apply
284/// `vulnerability` on the TARGET for `ability_info.effect_duration`.
285pub fn strike_then_vulnerability(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
286    let mut sink = NativeSink::default();
287    run_on_cast(ctx, &mut sink)?;
288    let ability_info = info(ctx, "01958ef2-dff5-76dd-89f1-d9c2707b2ffc")?;
289    let params = AttackParams {
290        power: ability_info.damage,
291        ..Default::default()
292    };
293    let damage = run_attack(ctx, &mut sink, ctx.target_entity, &params)?;
294    if damage.is_some() {
295        let mut effects = OverlordEffectCb;
296        fight::apply_entity_effect(
297            &mut sink,
298            ctx.lookups,
299            &mut effects,
300            ctx.target_entity,
301            "vulnerability",
302            ability_info.effect_duration.unwrap_or(0.0),
303        )
304        .map_err(|e| anyhow::anyhow!("apply_entity_effect: {e}"))?;
305    }
306    Ok(sink.events)
307}
308
309/// `01958efd-...` — on_cast + attack { dot_power: ability_info.damage } (no
310/// instant power; pure DoT application).
311pub fn dot_strike(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
312    let mut sink = NativeSink::default();
313    run_on_cast(ctx, &mut sink)?;
314    let ability_info = info(ctx, "01958efd-77f9-7dec-8444-c9d759549225")?;
315    let params = AttackParams {
316        dot_power: ability_info.damage,
317        ..Default::default()
318    };
319    run_attack(ctx, &mut sink, ctx.target_entity, &params)?;
320    Ok(sink.events)
321}
322
323/// `01958ed0-...` — on_cast + `spell_heal(caster, caster, #{ hot_power })`.
324pub fn self_hot_heal(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
325    let mut sink = NativeSink::default();
326    run_on_cast(ctx, &mut sink)?;
327    let ability_info = info(ctx, "01958ed0-d45f-7cad-b086-8f11962d3859")?;
328    let params = SpellHealParams {
329        hot_power: ability_info.hot,
330        ..Default::default()
331    };
332    spell_heal(
333        &mut sink,
334        ctx.rng,
335        ctx.lookups,
336        ctx.caster_entity,
337        ctx.caster_entity,
338        &params,
339    )
340    .map_err(|e| anyhow::anyhow!("spell_heal: {e}"))?;
341    Ok(sink.events)
342}
343
344// --- on_cast + single projectile (projectile id 01966cbc, AbilityLevel) ---
345
346/// `01958172-...` — on_cast + launch projectile `01966cbc`.
347pub fn fireball_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
348    on_cast_then_projectile(ctx, "01966cbc-879d-7b00-b1de-ad8ed932fb63")
349}
350
351/// `019dfc4c-...` / `019dfc4f` / `019dfcfb` / `019dfcfe` / `019dfd00` /
352/// `019dfd01` / `019dfd02-0c6a` / `019dfd02-add4` — all identical: on_cast +
353/// launch projectile `01966cbc`. (One shared native fn; config points all eight
354/// ability `script_native` refs at it.)
355pub fn fireball_projectile_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
356    on_cast_then_projectile(ctx, "01966cbc-879d-7b00-b1de-ad8ed932fb63")
357}
358
359/// `019589f7-...` — on_cast + launch projectile `0196a6b4` (vampiric strike).
360pub fn vampiric_touch_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
361    on_cast_then_projectile(ctx, "0196a6b4-cb8c-7bcb-a99c-1f0189dd8d5f")
362}
363
364// --- bare projectile push (no fight_context, no on_cast) ---
365
366/// `019a0245-ff0c-...` — bare push projectile `019a0244-4675`.
367pub fn bare_projectile_mushroom(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
368    bare_projectile(ctx, "019a0244-4675-7e4b-868a-c9b8ef46a091")
369}
370
371/// `019a0246-5aaf-...` — bare push projectile `019a0244-7a0a`.
372pub fn bare_projectile_chipmunk(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
373    bare_projectile(ctx, "019a0244-7a0a-7241-bdae-4541d9f972f6")
374}
375
376/// `019a0246-cf87-...` — bare push projectile `019a0244-aad4`.
377pub fn bare_projectile_bat(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
378    bare_projectile(ctx, "019a0244-aad4-7ae3-ae11-25d5facadf17")
379}
380
381/// `019c00a4-...` — bare push projectile `019c009c`.
382pub fn bare_projectile_shot(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
383    bare_projectile(ctx, "019c009c-22b2-7fa7-9599-97eb239b13b9")
384}
385
386// ---------------------------------------------------------------------------
387// Test-config ability ports (tests_game_config.rs fixtures)
388// ---------------------------------------------------------------------------
389
390/// Port of the shared test ability `script` (`generate_ability_script`):
391/// `Result.push(OverlordEventDamage(TargetEntity.id, unsigned(5), CustomEventData()));`
392/// RNG-free; used by the test-config gacha/class abilities.
393pub fn damage_target_5(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
394    Ok(vec![OverlordEvent::Damage {
395        entity_id: ctx.target_entity.id,
396        damage: 5,
397        damage_data: crate::event::CustomEventData::default(),
398    }])
399}
400
401/// Port of the `50260b1c-...` test ability `script`:
402/// RNG-free; used by `test_effects::test_effect_with_interval`.
403pub fn apply_bloodleak_effect(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
404    Ok(vec![
405        OverlordEvent::EntityIncrAttribute {
406            entity_id: ctx.target_entity.id,
407            attribute: "bloodleak".to_string(),
408            delta: 10,
409        },
410        OverlordEvent::EntityApplyEffect {
411            entity_id: ctx.target_entity.id,
412            effect_id: Uuid::parse_str("ccc47912-61c0-4efa-88f9-7911fa1b074f")?,
413        },
414    ])
415}
416
417/// Port of the `41ee5532-...` test ability `script`:
418/// `Result.push(OverlordEventEntityApplyEffect(Event.by_entity_id, uuid("3b136901-...")));`
419/// `Event.by_entity_id` is the caster, so the effect is applied to the caster.
420/// RNG-free; used by `test_effects::test_effect_with_subscribe`.
421pub fn apply_low_hp_heal_effect(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
422    Ok(vec![OverlordEvent::EntityApplyEffect {
423        entity_id: ctx.caster_entity.id,
424        effect_id: Uuid::parse_str("3b136901-137c-47cc-8c7e-bc0ce387eb1c")?,
425    }])
426}
427
428/// Register this category's native fns.
429pub fn register(registry: &mut BehaviorRegistry) {
430    let mut reg = |name: &str, title: &str, desc: &str, f: CastAbilityFn| {
431        registry.register_cast_ability(
432            BehaviorMeta {
433                name: name.to_string(),
434                category: BehaviorKind::CastAbility,
435                title: title.to_string(),
436                description: desc.to_string(),
437            },
438            f,
439        );
440    };
441
442    reg(
443        "ability_melee_strike",
444        "Способность: удар по цели (0194d64e)",
445        "on_cast + attack{power} (порт ability script 0194d64e).",
446        melee_strike,
447    );
448    reg(
449        "ability_sword_slash",
450        "Способность: удар по цели (019bff40)",
451        "on_cast + attack{power} (порт ability script 019bff40).",
452        sword_slash,
453    );
454    reg(
455        "ability_mob_melee_quick",
456        "Способность: удар по цели (019cc464)",
457        "on_cast + attack{power} (порт ability script 019cc464).",
458        mob_melee_quick,
459    );
460    reg(
461        "ability_mob_melee_average",
462        "Способность: удар по цели (019cc465-14b8)",
463        "on_cast + attack{power} (порт ability script 019cc465-14b8).",
464        mob_melee_average,
465    );
466    reg(
467        "ability_mob_melee_slow",
468        "Способность: удар по цели (019cc465-2f63)",
469        "on_cast + attack{power} (порт ability script 019cc465-2f63).",
470        mob_melee_slow,
471    );
472    reg(
473        "ability_apply_test_effect",
474        "Способность: наложить test_effect",
475        "apply_entity_effect(Caster, test_effect, 3) (порт ability script 01955be6).",
476        apply_test_effect,
477    );
478    reg(
479        "ability_damage_target_5",
480        "Способность: урон 5 по цели (тест)",
481        "Damage(TargetEntity, 5) — порт общего test ability script.",
482        damage_target_5,
483    );
484    reg(
485        "ability_apply_bloodleak_effect",
486        "Способность: bloodleak +10 + эффект (тест 50260b1c)",
487        "IncrAttribute(Target, bloodleak, 10) + ApplyEffect(Target, ccc47912).",
488        apply_bloodleak_effect,
489    );
490    reg(
491        "ability_apply_low_hp_heal_effect",
492        "Способность: наложить heal-эффект на кастера (тест 41ee5532)",
493        "ApplyEffect(Caster, 3b136901).",
494        apply_low_hp_heal_effect,
495    );
496    reg(
497        "ability_crit_strike",
498        "Способность: удар с бонусом крита (019584aa)",
499        "on_cast + attack{power, crit_chance_bonus} (порт ability script 019584aa).",
500        crit_strike,
501    );
502    reg(
503        "ability_cone_strike",
504        "Способность: конус по фронтальным врагам (019584f4)",
505        "on_cast, один общий crit-бросок, attack по врагам в 3-клеточной зоне по x \
506         (порт ability script 019584f4).",
507        cone_strike,
508    );
509    reg(
510        "ability_multi_projectile",
511        "Способность: серия снарядов (019589e6)",
512        "on_cast + N снарядов 0196a6b3 с задержкой i*150 (порт ability script 019589e6).",
513        multi_projectile,
514    );
515    reg(
516        "ability_strike_then_empower",
517        "Способность: удар + empower (01958a30)",
518        "on_cast + attack{power}; при уроне накладывает empower (порт ability script 01958a30).",
519        strike_then_empower,
520    );
521    reg(
522        "ability_strike_then_vulnerability",
523        "Способность: удар + vulnerability (01958ef2)",
524        "on_cast + attack{power}; при уроне накладывает vulnerability на цель \
525         (порт ability script 01958ef2).",
526        strike_then_vulnerability,
527    );
528    reg(
529        "ability_dot_strike",
530        "Способность: чистый DoT-удар (01958efd)",
531        "on_cast + attack{dot_power} (порт ability script 01958efd).",
532        dot_strike,
533    );
534    reg(
535        "ability_self_hot_heal",
536        "Способность: HoT-лечение себя (01958ed0)",
537        "on_cast + spell_heal(Caster, Caster, {hot_power}) (порт ability script 01958ed0).",
538        self_hot_heal,
539    );
540    reg(
541        "ability_fireball",
542        "Способность: запуск снаряда 01966cbc (01958172)",
543        "on_cast + StartCastProjectile(01966cbc) (порт ability script 01958172).",
544        fireball_cast,
545    );
546    reg(
547        "ability_fireball_projectile_shared",
548        "Способность: запуск снаряда 01966cbc (общий)",
549        "on_cast + StartCastProjectile(01966cbc); общий для 8 идентичных ability script \
550         (019dfc4c/019dfc4f/019dfcfb/019dfcfe/019dfd00/019dfd01/019dfd02-0c6a/019dfd02-add4).",
551        fireball_projectile_cast,
552    );
553    reg(
554        "ability_vampiric_touch",
555        "Способность: запуск снаряда 0196a6b4 (019589f7)",
556        "on_cast + StartCastProjectile(0196a6b4) (порт ability script 019589f7).",
557        vampiric_touch_cast,
558    );
559    reg(
560        "ability_bare_projectile_mushroom",
561        "Способность: голый запуск снаряда 019a0244-4675 (019a0245)",
562        "StartCastProjectile(019a0244-4675) без on_cast (порт ability script 019a0245-ff0c).",
563        bare_projectile_mushroom,
564    );
565    reg(
566        "ability_bare_projectile_chipmunk",
567        "Способность: голый запуск снаряда 019a0244-7a0a (019a0246-5aaf)",
568        "StartCastProjectile(019a0244-7a0a) без on_cast (порт ability script 019a0246-5aaf).",
569        bare_projectile_chipmunk,
570    );
571    reg(
572        "ability_bare_projectile_bat",
573        "Способность: голый запуск снаряда 019a0244-aad4 (019a0246-cf87)",
574        "StartCastProjectile(019a0244-aad4) без on_cast (порт ability script 019a0246-cf87).",
575        bare_projectile_bat,
576    );
577    reg(
578        "ability_bare_projectile_shot",
579        "Способность: голый запуск снаряда 019c009c (019c00a4)",
580        "StartCastProjectile(019c009c) без on_cast (порт ability script 019c00a4).",
581        bare_projectile_shot,
582    );
583}