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