veloren_server_agent/
data.rs

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
43// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
44// crate
45pub struct AgentData<'a> {
46    pub entity: &'a EcsEntity,
47    pub uid: &'a Uid,
48    /// If the agent is riding something this is the root mounts position.
49    pub pos: &'a Pos,
50    /// If the agent is riding something this is the root mounts velocity.
51    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    /// Assumes attacker is facing the enemy
160    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)]
188// When adding a new variant, first decide if it should instead fall under one
189// of the pre-existing tactics
190pub enum Tactic {
191    // General tactics
192    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    // u8s are weights that each ability gets used, if it can be used
206    RandomAbilities {
207        primary: u8,
208        secondary: u8,
209        abilities: [u8; BASE_ABILITY_LIMIT],
210    },
211
212    // Tool specific tactics
213    Axe,
214    Hammer,
215    Sword,
216    Bow,
217    Staff,
218    Sceptre,
219    // TODO: Remove tactic and ability spec
220    SwordSimple,
221
222    // Broad creature tactics
223    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    // Specific species tactics
246    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    Rocksnapper,
265    Roshwalr,
266    FrostGigas,
267    BorealHammer,
268    BorealBow,
269    FireGigas,
270    AshenAxe,
271    AshenStaff,
272    Dullahan,
273    Cyclops,
274    IceDrake,
275    Hydra,
276    Flamekeeper,
277    Forgemaster,
278
279    // Adlets
280    AdletHunter,
281    AdletIcepicker,
282    AdletTracker,
283    AdletElder,
284
285    // Haniwa
286    HaniwaSoldier,
287    HaniwaGuard,
288    HaniwaArcher,
289
290    // Terracotta
291    TerracottaStatue,
292    Cursekeeper,
293    CursekeeperFake,
294    ShamanicSpirit,
295    Jiangshi,
296
297    // VampireCastle
298    VampireBat,
299    BloodmoonBat,
300    BloodmoonHeiress,
301}
302
303#[derive(Copy, Clone, Debug)]
304pub enum SwordTactics {
305    Unskilled = 0,
306    Basic = 1,
307    HeavySimple = 2,
308    AgileSimple = 3,
309    DefensiveSimple = 4,
310    CripplingSimple = 5,
311    CleavingSimple = 6,
312    HeavyAdvanced = 7,
313    AgileAdvanced = 8,
314    DefensiveAdvanced = 9,
315    CripplingAdvanced = 10,
316    CleavingAdvanced = 11,
317}
318
319impl SwordTactics {
320    pub fn from_u8(x: u8) -> Self {
321        use SwordTactics::*;
322        match x {
323            0 => Unskilled,
324            1 => Basic,
325            2 => HeavySimple,
326            3 => AgileSimple,
327            4 => DefensiveSimple,
328            5 => CripplingSimple,
329            6 => CleavingSimple,
330            7 => HeavyAdvanced,
331            8 => AgileAdvanced,
332            9 => DefensiveAdvanced,
333            10 => CripplingAdvanced,
334            11 => CleavingAdvanced,
335            _ => Unskilled,
336        }
337    }
338}
339
340#[derive(Copy, Clone, Debug)]
341pub enum AxeTactics {
342    Unskilled = 0,
343    SavageSimple = 1,
344    MercilessSimple = 2,
345    RivingSimple = 3,
346    SavageIntermediate = 4,
347    MercilessIntermediate = 5,
348    RivingIntermediate = 6,
349    SavageAdvanced = 7,
350    MercilessAdvanced = 8,
351    RivingAdvanced = 9,
352}
353
354impl AxeTactics {
355    pub fn from_u8(x: u8) -> Self {
356        use AxeTactics::*;
357        match x {
358            0 => Unskilled,
359            1 => SavageSimple,
360            2 => MercilessSimple,
361            3 => RivingSimple,
362            4 => SavageIntermediate,
363            5 => MercilessIntermediate,
364            6 => RivingIntermediate,
365            7 => SavageAdvanced,
366            8 => MercilessAdvanced,
367            9 => RivingAdvanced,
368            _ => Unskilled,
369        }
370    }
371}
372
373#[derive(Copy, Clone, Debug)]
374pub enum HammerTactics {
375    Unskilled = 0,
376    Simple = 1,
377    AttackSimple = 2,
378    SupportSimple = 3,
379    AttackIntermediate = 4,
380    SupportIntermediate = 5,
381    AttackAdvanced = 6,
382    SupportAdvanced = 7,
383    AttackExpert = 8,
384    SupportExpert = 9,
385}
386
387impl HammerTactics {
388    pub fn from_u8(x: u8) -> Self {
389        use HammerTactics::*;
390        match x {
391            0 => Unskilled,
392            1 => Simple,
393            2 => AttackSimple,
394            3 => SupportSimple,
395            4 => AttackIntermediate,
396            5 => SupportIntermediate,
397            6 => AttackAdvanced,
398            7 => SupportAdvanced,
399            8 => AttackExpert,
400            9 => SupportExpert,
401            _ => Unskilled,
402        }
403    }
404}
405
406#[derive(SystemData)]
407pub struct ReadData<'a> {
408    pub entities: Entities<'a>,
409    pub id_maps: Read<'a, IdMaps>,
410    pub dt: Read<'a, DeltaTime>,
411    pub time: Read<'a, Time>,
412    pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
413    pub group_manager: Read<'a, group::GroupManager>,
414    pub energies: ReadStorage<'a, Energy>,
415    pub positions: ReadStorage<'a, Pos>,
416    pub velocities: ReadStorage<'a, Vel>,
417    pub orientations: ReadStorage<'a, Ori>,
418    pub scales: ReadStorage<'a, Scale>,
419    pub healths: ReadStorage<'a, Health>,
420    pub heads: ReadStorage<'a, Heads>,
421    pub inventories: ReadStorage<'a, Inventory>,
422    pub stats: ReadStorage<'a, Stats>,
423    pub skill_set: ReadStorage<'a, SkillSet>,
424    pub physics_states: ReadStorage<'a, PhysicsState>,
425    pub char_states: ReadStorage<'a, CharacterState>,
426    pub uids: ReadStorage<'a, Uid>,
427    pub groups: ReadStorage<'a, group::Group>,
428    pub terrain: ReadExpect<'a, TerrainGrid>,
429    pub alignments: ReadStorage<'a, Alignment>,
430    pub bodies: ReadStorage<'a, Body>,
431    pub is_mounts: ReadStorage<'a, Is<Mount>>,
432    pub is_riders: ReadStorage<'a, Is<Rider>>,
433    pub is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
434    pub interactors: ReadStorage<'a, Interactors>,
435    pub time_of_day: Read<'a, TimeOfDay>,
436    pub light_emitter: ReadStorage<'a, LightEmitter>,
437    #[cfg(feature = "worldgen")]
438    pub world: ReadExpect<'a, std::sync::Arc<world::World>>,
439    pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
440    pub buffs: ReadStorage<'a, Buffs>,
441    pub combos: ReadStorage<'a, Combo>,
442    pub active_abilities: ReadStorage<'a, ActiveAbilities>,
443    pub loot_owners: ReadStorage<'a, LootOwner>,
444    pub msm: ReadExpect<'a, MaterialStatManifest>,
445    pub poises: ReadStorage<'a, Poise>,
446    pub stances: ReadStorage<'a, Stance>,
447    pub presences: ReadStorage<'a, Presence>,
448    pub ability_map: ReadExpect<'a, AbilityMap>,
449}
450
451pub enum Path {
452    Full,
453    Separate,
454    Partial,
455}
456
457#[derive(Clone, Debug)]
458pub enum AbilityData {
459    ComboMelee {
460        range: f32,
461        angle: f32,
462        energy_per_strike: f32,
463        forced_movement: Option<ForcedMovement>,
464    },
465    FinisherMelee {
466        range: f32,
467        angle: f32,
468        energy: f32,
469        combo: u32,
470        combo_scales: bool,
471    },
472    SelfBuff {
473        buff_kinds: Vec<BuffKind>,
474        energy: f32,
475        combo: u32,
476        combo_scales: bool,
477    },
478    DiveMelee {
479        range: f32,
480        angle: f32,
481        energy: f32,
482    },
483    DashMelee {
484        range: f32,
485        angle: f32,
486        initial_energy: f32,
487        energy_drain: f32,
488        speed: f32,
489        charge_dur: f32,
490    },
491    RapidMelee {
492        range: f32,
493        angle: f32,
494        energy_per_strike: f32,
495        strikes: u32,
496        combo: u32,
497    },
498    ChargedMelee {
499        range: f32,
500        angle: f32,
501        initial_energy: f32,
502        energy_drain: f32,
503        charge_dur: f32,
504    },
505    RiposteMelee {
506        range: f32,
507        angle: f32,
508        energy: f32,
509    },
510    BasicBlock {
511        energy: f32,
512        blocked_attacks: AttackFilters,
513        angle: f32,
514    },
515    BasicRanged {
516        energy: f32,
517        projectile_speed: f32,
518        projectile_spread: f32,
519        num_projectiles: Amount,
520    },
521    BasicMelee {
522        energy: f32,
523        range: f32,
524        angle: f32,
525    },
526    LeapMelee {
527        energy: f32,
528        range: f32,
529        angle: f32,
530        forward_leap: f32,
531        vertical_leap: f32,
532        leap_dur: f32,
533    },
534    BasicBeam {
535        energy_drain: f32,
536        range: f32,
537        angle: f32,
538        ori_rate: f32,
539    },
540    Shockwave {
541        energy: f32,
542        angle: f32,
543        range: f32,
544        combo: u32,
545    },
546    BasicSummon,
547    // Note, buff check not done as auras could be non-buff and auras could target either in or
548    // out of group
549    BasicAura {
550        energy: f32,
551    },
552    StaticAura {
553        energy: f32,
554    },
555    RegrowHead {
556        energy: f32,
557    },
558}
559
560#[derive(Copy, Clone, Debug, Default)]
561pub struct AbilityPreferences {
562    pub desired_energy: f32,
563    pub combo_scaling_buildup: u32,
564}
565
566impl AbilityData {
567    pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
568        use CharacterAbility::*;
569        let inner = match ability {
570            ComboMelee2 {
571                strikes,
572                energy_cost_per_strike,
573                ..
574            } => {
575                let (range, angle, forced_movement) = strikes
576                    .iter()
577                    .map(|s| {
578                        (
579                            s.melee_constructor.range,
580                            s.melee_constructor.angle,
581                            s.movement.buildup.map(|m| m * s.buildup_duration),
582                        )
583                    })
584                    .fold(
585                        (100.0, 360.0, None),
586                        |(r1, a1, m1): (f32, f32, Option<ForcedMovement>),
587                         (r2, a2, m2): (f32, f32, Option<ForcedMovement>)| {
588                            (r1.min(r2), a1.min(a2), m1.or(m2))
589                        },
590                    );
591                Self::ComboMelee {
592                    range,
593                    angle,
594                    energy_per_strike: *energy_cost_per_strike,
595                    forced_movement,
596                }
597            },
598            FinisherMelee {
599                energy_cost,
600                melee_constructor,
601                minimum_combo,
602                scaling,
603                ..
604            } => Self::FinisherMelee {
605                energy: *energy_cost,
606                range: melee_constructor.range,
607                angle: melee_constructor.angle,
608                combo: *minimum_combo,
609                combo_scales: scaling.is_some(),
610            },
611            SelfBuff {
612                buffs,
613                energy_cost,
614                combo_cost,
615                combo_scaling,
616                ..
617            } => Self::SelfBuff {
618                buff_kinds: buffs.iter().map(|buff_desc| buff_desc.kind).collect(),
619                energy: *energy_cost,
620                combo: *combo_cost,
621                combo_scales: combo_scaling.is_some(),
622            },
623            DiveMelee {
624                energy_cost,
625                melee_constructor,
626                ..
627            } => Self::DiveMelee {
628                energy: *energy_cost,
629                range: melee_constructor.range,
630                angle: melee_constructor.angle,
631            },
632            DashMelee {
633                energy_cost,
634                energy_drain,
635                forward_speed,
636                melee_constructor,
637                charge_duration,
638                ..
639            } => Self::DashMelee {
640                initial_energy: *energy_cost,
641                energy_drain: *energy_drain,
642                range: melee_constructor.range,
643                angle: melee_constructor.angle,
644                charge_dur: *charge_duration,
645                speed: *forward_speed,
646            },
647            RapidMelee {
648                energy_cost,
649                max_strikes,
650                minimum_combo,
651                melee_constructor,
652                ..
653            } => Self::RapidMelee {
654                energy_per_strike: *energy_cost,
655                range: melee_constructor.range,
656                angle: melee_constructor.angle,
657                strikes: max_strikes.unwrap_or(100),
658                combo: *minimum_combo,
659            },
660            ChargedMelee {
661                energy_cost,
662                energy_drain,
663                charge_duration,
664                melee_constructor,
665                ..
666            } => Self::ChargedMelee {
667                initial_energy: *energy_cost,
668                energy_drain: *energy_drain,
669                charge_dur: *charge_duration,
670                range: melee_constructor.range,
671                angle: melee_constructor.angle,
672            },
673            RiposteMelee {
674                energy_cost,
675                melee_constructor,
676                ..
677            } => Self::RiposteMelee {
678                energy: *energy_cost,
679                range: melee_constructor.range,
680                angle: melee_constructor.angle,
681            },
682            BasicBlock {
683                max_angle,
684                energy_cost,
685                blocked_attacks,
686                ..
687            } => Self::BasicBlock {
688                energy: *energy_cost,
689                angle: *max_angle,
690                blocked_attacks: *blocked_attacks,
691            },
692            BasicRanged {
693                energy_cost,
694                projectile_speed,
695                projectile_spread,
696                num_projectiles,
697                ..
698            } => Self::BasicRanged {
699                energy: *energy_cost,
700                projectile_speed: *projectile_speed,
701                projectile_spread: *projectile_spread,
702                num_projectiles: *num_projectiles,
703            },
704            BasicMelee {
705                energy_cost,
706                melee_constructor,
707                ..
708            } => Self::BasicMelee {
709                energy: *energy_cost,
710                range: melee_constructor.range,
711                angle: melee_constructor.angle,
712            },
713            LeapMelee {
714                energy_cost,
715                movement_duration,
716                melee_constructor,
717                forward_leap_strength,
718                vertical_leap_strength,
719                ..
720            } => Self::LeapMelee {
721                energy: *energy_cost,
722                leap_dur: *movement_duration,
723                range: melee_constructor.range,
724                angle: melee_constructor.angle,
725                forward_leap: *forward_leap_strength,
726                vertical_leap: *vertical_leap_strength,
727            },
728            BasicBeam {
729                range,
730                max_angle,
731                ori_rate,
732                energy_drain,
733                ..
734            } => Self::BasicBeam {
735                range: *range,
736                angle: *max_angle,
737                ori_rate: *ori_rate,
738                energy_drain: *energy_drain,
739            },
740            Shockwave {
741                energy_cost,
742                shockwave_angle,
743                shockwave_speed,
744                shockwave_duration,
745                minimum_combo,
746                ..
747            } => Self::Shockwave {
748                energy: *energy_cost,
749                angle: *shockwave_angle,
750                range: *shockwave_speed * *shockwave_duration,
751                combo: minimum_combo.unwrap_or(0),
752            },
753            BasicSummon { .. } => Self::BasicSummon,
754            BasicAura { energy_cost, .. } => Self::BasicAura {
755                energy: *energy_cost,
756            },
757            StaticAura { energy_cost, .. } => Self::StaticAura {
758                energy: *energy_cost,
759            },
760            RegrowHead { energy_cost, .. } => Self::RegrowHead {
761                energy: *energy_cost,
762            },
763            _ => {
764                dev_panic!(
765                    "Agent tried to use ability with a character state they haven't learned to \
766                     understand"
767                );
768                return None;
769            },
770        };
771        Some(inner)
772    }
773
774    pub fn could_use(
775        &self,
776        attack_data: &AttackData,
777        agent_data: &AgentData,
778        tgt_data: &TargetData,
779        read_data: &ReadData,
780        ability_preferences: AbilityPreferences,
781    ) -> bool {
782        let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
783            let (range_inc, min_mult) = forced_movement.map_or((0.0, 0.0), |fm| match fm {
784                ForcedMovement::Forward(speed) => (speed * 15.0, 1.0),
785                ForcedMovement::Reverse(speed) => (-speed, 1.0),
786                ForcedMovement::Leap {
787                    vertical, forward, ..
788                } => (
789                    {
790                        let dur = vertical * 2.0 / GRAVITY;
791                        // 0.75 factor to allow for fact that agent looks down as they approach, so
792                        // won't go as far
793                        forward * dur * 0.75
794                    },
795                    0.0,
796                ),
797                _ => (0.0, 0.0),
798            });
799            let body_rad = agent_data.body.map_or(0.0, |b| b.max_radius());
800            attack_data.dist_sqrd < (range + range_inc + body_rad).powi(2)
801                && attack_data.angle < angle
802                && attack_data.dist_sqrd > (range_inc * min_mult).powi(2)
803        };
804        let energy_check = |energy: f32| {
805            agent_data.energy.current() >= energy
806                && (energy < f32::EPSILON
807                    || agent_data.energy.current() >= ability_preferences.desired_energy)
808        };
809        let combo_check = |combo, scales| {
810            let additional_combo = if scales {
811                ability_preferences.combo_scaling_buildup
812            } else {
813                0
814            };
815            agent_data
816                .combo
817                .is_some_and(|c| c.counter() >= combo + additional_combo)
818        };
819        let attack_kind_check = |attacks: AttackFilters| {
820            tgt_data
821                .char_state
822                .map(|cs| cs.attack_kind())
823                .unwrap_or_default()
824                .iter()
825                .any(|attack_source| attacks.applies(*attack_source))
826        };
827        let ranged_check = |proj_speed| {
828            let max_horiz_dist: f32 = {
829                let flight_time = proj_speed * 2_f32.sqrt() / GRAVITY;
830                proj_speed * 2_f32.sqrt() / 2.0 * flight_time
831            };
832            attack_data.dist_sqrd < max_horiz_dist.powi(2)
833                && entities_have_line_of_sight(
834                    agent_data.pos,
835                    agent_data.body,
836                    agent_data.scale,
837                    tgt_data.pos,
838                    tgt_data.body,
839                    tgt_data.scale,
840                    read_data,
841                )
842        };
843        let beam_check = |range: f32, angle, ori_rate: f32| {
844            let angle_inc = ori_rate.to_degrees();
845            attack_data.dist_sqrd < range.powi(2)
846                && attack_data.angle < angle + angle_inc
847                && entities_have_line_of_sight(
848                    agent_data.pos,
849                    agent_data.body,
850                    agent_data.scale,
851                    tgt_data.pos,
852                    tgt_data.body,
853                    tgt_data.scale,
854                    read_data,
855                )
856        };
857        use AbilityData::*;
858        match self {
859            ComboMelee {
860                range,
861                angle,
862                energy_per_strike,
863                forced_movement,
864            } => melee_check(*range, *angle, *forced_movement) && energy_check(*energy_per_strike),
865            FinisherMelee {
866                range,
867                angle,
868                energy,
869                combo,
870                combo_scales,
871            } => {
872                melee_check(*range, *angle, None)
873                    && energy_check(*energy)
874                    && combo_check(*combo, *combo_scales)
875            },
876            SelfBuff {
877                buff_kinds,
878                energy,
879                combo,
880                combo_scales,
881            } => {
882                energy_check(*energy)
883                    && combo_check(*combo, *combo_scales)
884                    && agent_data
885                        .buffs
886                        .is_some_and(|buffs| !buffs.contains_any(buff_kinds))
887            },
888            DiveMelee {
889                range,
890                angle,
891                energy,
892            } => melee_check(*range, *angle, None) && energy_check(*energy),
893            DashMelee {
894                range,
895                angle,
896                initial_energy,
897                energy_drain,
898                speed,
899                charge_dur,
900            } => {
901                // TODO: Maybe figure out better way of pulling in base accel from body and
902                // accounting for friction?
903                const BASE_SPEED: f32 = 3.0;
904                const ORI_RATE: f32 = 30.0;
905                let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
906                    .clamp(0.0, *charge_dur);
907                let charge_dist = charge_dur * speed * BASE_SPEED;
908                let attack_dist = charge_dist + range;
909                let ori_gap = ORI_RATE * charge_dur;
910                // TODO: Replace None with actual forced movement later
911                melee_check(attack_dist, angle + ori_gap, None)
912                    && energy_check(*initial_energy)
913                    && attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
914            },
915            RapidMelee {
916                range,
917                angle,
918                energy_per_strike,
919                strikes,
920                combo,
921            } => {
922                melee_check(*range, *angle, None)
923                    && energy_check(*energy_per_strike * *strikes as f32)
924                    && combo_check(*combo, false)
925            },
926            ChargedMelee {
927                range,
928                angle,
929                initial_energy,
930                energy_drain,
931                charge_dur,
932            } => {
933                melee_check(*range, *angle, None)
934                    && energy_check(*initial_energy + *energy_drain * *charge_dur)
935            },
936            RiposteMelee {
937                energy,
938                range,
939                angle,
940            } => {
941                melee_check(*range, *angle, None)
942                    && energy_check(*energy)
943                    && tgt_data.char_state.is_some_and(|cs| {
944                        cs.is_melee_attack()
945                            && matches!(
946                                cs.stage_section(),
947                                Some(
948                                    StageSection::Buildup
949                                        | StageSection::Charge
950                                        | StageSection::Movement
951                                )
952                            )
953                    })
954            },
955            BasicBlock {
956                energy,
957                angle,
958                blocked_attacks,
959            } => {
960                melee_check(25.0, *angle, None)
961                    && energy_check(*energy)
962                    && attack_kind_check(*blocked_attacks)
963                    && tgt_data
964                        .char_state
965                        .and_then(|cs| cs.stage_section())
966                        .is_some_and(|ss| !matches!(ss, StageSection::Recover))
967            },
968            BasicRanged {
969                energy,
970                projectile_speed,
971                projectile_spread: _,
972                num_projectiles: _,
973            } => ranged_check(*projectile_speed) && energy_check(*energy),
974            BasicMelee {
975                energy,
976                range,
977                angle,
978            } => melee_check(*range, *angle, None) && energy_check(*energy),
979            LeapMelee {
980                energy,
981                range,
982                angle,
983                leap_dur,
984                forward_leap,
985                vertical_leap,
986            } => {
987                use common::states::utils::MovementDirection;
988                let forced_move = Some(ForcedMovement::Leap {
989                    vertical: *vertical_leap * *leap_dur * 2.0,
990                    forward: *forward_leap,
991                    progress: 0.0,
992                    direction: MovementDirection::Look,
993                });
994                melee_check(*range, *angle, forced_move) && energy_check(*energy)
995            },
996            BasicBeam {
997                energy_drain,
998                range,
999                angle,
1000                ori_rate,
1001            } => beam_check(*range, *angle, *ori_rate) && energy_check(*energy_drain * 3.0),
1002            Shockwave {
1003                energy,
1004                range,
1005                angle,
1006                combo,
1007            } => {
1008                melee_check(*range, *angle, None)
1009                    && energy_check(*energy)
1010                    && combo_check(*combo, false)
1011            },
1012            BasicSummon => true,
1013            BasicAura { energy } => energy_check(*energy),
1014            StaticAura { energy } => energy_check(*energy),
1015            RegrowHead { energy } => energy_check(*energy),
1016        }
1017    }
1018}