veloren/common/src/comp/melee.rs
2022-01-19 00:56:44 -05:00

433 lines
15 KiB
Rust

use crate::{
combat::{
self, 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,
pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
}
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>;
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MeleeConstructor {
pub kind: MeleeConstructorKind,
// This multiplied by a fraction is added to what is specified in kind
pub scaled: Option<MeleeConstructorKind>,
pub range: f32,
pub angle: f32,
pub damage_effect: Option<CombatEffect>,
}
impl MeleeConstructor {
pub fn create_melee(self, (crit_chance, crit_mult): (f32, f32), buff_strength: f32) -> 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 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 * buff_strength),
chance: 0.1,
});
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Slashing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
)
.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,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
.with_combo_increment()
},
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 * buff_strength),
chance: 0.1,
});
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Piercing,
value: damage,
},
Some(GroupTarget::OutOfGroup),
)
.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,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
.with_combo_increment()
},
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),
);
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,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_effect(energy)
.with_effect(poise)
.with_effect(knockback)
.with_combo_increment()
},
NecroticVortex {
damage,
pull,
lifesteal,
} => {
let lifesteal = CombatEffect::Lifesteal(lifesteal);
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
kind: DamageKind::Energy,
value: damage,
},
None,
)
.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,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_effect(knockback)
.with_combo_increment()
},
};
Melee {
attack,
range: self.range,
max_angle: self.angle.to_radians(),
applied: false,
hit_count: 0,
break_block: None,
}
}
#[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::*;
let scaled = match (self.kind, max_scale) {
(
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),
},
_ => {
dev_panic!(
"Attempted to scale on a melee attack between two different kinds of \
melee constructors."
);
self.kind
},
};
self.kind = scaled;
self.scaled = None;
} else {
dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
}
self
}
#[must_use]
pub fn adjusted_by_stats(mut self, stats: Stats, regen: f32) -> Self {
self.range *= stats.range;
self.kind = self.kind.adjusted_by_stats(stats, regen);
if let Some(ref mut scaled) = &mut self.scaled {
*scaled = scaled.adjusted_by_stats(stats, regen);
}
if let Some(CombatEffect::Buff(combat::CombatBuff { strength, .. })) =
&mut self.damage_effect
{
*strength *= stats.buff_strength;
}
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,
},
NecroticVortex {
damage: f32,
pull: f32,
lifesteal: f32,
},
}
impl MeleeConstructorKind {
#[must_use]
pub fn adjusted_by_stats(mut self, stats: Stats, regen: f32) -> Self {
use MeleeConstructorKind::*;
match self {
Slash {
ref mut damage,
ref mut poise,
knockback: _,
ref mut energy_regen,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
*energy_regen *= regen;
},
Stab {
ref mut damage,
ref mut poise,
knockback: _,
ref mut energy_regen,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
*energy_regen *= regen;
},
Bash {
ref mut damage,
ref mut poise,
knockback: _,
ref mut energy_regen,
} => {
*damage *= stats.power;
*poise *= stats.effect_power;
*energy_regen *= regen;
},
NecroticVortex {
ref mut damage,
pull: _,
lifesteal: _,
} => {
*damage *= stats.power;
},
}
self
}
}