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