veloren/common/src/comp/melee.rs

623 lines
21 KiB
Rust
Raw Normal View History

use crate::{
combat::{
2022-07-15 14:00:27 +00:00
Attack, AttackDamage, AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect,
CombatRequirement, Damage, DamageKind, DamageSource, GroupTarget, Knockback, KnockbackDir,
},
comp::{
buff::BuffKind,
tool::{Stats, ToolKind},
},
};
use common_base::dev_panic;
use serde::{Deserialize, Serialize};
use specs::{Component, VecStorage};
use vek::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct Melee {
pub attack: Attack,
pub range: f32,
pub max_angle: f32,
pub applied: bool,
pub hit_count: u32,
2022-10-09 07:17:29 +00:00
pub multi_target: Option<MultiTarget>,
pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
pub simultaneous_hits: u32,
}
2022-10-09 07:17:29 +00:00
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MultiTarget {
Normal,
2022-10-23 17:10:37 +00:00
/// Applies scaling to the power of the attack based on how many consecutive
/// enemies have been hit. First enemy hit will be at a power of 1.0, second
/// enemy hit will be at a power of `1.0 + scaling`, nth enemy hit will be
/// at a power of `1.0 + (n - 1) * scaling`.
2022-10-09 07:17:29 +00:00
Scaling(f32),
}
impl Melee {
#[must_use]
pub fn with_block_breaking(
mut self,
break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
) -> Self {
self.break_block = break_block;
self
}
}
impl Component for Melee {
type Storage = VecStorage<Self>;
}
fn default_simultaneous_hits() -> u32 { 1 }
2023-05-14 22:07:45 +00:00
fn default_combo_gain() -> i32 { 1 }
2024-01-20 17:32:29 +00:00
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Scaled {
pub kind: MeleeConstructorKind,
#[serde(default)]
pub range: f32,
#[serde(default)]
pub angle: f32,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
2022-03-05 06:35:57 +00:00
#[serde(deny_unknown_fields)]
pub struct MeleeConstructor {
pub kind: MeleeConstructorKind,
/// This multiplied by a fraction is added to what is specified in `kind`.
///
/// Note, that this must be the same variant as what is specified in `kind`.
2024-01-20 17:32:29 +00:00
pub scaled: Option<Scaled>,
pub range: f32,
pub angle: f32,
2022-10-09 07:17:29 +00:00
pub multi_target: Option<MultiTarget>,
pub damage_effect: Option<CombatEffect>,
#[serde(default = "default_simultaneous_hits")]
pub simultaneous_hits: u32,
2023-05-14 22:07:45 +00:00
#[serde(default = "default_combo_gain")]
pub combo_gain: i32,
}
impl MeleeConstructor {
2023-11-11 19:20:38 +00:00
pub fn create_melee(self, precision_mult: f32, tool_stats: Stats) -> Melee {
use MeleeConstructorKind::*;
if self.scaled.is_some() {
dev_panic!(
"Attempted to create a melee attack that had a provided scaled value without \
scaling the melee attack."
)
}
let instance = rand::random();
let attack = match self.kind {
Slash {
damage,
poise,
knockback,
energy_regen,
} => {
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
.with_requirement(CombatRequirement::AnyDamage);
let buff = CombatEffect::Buff(CombatBuff {
kind: BuffKind::Bleeding,
dur_secs: 10.0,
strength: CombatBuffStrength::DamageFraction(0.1),
chance: 0.1,
})
.adjusted_by_stats(tool_stats);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Slashing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
)
.with_effect(buff);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let poise =
AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
})
.adjusted_by_stats(tool_stats),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
2023-05-14 22:07:45 +00:00
.with_combo(self.combo_gain)
},
Stab {
damage,
poise,
knockback,
energy_regen,
} => {
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
.with_requirement(CombatRequirement::AnyDamage);
let buff = CombatEffect::Buff(CombatBuff {
kind: BuffKind::Bleeding,
dur_secs: 5.0,
strength: CombatBuffStrength::DamageFraction(0.05),
chance: 0.1,
})
.adjusted_by_stats(tool_stats);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Piercing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
)
.with_effect(buff);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let poise =
AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
})
.adjusted_by_stats(tool_stats),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
2023-05-14 22:07:45 +00:00
.with_combo(self.combo_gain)
},
Bash {
damage,
poise,
knockback,
energy_regen,
} => {
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
.with_requirement(CombatRequirement::AnyDamage);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Crushing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let poise =
AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
})
.adjusted_by_stats(tool_stats),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
2023-05-14 22:07:45 +00:00
.with_combo(self.combo_gain)
},
2023-05-27 22:00:04 +00:00
Hook {
damage,
poise,
pull,
} => {
let buff = CombatEffect::Buff(CombatBuff {
kind: BuffKind::Bleeding,
dur_secs: 5.0,
strength: CombatBuffStrength::DamageFraction(0.2),
chance: 0.1,
})
.adjusted_by_stats(tool_stats);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Piercing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
)
.with_effect(buff);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let poise =
AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: pull,
direction: KnockbackDir::Towards,
})
.adjusted_by_stats(tool_stats),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
2023-05-27 22:00:04 +00:00
.with_effect(poise)
.with_effect(knockback)
.with_combo(self.combo_gain)
},
NecroticVortex {
damage,
pull,
lifesteal,
} => {
let lifesteal = CombatEffect::Lifesteal(lifesteal);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Energy,
value: damage,
},
None,
instance,
)
.with_effect(lifesteal);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: pull,
direction: KnockbackDir::Towards,
})
.adjusted_by_stats(tool_stats),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
.with_effect(knockback)
2023-05-14 22:07:45 +00:00
.with_combo(self.combo_gain)
},
2022-02-02 05:17:06 +00:00
SonicWave {
damage,
poise,
knockback,
} => {
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Energy,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
2022-02-02 05:17:06 +00:00
);
if let Some(damage_effect) = self.damage_effect {
damage = damage.with_effect(damage_effect);
}
let poise =
AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
})
.adjusted_by_stats(tool_stats),
2022-02-02 05:17:06 +00:00
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
2023-11-11 19:20:38 +00:00
.with_precision(precision_mult)
2022-02-02 05:17:06 +00:00
.with_effect(poise)
.with_effect(knockback)
2023-05-14 22:07:45 +00:00
.with_combo(self.combo_gain)
2022-02-02 05:17:06 +00:00
},
};
Melee {
attack,
range: self.range,
max_angle: self.angle.to_radians(),
applied: false,
hit_count: 0,
multi_target: self.multi_target,
break_block: None,
simultaneous_hits: self.simultaneous_hits,
}
}
#[must_use]
pub fn handle_scaling(mut self, scaling: f32) -> Self {
let scale_values = |a, b| a + b * scaling;
if let Some(max_scale) = self.scaled {
use MeleeConstructorKind::*;
2024-01-20 17:32:29 +00:00
let scaled = match (self.kind, max_scale.kind) {
(
Slash {
damage: a_damage,
poise: a_poise,
knockback: a_knockback,
energy_regen: a_energy_regen,
},
Slash {
damage: b_damage,
poise: b_poise,
knockback: b_knockback,
energy_regen: b_energy_regen,
},
) => Slash {
damage: scale_values(a_damage, b_damage),
poise: scale_values(a_poise, b_poise),
knockback: scale_values(a_knockback, b_knockback),
energy_regen: scale_values(a_energy_regen, b_energy_regen),
},
(
Stab {
damage: a_damage,
poise: a_poise,
knockback: a_knockback,
energy_regen: a_energy_regen,
},
Stab {
damage: b_damage,
poise: b_poise,
knockback: b_knockback,
energy_regen: b_energy_regen,
},
) => Stab {
damage: scale_values(a_damage, b_damage),
poise: scale_values(a_poise, b_poise),
knockback: scale_values(a_knockback, b_knockback),
energy_regen: scale_values(a_energy_regen, b_energy_regen),
},
(
Bash {
damage: a_damage,
poise: a_poise,
knockback: a_knockback,
energy_regen: a_energy_regen,
},
Bash {
damage: b_damage,
poise: b_poise,
knockback: b_knockback,
energy_regen: b_energy_regen,
},
) => Bash {
damage: scale_values(a_damage, b_damage),
poise: scale_values(a_poise, b_poise),
knockback: scale_values(a_knockback, b_knockback),
energy_regen: scale_values(a_energy_regen, b_energy_regen),
},
(
NecroticVortex {
damage: a_damage,
pull: a_pull,
lifesteal: a_lifesteal,
},
NecroticVortex {
damage: b_damage,
pull: b_pull,
lifesteal: b_lifesteal,
},
) => NecroticVortex {
damage: scale_values(a_damage, b_damage),
pull: scale_values(a_pull, b_pull),
lifesteal: scale_values(a_lifesteal, b_lifesteal),
},
2023-05-27 22:00:04 +00:00
(
Hook {
damage: a_damage,
poise: a_poise,
pull: a_pull,
},
Hook {
damage: b_damage,
poise: b_poise,
pull: b_pull,
},
) => Hook {
damage: scale_values(a_damage, b_damage),
poise: scale_values(a_poise, b_poise),
pull: scale_values(a_pull, b_pull),
},
2022-02-02 05:17:06 +00:00
(
SonicWave {
damage: a_damage,
poise: a_poise,
knockback: a_knockback,
},
SonicWave {
damage: b_damage,
poise: b_poise,
knockback: b_knockback,
},
) => SonicWave {
damage: scale_values(a_damage, b_damage),
poise: scale_values(a_poise, b_poise),
knockback: scale_values(a_knockback, b_knockback),
},
_ => {
dev_panic!(
"Attempted to scale on a melee attack between two different kinds of \
melee constructors."
);
self.kind
},
};
self.kind = scaled;
2024-01-20 17:32:29 +00:00
self.range = scale_values(self.range, max_scale.range);
self.angle = scale_values(self.angle, max_scale.angle);
self.scaled = None;
} else {
dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
}
self
}
#[must_use]
2022-01-23 06:39:59 +00:00
pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
self.range *= stats.range;
2022-01-23 06:39:59 +00:00
self.kind = self.kind.adjusted_by_stats(stats);
if let Some(ref mut scaled) = &mut self.scaled {
2024-01-20 17:32:29 +00:00
scaled.kind = scaled.kind.adjusted_by_stats(stats);
scaled.range *= stats.range;
}
self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats));
self
}
2023-05-14 22:07:45 +00:00
#[must_use]
pub fn with_combo(mut self, combo: i32) -> Self {
self.combo_gain = combo;
self
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
// TODO: Get someone not me to name these variants
pub enum MeleeConstructorKind {
Slash {
damage: f32,
poise: f32,
knockback: f32,
energy_regen: f32,
},
Stab {
damage: f32,
poise: f32,
knockback: f32,
energy_regen: f32,
},
Bash {
damage: f32,
poise: f32,
knockback: f32,
energy_regen: f32,
},
2023-05-27 22:00:04 +00:00
Hook {
damage: f32,
poise: f32,
pull: f32,
},
NecroticVortex {
damage: f32,
pull: f32,
lifesteal: f32,
},
2022-02-02 05:17:06 +00:00
SonicWave {
damage: f32,
poise: f32,
knockback: f32,
},
}
impl MeleeConstructorKind {
#[must_use]
2022-01-23 06:39:59 +00:00
pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
use MeleeConstructorKind::*;
match self {
Slash {
ref mut damage,
ref mut poise,
knockback: _,
2022-01-23 06:39:59 +00:00
energy_regen: _,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
},
Stab {
ref mut damage,
ref mut poise,
knockback: _,
2022-01-23 06:39:59 +00:00
energy_regen: _,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
},
Bash {
ref mut damage,
ref mut poise,
knockback: _,
2022-01-23 06:39:59 +00:00
energy_regen: _,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
},
2023-05-27 22:00:04 +00:00
Hook {
ref mut damage,
ref mut poise,
pull: _,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
},
NecroticVortex {
ref mut damage,
pull: _,
lifesteal: _,
} => {
*damage *= stats.power;
},
2022-02-02 05:17:06 +00:00
SonicWave {
ref mut damage,
ref mut poise,
knockback: _,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
},
}
self
}
}