1use 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
42pub struct CastAbilityCtx<'a> {
44 pub caster_entity: &'a Entity,
46 pub target_entity: &'a Entity,
48 pub fight: &'a ActiveFight,
50 pub rng: &'a GameRng,
52 pub ability_level: i64,
54 pub config: &'a GameConfig,
55 pub lookups: &'a ContentLookups,
56}
57
58pub type CastAbilityFn = fn(&CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>>;
60
61fn 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
69fn 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
90fn 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
101fn 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
111fn 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
128fn 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
137fn 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, ¶ms)?;
150 Ok(sink.events)
151}
152
153pub fn melee_strike(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
159 on_cast_then_attack(ctx, "0194d64e-20f2-75e5-89c8-4cb812672485")
160}
161
162pub fn sword_slash(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
164 on_cast_then_attack(ctx, "019bff40-af44-75b7-940c-6074097a2925")
165}
166
167pub fn mob_melee_quick(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
169 on_cast_then_attack(ctx, "019cc464-e752-71c1-a9dd-8fda9f212801")
170}
171
172pub fn mob_melee_average(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
174 on_cast_then_attack(ctx, "019cc465-14b8-7dbc-9799-4691b91805d3")
175}
176
177pub fn mob_melee_slow(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
179 on_cast_then_attack(ctx, "019cc465-2f63-7b54-8ddc-fcbcb483fe81")
180}
181
182pub 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
199pub 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, ¶ms)?;
210 Ok(sink.events)
211}
212
213pub 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 = 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 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, ¶ms)?;
233 }
234 }
235 Ok(sink.events)
236}
237
238pub 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
257pub 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, ¶ms)?;
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
283pub 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, ¶ms)?;
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
309pub 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, ¶ms)?;
320 Ok(sink.events)
321}
322
323pub 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 ¶ms,
339 )
340 .map_err(|e| anyhow::anyhow!("spell_heal: {e}"))?;
341 Ok(sink.events)
342}
343
344pub fn fireball_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
348 on_cast_then_projectile(ctx, "01966cbc-879d-7b00-b1de-ad8ed932fb63")
349}
350
351pub fn fireball_projectile_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
356 on_cast_then_projectile(ctx, "01966cbc-879d-7b00-b1de-ad8ed932fb63")
357}
358
359pub fn vampiric_touch_cast(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
361 on_cast_then_projectile(ctx, "0196a6b4-cb8c-7bcb-a99c-1f0189dd8d5f")
362}
363
364pub fn bare_projectile_mushroom(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
368 bare_projectile(ctx, "019a0244-4675-7e4b-868a-c9b8ef46a091")
369}
370
371pub fn bare_projectile_chipmunk(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
373 bare_projectile(ctx, "019a0244-7a0a-7241-bdae-4541d9f972f6")
374}
375
376pub fn bare_projectile_bat(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
378 bare_projectile(ctx, "019a0244-aad4-7ae3-ae11-25d5facadf17")
379}
380
381pub fn bare_projectile_shot(ctx: &CastAbilityCtx) -> anyhow::Result<Vec<OverlordEvent>> {
383 bare_projectile(ctx, "019c009c-22b2-7fa7-9599-97eb239b13b9")
384}
385
386pub 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
401pub 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
417pub 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
428pub 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}