Skip to main content

veloren_common/comp/
melee.rs

1use crate::{
2    combat::{
3        Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage, DamageKind,
4        FlankMults, GroupTarget, Knockback, KnockbackDir,
5    },
6    comp::tool::{Stats, ToolKind},
7    states::utils::AbilityInfo,
8    uid::Uid,
9};
10use common_base::dev_panic;
11use serde::{Deserialize, Serialize};
12use specs::{Component, VecStorage};
13use vek::*;
14
15use super::ability::Dodgeable;
16
17#[derive(Debug, Serialize, Deserialize)]
18pub struct Melee {
19    pub attack: Attack,
20    pub range: f32,
21    pub max_angle: f32,
22    pub applied: bool,
23    pub multi_target: Option<MultiTarget>,
24    pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
25    pub simultaneous_hits: u32,
26    pub precision_flank_multipliers: FlankMults,
27    pub precision_flank_invert: bool,
28    pub dodgeable: Dodgeable,
29    pub sustained: bool,
30    pub hit_entities: Vec<Uid>,
31}
32
33#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
34pub enum MultiTarget {
35    Normal,
36    /// Applies scaling to the power of the attack based on how many consecutive
37    /// enemies have been hit. First enemy hit will be at a power of 1.0, second
38    /// enemy hit will be at a power of `1.0 + scaling`, nth enemy hit will be
39    /// at a power of `1.0 + (n - 1) * scaling`.
40    Scaling(f32),
41}
42
43impl Melee {
44    #[must_use]
45    pub fn with_block_breaking(
46        mut self,
47        break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
48    ) -> Self {
49        self.break_block = break_block;
50        self
51    }
52}
53
54impl Component for Melee {
55    type Storage = VecStorage<Self>;
56}
57
58fn default_true() -> bool { true }
59fn default_simultaneous_hits() -> u32 { 1 }
60
61#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
62pub struct Scaled {
63    pub kind: MeleeConstructorKind,
64    #[serde(default)]
65    pub range: f32,
66    #[serde(default)]
67    pub angle: f32,
68}
69
70#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
71#[serde(deny_unknown_fields)]
72pub struct MeleeConstructor {
73    pub kind: MeleeConstructorKind,
74    /// This multiplied by a fraction is added to what is specified in `kind`.
75    ///
76    /// Note, that this must be the same variant as what is specified in `kind`.
77    pub scaled: Option<Scaled>,
78    pub range: f32,
79    pub angle: f32,
80    pub multi_target: Option<MultiTarget>,
81    pub damage_effect: Option<CombatEffect>,
82    pub attack_effect: Option<(CombatEffect, CombatRequirement)>,
83    #[serde(default)]
84    pub dodgeable: Dodgeable,
85    #[serde(default = "default_true")]
86    pub blockable: bool,
87    #[serde(default = "default_simultaneous_hits")]
88    pub simultaneous_hits: u32,
89    #[serde(default)]
90    pub custom_combo: CustomCombo,
91    #[serde(default)]
92    pub precision_flank_multipliers: FlankMults,
93    #[serde(default)]
94    pub precision_flank_invert: bool,
95}
96
97#[derive(Copy, Clone, Debug, PartialEq, Serialize, Default, Deserialize)]
98pub struct CustomCombo {
99    pub base: Option<i32>,
100    pub conditional: Option<(i32, CombatRequirement)>,
101}
102
103impl MeleeConstructor {
104    pub fn create_melee(self, precision_mult: f32, ability_info: AbilityInfo) -> Melee {
105        if self.scaled.is_some() {
106            dev_panic!(
107                "Attempted to create a melee attack that had a provided scaled value without \
108                 scaling the melee attack."
109            )
110        }
111
112        let instance = rand::random();
113        let attack = match self.kind {
114            MeleeConstructorKind::Slash {
115                damage,
116                poise,
117                knockback,
118                energy_regen,
119            } => {
120                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
121                    .with_requirement(CombatRequirement::AnyDamage);
122                let mut damage = AttackDamage::new(
123                    Damage {
124                        kind: DamageKind::Slashing,
125                        value: damage,
126                    },
127                    Some(GroupTarget::OutOfGroup),
128                    instance,
129                );
130
131                if let Some(damage_effect) = self.damage_effect {
132                    damage = damage.with_effect(damage_effect);
133                }
134
135                let poise =
136                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
137                        .with_requirement(CombatRequirement::AnyDamage);
138                let knockback = AttackEffect::new(
139                    Some(GroupTarget::OutOfGroup),
140                    CombatEffect::Knockback(Knockback {
141                        strength: knockback,
142                        direction: KnockbackDir::Away,
143                    }),
144                )
145                .with_requirement(CombatRequirement::AnyDamage);
146
147                Attack::new(Some(ability_info))
148                    .with_damage(damage)
149                    .with_effect(energy)
150                    .with_effect(poise)
151                    .with_effect(knockback)
152            },
153            MeleeConstructorKind::Stab {
154                damage,
155                poise,
156                knockback,
157                energy_regen,
158            } => {
159                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
160                    .with_requirement(CombatRequirement::AnyDamage);
161                let mut damage = AttackDamage::new(
162                    Damage {
163                        kind: DamageKind::Piercing,
164                        value: damage,
165                    },
166                    Some(GroupTarget::OutOfGroup),
167                    instance,
168                );
169
170                if let Some(damage_effect) = self.damage_effect {
171                    damage = damage.with_effect(damage_effect);
172                }
173
174                let poise =
175                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
176                        .with_requirement(CombatRequirement::AnyDamage);
177                let knockback = AttackEffect::new(
178                    Some(GroupTarget::OutOfGroup),
179                    CombatEffect::Knockback(Knockback {
180                        strength: knockback,
181                        direction: KnockbackDir::Away,
182                    }),
183                )
184                .with_requirement(CombatRequirement::AnyDamage);
185
186                Attack::new(Some(ability_info))
187                    .with_damage(damage)
188                    .with_effect(energy)
189                    .with_effect(poise)
190                    .with_effect(knockback)
191            },
192            MeleeConstructorKind::Sear {
193                damage,
194                poise,
195                knockback,
196                energy_regen,
197            } => {
198                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
199                    .with_requirement(CombatRequirement::AnyDamage);
200                let mut damage = AttackDamage::new(
201                    Damage {
202                        kind: DamageKind::Energy,
203                        value: damage,
204                    },
205                    Some(GroupTarget::OutOfGroup),
206                    instance,
207                );
208
209                if let Some(damage_effect) = self.damage_effect {
210                    damage = damage.with_effect(damage_effect);
211                }
212
213                let poise =
214                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
215                        .with_requirement(CombatRequirement::AnyDamage);
216                let knockback = AttackEffect::new(
217                    Some(GroupTarget::OutOfGroup),
218                    CombatEffect::Knockback(Knockback {
219                        strength: knockback,
220                        direction: KnockbackDir::Away,
221                    }),
222                )
223                .with_requirement(CombatRequirement::AnyDamage);
224
225                Attack::new(Some(ability_info))
226                    .with_damage(damage)
227                    .with_effect(energy)
228                    .with_effect(poise)
229                    .with_effect(knockback)
230            },
231            MeleeConstructorKind::Bash {
232                damage,
233                poise,
234                knockback,
235                energy_regen,
236            } => {
237                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
238                    .with_requirement(CombatRequirement::AnyDamage);
239                let mut damage = AttackDamage::new(
240                    Damage {
241                        kind: DamageKind::Crushing,
242                        value: damage,
243                    },
244                    Some(GroupTarget::OutOfGroup),
245                    instance,
246                );
247
248                if let Some(damage_effect) = self.damage_effect {
249                    damage = damage.with_effect(damage_effect);
250                }
251
252                let poise =
253                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
254                        .with_requirement(CombatRequirement::AnyDamage);
255                let knockback = AttackEffect::new(
256                    Some(GroupTarget::OutOfGroup),
257                    CombatEffect::Knockback(Knockback {
258                        strength: knockback,
259                        direction: KnockbackDir::Away,
260                    }),
261                )
262                .with_requirement(CombatRequirement::AnyDamage);
263
264                Attack::new(Some(ability_info))
265                    .with_damage(damage)
266                    .with_effect(energy)
267                    .with_effect(poise)
268                    .with_effect(knockback)
269            },
270            MeleeConstructorKind::Hook {
271                damage,
272                poise,
273                pull,
274            } => {
275                let mut damage = AttackDamage::new(
276                    Damage {
277                        kind: DamageKind::Piercing,
278                        value: damage,
279                    },
280                    Some(GroupTarget::OutOfGroup),
281                    instance,
282                );
283
284                if let Some(damage_effect) = self.damage_effect {
285                    damage = damage.with_effect(damage_effect);
286                }
287
288                let poise =
289                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
290                        .with_requirement(CombatRequirement::AnyDamage);
291                let knockback = AttackEffect::new(
292                    Some(GroupTarget::OutOfGroup),
293                    CombatEffect::Knockback(Knockback {
294                        strength: pull,
295                        direction: KnockbackDir::Towards,
296                    }),
297                )
298                .with_requirement(CombatRequirement::AnyDamage);
299
300                Attack::new(Some(ability_info))
301                    .with_damage(damage)
302                    .with_effect(poise)
303                    .with_effect(knockback)
304            },
305            MeleeConstructorKind::NecroticVortex {
306                damage,
307                pull,
308                lifesteal,
309                energy_regen,
310            } => {
311                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
312                    .with_requirement(CombatRequirement::AnyDamage);
313                let lifesteal = CombatEffect::Lifesteal(lifesteal);
314
315                let mut damage = AttackDamage::new(
316                    Damage {
317                        kind: DamageKind::Energy,
318                        value: damage,
319                    },
320                    None,
321                    instance,
322                )
323                .with_effect(lifesteal);
324
325                if let Some(damage_effect) = self.damage_effect {
326                    damage = damage.with_effect(damage_effect);
327                }
328
329                let knockback = AttackEffect::new(
330                    Some(GroupTarget::OutOfGroup),
331                    CombatEffect::Knockback(Knockback {
332                        strength: pull,
333                        direction: KnockbackDir::Towards,
334                    }),
335                )
336                .with_requirement(CombatRequirement::AnyDamage);
337
338                Attack::new(Some(ability_info))
339                    .with_damage(damage)
340                    .with_effect(energy)
341                    .with_effect(knockback)
342            },
343            MeleeConstructorKind::SonicWave {
344                damage,
345                poise,
346                knockback,
347            } => {
348                let mut damage = AttackDamage::new(
349                    Damage {
350                        kind: DamageKind::Energy,
351                        value: damage,
352                    },
353                    Some(GroupTarget::OutOfGroup),
354                    instance,
355                );
356
357                if let Some(damage_effect) = self.damage_effect {
358                    damage = damage.with_effect(damage_effect);
359                }
360
361                let poise =
362                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
363                        .with_requirement(CombatRequirement::AnyDamage);
364                let knockback = AttackEffect::new(
365                    Some(GroupTarget::OutOfGroup),
366                    CombatEffect::Knockback(Knockback {
367                        strength: knockback,
368                        direction: KnockbackDir::Away,
369                    }),
370                )
371                .with_requirement(CombatRequirement::AnyDamage);
372
373                Attack::new(Some(ability_info))
374                    .with_damage(damage)
375                    .with_effect(poise)
376                    .with_effect(knockback)
377            },
378        }
379        .with_precision(
380            precision_mult
381                * ability_info
382                    .ability_meta
383                    .precision_power_mult
384                    .unwrap_or(1.0),
385        )
386        .with_blockable(self.blockable);
387
388        let attack = if let Some((effect, requirement)) = self.attack_effect {
389            let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
390                .with_requirement(requirement);
391            attack.with_effect(effect)
392        } else {
393            attack
394        };
395
396        let attack = if let Some(combo) = self.custom_combo.base {
397            attack.with_combo(combo)
398        } else {
399            attack.with_combo_increment()
400        };
401
402        let attack = if let Some((additional, req)) = self.custom_combo.conditional {
403            attack.with_combo_requirement(additional, req)
404        } else {
405            attack
406        };
407
408        Melee {
409            attack,
410            range: self.range,
411            max_angle: self.angle.to_radians(),
412            applied: false,
413            multi_target: self.multi_target,
414            break_block: None,
415            simultaneous_hits: self.simultaneous_hits,
416            precision_flank_multipliers: self.precision_flank_multipliers,
417            precision_flank_invert: self.precision_flank_invert,
418            dodgeable: self.dodgeable,
419            sustained: false,
420            hit_entities: Vec::new(),
421        }
422    }
423
424    #[must_use]
425    pub fn handle_scaling(mut self, scaling: f32) -> Self {
426        let scale_values = |a, b| a + b * scaling;
427
428        if let Some(max_scale) = self.scaled {
429            use MeleeConstructorKind::*;
430            let scaled = match (self.kind, max_scale.kind) {
431                (
432                    Slash {
433                        damage: a_damage,
434                        poise: a_poise,
435                        knockback: a_knockback,
436                        energy_regen: a_energy_regen,
437                    },
438                    Slash {
439                        damage: b_damage,
440                        poise: b_poise,
441                        knockback: b_knockback,
442                        energy_regen: b_energy_regen,
443                    },
444                ) => Slash {
445                    damage: scale_values(a_damage, b_damage),
446                    poise: scale_values(a_poise, b_poise),
447                    knockback: scale_values(a_knockback, b_knockback),
448                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
449                },
450                (
451                    Stab {
452                        damage: a_damage,
453                        poise: a_poise,
454                        knockback: a_knockback,
455                        energy_regen: a_energy_regen,
456                    },
457                    Stab {
458                        damage: b_damage,
459                        poise: b_poise,
460                        knockback: b_knockback,
461                        energy_regen: b_energy_regen,
462                    },
463                ) => Stab {
464                    damage: scale_values(a_damage, b_damage),
465                    poise: scale_values(a_poise, b_poise),
466                    knockback: scale_values(a_knockback, b_knockback),
467                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
468                },
469                (
470                    Sear {
471                        damage: a_damage,
472                        poise: a_poise,
473                        knockback: a_knockback,
474                        energy_regen: a_energy_regen,
475                    },
476                    Sear {
477                        damage: b_damage,
478                        poise: b_poise,
479                        knockback: b_knockback,
480                        energy_regen: b_energy_regen,
481                    },
482                ) => Sear {
483                    damage: scale_values(a_damage, b_damage),
484                    poise: scale_values(a_poise, b_poise),
485                    knockback: scale_values(a_knockback, b_knockback),
486                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
487                },
488                (
489                    Bash {
490                        damage: a_damage,
491                        poise: a_poise,
492                        knockback: a_knockback,
493                        energy_regen: a_energy_regen,
494                    },
495                    Bash {
496                        damage: b_damage,
497                        poise: b_poise,
498                        knockback: b_knockback,
499                        energy_regen: b_energy_regen,
500                    },
501                ) => Bash {
502                    damage: scale_values(a_damage, b_damage),
503                    poise: scale_values(a_poise, b_poise),
504                    knockback: scale_values(a_knockback, b_knockback),
505                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
506                },
507                (
508                    NecroticVortex {
509                        damage: a_damage,
510                        pull: a_pull,
511                        lifesteal: a_lifesteal,
512                        energy_regen: a_energy_regen,
513                    },
514                    NecroticVortex {
515                        damage: b_damage,
516                        pull: b_pull,
517                        lifesteal: b_lifesteal,
518                        energy_regen: b_energy_regen,
519                    },
520                ) => NecroticVortex {
521                    damage: scale_values(a_damage, b_damage),
522                    pull: scale_values(a_pull, b_pull),
523                    lifesteal: scale_values(a_lifesteal, b_lifesteal),
524                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
525                },
526                (
527                    Hook {
528                        damage: a_damage,
529                        poise: a_poise,
530                        pull: a_pull,
531                    },
532                    Hook {
533                        damage: b_damage,
534                        poise: b_poise,
535                        pull: b_pull,
536                    },
537                ) => Hook {
538                    damage: scale_values(a_damage, b_damage),
539                    poise: scale_values(a_poise, b_poise),
540                    pull: scale_values(a_pull, b_pull),
541                },
542                (
543                    SonicWave {
544                        damage: a_damage,
545                        poise: a_poise,
546                        knockback: a_knockback,
547                    },
548                    SonicWave {
549                        damage: b_damage,
550                        poise: b_poise,
551                        knockback: b_knockback,
552                    },
553                ) => SonicWave {
554                    damage: scale_values(a_damage, b_damage),
555                    poise: scale_values(a_poise, b_poise),
556                    knockback: scale_values(a_knockback, b_knockback),
557                },
558                _ => {
559                    dev_panic!(
560                        "Attempted to scale on a melee attack between two different kinds of \
561                         melee constructors."
562                    );
563                    self.kind
564                },
565            };
566            self.kind = scaled;
567            self.range = scale_values(self.range, max_scale.range);
568            self.angle = scale_values(self.angle, max_scale.angle);
569            self.scaled = None;
570        } else {
571            dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
572        }
573        self
574    }
575
576    #[must_use]
577    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
578        self.range *= stats.range;
579        self.kind = self.kind.adjusted_by_stats(stats);
580        if let Some(scaled) = &mut self.scaled {
581            scaled.kind = scaled.kind.adjusted_by_stats(stats);
582            scaled.range *= stats.range;
583        }
584        self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats));
585        self.attack_effect = self
586            .attack_effect
587            .map(|(e, r)| (e.adjusted_by_stats(stats), r));
588        self
589    }
590
591    #[must_use]
592    pub fn custom_combo(mut self, custom: CustomCombo) -> Self {
593        self.custom_combo = custom;
594        self
595    }
596}
597
598#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
599// TODO: Get someone not me to name these variants
600pub enum MeleeConstructorKind {
601    Slash {
602        damage: f32,
603        poise: f32,
604        knockback: f32,
605        energy_regen: f32,
606    },
607    Stab {
608        damage: f32,
609        poise: f32,
610        knockback: f32,
611        energy_regen: f32,
612    },
613    Sear {
614        damage: f32,
615        poise: f32,
616        knockback: f32,
617        energy_regen: f32,
618    },
619    Bash {
620        damage: f32,
621        poise: f32,
622        knockback: f32,
623        energy_regen: f32,
624    },
625    Hook {
626        damage: f32,
627        poise: f32,
628        pull: f32,
629    },
630    NecroticVortex {
631        damage: f32,
632        pull: f32,
633        lifesteal: f32,
634        energy_regen: f32,
635    },
636    SonicWave {
637        damage: f32,
638        poise: f32,
639        knockback: f32,
640    },
641}
642
643impl MeleeConstructorKind {
644    #[must_use]
645    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
646        use MeleeConstructorKind::*;
647        match self {
648            Slash {
649                ref mut damage,
650                ref mut poise,
651                ref mut knockback,
652                energy_regen: _,
653            } => {
654                *damage *= stats.power;
655                *poise *= stats.effect_power;
656                *knockback *= stats.effect_power;
657            },
658            Stab {
659                ref mut damage,
660                ref mut poise,
661                ref mut knockback,
662                energy_regen: _,
663            } => {
664                *damage *= stats.power;
665                *poise *= stats.effect_power;
666                *knockback *= stats.effect_power;
667            },
668            Sear {
669                ref mut damage,
670                ref mut poise,
671                ref mut knockback,
672                energy_regen: _,
673            } => {
674                *damage *= stats.power;
675                *poise *= stats.effect_power;
676                *knockback *= stats.effect_power;
677            },
678            Bash {
679                ref mut damage,
680                ref mut poise,
681                ref mut knockback,
682                energy_regen: _,
683            } => {
684                *damage *= stats.power;
685                *poise *= stats.effect_power;
686                *knockback *= stats.effect_power;
687            },
688            Hook {
689                ref mut damage,
690                ref mut poise,
691                ref mut pull,
692            } => {
693                *damage *= stats.power;
694                *poise *= stats.effect_power;
695                *pull *= stats.effect_power;
696            },
697            NecroticVortex {
698                ref mut damage,
699                ref mut pull,
700                ref mut lifesteal,
701                energy_regen: _,
702            } => {
703                *damage *= stats.power;
704                *pull *= stats.effect_power;
705                *lifesteal *= stats.effect_power;
706            },
707            SonicWave {
708                ref mut damage,
709                ref mut poise,
710                ref mut knockback,
711            } => {
712                *damage *= stats.power;
713                *poise *= stats.effect_power;
714                *knockback *= stats.effect_power;
715            },
716        }
717        self
718    }
719}