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