1use crate::{
2 combat::{
3 Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage,
4 DamageKind, GroupTarget, Knockback, KnockbackDir,
5 },
6 comp::{
7 ArcProperties, CapsulePrism,
8 ability::Dodgeable,
9 item::{Reagent, tool},
10 pool::PoolProperties,
11 },
12 consts::GRAVITY,
13 explosion::{ColorPreset, Explosion, RadiusEffect},
14 resources::{Secs, Time},
15 states::utils::AbilityInfo,
16 uid::Uid,
17 util::Dir,
18};
19use common_base::dev_panic;
20use serde::{Deserialize, Serialize};
21use specs::Component;
22use std::time::Duration;
23use vek::*;
24
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub enum Effect {
27 Attack(Attack),
28 Explode(Explosion),
29 Vanish,
30 Stick,
31 Possess,
32 Bonk, Firework(Reagent),
34 SurpriseEgg,
35 TrainingDummy,
36 Arc(ArcProperties),
37 Split(SplitOptions),
38 Pool(PoolProperties),
39}
40
41#[derive(Clone, Debug)]
42pub struct Projectile {
43 pub hit_solid: Vec<Effect>,
45 pub hit_entity: Vec<Effect>,
46 pub timeout: Vec<Effect>,
47 pub time_left: Duration,
49 pub init_time: Secs,
52 pub owner: Option<Uid>,
53 pub ignore_group: bool,
56 pub is_sticky: bool,
58 pub is_point: bool,
60 pub homing: Option<(Uid, f32)>,
63 pub pierce_entities: bool,
66 pub hit_entities: Vec<Uid>,
69 pub limit_per_ability: bool,
72 pub override_collider: Option<CapsulePrism>,
75}
76
77impl Component for Projectile {
78 type Storage = specs::DenseVecStorage<Self>;
79}
80
81impl Projectile {
82 pub fn is_blockable(&self) -> bool {
83 !self.hit_entity.iter().any(|effect| {
84 matches!(
85 effect,
86 Effect::Attack(Attack {
87 blockable: false,
88 ..
89 })
90 )
91 })
92 }
93}
94
95#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
96#[serde(deny_unknown_fields)]
97pub struct ProjectileConstructor {
98 pub kind: ProjectileConstructorKind,
99 pub attack: Option<ProjectileAttack>,
100 pub scaled: Option<Scaled>,
101 pub homing_rate: Option<f32>,
103 pub split: Option<SplitOptions>,
104 pub lifetime_override: Option<Secs>,
105 #[serde(default)]
106 pub limit_per_ability: bool,
107 pub override_collider: Option<CapsulePrism>,
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
111#[serde(deny_unknown_fields)]
112pub struct SplitOptions {
113 pub split_on_terrain: bool,
114 pub amount: u32,
115 pub spread: f32,
116 pub new_lifetime: Secs,
117 pub override_collider: Option<CapsulePrism>,
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
123#[serde(deny_unknown_fields)]
124pub struct Scaled {
125 damage: f32,
126 poise: Option<f32>,
127 knockback: Option<f32>,
128 energy: Option<f32>,
129 damage_effect: Option<f32>,
130}
131
132fn default_true() -> bool { true }
133
134#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
135#[serde(deny_unknown_fields)]
136pub struct ProjectileAttack {
137 pub damage: f32,
138 pub poise: Option<f32>,
139 pub knockback: Option<f32>,
140 pub energy: Option<f32>,
141 pub buff: Option<CombatBuff>,
142 #[serde(default)]
143 pub friendly_fire: bool,
144 #[serde(default = "default_true")]
145 pub blockable: bool,
146 pub damage_effect: Option<CombatEffect>,
147 pub attack_effect: Option<(CombatEffect, CombatRequirement)>,
148 #[serde(default)]
149 pub without_combo: bool,
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub enum ProjectileConstructorKind {
155 Pointed,
157 Blunt,
158 Penetrating,
159 Explosive {
160 radius: f32,
161 min_falloff: f32,
162 reagent: Option<Reagent>,
163 terrain: Option<(f32, ColorPreset)>,
164 },
165 Arcing {
166 distance: f32,
167 arcs: u32,
168 min_delay: Secs,
169 max_delay: Secs,
170 #[serde(default)]
171 targets_owner: bool,
172 },
173 Possess,
174 Hazard {
175 is_sticky: bool,
176 duration: Secs,
177 },
178 ExplosiveHazard {
179 radius: f32,
180 min_falloff: f32,
181 reagent: Option<Reagent>,
182 terrain: Option<(f32, ColorPreset)>,
183 is_sticky: bool,
184 duration: Secs,
185 },
186 Firework(Reagent),
187 SurpriseEgg,
188 TrainingDummy,
189 Pool {
190 radius: f32,
191 tick_dur: Secs,
192 duration: Secs,
193 #[serde(default)]
194 dodgeable: Dodgeable,
195 },
196}
197
198impl ProjectileConstructor {
199 pub fn create_projectile(
200 self,
201 owner: Option<Uid>,
202 precision_mult: f32,
203 ability_info: Option<AbilityInfo>,
204 ) -> Projectile {
205 if self.scaled.is_some() {
206 dev_panic!(
207 "Attempted to create a projectile that had a provided scaled value without \
208 scaling the projectile."
209 )
210 }
211
212 let instance = rand::random();
213 let attack = self.attack.map(|a| {
214 let target = if a.friendly_fire {
215 Some(GroupTarget::All)
216 } else {
217 Some(GroupTarget::OutOfGroup)
218 };
219
220 let poise = a.poise.map(|poise| {
221 AttackEffect::new(target, CombatEffect::Poise(poise))
222 .with_requirement(CombatRequirement::AnyDamage)
223 });
224
225 let knockback = a.knockback.map(|kb| {
226 AttackEffect::new(
227 target,
228 CombatEffect::Knockback(Knockback {
229 strength: kb,
230 direction: KnockbackDir::Away,
231 }),
232 )
233 .with_requirement(CombatRequirement::AnyDamage)
234 });
235
236 let energy = a.energy.map(|energy| {
237 AttackEffect::new(None, CombatEffect::EnergyReward(energy))
238 .with_requirement(CombatRequirement::AnyDamage)
239 });
240
241 let buff = a.buff.map(CombatEffect::Buff);
242
243 let damage_kind = match self.kind {
244 ProjectileConstructorKind::Pointed
245 | ProjectileConstructorKind::Hazard { .. }
246 | ProjectileConstructorKind::Penetrating => DamageKind::Piercing,
247 ProjectileConstructorKind::Blunt => DamageKind::Crushing,
248 ProjectileConstructorKind::Explosive { .. }
249 | ProjectileConstructorKind::ExplosiveHazard { .. }
250 | ProjectileConstructorKind::Firework(_) => DamageKind::Energy,
251 ProjectileConstructorKind::Possess
252 | ProjectileConstructorKind::SurpriseEgg
253 | ProjectileConstructorKind::TrainingDummy => {
254 dev_panic!("This should be unreachable");
255 DamageKind::Piercing
256 },
257 ProjectileConstructorKind::Arcing { .. } => DamageKind::Energy,
258 ProjectileConstructorKind::Pool { .. } => DamageKind::Energy,
259 };
260
261 let mut damage = AttackDamage::new(
262 Damage {
263 kind: damage_kind,
264 value: a.damage,
265 },
266 target,
267 instance,
268 );
269
270 if let Some(buff) = buff {
271 damage = damage.with_effect(buff);
272 }
273
274 if let Some(damage_effect) = a.damage_effect {
275 damage = damage.with_effect(damage_effect);
276 }
277
278 let mut attack = Attack::new(ability_info)
279 .with_damage(damage)
280 .with_precision(
281 precision_mult
282 * ability_info
283 .and_then(|ai| ai.ability_meta.precision_power_mult)
284 .unwrap_or(1.0),
285 )
286 .with_blockable(a.blockable);
287
288 if !a.without_combo {
289 attack = attack.with_combo_increment();
290 }
291
292 if let Some(poise) = poise {
293 attack = attack.with_effect(poise);
294 }
295
296 if let Some(knockback) = knockback {
297 attack = attack.with_effect(knockback);
298 }
299
300 if let Some(energy) = energy {
301 attack = attack.with_effect(energy);
302 }
303
304 if let Some((effect, requirement)) = a.attack_effect {
305 let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
306 .with_requirement(requirement);
307 attack = attack.with_effect(effect);
308 }
309
310 attack
311 });
312
313 let homing = ability_info
314 .and_then(|a| a.input_attr)
315 .and_then(|i| i.target_entity)
316 .zip(self.homing_rate);
317
318 let mut timeout = Vec::new();
319 let mut hit_solid = Vec::new();
320
321 if let Some(split) = self.split {
322 timeout.push(Effect::Split(split));
323 if split.split_on_terrain {
324 hit_solid.push(Effect::Split(split));
325 }
326 }
327
328 match self.kind {
329 ProjectileConstructorKind::Pointed | ProjectileConstructorKind::Blunt => {
330 hit_solid.push(Effect::Stick);
331 hit_solid.push(Effect::Bonk);
332
333 let mut hit_entity = vec![Effect::Vanish];
334
335 if let Some(attack) = attack {
336 hit_entity.push(Effect::Attack(attack));
337 }
338
339 let lifetime = self.lifetime_override.unwrap_or(Secs(15.0));
340
341 Projectile {
342 hit_solid,
343 hit_entity,
344 timeout,
345 time_left: Duration::from_secs_f64(lifetime.0),
346 init_time: lifetime,
347 owner,
348 ignore_group: true,
349 is_sticky: true,
350 is_point: true,
351 homing,
352 pierce_entities: false,
353 hit_entities: Vec::new(),
354 limit_per_ability: self.limit_per_ability,
355 override_collider: self.override_collider,
356 }
357 },
358 ProjectileConstructorKind::Penetrating => {
359 hit_solid.push(Effect::Stick);
360 hit_solid.push(Effect::Bonk);
361
362 let mut hit_entity = Vec::new();
363
364 if let Some(attack) = attack {
365 hit_entity.push(Effect::Attack(attack));
366 }
367
368 let lifetime = self.lifetime_override.unwrap_or(Secs(15.0));
369
370 Projectile {
371 hit_solid,
372 hit_entity,
373 timeout,
374 time_left: Duration::from_secs_f64(lifetime.0),
375 init_time: lifetime,
376 owner,
377 ignore_group: true,
378 is_sticky: true,
379 is_point: true,
380 homing,
381 pierce_entities: true,
382 hit_entities: Vec::new(),
383 limit_per_ability: self.limit_per_ability,
384 override_collider: self.override_collider,
385 }
386 },
387 ProjectileConstructorKind::Hazard {
388 is_sticky,
389 duration,
390 } => {
391 hit_solid.push(Effect::Stick);
392 hit_solid.push(Effect::Bonk);
393
394 let mut hit_entity = vec![Effect::Vanish];
395
396 if let Some(attack) = attack {
397 hit_entity.push(Effect::Attack(attack));
398 }
399
400 let lifetime = self.lifetime_override.unwrap_or(duration);
401
402 Projectile {
403 hit_solid,
404 hit_entity,
405 timeout,
406 time_left: Duration::from_secs_f64(lifetime.0),
407 init_time: lifetime,
408 owner,
409 ignore_group: true,
410 is_sticky,
411 is_point: false,
412 homing,
413 pierce_entities: false,
414 hit_entities: Vec::new(),
415 limit_per_ability: self.limit_per_ability,
416 override_collider: self.override_collider,
417 }
418 },
419 ProjectileConstructorKind::Explosive {
420 radius,
421 min_falloff,
422 reagent,
423 terrain,
424 } => {
425 let terrain =
426 terrain.map(|(pow, col)| RadiusEffect::TerrainDestruction(pow, col.to_rgb()));
427
428 let mut effects = Vec::new();
429
430 if let Some(attack) = attack {
431 effects.push(RadiusEffect::Attack {
432 attack,
433 dodgeable: Dodgeable::Roll,
434 });
435 }
436
437 if let Some(terrain) = terrain {
438 effects.push(terrain);
439 }
440
441 let explosion = Explosion {
442 effects,
443 radius,
444 reagent,
445 min_falloff,
446 };
447
448 hit_solid.push(Effect::Explode(explosion.clone()));
449 hit_solid.push(Effect::Vanish);
450
451 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
452
453 Projectile {
454 hit_solid,
455 hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
456 timeout,
457 time_left: Duration::from_secs_f64(lifetime.0),
458 init_time: lifetime,
459 owner,
460 ignore_group: true,
461 is_sticky: true,
462 is_point: true,
463 homing,
464 pierce_entities: false,
465 hit_entities: Vec::new(),
466 limit_per_ability: self.limit_per_ability,
467 override_collider: self.override_collider,
468 }
469 },
470 ProjectileConstructorKind::Arcing {
471 distance,
472 arcs,
473 min_delay,
474 max_delay,
475 targets_owner,
476 } => {
477 let mut hit_entity = vec![Effect::Vanish];
478
479 if let Some(attack) = attack {
480 hit_entity.push(Effect::Attack(attack.clone()));
481
482 let arc = ArcProperties {
483 attack,
484 distance,
485 arcs,
486 min_delay,
487 max_delay,
488 targets_owner,
489 };
490
491 hit_entity.push(Effect::Arc(arc));
492 }
493
494 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
495
496 Projectile {
497 hit_solid,
498 hit_entity,
499 timeout,
500 time_left: Duration::from_secs_f64(lifetime.0),
501 init_time: lifetime,
502 owner,
503 ignore_group: true,
504 is_sticky: true,
505 is_point: true,
506 homing,
507 pierce_entities: false,
508 hit_entities: Vec::new(),
509 limit_per_ability: self.limit_per_ability,
510 override_collider: self.override_collider,
511 }
512 },
513 ProjectileConstructorKind::ExplosiveHazard {
514 radius,
515 min_falloff,
516 reagent,
517 terrain,
518 is_sticky,
519 duration,
520 } => {
521 let terrain =
522 terrain.map(|(pow, col)| RadiusEffect::TerrainDestruction(pow, col.to_rgb()));
523
524 let mut effects = Vec::new();
525
526 if let Some(attack) = attack {
527 effects.push(RadiusEffect::Attack {
528 attack,
529 dodgeable: Dodgeable::Roll,
530 });
531 }
532
533 if let Some(terrain) = terrain {
534 effects.push(terrain);
535 }
536
537 let explosion = Explosion {
538 effects,
539 radius,
540 reagent,
541 min_falloff,
542 };
543
544 let lifetime = self.lifetime_override.unwrap_or(duration);
545
546 Projectile {
547 hit_solid,
548 hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
549 timeout,
550 time_left: Duration::from_secs_f64(lifetime.0),
551 init_time: lifetime,
552 owner,
553 ignore_group: true,
554 is_sticky,
555 is_point: false,
556 homing,
557 pierce_entities: false,
558 hit_entities: Vec::new(),
559 limit_per_ability: self.limit_per_ability,
560 override_collider: self.override_collider,
561 }
562 },
563 ProjectileConstructorKind::Possess => {
564 hit_solid.push(Effect::Stick);
565
566 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
567
568 Projectile {
569 hit_solid,
570 hit_entity: vec![Effect::Stick, Effect::Possess],
571 timeout,
572 time_left: Duration::from_secs_f64(lifetime.0),
573 init_time: lifetime,
574 owner,
575 ignore_group: false,
576 is_sticky: true,
577 is_point: true,
578 homing,
579 pierce_entities: false,
580 hit_entities: Vec::new(),
581 limit_per_ability: self.limit_per_ability,
582 override_collider: self.override_collider,
583 }
584 },
585 ProjectileConstructorKind::Firework(reagent) => {
586 timeout.push(Effect::Firework(reagent));
587
588 let lifetime = self.lifetime_override.unwrap_or(Secs(3.0));
589
590 Projectile {
591 hit_solid,
592 hit_entity: Vec::new(),
593 timeout,
594 time_left: Duration::from_secs_f64(lifetime.0),
595 init_time: lifetime,
596 owner,
597 ignore_group: true,
598 is_sticky: true,
599 is_point: true,
600 homing,
601 pierce_entities: false,
602 hit_entities: Vec::new(),
603 limit_per_ability: self.limit_per_ability,
604 override_collider: self.override_collider,
605 }
606 },
607 ProjectileConstructorKind::SurpriseEgg => {
608 hit_solid.push(Effect::SurpriseEgg);
609 hit_solid.push(Effect::Vanish);
610
611 let lifetime = self.lifetime_override.unwrap_or(Secs(15.0));
612
613 Projectile {
614 hit_solid,
615 hit_entity: vec![Effect::SurpriseEgg, Effect::Vanish],
616 timeout,
617 time_left: Duration::from_secs_f64(lifetime.0),
618 init_time: lifetime,
619 owner,
620 ignore_group: true,
621 is_sticky: true,
622 is_point: true,
623 homing,
624 pierce_entities: false,
625 hit_entities: Vec::new(),
626 limit_per_ability: self.limit_per_ability,
627 override_collider: self.override_collider,
628 }
629 },
630 ProjectileConstructorKind::Pool {
631 radius,
632 tick_dur,
633 duration,
634 dodgeable,
635 } => {
636 let pool_props = attack.map(|atk| PoolProperties {
637 attack: atk,
638 radius,
639 tick_dur,
640 duration,
641 dodgeable,
642 });
643
644 if let Some(props) = pool_props {
645 hit_solid.push(Effect::Pool(props.clone()));
646 hit_solid.push(Effect::Vanish);
647
648 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
649
650 return Projectile {
651 hit_solid,
652 hit_entity: vec![Effect::Pool(props), Effect::Vanish],
653 timeout,
654 time_left: Duration::from_secs_f64(lifetime.0),
655 init_time: lifetime,
656 owner,
657 ignore_group: true,
658 is_sticky: true,
659 is_point: true,
660 homing,
661 pierce_entities: false,
662 hit_entities: Vec::new(),
663 limit_per_ability: self.limit_per_ability,
664 override_collider: self.override_collider,
665 };
666 }
667
668 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
669
670 Projectile {
671 hit_solid,
672 hit_entity: vec![Effect::Vanish],
673 timeout,
674 time_left: Duration::from_secs_f64(lifetime.0),
675 init_time: lifetime,
676 owner,
677 ignore_group: true,
678 is_sticky: true,
679 is_point: true,
680 homing,
681 pierce_entities: false,
682 hit_entities: Vec::new(),
683 limit_per_ability: self.limit_per_ability,
684 override_collider: self.override_collider,
685 }
686 },
687 ProjectileConstructorKind::TrainingDummy => {
688 hit_solid.push(Effect::TrainingDummy);
689 hit_solid.push(Effect::Vanish);
690
691 timeout.push(Effect::TrainingDummy);
692
693 let lifetime = self.lifetime_override.unwrap_or(Secs(15.0));
694
695 Projectile {
696 hit_solid,
697 hit_entity: vec![Effect::TrainingDummy, Effect::Vanish],
698 timeout,
699 time_left: Duration::from_secs_f64(lifetime.0),
700 init_time: lifetime,
701 owner,
702 ignore_group: true,
703 is_sticky: true,
704 is_point: false,
705 homing,
706 pierce_entities: false,
707 hit_entities: Vec::new(),
708 limit_per_ability: self.limit_per_ability,
709 override_collider: self.override_collider,
710 }
711 },
712 }
713 }
714
715 pub fn handle_scaling(mut self, scaling: f32) -> Self {
716 let scale_values = |a, b| a + b * scaling;
717
718 if let Some(scaled) = self.scaled {
719 if let Some(ref mut attack) = self.attack {
720 attack.damage = scale_values(attack.damage, scaled.damage);
721 if let Some(s_poise) = scaled.poise {
722 attack.poise = Some(scale_values(attack.poise.unwrap_or(0.0), s_poise));
723 }
724 if let Some(s_kb) = scaled.knockback {
725 attack.knockback = Some(scale_values(attack.knockback.unwrap_or(0.0), s_kb));
726 }
727 if let Some(s_energy) = scaled.energy {
728 attack.energy = Some(scale_values(attack.energy.unwrap_or(0.0), s_energy));
729 }
730 if let Some(s_dmg_eff) = scaled.damage_effect {
731 if attack.damage_effect.is_some() {
732 attack.damage_effect =
733 attack.damage_effect.as_ref().cloned().map(|dmg_eff| {
734 dmg_eff.apply_multiplier(scale_values(1.0, s_dmg_eff))
735 });
736 } else {
737 dev_panic!(
738 "Attempted to scale damage effect on a projectile that doesn't have a \
739 damage effect."
740 )
741 }
742 }
743 } else {
744 dev_panic!("Attempted to scale on a projectile that has no attack to scale.")
745 }
746 } else {
747 dev_panic!("Attempted to scale on a projectile that has no provided scaling value.")
748 }
749
750 self.scaled = None;
751
752 self
753 }
754
755 pub fn adjusted_by_stats(mut self, stats: tool::Stats) -> Self {
756 self.attack = self.attack.map(|mut a| {
757 a.damage *= stats.power;
758 a.poise = a.poise.map(|poise| poise * stats.effect_power);
759 a.knockback = a.knockback.map(|kb| kb * stats.effect_power);
760 a.buff = a.buff.map(|mut b| {
761 b.strength *= stats.buff_strength;
762 b
763 });
764 a.damage_effect = a.damage_effect.map(|de| de.adjusted_by_stats(stats));
765 a.attack_effect = a
766 .attack_effect
767 .map(|(e, r)| (e.adjusted_by_stats(stats), r));
768 a
769 });
770
771 self.scaled = self.scaled.map(|mut s| {
772 s.damage *= stats.power;
773 s.poise = s.poise.map(|poise| poise * stats.effect_power);
774 s.knockback = s.knockback.map(|kb| kb * stats.effect_power);
775 s
776 });
777
778 match self.kind {
779 ProjectileConstructorKind::Pointed
780 | ProjectileConstructorKind::Blunt
781 | ProjectileConstructorKind::Penetrating
782 | ProjectileConstructorKind::Possess
783 | ProjectileConstructorKind::Hazard { .. }
784 | ProjectileConstructorKind::Firework(_)
785 | ProjectileConstructorKind::SurpriseEgg
786 | ProjectileConstructorKind::TrainingDummy => {},
787 ProjectileConstructorKind::Explosive { ref mut radius, .. }
788 | ProjectileConstructorKind::ExplosiveHazard { ref mut radius, .. }
789 | ProjectileConstructorKind::Pool { ref mut radius, .. } => {
790 *radius *= stats.range;
791 },
792 ProjectileConstructorKind::Arcing {
793 ref mut distance, ..
794 } => {
795 *distance *= stats.range;
796 },
797 }
798
799 self.split = self.split.map(|mut s| {
800 s.amount = (s.amount as f32 * stats.effect_power).ceil().max(0.0) as u32;
801 s
802 });
803
804 self
805 }
806
807 pub fn legacy_modified_by_skills(
810 mut self,
811 power: f32,
812 regen: f32,
813 range: f32,
814 kb: f32,
815 ) -> Self {
816 self.attack = self.attack.map(|mut a| {
817 a.damage *= power;
818 a.knockback = a.knockback.map(|k| k * kb);
819 a.energy = a.energy.map(|e| e * regen);
820 a
821 });
822 self.scaled = self.scaled.map(|mut s| {
823 s.damage *= power;
824 s.knockback = s.knockback.map(|k| k * kb);
825 s.energy = s.energy.map(|e| e * regen);
826 s
827 });
828 if let ProjectileConstructorKind::Explosive { ref mut radius, .. } = self.kind {
829 *radius *= range;
830 }
831 self
832 }
833
834 pub fn is_explosive(&self) -> bool {
835 match self.kind {
836 ProjectileConstructorKind::Pointed
837 | ProjectileConstructorKind::Blunt
838 | ProjectileConstructorKind::Penetrating
839 | ProjectileConstructorKind::Possess
840 | ProjectileConstructorKind::Hazard { .. }
841 | ProjectileConstructorKind::Firework(_)
842 | ProjectileConstructorKind::SurpriseEgg
843 | ProjectileConstructorKind::TrainingDummy
844 | ProjectileConstructorKind::Arcing { .. }
845 | ProjectileConstructorKind::Pool { .. } => false,
846 ProjectileConstructorKind::Explosive { .. }
847 | ProjectileConstructorKind::ExplosiveHazard { .. } => true,
848 }
849 }
850}
851
852pub fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>, high_arc: bool) -> Option<Dir> {
855 let mut to_tgt = tgt - pos;
856 let dist_sqrd = to_tgt.xy().magnitude_squared();
857 let u_sqrd = speed.powi(2);
858 if high_arc {
859 to_tgt.z = (u_sqrd
860 + (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd))
861 .sqrt()
862 .max(0.0))
863 / GRAVITY;
864 } else {
865 to_tgt.z = (u_sqrd
866 - (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd))
867 .sqrt()
868 .max(0.0))
869 / GRAVITY;
870 }
871 Dir::from_unnormalized(to_tgt)
872}
873
874#[derive(Clone, Debug, Default)]
875pub struct ProjectileHitEntities {
876 pub hit_entities: Vec<(Uid, Time)>,
877}
878
879impl Component for ProjectileHitEntities {
880 type Storage = specs::DenseVecStorage<Self>;
881}