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