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 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 AdletHunter,
281 AdletIcepicker,
282 AdletTracker,
283 AdletElder,
284
285 HaniwaSoldier,
287 HaniwaGuard,
288 HaniwaArcher,
289
290 TerracottaStatue,
292 Cursekeeper,
293 CursekeeperFake,
294 ShamanicSpirit,
295 Jiangshi,
296
297 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 AtTarget,
454 Separate,
456}
457
458#[derive(Clone, Debug)]
459pub enum AbilityData {
460 ComboMelee {
461 range: f32,
462 angle: f32,
463 energy_per_strike: f32,
464 forced_movement: Option<ForcedMovement>,
465 },
466 FinisherMelee {
467 range: f32,
468 angle: f32,
469 energy: f32,
470 combo: u32,
471 combo_scales: bool,
472 },
473 SelfBuff {
474 buff_kinds: Vec<BuffKind>,
475 energy: f32,
476 combo: u32,
477 combo_scales: bool,
478 },
479 DiveMelee {
480 range: f32,
481 angle: f32,
482 energy: f32,
483 },
484 DashMelee {
485 range: f32,
486 angle: f32,
487 initial_energy: f32,
488 energy_drain: f32,
489 speed: f32,
490 charge_dur: f32,
491 },
492 RapidMelee {
493 range: f32,
494 angle: f32,
495 energy_per_strike: f32,
496 strikes: u32,
497 combo: u32,
498 },
499 ChargedMelee {
500 range: f32,
501 angle: f32,
502 initial_energy: f32,
503 energy_drain: f32,
504 charge_dur: f32,
505 },
506 RiposteMelee {
507 range: f32,
508 angle: f32,
509 energy: f32,
510 },
511 BasicBlock {
512 energy: f32,
513 blocked_attacks: AttackFilters,
514 angle: f32,
515 },
516 BasicRanged {
517 energy: f32,
518 projectile_speed: f32,
519 projectile_spread: f32,
520 num_projectiles: Amount,
521 },
522 BasicMelee {
523 energy: f32,
524 range: f32,
525 angle: f32,
526 },
527 LeapMelee {
528 energy: f32,
529 range: f32,
530 angle: f32,
531 forward_leap: f32,
532 vertical_leap: f32,
533 leap_dur: f32,
534 },
535 BasicBeam {
536 energy_drain: f32,
537 range: f32,
538 angle: f32,
539 ori_rate: f32,
540 },
541 Shockwave {
542 energy: f32,
543 angle: f32,
544 range: f32,
545 combo: u32,
546 },
547 BasicSummon,
548 BasicAura {
551 energy: f32,
552 },
553 StaticAura {
554 energy: f32,
555 },
556 RegrowHead {
557 energy: f32,
558 },
559}
560
561#[derive(Copy, Clone, Debug, Default)]
562pub struct AbilityPreferences {
563 pub desired_energy: f32,
564 pub combo_scaling_buildup: u32,
565}
566
567impl AbilityData {
568 pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
569 use CharacterAbility::*;
570 let inner = match ability {
571 ComboMelee2 {
572 strikes,
573 energy_cost_per_strike,
574 ..
575 } => {
576 let (range, angle, forced_movement) = strikes
577 .iter()
578 .map(|s| {
579 (
580 s.melee_constructor.range,
581 s.melee_constructor.angle,
582 s.movement.buildup.map(|m| m * s.buildup_duration),
583 )
584 })
585 .fold(
586 (100.0, 360.0, None),
587 |(r1, a1, m1): (f32, f32, Option<ForcedMovement>),
588 (r2, a2, m2): (f32, f32, Option<ForcedMovement>)| {
589 (r1.min(r2), a1.min(a2), m1.or(m2))
590 },
591 );
592 Self::ComboMelee {
593 range,
594 angle,
595 energy_per_strike: *energy_cost_per_strike,
596 forced_movement,
597 }
598 },
599 FinisherMelee {
600 energy_cost,
601 melee_constructor,
602 minimum_combo,
603 scaling,
604 ..
605 } => Self::FinisherMelee {
606 energy: *energy_cost,
607 range: melee_constructor.range,
608 angle: melee_constructor.angle,
609 combo: *minimum_combo,
610 combo_scales: scaling.is_some(),
611 },
612 SelfBuff {
613 buffs,
614 energy_cost,
615 combo_cost,
616 combo_scaling,
617 ..
618 } => Self::SelfBuff {
619 buff_kinds: buffs.iter().map(|buff_desc| buff_desc.kind).collect(),
620 energy: *energy_cost,
621 combo: *combo_cost,
622 combo_scales: combo_scaling.is_some(),
623 },
624 DiveMelee {
625 energy_cost,
626 melee_constructor,
627 ..
628 } => Self::DiveMelee {
629 energy: *energy_cost,
630 range: melee_constructor.range,
631 angle: melee_constructor.angle,
632 },
633 DashMelee {
634 energy_cost,
635 energy_drain,
636 forward_speed,
637 melee_constructor,
638 charge_duration,
639 ..
640 } => Self::DashMelee {
641 initial_energy: *energy_cost,
642 energy_drain: *energy_drain,
643 range: melee_constructor.range,
644 angle: melee_constructor.angle,
645 charge_dur: *charge_duration,
646 speed: *forward_speed,
647 },
648 RapidMelee {
649 energy_cost,
650 max_strikes,
651 minimum_combo,
652 melee_constructor,
653 ..
654 } => Self::RapidMelee {
655 energy_per_strike: *energy_cost,
656 range: melee_constructor.range,
657 angle: melee_constructor.angle,
658 strikes: max_strikes.unwrap_or(100),
659 combo: *minimum_combo,
660 },
661 ChargedMelee {
662 energy_cost,
663 energy_drain,
664 charge_duration,
665 melee_constructor,
666 ..
667 } => Self::ChargedMelee {
668 initial_energy: *energy_cost,
669 energy_drain: *energy_drain,
670 charge_dur: *charge_duration,
671 range: melee_constructor.range,
672 angle: melee_constructor.angle,
673 },
674 RiposteMelee {
675 energy_cost,
676 melee_constructor,
677 ..
678 } => Self::RiposteMelee {
679 energy: *energy_cost,
680 range: melee_constructor.range,
681 angle: melee_constructor.angle,
682 },
683 BasicBlock {
684 max_angle,
685 energy_cost,
686 blocked_attacks,
687 ..
688 } => Self::BasicBlock {
689 energy: *energy_cost,
690 angle: *max_angle,
691 blocked_attacks: *blocked_attacks,
692 },
693 BasicRanged {
694 energy_cost,
695 projectile_speed,
696 projectile_spread,
697 num_projectiles,
698 ..
699 } => Self::BasicRanged {
700 energy: *energy_cost,
701 projectile_speed: *projectile_speed,
702 projectile_spread: *projectile_spread,
703 num_projectiles: *num_projectiles,
704 },
705 BasicMelee {
706 energy_cost,
707 melee_constructor,
708 ..
709 } => Self::BasicMelee {
710 energy: *energy_cost,
711 range: melee_constructor.range,
712 angle: melee_constructor.angle,
713 },
714 LeapMelee {
715 energy_cost,
716 movement_duration,
717 melee_constructor,
718 forward_leap_strength,
719 vertical_leap_strength,
720 ..
721 } => Self::LeapMelee {
722 energy: *energy_cost,
723 leap_dur: *movement_duration,
724 range: melee_constructor.range,
725 angle: melee_constructor.angle,
726 forward_leap: *forward_leap_strength,
727 vertical_leap: *vertical_leap_strength,
728 },
729 BasicBeam {
730 range,
731 max_angle,
732 ori_rate,
733 energy_drain,
734 ..
735 } => Self::BasicBeam {
736 range: *range,
737 angle: *max_angle,
738 ori_rate: *ori_rate,
739 energy_drain: *energy_drain,
740 },
741 Shockwave {
742 energy_cost,
743 shockwave_angle,
744 shockwave_speed,
745 shockwave_duration,
746 minimum_combo,
747 ..
748 } => Self::Shockwave {
749 energy: *energy_cost,
750 angle: *shockwave_angle,
751 range: *shockwave_speed * *shockwave_duration,
752 combo: minimum_combo.unwrap_or(0),
753 },
754 BasicSummon { .. } => Self::BasicSummon,
755 BasicAura { energy_cost, .. } => Self::BasicAura {
756 energy: *energy_cost,
757 },
758 StaticAura { energy_cost, .. } => Self::StaticAura {
759 energy: *energy_cost,
760 },
761 RegrowHead { energy_cost, .. } => Self::RegrowHead {
762 energy: *energy_cost,
763 },
764 _ => {
765 dev_panic!(
766 "Agent tried to use ability with a character state they haven't learned to \
767 understand"
768 );
769 return None;
770 },
771 };
772 Some(inner)
773 }
774
775 pub fn could_use(
776 &self,
777 attack_data: &AttackData,
778 agent_data: &AgentData,
779 tgt_data: &TargetData,
780 read_data: &ReadData,
781 ability_preferences: AbilityPreferences,
782 ) -> bool {
783 let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
784 let (range_inc, min_mult) = forced_movement.map_or((0.0, 0.0), |fm| match fm {
785 ForcedMovement::Forward(speed) => (speed * 15.0, 1.0),
786 ForcedMovement::Reverse(speed) => (-speed, 1.0),
787 ForcedMovement::Leap {
788 vertical, forward, ..
789 } => (
790 {
791 let dur = vertical * 2.0 / GRAVITY;
792 forward * dur * 0.75
795 },
796 0.0,
797 ),
798 _ => (0.0, 0.0),
799 });
800 let body_rad = agent_data.body.map_or(0.0, |b| b.max_radius());
801 attack_data.dist_sqrd < (range + range_inc + body_rad).powi(2)
802 && attack_data.angle < angle
803 && attack_data.dist_sqrd > (range_inc * min_mult).powi(2)
804 };
805 let energy_check = |energy: f32| {
806 agent_data.energy.current() >= energy
807 && (energy < f32::EPSILON
808 || agent_data.energy.current() >= ability_preferences.desired_energy)
809 };
810 let combo_check = |combo, scales| {
811 let additional_combo = if scales {
812 ability_preferences.combo_scaling_buildup
813 } else {
814 0
815 };
816 agent_data
817 .combo
818 .is_some_and(|c| c.counter() >= combo + additional_combo)
819 };
820 let attack_kind_check = |attacks: AttackFilters| {
821 tgt_data
822 .char_state
823 .map(|cs| cs.attack_kind())
824 .unwrap_or_default()
825 .iter()
826 .any(|attack_source| attacks.applies(*attack_source))
827 };
828 let ranged_check = |proj_speed| {
829 let max_horiz_dist: f32 = {
830 let flight_time = proj_speed * 2_f32.sqrt() / GRAVITY;
831 proj_speed * 2_f32.sqrt() / 2.0 * flight_time
832 };
833 attack_data.dist_sqrd < max_horiz_dist.powi(2)
834 && entities_have_line_of_sight(
835 agent_data.pos,
836 agent_data.body,
837 agent_data.scale,
838 tgt_data.pos,
839 tgt_data.body,
840 tgt_data.scale,
841 read_data,
842 )
843 };
844 let beam_check = |range: f32, angle, ori_rate: f32| {
845 let angle_inc = ori_rate.to_degrees();
846 attack_data.dist_sqrd < range.powi(2)
847 && attack_data.angle < angle + angle_inc
848 && entities_have_line_of_sight(
849 agent_data.pos,
850 agent_data.body,
851 agent_data.scale,
852 tgt_data.pos,
853 tgt_data.body,
854 tgt_data.scale,
855 read_data,
856 )
857 };
858 use AbilityData::*;
859 match self {
860 ComboMelee {
861 range,
862 angle,
863 energy_per_strike,
864 forced_movement,
865 } => melee_check(*range, *angle, *forced_movement) && energy_check(*energy_per_strike),
866 FinisherMelee {
867 range,
868 angle,
869 energy,
870 combo,
871 combo_scales,
872 } => {
873 melee_check(*range, *angle, None)
874 && energy_check(*energy)
875 && combo_check(*combo, *combo_scales)
876 },
877 SelfBuff {
878 buff_kinds,
879 energy,
880 combo,
881 combo_scales,
882 } => {
883 energy_check(*energy)
884 && combo_check(*combo, *combo_scales)
885 && agent_data
886 .buffs
887 .is_some_and(|buffs| !buffs.contains_any(buff_kinds))
888 },
889 DiveMelee {
890 range,
891 angle,
892 energy,
893 } => melee_check(*range, *angle, None) && energy_check(*energy),
894 DashMelee {
895 range,
896 angle,
897 initial_energy,
898 energy_drain,
899 speed,
900 charge_dur,
901 } => {
902 const BASE_SPEED: f32 = 3.0;
905 const ORI_RATE: f32 = 30.0;
906 let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
907 .clamp(0.0, *charge_dur);
908 let charge_dist = charge_dur * speed * BASE_SPEED;
909 let attack_dist = charge_dist + range;
910 let ori_gap = ORI_RATE * charge_dur;
911 melee_check(attack_dist, angle + ori_gap, None)
913 && energy_check(*initial_energy)
914 && attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
915 },
916 RapidMelee {
917 range,
918 angle,
919 energy_per_strike,
920 strikes,
921 combo,
922 } => {
923 melee_check(*range, *angle, None)
924 && energy_check(*energy_per_strike * *strikes as f32)
925 && combo_check(*combo, false)
926 },
927 ChargedMelee {
928 range,
929 angle,
930 initial_energy,
931 energy_drain,
932 charge_dur,
933 } => {
934 melee_check(*range, *angle, None)
935 && energy_check(*initial_energy + *energy_drain * *charge_dur)
936 },
937 RiposteMelee {
938 energy,
939 range,
940 angle,
941 } => {
942 melee_check(*range, *angle, None)
943 && energy_check(*energy)
944 && tgt_data.char_state.is_some_and(|cs| {
945 cs.is_melee_attack()
946 && matches!(
947 cs.stage_section(),
948 Some(
949 StageSection::Buildup
950 | StageSection::Charge
951 | StageSection::Movement
952 )
953 )
954 })
955 },
956 BasicBlock {
957 energy,
958 angle,
959 blocked_attacks,
960 } => {
961 melee_check(25.0, *angle, None)
962 && energy_check(*energy)
963 && attack_kind_check(*blocked_attacks)
964 && tgt_data
965 .char_state
966 .and_then(|cs| cs.stage_section())
967 .is_some_and(|ss| !matches!(ss, StageSection::Recover))
968 },
969 BasicRanged {
970 energy,
971 projectile_speed,
972 projectile_spread: _,
973 num_projectiles: _,
974 } => ranged_check(*projectile_speed) && energy_check(*energy),
975 BasicMelee {
976 energy,
977 range,
978 angle,
979 } => melee_check(*range, *angle, None) && energy_check(*energy),
980 LeapMelee {
981 energy,
982 range,
983 angle,
984 leap_dur,
985 forward_leap,
986 vertical_leap,
987 } => {
988 use common::states::utils::MovementDirection;
989 let forced_move = Some(ForcedMovement::Leap {
990 vertical: *vertical_leap * *leap_dur * 2.0,
991 forward: *forward_leap,
992 progress: 0.0,
993 direction: MovementDirection::Look,
994 });
995 melee_check(*range, *angle, forced_move) && energy_check(*energy)
996 },
997 BasicBeam {
998 energy_drain,
999 range,
1000 angle,
1001 ori_rate,
1002 } => beam_check(*range, *angle, *ori_rate) && energy_check(*energy_drain * 3.0),
1003 Shockwave {
1004 energy,
1005 range,
1006 angle,
1007 combo,
1008 } => {
1009 melee_check(*range, *angle, None)
1010 && energy_check(*energy)
1011 && combo_check(*combo, false)
1012 },
1013 BasicSummon => true,
1014 BasicAura { energy } => energy_check(*energy),
1015 StaticAura { energy } => energy_check(*energy),
1016 RegrowHead { energy } => energy_check(*energy),
1017 }
1018 }
1019}