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 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 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 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 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 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}