veloren_common/comp/
melee.rs

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