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    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    // Adlets
281    AdletHunter,
282    AdletIcepicker,
283    AdletTracker,
284    AdletElder,
285
286    // Haniwa
287    HaniwaSoldier,
288    HaniwaGuard,
289    HaniwaArcher,
290
291    // Terracotta
292    TerracottaStatue,
293    Cursekeeper,
294    CursekeeperFake,
295    ShamanicSpirit,
296    Jiangshi,
297
298    // VampireCastle
299    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(Copy, Clone, Debug)]
408pub enum BowTactics {
409    Unskilled = 0,
410    Simple = 1,
411    HunterSimple = 2,
412    TricksterSimple = 3,
413    ArtillerySimple = 4,
414    HunterIntermediate = 5,
415    TricksterIntermediate = 6,
416    ArtilleryIntermediate = 7,
417    HunterAdvanced = 8,
418    TricksterAdvanced = 9,
419    ArtilleryAdvanced = 10,
420}
421
422impl BowTactics {
423    pub fn from_u8(x: u8) -> Self {
424        use BowTactics::*;
425        match x {
426            0 => Unskilled,
427            1 => Simple,
428            2 => HunterSimple,
429            3 => TricksterSimple,
430            4 => ArtillerySimple,
431            5 => HunterIntermediate,
432            6 => TricksterIntermediate,
433            7 => ArtilleryIntermediate,
434            8 => HunterAdvanced,
435            9 => TricksterAdvanced,
436            10 => ArtilleryAdvanced,
437            _ => Unskilled,
438        }
439    }
440}
441
442#[derive(SystemData)]
443pub struct ReadData<'a> {
444    pub entities: Entities<'a>,
445    pub id_maps: Read<'a, IdMaps>,
446    pub dt: Read<'a, DeltaTime>,
447    pub time: Read<'a, Time>,
448    pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
449    pub group_manager: Read<'a, group::GroupManager>,
450    pub energies: ReadStorage<'a, Energy>,
451    pub positions: ReadStorage<'a, Pos>,
452    pub velocities: ReadStorage<'a, Vel>,
453    pub orientations: ReadStorage<'a, Ori>,
454    pub scales: ReadStorage<'a, Scale>,
455    pub healths: ReadStorage<'a, Health>,
456    pub heads: ReadStorage<'a, Heads>,
457    pub inventories: ReadStorage<'a, Inventory>,
458    pub stats: ReadStorage<'a, Stats>,
459    pub skill_set: ReadStorage<'a, SkillSet>,
460    pub physics_states: ReadStorage<'a, PhysicsState>,
461    pub char_states: ReadStorage<'a, CharacterState>,
462    pub uids: ReadStorage<'a, Uid>,
463    pub groups: ReadStorage<'a, group::Group>,
464    pub terrain: ReadExpect<'a, TerrainGrid>,
465    pub alignments: ReadStorage<'a, Alignment>,
466    pub bodies: ReadStorage<'a, Body>,
467    pub is_mounts: ReadStorage<'a, Is<Mount>>,
468    pub is_riders: ReadStorage<'a, Is<Rider>>,
469    pub is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
470    pub interactors: ReadStorage<'a, Interactors>,
471    pub time_of_day: Read<'a, TimeOfDay>,
472    pub light_emitter: ReadStorage<'a, LightEmitter>,
473    #[cfg(feature = "worldgen")]
474    pub world: ReadExpect<'a, std::sync::Arc<world::World>>,
475    pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
476    pub buffs: ReadStorage<'a, Buffs>,
477    pub combos: ReadStorage<'a, Combo>,
478    pub active_abilities: ReadStorage<'a, ActiveAbilities>,
479    pub loot_owners: ReadStorage<'a, LootOwner>,
480    pub msm: ReadExpect<'a, MaterialStatManifest>,
481    pub poises: ReadStorage<'a, Poise>,
482    pub stances: ReadStorage<'a, Stance>,
483    pub presences: ReadStorage<'a, Presence>,
484    pub ability_map: ReadExpect<'a, AbilityMap>,
485}
486
487pub enum Path {
488    /// Try to path exactly to the target.
489    AtTarget,
490    /// Try to path to a position close to the target.
491    Separate,
492}
493
494#[derive(Clone, Debug)]
495pub enum AbilityData {
496    ComboMelee {
497        range: f32,
498        angle: f32,
499        energy_per_strike: f32,
500        forced_movement: Option<ForcedMovement>,
501    },
502    FinisherMelee {
503        range: f32,
504        angle: f32,
505        energy: f32,
506        combo: u32,
507        combo_scales: bool,
508    },
509    SelfBuff {
510        buff_kinds: Vec<BuffKind>,
511        energy: f32,
512        combo: u32,
513        combo_scales: bool,
514    },
515    DiveMelee {
516        range: f32,
517        angle: f32,
518        energy: f32,
519    },
520    DashMelee {
521        range: f32,
522        angle: f32,
523        initial_energy: f32,
524        energy_drain: f32,
525        speed: f32,
526        charge_dur: f32,
527    },
528    RapidMelee {
529        range: f32,
530        angle: f32,
531        energy_per_strike: f32,
532        strikes: u32,
533        combo: u32,
534    },
535    ChargedMelee {
536        range: f32,
537        angle: f32,
538        initial_energy: f32,
539        energy_drain: f32,
540        charge_dur: f32,
541    },
542    RiposteMelee {
543        range: f32,
544        angle: f32,
545        energy: f32,
546    },
547    BasicBlock {
548        energy: f32,
549        blocked_attacks: AttackFilters,
550        angle: f32,
551    },
552    BasicRanged {
553        energy: f32,
554        projectile_speed: f32,
555        projectile_spread: f32,
556        num_projectiles: Amount,
557    },
558    BasicMelee {
559        energy: f32,
560        range: f32,
561        angle: f32,
562    },
563    LeapMelee {
564        energy: f32,
565        range: f32,
566        angle: f32,
567        forward_leap: f32,
568        vertical_leap: f32,
569        leap_dur: f32,
570    },
571    BasicBeam {
572        energy_drain: f32,
573        range: f32,
574        angle: f32,
575        ori_rate: f32,
576    },
577    Shockwave {
578        energy: f32,
579        angle: f32,
580        range: f32,
581        combo: u32,
582    },
583    BasicSummon,
584    // Note, buff check not done as auras could be non-buff and auras could target either in or
585    // out of group
586    BasicAura {
587        energy: f32,
588    },
589    StaticAura {
590        energy: f32,
591    },
592    RegrowHead {
593        energy: f32,
594    },
595    Simple {
596        energy: f32,
597        combo: u32,
598    },
599    ChargedRanged {
600        projectile_speed: f32,
601        initial_energy: f32,
602        energy_drain: f32,
603        charge_dur: f32,
604    },
605    RapidRanged {
606        projectile_speed: f32,
607        energy_per_shot: f32,
608        shots: u32,
609    },
610    LeapRanged {
611        // range, angle
612        melee: Option<(f32, f32)>,
613        melee_required: bool,
614        projectile_speed: f32,
615        energy: f32,
616    },
617}
618
619#[derive(Copy, Clone, Debug, Default)]
620pub struct AbilityPreferences {
621    pub desired_energy: f32,
622    pub combo_scaling_buildup: u32,
623}
624
625impl AbilityData {
626    pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
627        use CharacterAbility::*;
628        let inner = match ability {
629            ComboMelee2 {
630                strikes,
631                energy_cost_per_strike,
632                ..
633            } => {
634                let (range, angle, forced_movement) = strikes
635                    .iter()
636                    .map(|s| {
637                        (
638                            s.melee_constructor.range,
639                            s.melee_constructor.angle,
640                            s.movement.buildup.map(|m| m * s.buildup_duration),
641                        )
642                    })
643                    .fold(
644                        (100.0, 360.0, None),
645                        |(r1, a1, m1): (f32, f32, Option<ForcedMovement>),
646                         (r2, a2, m2): (f32, f32, Option<ForcedMovement>)| {
647                            (r1.min(r2), a1.min(a2), m1.or(m2))
648                        },
649                    );
650                Self::ComboMelee {
651                    range,
652                    angle,
653                    energy_per_strike: *energy_cost_per_strike,
654                    forced_movement,
655                }
656            },
657            FinisherMelee {
658                energy_cost,
659                melee_constructor,
660                minimum_combo,
661                scaling,
662                ..
663            } => Self::FinisherMelee {
664                energy: *energy_cost,
665                range: melee_constructor.range,
666                angle: melee_constructor.angle,
667                combo: *minimum_combo,
668                combo_scales: scaling.is_some(),
669            },
670            SelfBuff {
671                buffs,
672                energy_cost,
673                combo_cost,
674                combo_scaling,
675                ..
676            } => Self::SelfBuff {
677                buff_kinds: buffs.iter().map(|buff_desc| buff_desc.kind).collect(),
678                energy: *energy_cost,
679                combo: *combo_cost,
680                combo_scales: combo_scaling.is_some(),
681            },
682            DiveMelee {
683                energy_cost,
684                melee_constructor,
685                ..
686            } => Self::DiveMelee {
687                energy: *energy_cost,
688                range: melee_constructor.range,
689                angle: melee_constructor.angle,
690            },
691            DashMelee {
692                energy_cost,
693                energy_drain,
694                forward_speed,
695                melee_constructor,
696                charge_duration,
697                ..
698            } => Self::DashMelee {
699                initial_energy: *energy_cost,
700                energy_drain: *energy_drain,
701                range: melee_constructor.range,
702                angle: melee_constructor.angle,
703                charge_dur: *charge_duration,
704                speed: *forward_speed,
705            },
706            RapidMelee {
707                energy_cost,
708                max_strikes,
709                minimum_combo,
710                melee_constructor,
711                ..
712            } => Self::RapidMelee {
713                energy_per_strike: *energy_cost,
714                range: melee_constructor.range,
715                angle: melee_constructor.angle,
716                strikes: max_strikes.unwrap_or(10),
717                combo: *minimum_combo,
718            },
719            ChargedMelee {
720                energy_cost,
721                energy_drain,
722                charge_duration,
723                melee_constructor,
724                ..
725            } => Self::ChargedMelee {
726                initial_energy: *energy_cost,
727                energy_drain: *energy_drain,
728                charge_dur: *charge_duration,
729                range: melee_constructor.range,
730                angle: melee_constructor.angle,
731            },
732            RiposteMelee {
733                energy_cost,
734                melee_constructor,
735                ..
736            } => Self::RiposteMelee {
737                energy: *energy_cost,
738                range: melee_constructor.range,
739                angle: melee_constructor.angle,
740            },
741            BasicBlock {
742                max_angle,
743                energy_cost,
744                blocked_attacks,
745                ..
746            } => Self::BasicBlock {
747                energy: *energy_cost,
748                angle: *max_angle,
749                blocked_attacks: *blocked_attacks,
750            },
751            BasicRanged {
752                energy_cost,
753                projectile_speed,
754                projectile_spread,
755                num_projectiles,
756                ..
757            } => Self::BasicRanged {
758                energy: *energy_cost,
759                projectile_speed: *projectile_speed,
760                projectile_spread: projectile_spread.map_or(0.0, |sprd| sprd.estimated_spread()),
761                num_projectiles: *num_projectiles,
762            },
763            BasicMelee {
764                energy_cost,
765                melee_constructor,
766                ..
767            } => Self::BasicMelee {
768                energy: *energy_cost,
769                range: melee_constructor.range,
770                angle: melee_constructor.angle,
771            },
772            LeapMelee {
773                energy_cost,
774                movement_duration,
775                melee_constructor,
776                forward_leap_strength,
777                vertical_leap_strength,
778                ..
779            } => Self::LeapMelee {
780                energy: *energy_cost,
781                leap_dur: *movement_duration,
782                range: melee_constructor.range,
783                angle: melee_constructor.angle,
784                forward_leap: *forward_leap_strength,
785                vertical_leap: *vertical_leap_strength,
786            },
787            BasicBeam {
788                range,
789                max_angle,
790                ori_rate,
791                energy_drain,
792                ..
793            } => Self::BasicBeam {
794                range: *range,
795                angle: *max_angle,
796                ori_rate: *ori_rate,
797                energy_drain: *energy_drain,
798            },
799            Shockwave {
800                energy_cost,
801                shockwave_angle,
802                shockwave_speed,
803                shockwave_duration,
804                minimum_combo,
805                ..
806            } => Self::Shockwave {
807                energy: *energy_cost,
808                angle: *shockwave_angle,
809                range: *shockwave_speed * *shockwave_duration,
810                combo: minimum_combo.unwrap_or(0),
811            },
812            BasicSummon { .. } => Self::BasicSummon,
813            BasicAura { energy_cost, .. } => Self::BasicAura {
814                energy: *energy_cost,
815            },
816            StaticAura { energy_cost, .. } => Self::StaticAura {
817                energy: *energy_cost,
818            },
819            RegrowHead { energy_cost, .. } => Self::RegrowHead {
820                energy: *energy_cost,
821            },
822            Simple {
823                energy_cost,
824                combo_cost,
825                ..
826            } => Self::Simple {
827                energy: *energy_cost,
828                combo: *combo_cost,
829            },
830            ChargedRanged {
831                energy_cost,
832                energy_drain,
833                initial_projectile_speed,
834                scaled_projectile_speed,
835                charge_duration,
836                ..
837            } => Self::ChargedRanged {
838                projectile_speed: *initial_projectile_speed + *scaled_projectile_speed,
839                initial_energy: *energy_cost,
840                energy_drain: *energy_drain,
841                charge_dur: *charge_duration,
842            },
843            RapidRanged {
844                energy_cost,
845                projectile_speed,
846                options,
847                ..
848            } => Self::RapidRanged {
849                projectile_speed: *projectile_speed,
850                energy_per_shot: *energy_cost,
851                shots: options.max_projectiles.unwrap_or(10),
852            },
853            LeapRanged {
854                energy_cost,
855                melee_required,
856                melee,
857                projectile_speed,
858                ..
859            } => Self::LeapRanged {
860                energy: *energy_cost,
861                projectile_speed: *projectile_speed,
862                melee_required: *melee_required,
863                melee: melee.as_ref().map(|m| (m.range, m.angle)),
864            },
865            _ => {
866                dev_panic!(
867                    "Agent tried to use ability with a character state they haven't learned to \
868                     understand"
869                );
870                return None;
871            },
872        };
873        Some(inner)
874    }
875
876    pub fn could_use(
877        &self,
878        attack_data: &AttackData,
879        agent_data: &AgentData,
880        tgt_data: &TargetData,
881        read_data: &ReadData,
882        ability_preferences: AbilityPreferences,
883    ) -> bool {
884        let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
885            let (range_inc, min_mult) = forced_movement.map_or((0.0, 0.0), |fm| match fm {
886                ForcedMovement::Forward(speed) => (speed * 15.0, 1.0),
887                ForcedMovement::Reverse(speed) => (-speed, 1.0),
888                ForcedMovement::Leap {
889                    vertical, forward, ..
890                } => (
891                    {
892                        let dur = vertical * 2.0 / GRAVITY;
893                        // 0.75 factor to allow for fact that agent looks down as they approach, so
894                        // won't go as far
895                        forward * dur * 0.75
896                    },
897                    0.0,
898                ),
899                _ => (0.0, 0.0),
900            });
901            let body_rad = agent_data.body.map_or(0.0, |b| b.max_radius());
902            attack_data.dist_sqrd < (range + range_inc + body_rad).powi(2)
903                && attack_data.angle < angle
904                && attack_data.dist_sqrd > (range_inc * min_mult).powi(2)
905        };
906        let energy_check = |energy: f32| {
907            agent_data.energy.current() >= energy
908                && (energy < f32::EPSILON
909                    || agent_data.energy.current() >= ability_preferences.desired_energy)
910        };
911        let combo_check = |combo, scales| {
912            let additional_combo = if scales {
913                ability_preferences.combo_scaling_buildup
914            } else {
915                0
916            };
917            agent_data
918                .combo
919                .is_some_and(|c| c.counter() >= combo + additional_combo)
920        };
921        let attack_kind_check = |attacks: AttackFilters| {
922            tgt_data
923                .char_state
924                .map(|cs| cs.attack_kind())
925                .unwrap_or_default()
926                .iter()
927                .any(|attack_source| attacks.applies(*attack_source))
928        };
929        let ranged_check = |proj_speed| {
930            let max_horiz_dist: f32 = {
931                let flight_time = proj_speed * 2_f32.sqrt() / GRAVITY;
932                proj_speed * 2_f32.sqrt() / 2.0 * flight_time
933            };
934            attack_data.dist_sqrd < max_horiz_dist.powi(2)
935                && entities_have_line_of_sight(
936                    agent_data.pos,
937                    agent_data.body,
938                    agent_data.scale,
939                    tgt_data.pos,
940                    tgt_data.body,
941                    tgt_data.scale,
942                    read_data,
943                )
944        };
945        let beam_check = |range: f32, angle, ori_rate: f32| {
946            let angle_inc = ori_rate.to_degrees();
947            attack_data.dist_sqrd < range.powi(2)
948                && attack_data.angle < angle + angle_inc
949                && entities_have_line_of_sight(
950                    agent_data.pos,
951                    agent_data.body,
952                    agent_data.scale,
953                    tgt_data.pos,
954                    tgt_data.body,
955                    tgt_data.scale,
956                    read_data,
957                )
958        };
959        use AbilityData::*;
960        match self {
961            ComboMelee {
962                range,
963                angle,
964                energy_per_strike,
965                forced_movement,
966            } => melee_check(*range, *angle, *forced_movement) && energy_check(*energy_per_strike),
967            FinisherMelee {
968                range,
969                angle,
970                energy,
971                combo,
972                combo_scales,
973            } => {
974                melee_check(*range, *angle, None)
975                    && energy_check(*energy)
976                    && combo_check(*combo, *combo_scales)
977            },
978            SelfBuff {
979                buff_kinds,
980                energy,
981                combo,
982                combo_scales,
983            } => {
984                energy_check(*energy)
985                    && combo_check(*combo, *combo_scales)
986                    && agent_data
987                        .buffs
988                        .is_some_and(|buffs| !buffs.contains_any(buff_kinds))
989            },
990            DiveMelee {
991                range,
992                angle,
993                energy,
994            } => melee_check(*range, *angle, None) && energy_check(*energy),
995            DashMelee {
996                range,
997                angle,
998                initial_energy,
999                energy_drain,
1000                speed,
1001                charge_dur,
1002            } => {
1003                // TODO: Maybe figure out better way of pulling in base accel from body and
1004                // accounting for friction?
1005                const BASE_SPEED: f32 = 3.0;
1006                const ORI_RATE: f32 = 30.0;
1007                let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
1008                    .clamp(0.0, *charge_dur);
1009                let charge_dist = charge_dur * speed * BASE_SPEED;
1010                let attack_dist = charge_dist + range;
1011                let ori_gap = ORI_RATE * charge_dur;
1012                // TODO: Replace None with actual forced movement later
1013                melee_check(attack_dist, angle + ori_gap, None)
1014                    && energy_check(*initial_energy)
1015                    && attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
1016            },
1017            RapidMelee {
1018                range,
1019                angle,
1020                energy_per_strike,
1021                strikes,
1022                combo,
1023            } => {
1024                melee_check(*range, *angle, None)
1025                    && energy_check(*energy_per_strike * *strikes as f32)
1026                    && combo_check(*combo, false)
1027            },
1028            ChargedMelee {
1029                range,
1030                angle,
1031                initial_energy,
1032                energy_drain,
1033                charge_dur,
1034            } => {
1035                melee_check(*range, *angle, None)
1036                    && energy_check(*initial_energy + *energy_drain * *charge_dur)
1037            },
1038            RiposteMelee {
1039                energy,
1040                range,
1041                angle,
1042            } => {
1043                melee_check(*range, *angle, None)
1044                    && energy_check(*energy)
1045                    && tgt_data.char_state.is_some_and(|cs| {
1046                        cs.is_melee_attack()
1047                            && cs.is_blockable().unwrap_or(false)
1048                            && matches!(
1049                                cs.stage_section(),
1050                                Some(
1051                                    StageSection::Buildup
1052                                        | StageSection::Charge
1053                                        | StageSection::Movement
1054                                )
1055                            )
1056                    })
1057            },
1058            BasicBlock {
1059                energy,
1060                angle,
1061                blocked_attacks,
1062            } => {
1063                melee_check(25.0, *angle, None)
1064                    && energy_check(*energy)
1065                    && attack_kind_check(*blocked_attacks)
1066                    && tgt_data.char_state.is_some_and(|cs| {
1067                        cs.is_blockable().unwrap_or(false)
1068                            && cs
1069                                .stage_section()
1070                                .is_some_and(|ss| !matches!(ss, StageSection::Recover))
1071                    })
1072            },
1073            BasicRanged {
1074                energy,
1075                projectile_speed,
1076                projectile_spread: _,
1077                num_projectiles: _,
1078            } => ranged_check(*projectile_speed) && energy_check(*energy),
1079            BasicMelee {
1080                energy,
1081                range,
1082                angle,
1083            } => melee_check(*range, *angle, None) && energy_check(*energy),
1084            LeapMelee {
1085                energy,
1086                range,
1087                angle,
1088                leap_dur,
1089                forward_leap,
1090                vertical_leap,
1091            } => {
1092                use common::states::utils::MovementDirection;
1093                let forced_move = Some(ForcedMovement::Leap {
1094                    vertical: *vertical_leap * *leap_dur * 2.0,
1095                    forward: *forward_leap,
1096                    progress: 0.0,
1097                    direction: MovementDirection::Look,
1098                });
1099                melee_check(*range, *angle, forced_move) && energy_check(*energy)
1100            },
1101            BasicBeam {
1102                energy_drain,
1103                range,
1104                angle,
1105                ori_rate,
1106            } => beam_check(*range, *angle, *ori_rate) && energy_check(*energy_drain * 3.0),
1107            Shockwave {
1108                energy,
1109                range,
1110                angle,
1111                combo,
1112            } => {
1113                melee_check(*range, *angle, None)
1114                    && energy_check(*energy)
1115                    && combo_check(*combo, false)
1116            },
1117            BasicSummon => true,
1118            BasicAura { energy } => energy_check(*energy),
1119            StaticAura { energy } => energy_check(*energy),
1120            RegrowHead { energy } => energy_check(*energy),
1121            Simple { energy, combo } => energy_check(*energy) && combo_check(*combo, false),
1122            ChargedRanged {
1123                projectile_speed,
1124                initial_energy,
1125                energy_drain,
1126                charge_dur,
1127            } => {
1128                energy_check(*initial_energy + *energy_drain + *charge_dur)
1129                    && ranged_check(*projectile_speed)
1130            },
1131            RapidRanged {
1132                projectile_speed,
1133                energy_per_shot,
1134                shots,
1135            } => energy_check(*energy_per_shot * *shots as f32) && ranged_check(*projectile_speed),
1136            LeapRanged {
1137                melee,
1138                melee_required,
1139                projectile_speed,
1140                energy,
1141            } => {
1142                energy_check(*energy)
1143                    && (!melee_required || melee.is_some_and(|(r, a)| melee_check(r, a, None)))
1144                    && ranged_check(*projectile_speed)
1145            },
1146        }
1147    }
1148}