veloren_common/comp/
projectile.rs

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, // Knock/dislodge/change objects on hit
33    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    // TODO: use SmallVec for these effects
44    pub hit_solid: Vec<Effect>,
45    pub hit_entity: Vec<Effect>,
46    pub timeout: Vec<Effect>,
47    /// Time left until the projectile will despawn
48    pub time_left: Duration,
49    /// Max duration of projectile (should be equal to time_left when projectile
50    /// is created)
51    pub init_time: Secs,
52    pub owner: Option<Uid>,
53    /// Whether projectile collides with entities in the same group as its
54    /// owner
55    pub ignore_group: bool,
56    /// Whether the projectile is sticky
57    pub is_sticky: bool,
58    /// Whether the projectile should use a point collider
59    pub is_point: bool,
60    /// Whether the projectile should home towards a target entity and at what
61    /// rate (in deg/s)
62    pub homing: Option<(Uid, f32)>,
63    /// Whether the projectile should hit and apply its effects to multiple
64    /// entities
65    pub pierce_entities: bool,
66    /// Entities that the projectile has hit (only relevant for projectiles that
67    /// can pierce entities)
68    pub hit_entities: Vec<Uid>,
69    /// Whether to limit the number of projectiles from from an ability can
70    /// damage the target in a short duration
71    pub limit_per_ability: bool,
72    /// Whether to override the collider used by the projectile for detecting
73    /// hit entities
74    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    /// In degrees per second
102    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    /// If this is used, it will only apply to projectiles created after the
118    /// split, and will also override this option on the parent projectile
119    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    // I want a better name for 'Pointed' and 'Blunt'
156    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    // Remove this function after skill tree overhaul completed for bow and fire
808    // staff
809    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
852/// Projectile motion: Returns the direction to aim for the projectile to reach
853/// target position. Does not take any forces but gravity into account.
854pub 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}