veloren/common/src/combat.rs

261 lines
8.0 KiB
Rust
Raw Normal View History

use crate::{
comp::{
inventory::{
item::{
armor::Protection,
tool::{Tool, ToolKind},
ItemKind,
},
slot::EquipSlot,
},
skills::{SkillGroupKind, SkillSet},
Body, BuffKind, Health, HealthChange, HealthSource, Inventory, Stats,
},
uid::Uid,
2020-10-18 18:21:58 +00:00
util::Dir,
};
use serde::{Deserialize, Serialize};
2020-10-18 18:21:58 +00:00
use vek::*;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum GroupTarget {
InGroup,
OutOfGroup,
}
2020-11-05 01:21:42 +00:00
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum DamageSource {
Buff(BuffKind),
Melee,
Healing,
Projectile,
Explosion,
Falling,
Shockwave,
Energy,
2020-11-05 01:21:42 +00:00
Other,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Damage {
pub source: DamageSource,
pub value: f32,
}
impl Damage {
/// Returns the total damage reduction provided by all equipped items
pub fn compute_damage_reduction(inventory: &Inventory) -> f32 {
let protection = inventory
.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
Some(armor.get_protection())
} else {
None
}
})
.map(|protection| match protection {
Protection::Normal(protection) => Some(protection),
Protection::Invincible => None,
})
.sum::<Option<f32>>();
match protection {
Some(dr) => dr / (60.0 + dr.abs()),
None => 1.0,
}
}
pub fn modify_damage(self, inventory: Option<&Inventory>, uid: Option<Uid>) -> HealthChange {
let mut damage = self.value;
let damage_reduction = inventory.map_or(0.0, |inv| Damage::compute_damage_reduction(inv));
match self.source {
DamageSource::Melee => {
// Critical hit
let mut critdamage = 0.0;
if rand::random() {
critdamage = damage * 0.3;
}
// Armor
damage *= 1.0 - damage_reduction;
// Critical damage applies after armor for melee
if (damage_reduction - 1.0).abs() > f32::EPSILON {
damage += critdamage;
}
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
2020-11-05 01:21:42 +00:00
},
2020-12-06 02:29:46 +00:00
}
},
DamageSource::Projectile => {
// Critical hit
if rand::random() {
damage *= 1.2;
}
// Armor
damage *= 1.0 - damage_reduction;
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
2020-11-05 01:21:42 +00:00
},
2020-12-06 02:29:46 +00:00
}
},
DamageSource::Explosion => {
// Armor
damage *= 1.0 - damage_reduction;
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
2020-11-05 01:21:42 +00:00
},
2020-12-06 02:29:46 +00:00
}
},
DamageSource::Shockwave => {
// Armor
damage *= 1.0 - damage_reduction;
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
2020-11-05 01:21:42 +00:00
},
2020-12-06 02:29:46 +00:00
}
},
DamageSource::Energy => {
// Armor
damage *= 1.0 - damage_reduction;
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
2020-11-05 01:21:42 +00:00
},
2020-12-06 02:29:46 +00:00
}
},
DamageSource::Healing => HealthChange {
amount: damage as i32,
cause: HealthSource::Heal { by: uid },
},
DamageSource::Falling => {
// Armor
if (damage_reduction - 1.0).abs() < f32::EPSILON {
damage = 0.0;
}
2020-12-06 02:29:46 +00:00
HealthChange {
amount: -damage as i32,
cause: HealthSource::World,
2021-01-15 03:32:12 +00:00
}
},
DamageSource::Buff(_) => HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
},
},
2020-11-05 01:21:42 +00:00
DamageSource::Other => HealthChange {
amount: -damage as i32,
cause: HealthSource::Damage {
kind: self.source,
by: uid,
},
2020-12-06 02:29:46 +00:00
},
}
}
pub fn interpolate_damage(&mut self, frac: f32, min: f32) {
let new_damage = min + frac * (self.value - min);
self.value = new_damage;
}
}
2020-10-18 18:21:58 +00:00
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Knockback {
pub direction: KnockbackDir,
pub strength: f32,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum KnockbackDir {
Away,
Towards,
Up,
TowardsUp,
2020-10-18 18:21:58 +00:00
}
impl Knockback {
2020-10-27 22:16:17 +00:00
pub fn calculate_impulse(self, dir: Dir) -> Vec3<f32> {
match self.direction {
KnockbackDir::Away => self.strength * *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5),
KnockbackDir::Towards => {
self.strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5)
2020-10-18 18:21:58 +00:00
},
KnockbackDir::Up => self.strength * Vec3::unit_z(),
KnockbackDir::TowardsUp => {
self.strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.85)
2020-10-18 18:21:58 +00:00
},
}
}
2020-12-24 17:54:00 +00:00
pub fn modify_strength(mut self, power: f32) -> Self {
self.strength *= power;
2020-12-24 17:54:00 +00:00
self
}
2020-10-18 18:21:58 +00:00
}
fn equipped_tool(inv: &Inventory, slot: EquipSlot) -> Option<&Tool> {
inv.equipped(slot).and_then(|i| {
if let ItemKind::Tool(tool) = &i.kind() {
Some(tool)
} else {
None
}
})
}
pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
(
equipped_tool(inv, EquipSlot::Mainhand).map(|tool| tool.kind),
equipped_tool(inv, EquipSlot::Offhand).map(|tool| tool.kind),
)
}
fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 {
let active_damage = equipped_tool(inv, EquipSlot::Mainhand).map_or(0.0, |tool| {
tool.base_power()
* tool.base_speed()
* (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32)
});
let second_damage = equipped_tool(inv, EquipSlot::Offhand).map_or(0.0, |tool| {
tool.base_power()
* tool.base_speed()
* (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32)
});
active_damage.max(second_damage)
}
pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats, body: Body) -> f32 {
let defensive_weighting = 1.0;
let offensive_weighting = 1.0;
let defensive_rating = health.maximum() as f32
/ (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001)
/ 100.0;
let offensive_rating = offensive_rating(inventory, &stats.skill_set).max(0.1)
+ 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32;
let combined_rating = (offensive_rating * offensive_weighting
+ defensive_rating * defensive_weighting)
/ (offensive_weighting + defensive_weighting);
combined_rating * body.combat_multiplier()
}