veloren_common/comp/
melee.rs

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