2020-10-17 22:42:43 +00:00
|
|
|
use crate::{
|
2021-01-08 19:12:09 +00:00
|
|
|
comp::{
|
2021-01-28 01:44:49 +00:00
|
|
|
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
|
2021-01-08 20:53:52 +00:00
|
|
|
inventory::{
|
2021-01-16 17:01:57 +00:00
|
|
|
item::{
|
|
|
|
armor::Protection,
|
|
|
|
tool::{Tool, ToolKind},
|
|
|
|
ItemKind,
|
|
|
|
},
|
2021-01-08 20:53:52 +00:00
|
|
|
slot::EquipSlot,
|
|
|
|
},
|
2021-01-30 04:40:03 +00:00
|
|
|
poise::PoiseChange,
|
2021-01-18 19:08:13 +00:00
|
|
|
skills::{SkillGroupKind, SkillSet},
|
2021-01-30 05:17:40 +00:00
|
|
|
Body, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource, Inventory,
|
|
|
|
Stats,
|
2021-01-08 19:12:09 +00:00
|
|
|
},
|
2021-01-26 03:53:52 +00:00
|
|
|
event::ServerEvent,
|
2020-12-13 17:11:55 +00:00
|
|
|
uid::Uid,
|
2020-10-18 18:21:58 +00:00
|
|
|
util::Dir,
|
2020-10-17 22:42:43 +00:00
|
|
|
};
|
2021-01-26 03:53:52 +00:00
|
|
|
use rand::{thread_rng, Rng};
|
2020-10-17 22:42:43 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-01-26 03:53:52 +00:00
|
|
|
use specs::Entity as EcsEntity;
|
2021-01-28 01:44:49 +00:00
|
|
|
use std::time::Duration;
|
2020-10-18 18:21:58 +00:00
|
|
|
use vek::*;
|
2020-10-17 22:42:43 +00:00
|
|
|
|
2020-11-02 00:26:01 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub enum GroupTarget {
|
|
|
|
InGroup,
|
|
|
|
OutOfGroup,
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct AttackerInfo<'a> {
|
|
|
|
pub entity: EcsEntity,
|
|
|
|
pub uid: Uid,
|
|
|
|
pub energy: Option<&'a Energy>,
|
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
|
2021-01-26 00:01:07 +00:00
|
|
|
pub struct Attack {
|
2021-02-02 18:02:40 +00:00
|
|
|
damages: Vec<AttackDamage>,
|
|
|
|
effects: Vec<AttackEffect>,
|
2021-01-26 03:53:52 +00:00
|
|
|
crit_chance: f32,
|
|
|
|
crit_multiplier: f32,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Attack {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
damages: Vec::new(),
|
|
|
|
effects: Vec::new(),
|
|
|
|
crit_chance: 0.0,
|
|
|
|
crit_multiplier: 1.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Attack {
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn with_damage(mut self, damage: AttackDamage) -> Self {
|
2021-01-26 00:01:07 +00:00
|
|
|
self.damages.push(damage);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn with_effect(mut self, effect: AttackEffect) -> Self {
|
2021-01-26 00:01:07 +00:00
|
|
|
self.effects.push(effect);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn with_crit(mut self, crit_chance: f32, crit_multiplier: f32) -> Self {
|
|
|
|
self.crit_chance = crit_chance;
|
|
|
|
self.crit_multiplier = crit_multiplier;
|
2021-01-26 00:01:07 +00:00
|
|
|
self
|
|
|
|
}
|
2021-01-26 02:50:16 +00:00
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() }
|
2021-01-31 17:15:28 +00:00
|
|
|
|
2021-01-28 01:54:45 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2021-01-26 03:53:52 +00:00
|
|
|
pub fn apply_attack(
|
|
|
|
&self,
|
|
|
|
target_group: GroupTarget,
|
2021-02-02 18:02:40 +00:00
|
|
|
attacker_info: Option<AttackerInfo>,
|
2021-01-26 03:53:52 +00:00
|
|
|
target_entity: EcsEntity,
|
2021-01-30 05:17:40 +00:00
|
|
|
target_inventory: Option<&Inventory>,
|
2021-01-26 03:53:52 +00:00
|
|
|
dir: Dir,
|
2021-01-28 01:54:45 +00:00
|
|
|
target_dodging: bool,
|
2021-01-31 16:40:04 +00:00
|
|
|
// Currently just modifies damage, maybe look into modifying strength of other effects?
|
|
|
|
strength_modifier: f32,
|
2021-02-02 18:02:40 +00:00
|
|
|
mut emit: impl FnMut(ServerEvent),
|
|
|
|
) {
|
2021-01-26 03:53:52 +00:00
|
|
|
let is_crit = thread_rng().gen::<f32>() < self.crit_chance;
|
|
|
|
let mut accumulated_damage = 0.0;
|
|
|
|
for damage in self
|
|
|
|
.damages
|
|
|
|
.iter()
|
|
|
|
.filter(|d| d.target.map_or(true, |t| t == target_group))
|
2021-01-28 01:54:45 +00:00
|
|
|
.filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
|
2021-01-26 03:53:52 +00:00
|
|
|
{
|
2021-02-02 18:02:40 +00:00
|
|
|
let change = damage.damage.calculate_health_change(
|
2021-01-30 05:17:40 +00:00
|
|
|
target_inventory,
|
2021-02-02 18:02:40 +00:00
|
|
|
attacker_info.map(|a| a.uid),
|
2021-01-28 01:44:49 +00:00
|
|
|
is_crit,
|
|
|
|
self.crit_multiplier,
|
2021-01-31 16:40:04 +00:00
|
|
|
strength_modifier,
|
2021-01-28 01:44:49 +00:00
|
|
|
);
|
2021-01-31 17:33:22 +00:00
|
|
|
let applied_damage = -change.amount as f32;
|
|
|
|
accumulated_damage += applied_damage;
|
2021-01-26 03:53:52 +00:00
|
|
|
if change.amount != 0 {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Damage {
|
2021-01-26 03:53:52 +00:00
|
|
|
entity: target_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
for effect in damage.effects.iter() {
|
|
|
|
match effect {
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Knockback(kb) => {
|
2021-01-26 03:53:52 +00:00
|
|
|
let impulse = kb.calculate_impulse(dir);
|
|
|
|
if !impulse.is_approx_zero() {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Knockback {
|
2021-01-26 03:53:52 +00:00
|
|
|
entity: target_entity,
|
|
|
|
impulse,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::EnergyReward(ec) => {
|
|
|
|
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
|
|
|
|
emit(ServerEvent::EnergyChange {
|
2021-02-01 02:43:26 +00:00
|
|
|
entity: attacker_entity,
|
|
|
|
change: EnergyChange {
|
|
|
|
amount: *ec as i32,
|
|
|
|
source: EnergySource::HitEnemy,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2021-01-26 17:58:52 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Buff(b) => {
|
2021-01-28 01:44:49 +00:00
|
|
|
if thread_rng().gen::<f32>() < b.chance {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Buff {
|
2021-01-28 01:44:49 +00:00
|
|
|
entity: target_entity,
|
|
|
|
buff_change: BuffChange::Add(
|
2021-02-02 18:02:40 +00:00
|
|
|
b.to_buff(attacker_info.map(|a| a.uid), applied_damage),
|
2021-01-28 01:44:49 +00:00
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Lifesteal(l) => {
|
|
|
|
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
|
2021-02-01 02:43:26 +00:00
|
|
|
let change = HealthChange {
|
|
|
|
amount: (applied_damage * l) as i32,
|
2021-02-02 18:02:40 +00:00
|
|
|
cause: HealthSource::Heal {
|
|
|
|
by: attacker_info.map(|a| a.uid),
|
|
|
|
},
|
2021-02-01 02:43:26 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::Damage {
|
|
|
|
entity: attacker_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
CombatEffect::Poise(p) => {
|
|
|
|
let change = PoiseChange::from_value(*p, target_inventory);
|
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::PoiseChange {
|
|
|
|
entity: target_entity,
|
2021-02-01 02:43:26 +00:00
|
|
|
change,
|
2021-02-02 18:02:40 +00:00
|
|
|
kb_dir: *dir,
|
2021-02-01 02:43:26 +00:00
|
|
|
});
|
|
|
|
}
|
2021-01-29 01:55:43 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Heal(h) => {
|
2021-01-30 05:03:23 +00:00
|
|
|
let change = HealthChange {
|
|
|
|
amount: *h as i32,
|
2021-02-02 18:02:40 +00:00
|
|
|
cause: HealthSource::Heal {
|
|
|
|
by: attacker_info.map(|a| a.uid),
|
|
|
|
},
|
2021-01-30 05:03:23 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::Damage {
|
|
|
|
entity: target_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
2021-01-30 05:03:23 +00:00
|
|
|
},
|
2021-01-26 03:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-28 02:15:25 +00:00
|
|
|
for effect in self
|
|
|
|
.effects
|
|
|
|
.iter()
|
|
|
|
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
|
|
|
.filter(|e| !(matches!(e.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
|
|
|
|
{
|
2021-01-28 18:50:56 +00:00
|
|
|
if match &effect.requirement {
|
2021-01-30 22:35:00 +00:00
|
|
|
Some(CombatRequirement::AnyDamage) => accumulated_damage > 0.0,
|
2021-01-30 05:17:40 +00:00
|
|
|
Some(CombatRequirement::SufficientEnergy(r)) => {
|
2021-02-02 18:02:40 +00:00
|
|
|
if let Some(AttackerInfo {
|
|
|
|
entity,
|
|
|
|
energy: Some(e),
|
|
|
|
..
|
|
|
|
}) = attacker_info
|
|
|
|
{
|
|
|
|
let sufficient_energy = e.current() >= *r;
|
|
|
|
if sufficient_energy {
|
|
|
|
emit(ServerEvent::EnergyChange {
|
|
|
|
entity,
|
2021-02-01 02:43:26 +00:00
|
|
|
change: EnergyChange {
|
|
|
|
amount: -(*r as i32),
|
|
|
|
source: EnergySource::Ability,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2021-02-02 18:02:40 +00:00
|
|
|
|
|
|
|
sufficient_energy
|
2021-01-30 05:17:40 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
2021-01-28 18:50:56 +00:00
|
|
|
None => true,
|
|
|
|
} {
|
|
|
|
match effect.effect {
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Knockback(kb) => {
|
2021-01-28 18:50:56 +00:00
|
|
|
let impulse = kb.calculate_impulse(dir);
|
|
|
|
if !impulse.is_approx_zero() {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Knockback {
|
2021-01-28 18:50:56 +00:00
|
|
|
entity: target_entity,
|
|
|
|
impulse,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::EnergyReward(ec) => {
|
|
|
|
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
|
|
|
|
emit(ServerEvent::EnergyChange {
|
2021-02-01 02:43:26 +00:00
|
|
|
entity: attacker_entity,
|
|
|
|
change: EnergyChange {
|
|
|
|
amount: ec as i32,
|
|
|
|
source: EnergySource::HitEnemy,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2021-01-28 18:50:56 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Buff(b) => {
|
2021-01-28 18:50:56 +00:00
|
|
|
if thread_rng().gen::<f32>() < b.chance {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Buff {
|
2021-01-28 18:50:56 +00:00
|
|
|
entity: target_entity,
|
|
|
|
buff_change: BuffChange::Add(
|
2021-02-02 18:02:40 +00:00
|
|
|
b.to_buff(attacker_info.map(|a| a.uid), accumulated_damage),
|
2021-01-28 18:50:56 +00:00
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Lifesteal(l) => {
|
|
|
|
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
|
2021-02-01 02:43:26 +00:00
|
|
|
let change = HealthChange {
|
|
|
|
amount: (accumulated_damage * l) as i32,
|
2021-02-02 18:02:40 +00:00
|
|
|
cause: HealthSource::Heal {
|
|
|
|
by: attacker_info.map(|a| a.uid),
|
|
|
|
},
|
2021-02-01 02:43:26 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::Damage {
|
|
|
|
entity: attacker_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
CombatEffect::Poise(p) => {
|
|
|
|
let change = PoiseChange::from_value(p, target_inventory);
|
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::PoiseChange {
|
|
|
|
entity: target_entity,
|
2021-02-01 02:43:26 +00:00
|
|
|
change,
|
2021-02-02 18:02:40 +00:00
|
|
|
kb_dir: *dir,
|
2021-02-01 02:43:26 +00:00
|
|
|
});
|
|
|
|
}
|
2021-01-29 01:55:43 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Heal(h) => {
|
2021-01-30 05:03:23 +00:00
|
|
|
let change = HealthChange {
|
|
|
|
amount: h as i32,
|
2021-02-02 18:02:40 +00:00
|
|
|
cause: HealthSource::Heal {
|
|
|
|
by: attacker_info.map(|a| a.uid),
|
|
|
|
},
|
2021-01-30 05:03:23 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
if change.amount != 0 {
|
|
|
|
emit(ServerEvent::Damage {
|
|
|
|
entity: target_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
2021-01-30 05:03:23 +00:00
|
|
|
},
|
2021-01-28 18:50:56 +00:00
|
|
|
}
|
2021-01-28 02:15:25 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-26 02:50:16 +00:00
|
|
|
}
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2021-02-02 18:02:40 +00:00
|
|
|
pub struct AttackDamage {
|
2021-01-26 00:01:07 +00:00
|
|
|
damage: Damage,
|
|
|
|
target: Option<GroupTarget>,
|
2021-02-02 18:02:40 +00:00
|
|
|
effects: Vec<CombatEffect>,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
impl AttackDamage {
|
2021-01-26 00:01:07 +00:00
|
|
|
pub fn new(damage: Damage, target: Option<GroupTarget>) -> Self {
|
|
|
|
Self {
|
|
|
|
damage,
|
|
|
|
target,
|
|
|
|
effects: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn with_effect(mut self, effect: CombatEffect) -> Self {
|
2021-01-26 00:01:07 +00:00
|
|
|
self.effects.push(effect);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2021-02-02 18:02:40 +00:00
|
|
|
pub struct AttackEffect {
|
2021-01-26 00:01:07 +00:00
|
|
|
target: Option<GroupTarget>,
|
2021-02-02 18:02:40 +00:00
|
|
|
effect: CombatEffect,
|
2021-01-28 18:50:56 +00:00
|
|
|
requirement: Option<CombatRequirement>,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
impl AttackEffect {
|
|
|
|
pub fn new(target: Option<GroupTarget>, effect: CombatEffect) -> Self {
|
2021-01-28 18:50:56 +00:00
|
|
|
Self {
|
|
|
|
target,
|
|
|
|
effect,
|
|
|
|
requirement: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_requirement(mut self, requirement: CombatRequirement) -> Self {
|
|
|
|
self.requirement = Some(requirement);
|
|
|
|
self
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
2021-01-31 17:15:28 +00:00
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn effect(&self) -> &CombatEffect { &self.effect }
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2021-02-02 18:02:40 +00:00
|
|
|
pub enum CombatEffect {
|
2021-01-30 05:03:23 +00:00
|
|
|
Heal(f32),
|
2021-01-28 01:44:49 +00:00
|
|
|
Buff(CombatBuff),
|
2021-01-26 00:31:06 +00:00
|
|
|
Knockback(Knockback),
|
2021-01-26 17:58:52 +00:00
|
|
|
EnergyReward(u32),
|
2021-01-29 01:55:43 +00:00
|
|
|
Lifesteal(f32),
|
2021-01-30 04:40:03 +00:00
|
|
|
Poise(f32),
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2021-01-28 18:50:56 +00:00
|
|
|
pub enum CombatRequirement {
|
|
|
|
AnyDamage,
|
2021-01-30 05:17:40 +00:00
|
|
|
SufficientEnergy(u32),
|
2021-01-28 18:50:56 +00:00
|
|
|
}
|
|
|
|
|
2021-01-29 01:37:33 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
2020-10-30 20:41:21 +00:00
|
|
|
pub enum DamageSource {
|
2021-01-18 05:46:53 +00:00
|
|
|
Buff(BuffKind),
|
2020-10-30 20:41:21 +00:00
|
|
|
Melee,
|
|
|
|
Projectile,
|
|
|
|
Explosion,
|
|
|
|
Falling,
|
|
|
|
Shockwave,
|
|
|
|
Energy,
|
2020-11-05 01:21:42 +00:00
|
|
|
Other,
|
2020-10-17 22:42:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2020-10-30 20:41:21 +00:00
|
|
|
pub struct Damage {
|
|
|
|
pub source: DamageSource,
|
|
|
|
pub value: f32,
|
2020-10-17 22:42:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Damage {
|
2021-01-08 19:12:09 +00:00
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn calculate_health_change(
|
2021-01-26 03:53:52 +00:00
|
|
|
self,
|
|
|
|
inventory: Option<&Inventory>,
|
|
|
|
uid: Option<Uid>,
|
|
|
|
is_crit: bool,
|
|
|
|
crit_mult: f32,
|
2021-01-31 16:40:04 +00:00
|
|
|
damage_modifier: f32,
|
2021-01-26 03:53:52 +00:00
|
|
|
) -> HealthChange {
|
2021-01-31 16:40:04 +00:00
|
|
|
let mut damage = self.value * damage_modifier;
|
2021-02-02 18:02:40 +00:00
|
|
|
let damage_reduction = inventory.map_or(0.0, Damage::compute_damage_reduction);
|
2020-10-30 20:41:21 +00:00
|
|
|
match self.source {
|
|
|
|
DamageSource::Melee => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// Critical hit
|
|
|
|
let mut critdamage = 0.0;
|
2021-01-26 03:53:52 +00:00
|
|
|
if is_crit {
|
|
|
|
critdamage = damage * (crit_mult - 1.0);
|
2020-12-26 11:25:50 +00:00
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2020-10-30 20:41:21 +00:00
|
|
|
DamageSource::Projectile => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// Critical hit
|
2021-01-26 03:53:52 +00:00
|
|
|
if is_crit {
|
|
|
|
damage *= crit_mult;
|
2020-12-26 11:25:50 +00:00
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2020-10-30 20:41:21 +00:00
|
|
|
DamageSource::Explosion => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2020-10-30 20:41:21 +00:00
|
|
|
DamageSource::Shockwave => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2020-10-30 20:41:21 +00:00
|
|
|
DamageSource::Energy => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
|
|
|
},
|
2020-10-30 20:41:21 +00:00
|
|
|
DamageSource::Falling => {
|
2020-10-17 22:42:43 +00:00
|
|
|
// 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
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2021-01-18 05:46:53 +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
|
|
|
},
|
2020-10-17 22:42:43 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-30 20:41:21 +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-17 22:42:43 +00:00
|
|
|
}
|
2020-10-18 18:21:58 +00:00
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2021-01-25 22:46:43 +00:00
|
|
|
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> {
|
2021-01-25 22:46:43 +00:00
|
|
|
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
|
|
|
},
|
2021-01-25 22:46:43 +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 {
|
2021-01-25 22:46:43 +00:00
|
|
|
self.strength *= power;
|
2020-12-24 17:54:00 +00:00
|
|
|
self
|
|
|
|
}
|
2020-10-18 18:21:58 +00:00
|
|
|
}
|
2020-11-15 02:05:18 +00:00
|
|
|
|
2021-01-28 01:44:49 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
|
|
pub struct CombatBuff {
|
|
|
|
pub kind: BuffKind,
|
|
|
|
pub dur_secs: f32,
|
|
|
|
pub strength: CombatBuffStrength,
|
|
|
|
pub chance: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum CombatBuffStrength {
|
|
|
|
DamageFraction(f32),
|
|
|
|
Value(f32),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CombatBuffStrength {
|
|
|
|
fn to_strength(self, damage: f32) -> f32 {
|
|
|
|
match self {
|
|
|
|
CombatBuffStrength::DamageFraction(f) => damage * f,
|
|
|
|
CombatBuffStrength::Value(v) => v,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CombatBuff {
|
2021-02-01 02:43:26 +00:00
|
|
|
fn to_buff(self, uid: Option<Uid>, damage: f32) -> Buff {
|
2021-01-28 01:44:49 +00:00
|
|
|
// TODO: Generate BufCategoryId vec (probably requires damage overhaul?)
|
2021-02-01 02:43:26 +00:00
|
|
|
let source = if let Some(uid) = uid {
|
|
|
|
BuffSource::Character { by: uid }
|
|
|
|
} else {
|
|
|
|
BuffSource::Unknown
|
|
|
|
};
|
2021-01-28 01:44:49 +00:00
|
|
|
Buff::new(
|
|
|
|
self.kind,
|
|
|
|
BuffData::new(
|
|
|
|
self.strength.to_strength(damage),
|
|
|
|
Some(Duration::from_secs_f32(self.dur_secs)),
|
|
|
|
),
|
|
|
|
Vec::new(),
|
2021-02-01 02:43:26 +00:00
|
|
|
source,
|
2021-01-28 01:44:49 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-29 00:04:44 +00:00
|
|
|
pub fn default_physical() -> Self {
|
2021-01-28 01:44:49 +00:00
|
|
|
Self {
|
|
|
|
kind: BuffKind::Bleeding,
|
|
|
|
dur_secs: 10.0,
|
|
|
|
strength: CombatBuffStrength::DamageFraction(0.1),
|
|
|
|
chance: 0.1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-16 17:01:57 +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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-15 02:05:18 +00:00
|
|
|
pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
|
|
|
|
(
|
2021-01-16 17:01:57 +00:00
|
|
|
equipped_tool(inv, EquipSlot::Mainhand).map(|tool| tool.kind),
|
|
|
|
equipped_tool(inv, EquipSlot::Offhand).map(|tool| tool.kind),
|
2020-11-15 02:05:18 +00:00
|
|
|
)
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 19:08:13 +00:00
|
|
|
fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 {
|
2021-01-16 17:01:57 +00:00
|
|
|
let active_damage = equipped_tool(inv, EquipSlot::Mainhand).map_or(0.0, |tool| {
|
|
|
|
tool.base_power()
|
|
|
|
* tool.base_speed()
|
2021-01-18 19:08:13 +00:00
|
|
|
* (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32)
|
2021-01-07 05:23:24 +00:00
|
|
|
});
|
2021-01-16 17:01:57 +00:00
|
|
|
let second_damage = equipped_tool(inv, EquipSlot::Offhand).map_or(0.0, |tool| {
|
|
|
|
tool.base_power()
|
|
|
|
* tool.base_speed()
|
2021-01-18 19:08:13 +00:00
|
|
|
* (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32)
|
2021-01-07 05:23:24 +00:00
|
|
|
});
|
2021-01-10 01:05:13 +00:00
|
|
|
active_damage.max(second_damage)
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-22 21:12:16 +00:00
|
|
|
pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats, body: Body) -> f32 {
|
2021-01-16 17:01:57 +00:00
|
|
|
let defensive_weighting = 1.0;
|
|
|
|
let offensive_weighting = 1.0;
|
2021-01-10 01:05:13 +00:00
|
|
|
let defensive_rating = health.maximum() as f32
|
|
|
|
/ (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001)
|
|
|
|
/ 100.0;
|
2021-01-18 19:08:13 +00:00
|
|
|
let offensive_rating = offensive_rating(inventory, &stats.skill_set).max(0.1)
|
|
|
|
+ 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32;
|
2021-01-07 05:23:24 +00:00
|
|
|
let combined_rating = (offensive_rating * offensive_weighting
|
|
|
|
+ defensive_rating * defensive_weighting)
|
2021-01-16 17:01:57 +00:00
|
|
|
/ (offensive_weighting + defensive_weighting);
|
2021-01-22 21:12:16 +00:00
|
|
|
combined_rating * body.combat_multiplier()
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|