1use crate::util::*;
2use common::{
3 comp::{
4 ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
5 LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, Scale, SkillSet, Stance,
6 Stats, Vel,
7 ability::{Amount, BASE_ABILITY_LIMIT, CharacterAbility},
8 body::parts::Heads,
9 buff::{BuffKind, Buffs},
10 character_state::AttackFilters,
11 group,
12 inventory::{
13 item::{
14 ItemKind, MaterialStatManifest,
15 tool::{AbilityMap, ToolKind},
16 },
17 slot::EquipSlot,
18 },
19 },
20 consts::GRAVITY,
21 event, event_emitters,
22 interaction::Interactors,
23 link::Is,
24 mounting::{Mount, Rider, VolumeRider},
25 path::TraversalConfig,
26 resources::{DeltaTime, Time, TimeOfDay},
27 rtsim::RtSimEntity,
28 states::utils::{ForcedMovement, StageSection},
29 terrain::TerrainGrid,
30 uid::{IdMaps, Uid},
31};
32use common_base::dev_panic;
33use specs::{Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData, shred};
34
35event_emitters! {
36 pub struct AgentEvents[AgentEmitters] {
37 chat: event::ChatEvent,
38 sound: event::SoundEvent,
39 process_trade_action: event::ProcessTradeActionEvent,
40 }
41}
42
43pub struct AgentData<'a> {
46 pub entity: &'a EcsEntity,
47 pub uid: &'a Uid,
48 pub pos: &'a Pos,
50 pub vel: &'a Vel,
52 pub ori: &'a Ori,
53 pub energy: &'a Energy,
54 pub body: Option<&'a Body>,
55 pub inventory: &'a Inventory,
56 pub skill_set: &'a SkillSet,
57 pub physics_state: &'a PhysicsState,
58 pub alignment: Option<&'a Alignment>,
59 pub traversal_config: TraversalConfig,
60 pub scale: f32,
61 pub damage: f32,
62 pub light_emitter: Option<&'a LightEmitter>,
63 pub glider_equipped: bool,
64 pub is_gliding: bool,
65 pub health: Option<&'a Health>,
66 pub heads: Option<&'a Heads>,
67 pub char_state: &'a CharacterState,
68 pub active_abilities: &'a ActiveAbilities,
69 pub combo: Option<&'a Combo>,
70 pub buffs: Option<&'a Buffs>,
71 pub stats: Option<&'a Stats>,
72 pub poise: Option<&'a Poise>,
73 pub stance: Option<&'a Stance>,
74 pub cached_spatial_grid: &'a common::CachedSpatialGrid,
75 pub msm: &'a MaterialStatManifest,
76 pub rtsim_entity: Option<&'a RtSimEntity>,
77}
78
79pub struct TargetData<'a> {
80 pub pos: &'a Pos,
81 pub ori: Option<&'a Ori>,
82 pub body: Option<&'a Body>,
83 pub scale: Option<&'a Scale>,
84 pub char_state: Option<&'a CharacterState>,
85 pub health: Option<&'a Health>,
86 pub buffs: Option<&'a Buffs>,
87 pub drawn_weapons: (Option<ToolKind>, Option<ToolKind>),
88}
89
90impl<'a> TargetData<'a> {
91 pub fn new(pos: &'a Pos, target: EcsEntity, read_data: &'a ReadData) -> Self {
92 Self {
93 pos,
94 ori: read_data.orientations.get(target),
95 body: read_data.bodies.get(target),
96 scale: read_data.scales.get(target),
97 char_state: read_data.char_states.get(target),
98 health: read_data.healths.get(target),
99 buffs: read_data.buffs.get(target),
100 drawn_weapons: {
101 let slotted_tool = |inv: &Inventory, slot| {
102 if let Some(ItemKind::Tool(tool)) =
103 inv.equipped(slot).map(|i| i.kind()).as_deref()
104 {
105 Some(tool.kind)
106 } else {
107 None
108 }
109 };
110 read_data
111 .inventories
112 .get(target)
113 .map_or((None, None), |inv| {
114 (
115 slotted_tool(inv, EquipSlot::ActiveMainhand),
116 slotted_tool(inv, EquipSlot::ActiveOffhand),
117 )
118 })
119 },
120 }
121 }
122
123 pub fn considered_ranged(&self) -> bool {
124 let is_ranged_tool = |tool| match tool {
125 Some(
126 ToolKind::Sword
127 | ToolKind::Axe
128 | ToolKind::Hammer
129 | ToolKind::Dagger
130 | ToolKind::Shield
131 | ToolKind::Spear
132 | ToolKind::Farming
133 | ToolKind::Pick
134 | ToolKind::Shovel
135 | ToolKind::Natural
136 | ToolKind::Throwable
137 | ToolKind::Empty,
138 )
139 | None => false,
140 Some(
141 ToolKind::Bow
142 | ToolKind::Staff
143 | ToolKind::Sceptre
144 | ToolKind::Blowgun
145 | ToolKind::Debug
146 | ToolKind::Instrument,
147 ) => true,
148 };
149 is_ranged_tool(self.drawn_weapons.0) || is_ranged_tool(self.drawn_weapons.1)
150 }
151}
152
153pub struct AttackData {
154 pub body_dist: f32,
155 pub min_attack_dist: f32,
157 pub dist_sqrd: f32,
158 pub angle: f32,
159 pub angle_xy: f32,
160}
161
162impl AttackData {
163 pub fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
164}
165
166pub enum ActionMode {
167 Reckless = 0,
168 Guarded = 1,
169 Fleeing = 2,
170}
171
172impl ActionMode {
173 pub fn from_u8(x: u8) -> Self {
174 match x {
175 0 => ActionMode::Reckless,
176 1 => ActionMode::Guarded,
177 2 => ActionMode::Fleeing,
178 _ => ActionMode::Guarded,
179 }
180 }
181}
182
183#[derive(Eq, PartialEq)]
184pub enum Tactic {
187 SimpleMelee,
189 SimpleFlyingMelee,
190 SimpleBackstab,
191 ElevatedRanged,
192 Turret,
193 FixedTurret,
194 RotatingTurret,
195 RadialTurret,
196 FieryTornado,
197 SimpleDouble,
198 ClayGolem,
199 ClaySteed,
200 AncientEffigy,
201 RandomAbilities {
203 primary: u8,
204 secondary: u8,
205 abilities: [u8; BASE_ABILITY_LIMIT],
206 },
207
208 Axe,
210 Hammer,
211 Sword,
212 Bow,
213 Staff,
214 Sceptre,
215 SwordSimple,
217
218 CircleCharge {
220 radius: u32,
221 circle_time: u32,
222 },
223 QuadLowRanged,
224 TailSlap,
225 QuadLowQuick,
226 QuadLowBasic,
227 QuadLowBeam,
228 QuadMedJump,
229 QuadMedBasic,
230 QuadMedHoof,
231 Theropod,
232 BirdLargeBreathe,
233 BirdLargeFire,
234 BirdLargeBasic,
235 Wyvern,
236 BirdMediumBasic,
237 ArthropodMelee,
238 ArthropodRanged,
239 ArthropodAmbush,
240
241 Mindflayer,
243 Minotaur,
244 GraveWarden,
245 TidalWarrior,
246 Karkatha,
247 Yeti,
248 Harvester,
249 StoneGolem,
250 Deadwood,
251 Mandragora,
252 WoodGolem,
253 IronGolem,
254 GnarlingChieftain,
255 OrganAura,
256 Dagon,
257 Snaretongue,
258 Cardinal,
259 SeaBishop,
260 Rocksnapper,
261 Roshwalr,
262 FrostGigas,
263 BorealHammer,
264 BorealBow,
265 Dullahan,
266 Cyclops,
267 IceDrake,
268 Hydra,
269 Flamekeeper,
270 Forgemaster,
271
272 AdletHunter,
274 AdletIcepicker,
275 AdletTracker,
276 AdletElder,
277
278 HaniwaSoldier,
280 HaniwaGuard,
281 HaniwaArcher,
282
283 TerracottaStatue,
285 Cursekeeper,
286 CursekeeperFake,
287 ShamanicSpirit,
288 Jiangshi,
289
290 VampireBat,
292 BloodmoonBat,
293 BloodmoonHeiress,
294}
295
296#[derive(Copy, Clone, Debug)]
297pub enum SwordTactics {
298 Unskilled = 0,
299 Basic = 1,
300 HeavySimple = 2,
301 AgileSimple = 3,
302 DefensiveSimple = 4,
303 CripplingSimple = 5,
304 CleavingSimple = 6,
305 HeavyAdvanced = 7,
306 AgileAdvanced = 8,
307 DefensiveAdvanced = 9,
308 CripplingAdvanced = 10,
309 CleavingAdvanced = 11,
310}
311
312impl SwordTactics {
313 pub fn from_u8(x: u8) -> Self {
314 use SwordTactics::*;
315 match x {
316 0 => Unskilled,
317 1 => Basic,
318 2 => HeavySimple,
319 3 => AgileSimple,
320 4 => DefensiveSimple,
321 5 => CripplingSimple,
322 6 => CleavingSimple,
323 7 => HeavyAdvanced,
324 8 => AgileAdvanced,
325 9 => DefensiveAdvanced,
326 10 => CripplingAdvanced,
327 11 => CleavingAdvanced,
328 _ => Unskilled,
329 }
330 }
331}
332
333#[derive(Copy, Clone, Debug)]
334pub enum AxeTactics {
335 Unskilled = 0,
336 SavageSimple = 1,
337 MercilessSimple = 2,
338 RivingSimple = 3,
339 SavageIntermediate = 4,
340 MercilessIntermediate = 5,
341 RivingIntermediate = 6,
342 SavageAdvanced = 7,
343 MercilessAdvanced = 8,
344 RivingAdvanced = 9,
345}
346
347impl AxeTactics {
348 pub fn from_u8(x: u8) -> Self {
349 use AxeTactics::*;
350 match x {
351 0 => Unskilled,
352 1 => SavageSimple,
353 2 => MercilessSimple,
354 3 => RivingSimple,
355 4 => SavageIntermediate,
356 5 => MercilessIntermediate,
357 6 => RivingIntermediate,
358 7 => SavageAdvanced,
359 8 => MercilessAdvanced,
360 9 => RivingAdvanced,
361 _ => Unskilled,
362 }
363 }
364}
365
366#[derive(Copy, Clone, Debug)]
367pub enum HammerTactics {
368 Unskilled = 0,
369 Simple = 1,
370 AttackSimple = 2,
371 SupportSimple = 3,
372 AttackIntermediate = 4,
373 SupportIntermediate = 5,
374 AttackAdvanced = 6,
375 SupportAdvanced = 7,
376 AttackExpert = 8,
377 SupportExpert = 9,
378}
379
380impl HammerTactics {
381 pub fn from_u8(x: u8) -> Self {
382 use HammerTactics::*;
383 match x {
384 0 => Unskilled,
385 1 => Simple,
386 2 => AttackSimple,
387 3 => SupportSimple,
388 4 => AttackIntermediate,
389 5 => SupportIntermediate,
390 6 => AttackAdvanced,
391 7 => SupportAdvanced,
392 8 => AttackExpert,
393 9 => SupportExpert,
394 _ => Unskilled,
395 }
396 }
397}
398
399#[derive(SystemData)]
400pub struct ReadData<'a> {
401 pub entities: Entities<'a>,
402 pub id_maps: Read<'a, IdMaps>,
403 pub dt: Read<'a, DeltaTime>,
404 pub time: Read<'a, Time>,
405 pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
406 pub group_manager: Read<'a, group::GroupManager>,
407 pub energies: ReadStorage<'a, Energy>,
408 pub positions: ReadStorage<'a, Pos>,
409 pub velocities: ReadStorage<'a, Vel>,
410 pub orientations: ReadStorage<'a, Ori>,
411 pub scales: ReadStorage<'a, Scale>,
412 pub healths: ReadStorage<'a, Health>,
413 pub heads: ReadStorage<'a, Heads>,
414 pub inventories: ReadStorage<'a, Inventory>,
415 pub stats: ReadStorage<'a, Stats>,
416 pub skill_set: ReadStorage<'a, SkillSet>,
417 pub physics_states: ReadStorage<'a, PhysicsState>,
418 pub char_states: ReadStorage<'a, CharacterState>,
419 pub uids: ReadStorage<'a, Uid>,
420 pub groups: ReadStorage<'a, group::Group>,
421 pub terrain: ReadExpect<'a, TerrainGrid>,
422 pub alignments: ReadStorage<'a, Alignment>,
423 pub bodies: ReadStorage<'a, Body>,
424 pub is_mounts: ReadStorage<'a, Is<Mount>>,
425 pub is_riders: ReadStorage<'a, Is<Rider>>,
426 pub is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
427 pub interactors: ReadStorage<'a, Interactors>,
428 pub time_of_day: Read<'a, TimeOfDay>,
429 pub light_emitter: ReadStorage<'a, LightEmitter>,
430 #[cfg(feature = "worldgen")]
431 pub world: ReadExpect<'a, std::sync::Arc<world::World>>,
432 pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
433 pub buffs: ReadStorage<'a, Buffs>,
434 pub combos: ReadStorage<'a, Combo>,
435 pub active_abilities: ReadStorage<'a, ActiveAbilities>,
436 pub loot_owners: ReadStorage<'a, LootOwner>,
437 pub msm: ReadExpect<'a, MaterialStatManifest>,
438 pub poises: ReadStorage<'a, Poise>,
439 pub stances: ReadStorage<'a, Stance>,
440 pub presences: ReadStorage<'a, Presence>,
441 pub ability_map: ReadExpect<'a, AbilityMap>,
442}
443
444pub enum Path {
445 Full,
446 Separate,
447 Partial,
448}
449
450#[derive(Copy, Clone, Debug)]
451pub enum AbilityData {
452 ComboMelee {
453 range: f32,
454 angle: f32,
455 energy_per_strike: f32,
456 forced_movement: Option<ForcedMovement>,
457 },
458 FinisherMelee {
459 range: f32,
460 angle: f32,
461 energy: f32,
462 combo: u32,
463 combo_scales: bool,
464 },
465 SelfBuff {
466 buff: BuffKind,
467 energy: f32,
468 combo: u32,
469 combo_scales: bool,
470 },
471 DiveMelee {
472 range: f32,
473 angle: f32,
474 energy: f32,
475 },
476 DashMelee {
477 range: f32,
478 angle: f32,
479 initial_energy: f32,
480 energy_drain: f32,
481 speed: f32,
482 charge_dur: f32,
483 },
484 RapidMelee {
485 range: f32,
486 angle: f32,
487 energy_per_strike: f32,
488 strikes: u32,
489 combo: u32,
490 },
491 ChargedMelee {
492 range: f32,
493 angle: f32,
494 initial_energy: f32,
495 energy_drain: f32,
496 charge_dur: f32,
497 },
498 RiposteMelee {
499 range: f32,
500 angle: f32,
501 energy: f32,
502 },
503 BasicBlock {
504 energy: f32,
505 blocked_attacks: AttackFilters,
506 angle: f32,
507 },
508 BasicRanged {
509 energy: f32,
510 projectile_speed: f32,
511 projectile_spread: f32,
512 num_projectiles: Amount,
513 },
514 BasicMelee {
515 energy: f32,
516 range: f32,
517 angle: f32,
518 },
519 LeapMelee {
520 energy: f32,
521 range: f32,
522 angle: f32,
523 forward_leap: f32,
524 vertical_leap: f32,
525 leap_dur: f32,
526 },
527 BasicBeam {
528 energy_drain: f32,
529 range: f32,
530 angle: f32,
531 ori_rate: f32,
532 },
533 Shockwave {
534 energy: f32,
535 angle: f32,
536 range: f32,
537 combo: u32,
538 },
539 StaticAura {
542 energy: f32,
543 },
544 RegrowHead {
545 energy: f32,
546 },
547}
548
549#[derive(Copy, Clone, Debug, Default)]
550pub struct AbilityPreferences {
551 pub desired_energy: f32,
552 pub combo_scaling_buildup: u32,
553}
554
555impl AbilityData {
556 pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
557 use CharacterAbility::*;
558 let inner = match ability {
559 ComboMelee2 {
560 strikes,
561 energy_cost_per_strike,
562 ..
563 } => {
564 let (range, angle, forced_movement) = strikes
565 .iter()
566 .map(|s| {
567 (
568 s.melee_constructor.range,
569 s.melee_constructor.angle,
570 s.movement.buildup.map(|m| m * s.buildup_duration),
571 )
572 })
573 .fold(
574 (100.0, 360.0, None),
575 |(r1, a1, m1): (f32, f32, Option<ForcedMovement>),
576 (r2, a2, m2): (f32, f32, Option<ForcedMovement>)| {
577 (r1.min(r2), a1.min(a2), m1.or(m2))
578 },
579 );
580 Self::ComboMelee {
581 range,
582 angle,
583 energy_per_strike: *energy_cost_per_strike,
584 forced_movement,
585 }
586 },
587 FinisherMelee {
588 energy_cost,
589 melee_constructor,
590 minimum_combo,
591 scaling,
592 ..
593 } => Self::FinisherMelee {
594 energy: *energy_cost,
595 range: melee_constructor.range,
596 angle: melee_constructor.angle,
597 combo: *minimum_combo,
598 combo_scales: scaling.is_some(),
599 },
600 SelfBuff {
601 buff_kind,
602 energy_cost,
603 combo_cost,
604 combo_scaling,
605 ..
606 } => Self::SelfBuff {
607 buff: *buff_kind,
608 energy: *energy_cost,
609 combo: *combo_cost,
610 combo_scales: combo_scaling.is_some(),
611 },
612 DiveMelee {
613 energy_cost,
614 melee_constructor,
615 ..
616 } => Self::DiveMelee {
617 energy: *energy_cost,
618 range: melee_constructor.range,
619 angle: melee_constructor.angle,
620 },
621 DashMelee {
622 energy_cost,
623 energy_drain,
624 forward_speed,
625 melee_constructor,
626 charge_duration,
627 ..
628 } => Self::DashMelee {
629 initial_energy: *energy_cost,
630 energy_drain: *energy_drain,
631 range: melee_constructor.range,
632 angle: melee_constructor.angle,
633 charge_dur: *charge_duration,
634 speed: *forward_speed,
635 },
636 RapidMelee {
637 energy_cost,
638 max_strikes,
639 minimum_combo,
640 melee_constructor,
641 ..
642 } => Self::RapidMelee {
643 energy_per_strike: *energy_cost,
644 range: melee_constructor.range,
645 angle: melee_constructor.angle,
646 strikes: max_strikes.unwrap_or(100),
647 combo: *minimum_combo,
648 },
649 ChargedMelee {
650 energy_cost,
651 energy_drain,
652 charge_duration,
653 melee_constructor,
654 ..
655 } => Self::ChargedMelee {
656 initial_energy: *energy_cost,
657 energy_drain: *energy_drain,
658 charge_dur: *charge_duration,
659 range: melee_constructor.range,
660 angle: melee_constructor.angle,
661 },
662 RiposteMelee {
663 energy_cost,
664 melee_constructor,
665 ..
666 } => Self::RiposteMelee {
667 energy: *energy_cost,
668 range: melee_constructor.range,
669 angle: melee_constructor.angle,
670 },
671 BasicBlock {
672 max_angle,
673 energy_cost,
674 blocked_attacks,
675 ..
676 } => Self::BasicBlock {
677 energy: *energy_cost,
678 angle: *max_angle,
679 blocked_attacks: *blocked_attacks,
680 },
681 BasicRanged {
682 energy_cost,
683 projectile_speed,
684 projectile_spread,
685 num_projectiles,
686 ..
687 } => Self::BasicRanged {
688 energy: *energy_cost,
689 projectile_speed: *projectile_speed,
690 projectile_spread: *projectile_spread,
691 num_projectiles: *num_projectiles,
692 },
693 BasicMelee {
694 energy_cost,
695 melee_constructor,
696 ..
697 } => Self::BasicMelee {
698 energy: *energy_cost,
699 range: melee_constructor.range,
700 angle: melee_constructor.angle,
701 },
702 LeapMelee {
703 energy_cost,
704 movement_duration,
705 melee_constructor,
706 forward_leap_strength,
707 vertical_leap_strength,
708 ..
709 } => Self::LeapMelee {
710 energy: *energy_cost,
711 leap_dur: *movement_duration,
712 range: melee_constructor.range,
713 angle: melee_constructor.angle,
714 forward_leap: *forward_leap_strength,
715 vertical_leap: *vertical_leap_strength,
716 },
717 BasicBeam {
718 range,
719 max_angle,
720 ori_rate,
721 energy_drain,
722 ..
723 } => Self::BasicBeam {
724 range: *range,
725 angle: *max_angle,
726 ori_rate: *ori_rate,
727 energy_drain: *energy_drain,
728 },
729 Shockwave {
730 energy_cost,
731 shockwave_angle,
732 shockwave_speed,
733 shockwave_duration,
734 minimum_combo,
735 ..
736 } => Self::Shockwave {
737 energy: *energy_cost,
738 angle: *shockwave_angle,
739 range: *shockwave_speed * *shockwave_duration,
740 combo: minimum_combo.unwrap_or(0),
741 },
742 StaticAura { energy_cost, .. } => Self::StaticAura {
743 energy: *energy_cost,
744 },
745 RegrowHead { energy_cost, .. } => Self::RegrowHead {
746 energy: *energy_cost,
747 },
748 _ => {
749 dev_panic!(
750 "Agent tried to use ability with a character state they haven't learned to \
751 understand"
752 );
753 return None;
754 },
755 };
756 Some(inner)
757 }
758
759 pub fn could_use(
760 &self,
761 attack_data: &AttackData,
762 agent_data: &AgentData,
763 tgt_data: &TargetData,
764 read_data: &ReadData,
765 ability_preferences: AbilityPreferences,
766 ) -> bool {
767 let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
768 let (range_inc, min_mult) = forced_movement.map_or((0.0, 0.0), |fm| match fm {
769 ForcedMovement::Forward(speed) => (speed * 15.0, 1.0),
770 ForcedMovement::Reverse(speed) => (-speed, 1.0),
771 ForcedMovement::Leap {
772 vertical, forward, ..
773 } => (
774 {
775 let dur = vertical * 2.0 / GRAVITY;
776 forward * dur * 0.75
779 },
780 0.0,
781 ),
782 _ => (0.0, 0.0),
783 });
784 let body_rad = agent_data.body.map_or(0.0, |b| b.max_radius());
785 attack_data.dist_sqrd < (range + range_inc + body_rad).powi(2)
786 && attack_data.angle < angle
787 && attack_data.dist_sqrd > (range_inc * min_mult).powi(2)
788 };
789 let energy_check = |energy: f32| {
790 agent_data.energy.current() >= energy
791 && (energy < f32::EPSILON
792 || agent_data.energy.current() >= ability_preferences.desired_energy)
793 };
794 let combo_check = |combo, scales| {
795 let additional_combo = if scales {
796 ability_preferences.combo_scaling_buildup
797 } else {
798 0
799 };
800 agent_data
801 .combo
802 .is_some_and(|c| c.counter() >= combo + additional_combo)
803 };
804 let attack_kind_check = |attacks: AttackFilters| {
805 tgt_data
806 .char_state
807 .and_then(|cs| cs.attack_kind())
808 .is_some_and(|ak| attacks.applies(ak))
809 };
810 let ranged_check = |proj_speed| {
811 let max_horiz_dist: f32 = {
812 let flight_time = proj_speed * 2_f32.sqrt() / GRAVITY;
813 proj_speed * 2_f32.sqrt() / 2.0 * flight_time
814 };
815 attack_data.dist_sqrd < max_horiz_dist.powi(2)
816 && entities_have_line_of_sight(
817 agent_data.pos,
818 agent_data.body,
819 agent_data.scale,
820 tgt_data.pos,
821 tgt_data.body,
822 tgt_data.scale,
823 read_data,
824 )
825 };
826 let beam_check = |range: f32, angle, ori_rate: f32| {
827 let angle_inc = ori_rate.to_degrees();
828 attack_data.dist_sqrd < range.powi(2)
829 && attack_data.angle < angle + angle_inc
830 && entities_have_line_of_sight(
831 agent_data.pos,
832 agent_data.body,
833 agent_data.scale,
834 tgt_data.pos,
835 tgt_data.body,
836 tgt_data.scale,
837 read_data,
838 )
839 };
840 use AbilityData::*;
841 match self {
842 ComboMelee {
843 range,
844 angle,
845 energy_per_strike,
846 forced_movement,
847 } => melee_check(*range, *angle, *forced_movement) && energy_check(*energy_per_strike),
848 FinisherMelee {
849 range,
850 angle,
851 energy,
852 combo,
853 combo_scales,
854 } => {
855 melee_check(*range, *angle, None)
856 && energy_check(*energy)
857 && combo_check(*combo, *combo_scales)
858 },
859 SelfBuff {
860 buff,
861 energy,
862 combo,
863 combo_scales,
864 } => {
865 energy_check(*energy)
866 && combo_check(*combo, *combo_scales)
867 && agent_data.buffs.is_some_and(|buffs| !buffs.contains(*buff))
868 },
869 DiveMelee {
870 range,
871 angle,
872 energy,
873 } => melee_check(*range, *angle, None) && energy_check(*energy),
874 DashMelee {
875 range,
876 angle,
877 initial_energy,
878 energy_drain,
879 speed,
880 charge_dur,
881 } => {
882 const BASE_SPEED: f32 = 3.0;
885 const ORI_RATE: f32 = 30.0;
886 let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
887 .clamp(0.0, *charge_dur);
888 let charge_dist = charge_dur * speed * BASE_SPEED;
889 let attack_dist = charge_dist + range;
890 let ori_gap = ORI_RATE * charge_dur;
891 melee_check(attack_dist, angle + ori_gap, None)
893 && energy_check(*initial_energy)
894 && attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
895 },
896 RapidMelee {
897 range,
898 angle,
899 energy_per_strike,
900 strikes,
901 combo,
902 } => {
903 melee_check(*range, *angle, None)
904 && energy_check(*energy_per_strike * *strikes as f32)
905 && combo_check(*combo, false)
906 },
907 ChargedMelee {
908 range,
909 angle,
910 initial_energy,
911 energy_drain,
912 charge_dur,
913 } => {
914 melee_check(*range, *angle, None)
915 && energy_check(*initial_energy + *energy_drain * *charge_dur)
916 },
917 RiposteMelee {
918 energy,
919 range,
920 angle,
921 } => {
922 melee_check(*range, *angle, None)
923 && energy_check(*energy)
924 && tgt_data.char_state.is_some_and(|cs| {
925 cs.is_melee_attack()
926 && matches!(
927 cs.stage_section(),
928 Some(
929 StageSection::Buildup
930 | StageSection::Charge
931 | StageSection::Movement
932 )
933 )
934 })
935 },
936 BasicBlock {
937 energy,
938 angle,
939 blocked_attacks,
940 } => {
941 melee_check(25.0, *angle, None)
942 && energy_check(*energy)
943 && attack_kind_check(*blocked_attacks)
944 && tgt_data
945 .char_state
946 .and_then(|cs| cs.stage_section())
947 .is_some_and(|ss| !matches!(ss, StageSection::Recover))
948 },
949 BasicRanged {
950 energy,
951 projectile_speed,
952 projectile_spread: _,
953 num_projectiles: _,
954 } => ranged_check(*projectile_speed) && energy_check(*energy),
955 BasicMelee {
956 energy,
957 range,
958 angle,
959 } => melee_check(*range, *angle, None) && energy_check(*energy),
960 LeapMelee {
961 energy,
962 range,
963 angle,
964 leap_dur,
965 forward_leap,
966 vertical_leap,
967 } => {
968 use common::states::utils::MovementDirection;
969 let forced_move = Some(ForcedMovement::Leap {
970 vertical: *vertical_leap * *leap_dur * 2.0,
971 forward: *forward_leap,
972 progress: 0.0,
973 direction: MovementDirection::Look,
974 });
975 melee_check(*range, *angle, forced_move) && energy_check(*energy)
976 },
977 BasicBeam {
978 energy_drain,
979 range,
980 angle,
981 ori_rate,
982 } => beam_check(*range, *angle, *ori_rate) && energy_check(*energy_drain * 3.0),
983 Shockwave {
984 energy,
985 range,
986 angle,
987 combo,
988 } => {
989 melee_check(*range, *angle, None)
990 && energy_check(*energy)
991 && combo_check(*combo, false)
992 },
993 StaticAura { energy } => energy_check(*energy),
994 RegrowHead { energy } => energy_check(*energy),
995 }
996 }
997}