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        use MeleeConstructorKind::*;
104        if self.scaled.is_some() {
105            dev_panic!(
106                "Attempted to create a melee attack that had a provided scaled value without \
107                 scaling the melee attack."
108            )
109        }
110        let instance = rand::random();
111        let attack = match self.kind {
112            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_precision(precision_mult)
157                    .with_effect(energy)
158                    .with_effect(poise)
159                    .with_effect(knockback)
160            },
161            Stab {
162                damage,
163                poise,
164                knockback,
165                energy_regen,
166            } => {
167                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
168                    .with_requirement(CombatRequirement::AnyDamage);
169                let buff = CombatEffect::Buff(CombatBuff {
170                    kind: BuffKind::Bleeding,
171                    dur_secs: 5.0,
172                    strength: CombatBuffStrength::DamageFraction(0.05),
173                    chance: 0.1,
174                })
175                .adjusted_by_stats(tool_stats);
176                let mut damage = AttackDamage::new(
177                    Damage {
178                        source: DamageSource::Melee,
179                        kind: DamageKind::Piercing,
180                        value: damage,
181                    },
182                    Some(GroupTarget::OutOfGroup),
183                    instance,
184                )
185                .with_effect(buff);
186
187                if let Some(damage_effect) = self.damage_effect {
188                    damage = damage.with_effect(damage_effect);
189                }
190
191                let poise =
192                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
193                        .with_requirement(CombatRequirement::AnyDamage);
194                let knockback = AttackEffect::new(
195                    Some(GroupTarget::OutOfGroup),
196                    CombatEffect::Knockback(Knockback {
197                        strength: knockback,
198                        direction: KnockbackDir::Away,
199                    }),
200                )
201                .with_requirement(CombatRequirement::AnyDamage);
202
203                Attack::default()
204                    .with_damage(damage)
205                    .with_precision(precision_mult)
206                    .with_effect(energy)
207                    .with_effect(poise)
208                    .with_effect(knockback)
209            },
210            Bash {
211                damage,
212                poise,
213                knockback,
214                energy_regen,
215            } => {
216                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
217                    .with_requirement(CombatRequirement::AnyDamage);
218                let mut damage = AttackDamage::new(
219                    Damage {
220                        source: DamageSource::Melee,
221                        kind: DamageKind::Crushing,
222                        value: damage,
223                    },
224                    Some(GroupTarget::OutOfGroup),
225                    instance,
226                );
227
228                if let Some(damage_effect) = self.damage_effect {
229                    damage = damage.with_effect(damage_effect);
230                }
231
232                let poise =
233                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
234                        .with_requirement(CombatRequirement::AnyDamage);
235                let knockback = AttackEffect::new(
236                    Some(GroupTarget::OutOfGroup),
237                    CombatEffect::Knockback(Knockback {
238                        strength: knockback,
239                        direction: KnockbackDir::Away,
240                    }),
241                )
242                .with_requirement(CombatRequirement::AnyDamage);
243
244                Attack::default()
245                    .with_damage(damage)
246                    .with_precision(precision_mult)
247                    .with_effect(energy)
248                    .with_effect(poise)
249                    .with_effect(knockback)
250            },
251            Hook {
252                damage,
253                poise,
254                pull,
255            } => {
256                let buff = CombatEffect::Buff(CombatBuff {
257                    kind: BuffKind::Bleeding,
258                    dur_secs: 5.0,
259                    strength: CombatBuffStrength::DamageFraction(0.2),
260                    chance: 0.1,
261                })
262                .adjusted_by_stats(tool_stats);
263                let mut damage = AttackDamage::new(
264                    Damage {
265                        source: DamageSource::Melee,
266                        kind: DamageKind::Piercing,
267                        value: damage,
268                    },
269                    Some(GroupTarget::OutOfGroup),
270                    instance,
271                )
272                .with_effect(buff);
273
274                if let Some(damage_effect) = self.damage_effect {
275                    damage = damage.with_effect(damage_effect);
276                }
277
278                let poise =
279                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
280                        .with_requirement(CombatRequirement::AnyDamage);
281                let knockback = AttackEffect::new(
282                    Some(GroupTarget::OutOfGroup),
283                    CombatEffect::Knockback(Knockback {
284                        strength: pull,
285                        direction: KnockbackDir::Towards,
286                    }),
287                )
288                .with_requirement(CombatRequirement::AnyDamage);
289
290                Attack::default()
291                    .with_damage(damage)
292                    .with_precision(precision_mult)
293                    .with_effect(poise)
294                    .with_effect(knockback)
295            },
296            NecroticVortex {
297                damage,
298                pull,
299                lifesteal,
300                energy_regen,
301            } => {
302                let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
303                    .with_requirement(CombatRequirement::AnyDamage);
304                let lifesteal = CombatEffect::Lifesteal(lifesteal);
305
306                let mut damage = AttackDamage::new(
307                    Damage {
308                        source: DamageSource::Melee,
309                        kind: DamageKind::Energy,
310                        value: damage,
311                    },
312                    None,
313                    instance,
314                )
315                .with_effect(lifesteal);
316
317                if let Some(damage_effect) = self.damage_effect {
318                    damage = damage.with_effect(damage_effect);
319                }
320
321                let knockback = AttackEffect::new(
322                    Some(GroupTarget::OutOfGroup),
323                    CombatEffect::Knockback(Knockback {
324                        strength: pull,
325                        direction: KnockbackDir::Towards,
326                    }),
327                )
328                .with_requirement(CombatRequirement::AnyDamage);
329
330                Attack::default()
331                    .with_damage(damage)
332                    .with_precision(precision_mult)
333                    .with_effect(energy)
334                    .with_effect(knockback)
335            },
336            SonicWave {
337                damage,
338                poise,
339                knockback,
340            } => {
341                let mut damage = AttackDamage::new(
342                    Damage {
343                        source: DamageSource::Melee,
344                        kind: DamageKind::Energy,
345                        value: damage,
346                    },
347                    Some(GroupTarget::OutOfGroup),
348                    instance,
349                );
350
351                if let Some(damage_effect) = self.damage_effect {
352                    damage = damage.with_effect(damage_effect);
353                }
354
355                let poise =
356                    AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
357                        .with_requirement(CombatRequirement::AnyDamage);
358                let knockback = AttackEffect::new(
359                    Some(GroupTarget::OutOfGroup),
360                    CombatEffect::Knockback(Knockback {
361                        strength: knockback,
362                        direction: KnockbackDir::Away,
363                    }),
364                )
365                .with_requirement(CombatRequirement::AnyDamage);
366
367                Attack::default()
368                    .with_damage(damage)
369                    .with_precision(precision_mult)
370                    .with_effect(poise)
371                    .with_effect(knockback)
372            },
373        };
374
375        let attack = if let Some((effect, requirement)) = self.attack_effect {
376            let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
377                .with_requirement(requirement);
378            attack.with_effect(effect)
379        } else {
380            attack
381        };
382
383        let attack = if let Some(combo) = self.custom_combo.base {
384            attack.with_combo(combo)
385        } else {
386            attack.with_combo_increment()
387        };
388
389        let attack = if let Some((additional, req)) = self.custom_combo.conditional {
390            attack.with_combo_requirement(additional, req)
391        } else {
392            attack
393        };
394
395        Melee {
396            attack,
397            range: self.range,
398            max_angle: self.angle.to_radians(),
399            applied: false,
400            hit_count: 0,
401            multi_target: self.multi_target,
402            break_block: None,
403            simultaneous_hits: self.simultaneous_hits,
404            precision_flank_multipliers: self.precision_flank_multipliers,
405            precision_flank_invert: self.precision_flank_invert,
406            dodgeable: self.dodgeable,
407        }
408    }
409
410    #[must_use]
411    pub fn handle_scaling(mut self, scaling: f32) -> Self {
412        let scale_values = |a, b| a + b * scaling;
413
414        if let Some(max_scale) = self.scaled {
415            use MeleeConstructorKind::*;
416            let scaled = match (self.kind, max_scale.kind) {
417                (
418                    Slash {
419                        damage: a_damage,
420                        poise: a_poise,
421                        knockback: a_knockback,
422                        energy_regen: a_energy_regen,
423                    },
424                    Slash {
425                        damage: b_damage,
426                        poise: b_poise,
427                        knockback: b_knockback,
428                        energy_regen: b_energy_regen,
429                    },
430                ) => Slash {
431                    damage: scale_values(a_damage, b_damage),
432                    poise: scale_values(a_poise, b_poise),
433                    knockback: scale_values(a_knockback, b_knockback),
434                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
435                },
436                (
437                    Stab {
438                        damage: a_damage,
439                        poise: a_poise,
440                        knockback: a_knockback,
441                        energy_regen: a_energy_regen,
442                    },
443                    Stab {
444                        damage: b_damage,
445                        poise: b_poise,
446                        knockback: b_knockback,
447                        energy_regen: b_energy_regen,
448                    },
449                ) => Stab {
450                    damage: scale_values(a_damage, b_damage),
451                    poise: scale_values(a_poise, b_poise),
452                    knockback: scale_values(a_knockback, b_knockback),
453                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
454                },
455                (
456                    Bash {
457                        damage: a_damage,
458                        poise: a_poise,
459                        knockback: a_knockback,
460                        energy_regen: a_energy_regen,
461                    },
462                    Bash {
463                        damage: b_damage,
464                        poise: b_poise,
465                        knockback: b_knockback,
466                        energy_regen: b_energy_regen,
467                    },
468                ) => Bash {
469                    damage: scale_values(a_damage, b_damage),
470                    poise: scale_values(a_poise, b_poise),
471                    knockback: scale_values(a_knockback, b_knockback),
472                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
473                },
474                (
475                    NecroticVortex {
476                        damage: a_damage,
477                        pull: a_pull,
478                        lifesteal: a_lifesteal,
479                        energy_regen: a_energy_regen,
480                    },
481                    NecroticVortex {
482                        damage: b_damage,
483                        pull: b_pull,
484                        lifesteal: b_lifesteal,
485                        energy_regen: b_energy_regen,
486                    },
487                ) => NecroticVortex {
488                    damage: scale_values(a_damage, b_damage),
489                    pull: scale_values(a_pull, b_pull),
490                    lifesteal: scale_values(a_lifesteal, b_lifesteal),
491                    energy_regen: scale_values(a_energy_regen, b_energy_regen),
492                },
493                (
494                    Hook {
495                        damage: a_damage,
496                        poise: a_poise,
497                        pull: a_pull,
498                    },
499                    Hook {
500                        damage: b_damage,
501                        poise: b_poise,
502                        pull: b_pull,
503                    },
504                ) => Hook {
505                    damage: scale_values(a_damage, b_damage),
506                    poise: scale_values(a_poise, b_poise),
507                    pull: scale_values(a_pull, b_pull),
508                },
509                (
510                    SonicWave {
511                        damage: a_damage,
512                        poise: a_poise,
513                        knockback: a_knockback,
514                    },
515                    SonicWave {
516                        damage: b_damage,
517                        poise: b_poise,
518                        knockback: b_knockback,
519                    },
520                ) => SonicWave {
521                    damage: scale_values(a_damage, b_damage),
522                    poise: scale_values(a_poise, b_poise),
523                    knockback: scale_values(a_knockback, b_knockback),
524                },
525                _ => {
526                    dev_panic!(
527                        "Attempted to scale on a melee attack between two different kinds of \
528                         melee constructors."
529                    );
530                    self.kind
531                },
532            };
533            self.kind = scaled;
534            self.range = scale_values(self.range, max_scale.range);
535            self.angle = scale_values(self.angle, max_scale.angle);
536            self.scaled = None;
537        } else {
538            dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
539        }
540        self
541    }
542
543    #[must_use]
544    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
545        self.range *= stats.range;
546        self.kind = self.kind.adjusted_by_stats(stats);
547        if let Some(ref mut scaled) = &mut self.scaled {
548            scaled.kind = scaled.kind.adjusted_by_stats(stats);
549            scaled.range *= stats.range;
550        }
551        self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats));
552        self
553    }
554
555    #[must_use]
556    pub fn custom_combo(mut self, custom: CustomCombo) -> Self {
557        self.custom_combo = custom;
558        self
559    }
560}
561
562#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
563// TODO: Get someone not me to name these variants
564pub enum MeleeConstructorKind {
565    Slash {
566        damage: f32,
567        poise: f32,
568        knockback: f32,
569        energy_regen: f32,
570    },
571    Stab {
572        damage: f32,
573        poise: f32,
574        knockback: f32,
575        energy_regen: f32,
576    },
577    Bash {
578        damage: f32,
579        poise: f32,
580        knockback: f32,
581        energy_regen: f32,
582    },
583    Hook {
584        damage: f32,
585        poise: f32,
586        pull: f32,
587    },
588    NecroticVortex {
589        damage: f32,
590        pull: f32,
591        lifesteal: f32,
592        energy_regen: f32,
593    },
594    SonicWave {
595        damage: f32,
596        poise: f32,
597        knockback: f32,
598    },
599}
600
601impl MeleeConstructorKind {
602    #[must_use]
603    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
604        use MeleeConstructorKind::*;
605        match self {
606            Slash {
607                ref mut damage,
608                ref mut poise,
609                ref mut knockback,
610                energy_regen: _,
611            } => {
612                *damage *= stats.power;
613                *poise *= stats.effect_power;
614                *knockback *= stats.effect_power;
615            },
616            Stab {
617                ref mut damage,
618                ref mut poise,
619                ref mut knockback,
620                energy_regen: _,
621            } => {
622                *damage *= stats.power;
623                *poise *= stats.effect_power;
624                *knockback *= stats.effect_power;
625            },
626            Bash {
627                ref mut damage,
628                ref mut poise,
629                ref mut knockback,
630                energy_regen: _,
631            } => {
632                *damage *= stats.power;
633                *poise *= stats.effect_power;
634                *knockback *= stats.effect_power;
635            },
636            Hook {
637                ref mut damage,
638                ref mut poise,
639                ref mut pull,
640            } => {
641                *damage *= stats.power;
642                *poise *= stats.effect_power;
643                *pull *= stats.effect_power;
644            },
645            NecroticVortex {
646                ref mut damage,
647                ref mut pull,
648                ref mut lifesteal,
649                energy_regen: _,
650            } => {
651                *damage *= stats.power;
652                *pull *= stats.effect_power;
653                *lifesteal *= stats.effect_power;
654            },
655            SonicWave {
656                ref mut damage,
657                ref mut poise,
658                ref mut knockback,
659            } => {
660                *damage *= stats.power;
661                *poise *= stats.effect_power;
662                *knockback *= stats.effect_power;
663            },
664        }
665        self
666    }
667}