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