2020-10-17 22:42:43 +00:00
|
|
|
use crate::{
|
2021-01-08 19:12:09 +00:00
|
|
|
comp::{
|
2022-10-24 01:12:59 +00:00
|
|
|
ability::Capability,
|
2023-06-18 15:23:31 +00:00
|
|
|
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
|
2021-01-08 20:53:52 +00:00
|
|
|
inventory::{
|
2023-02-14 02:43:52 +00:00
|
|
|
item::{
|
|
|
|
armor::Protection,
|
|
|
|
tool::{self, ToolKind},
|
|
|
|
ItemDesc, ItemKind, MaterialStatManifest,
|
|
|
|
},
|
2021-01-08 20:53:52 +00:00
|
|
|
slot::EquipSlot,
|
|
|
|
},
|
2022-01-14 03:41:19 +00:00
|
|
|
skillset::SkillGroupKind,
|
2023-06-18 15:23:31 +00:00
|
|
|
Alignment, Body, Buffs, CharacterState, Combo, Energy, Group, Health, HealthChange,
|
|
|
|
Inventory, Ori, Player, Poise, PoiseChange, SkillSet, Stats,
|
2021-01-08 19:12:09 +00:00
|
|
|
},
|
2021-01-26 03:53:52 +00:00
|
|
|
event::ServerEvent,
|
2022-01-23 10:41:57 +00:00
|
|
|
outcome::Outcome,
|
2023-06-18 15:23:31 +00:00
|
|
|
resources::{Secs, Time},
|
2022-09-07 01:23:12 +00:00
|
|
|
states::utils::StageSection,
|
2023-05-21 20:32:51 +00:00
|
|
|
uid::{IdMaps, Uid},
|
2020-10-18 18:21:58 +00:00
|
|
|
util::Dir,
|
2020-10-17 22:42:43 +00:00
|
|
|
};
|
2023-06-18 15:23:31 +00:00
|
|
|
use rand::Rng;
|
2020-10-17 22:42:43 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-06-18 15:23:31 +00:00
|
|
|
use specs::{Entity as EcsEntity, ReadStorage};
|
|
|
|
use std::ops::{Mul, MulAssign};
|
|
|
|
use vek::*;
|
2021-02-17 13:03:20 +00:00
|
|
|
|
2020-11-02 00:26:01 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub enum GroupTarget {
|
|
|
|
InGroup,
|
|
|
|
OutOfGroup,
|
|
|
|
}
|
|
|
|
|
2021-04-10 03:40:20 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum AttackSource {
|
|
|
|
Melee,
|
|
|
|
Projectile,
|
|
|
|
Beam,
|
2022-12-16 03:37:05 +00:00
|
|
|
GroundShockwave,
|
|
|
|
AirShockwave,
|
2023-10-08 11:35:01 +00:00
|
|
|
UndodgeableShockwave,
|
2021-04-10 03:40:20 +00:00
|
|
|
Explosion,
|
|
|
|
}
|
|
|
|
|
2023-10-22 21:00:09 +00:00
|
|
|
pub const FULL_FLANK_ANGLE: f32 = std::f32::consts::PI / 4.0;
|
|
|
|
pub const PARTIAL_FLANK_ANGLE: f32 = std::f32::consts::PI * 3.0 / 4.0;
|
|
|
|
// NOTE: Do we want to change this to be a configurable parameter on body?
|
|
|
|
pub const PROJECTILE_HEADSHOT_PROPORTION: f32 = 0.1;
|
|
|
|
pub const BEAM_DURATION_PRECISION: f32 = 2.5;
|
2023-11-11 17:53:14 +00:00
|
|
|
pub const MAX_BACK_FLANK_PRECISION: f32 = 0.75;
|
|
|
|
pub const MAX_SIDE_FLANK_PRECISION: f32 = 0.25;
|
|
|
|
pub const MAX_HEADSHOT_PRECISION: f32 = 1.0;
|
2023-11-11 21:05:23 +00:00
|
|
|
pub const MAX_TOP_HEADSHOT_PRECISION: f32 = 0.5;
|
2023-11-11 17:53:14 +00:00
|
|
|
pub const MAX_BEAM_DUR_PRECISION: f32 = 0.25;
|
|
|
|
pub const MAX_MELEE_POISE_PRECISION: f32 = 0.5;
|
2023-10-22 21:00:09 +00:00
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct AttackerInfo<'a> {
|
|
|
|
pub entity: EcsEntity,
|
|
|
|
pub uid: Uid,
|
2021-11-13 20:46:45 +00:00
|
|
|
pub group: Option<&'a Group>,
|
2021-02-02 18:02:40 +00:00
|
|
|
pub energy: Option<&'a Energy>,
|
2021-03-02 22:19:38 +00:00
|
|
|
pub combo: Option<&'a Combo>,
|
2021-05-19 01:42:14 +00:00
|
|
|
pub inventory: Option<&'a Inventory>,
|
2022-12-16 02:51:03 +00:00
|
|
|
pub stats: Option<&'a Stats>,
|
2021-02-02 18:02:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-28 20:02:03 +00:00
|
|
|
pub struct TargetInfo<'a> {
|
|
|
|
pub entity: EcsEntity,
|
2021-04-24 04:11:41 +00:00
|
|
|
pub uid: Uid,
|
2021-02-28 20:02:03 +00:00
|
|
|
pub inventory: Option<&'a Inventory>,
|
|
|
|
pub stats: Option<&'a Stats>,
|
2021-03-20 20:26:10 +00:00
|
|
|
pub health: Option<&'a Health>,
|
2021-04-04 03:04:02 +00:00
|
|
|
pub pos: Vec3<f32>,
|
2021-04-10 03:40:20 +00:00
|
|
|
pub ori: Option<&'a Ori>,
|
|
|
|
pub char_state: Option<&'a CharacterState>,
|
2022-01-07 05:30:28 +00:00
|
|
|
pub energy: Option<&'a Energy>,
|
2022-11-27 21:11:19 +00:00
|
|
|
pub buffs: Option<&'a Buffs>,
|
2021-02-28 20:02:03 +00:00
|
|
|
}
|
|
|
|
|
2021-07-26 13:52:43 +00:00
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
pub struct AttackOptions {
|
|
|
|
pub target_dodging: bool,
|
2021-07-31 17:53:09 +00:00
|
|
|
pub may_harm: bool,
|
2021-07-26 13:52:43 +00:00
|
|
|
pub target_group: GroupTarget,
|
2023-10-22 21:00:09 +00:00
|
|
|
pub precision_mult: Option<f32>,
|
2021-07-26 13:52:43 +00:00
|
|
|
}
|
|
|
|
|
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>,
|
2023-11-11 19:20:38 +00:00
|
|
|
precision_multiplier: f32,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Attack {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
damages: Vec::new(),
|
|
|
|
effects: Vec::new(),
|
2023-11-11 19:20:38 +00:00
|
|
|
precision_multiplier: 1.0,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Attack {
|
2021-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
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-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
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-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
2023-11-11 19:20:38 +00:00
|
|
|
pub fn with_precision(mut self, precision_multiplier: f32) -> Self {
|
|
|
|
self.precision_multiplier = precision_multiplier;
|
2021-01-26 00:01:07 +00:00
|
|
|
self
|
|
|
|
}
|
2021-01-26 02:50:16 +00:00
|
|
|
|
2021-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
2023-05-14 22:07:45 +00:00
|
|
|
pub fn with_combo(self, combo: i32) -> Self {
|
2021-02-27 19:55:06 +00:00
|
|
|
self.with_effect(
|
2023-05-14 22:07:45 +00:00
|
|
|
AttackEffect::new(None, CombatEffect::Combo(combo))
|
2021-02-27 19:55:06 +00:00
|
|
|
.with_requirement(CombatRequirement::AnyDamage),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-05-14 22:07:45 +00:00
|
|
|
#[must_use]
|
|
|
|
pub fn with_combo_increment(self) -> Self { self.with_combo(1) }
|
|
|
|
|
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-04-19 05:35:46 +00:00
|
|
|
pub fn compute_damage_reduction(
|
2022-03-05 18:52:43 +00:00
|
|
|
attacker: Option<&AttackerInfo>,
|
2021-04-19 05:35:46 +00:00
|
|
|
target: &TargetInfo,
|
|
|
|
source: AttackSource,
|
|
|
|
dir: Dir,
|
2022-01-07 05:30:28 +00:00
|
|
|
damage: Damage,
|
2022-05-28 23:41:31 +00:00
|
|
|
msm: &MaterialStatManifest,
|
2021-08-31 06:47:37 +00:00
|
|
|
mut emit: impl FnMut(ServerEvent),
|
2021-04-19 05:35:46 +00:00
|
|
|
mut emit_outcome: impl FnMut(Outcome),
|
|
|
|
) -> f32 {
|
2022-11-13 19:03:37 +00:00
|
|
|
if damage.value > 0.0 {
|
2023-05-21 00:21:23 +00:00
|
|
|
let attacker_penetration = attacker
|
|
|
|
.and_then(|a| a.stats)
|
2023-06-27 01:27:52 +00:00
|
|
|
.map_or(0.0, |s| s.mitigations_penetration)
|
2023-05-21 00:21:23 +00:00
|
|
|
.clamp(0.0, 1.0);
|
|
|
|
let raw_damage_reduction =
|
2022-11-13 19:03:37 +00:00
|
|
|
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm);
|
2023-06-27 01:27:52 +00:00
|
|
|
let damage_reduction = (1.0 - attacker_penetration) * raw_damage_reduction;
|
2022-12-16 03:37:05 +00:00
|
|
|
let block_reduction =
|
|
|
|
if let (Some(char_state), Some(ori)) = (target.char_state, target.ori) {
|
|
|
|
if ori.look_vec().angle_between(-*dir) < char_state.block_angle() {
|
|
|
|
if char_state.is_parry(source) {
|
|
|
|
emit_outcome(Outcome::Block {
|
|
|
|
parry: true,
|
|
|
|
pos: target.pos,
|
|
|
|
uid: target.uid,
|
|
|
|
});
|
|
|
|
emit(ServerEvent::ParryHook {
|
|
|
|
defender: target.entity,
|
|
|
|
attacker: attacker.map(|a| a.entity),
|
2023-07-28 21:29:32 +00:00
|
|
|
source,
|
2022-12-16 03:37:05 +00:00
|
|
|
});
|
|
|
|
1.0
|
|
|
|
} else if let Some(block_strength) = char_state.block_strength(source) {
|
|
|
|
emit_outcome(Outcome::Block {
|
|
|
|
parry: false,
|
|
|
|
pos: target.pos,
|
|
|
|
uid: target.uid,
|
|
|
|
});
|
|
|
|
block_strength
|
2021-04-13 05:46:42 +00:00
|
|
|
} else {
|
2022-03-05 18:52:43 +00:00
|
|
|
0.0
|
2021-04-13 05:46:42 +00:00
|
|
|
}
|
2021-04-10 03:40:20 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
2022-12-16 03:37:05 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2023-06-27 01:27:52 +00:00
|
|
|
1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction)
|
2022-11-13 19:03:37 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
2021-04-10 03:40:20 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 03:53:52 +00:00
|
|
|
pub fn apply_attack(
|
|
|
|
&self,
|
2021-02-28 20:02:03 +00:00
|
|
|
attacker: Option<AttackerInfo>,
|
2023-04-24 01:31:29 +00:00
|
|
|
target: &TargetInfo,
|
2021-01-26 03:53:52 +00:00
|
|
|
dir: Dir,
|
2021-07-26 13:52:43 +00:00
|
|
|
options: AttackOptions,
|
|
|
|
// Currently strength_modifier just modifies damage,
|
|
|
|
// maybe look into modifying strength of other effects?
|
2021-01-31 16:40:04 +00:00
|
|
|
strength_modifier: f32,
|
2021-04-10 03:40:20 +00:00
|
|
|
attack_source: AttackSource,
|
2021-11-13 20:46:45 +00:00
|
|
|
time: Time,
|
2021-02-02 18:02:40 +00:00
|
|
|
mut emit: impl FnMut(ServerEvent),
|
2021-04-04 03:04:02 +00:00
|
|
|
mut emit_outcome: impl FnMut(Outcome),
|
2023-04-06 19:48:23 +00:00
|
|
|
rng: &mut rand::rngs::ThreadRng,
|
2023-04-24 23:55:14 +00:00
|
|
|
damage_instance_offset: u64,
|
2021-07-08 19:05:32 +00:00
|
|
|
) -> bool {
|
2022-05-28 23:41:31 +00:00
|
|
|
// TODO: Maybe move this higher and pass it as argument into this function?
|
|
|
|
let msm = &MaterialStatManifest::load().read();
|
|
|
|
|
2021-07-26 13:52:43 +00:00
|
|
|
let AttackOptions {
|
|
|
|
target_dodging,
|
2021-07-31 17:53:09 +00:00
|
|
|
may_harm,
|
2021-07-26 13:52:43 +00:00
|
|
|
target_group,
|
2023-10-22 21:00:09 +00:00
|
|
|
precision_mult,
|
2021-07-26 13:52:43 +00:00
|
|
|
} = options;
|
2021-07-27 21:57:48 +00:00
|
|
|
|
2021-07-26 13:52:43 +00:00
|
|
|
// target == OutOfGroup is basic heuristic that this
|
|
|
|
// "attack" has negative effects.
|
|
|
|
//
|
|
|
|
// so if target dodges this "attack" or we don't want to harm target,
|
|
|
|
// it should avoid such "damage" or effect
|
|
|
|
let avoid_damage = |attack_damage: &AttackDamage| {
|
|
|
|
matches!(attack_damage.target, Some(GroupTarget::OutOfGroup))
|
2021-07-31 17:53:09 +00:00
|
|
|
&& (target_dodging || !may_harm)
|
2021-07-26 13:52:43 +00:00
|
|
|
};
|
|
|
|
let avoid_effect = |attack_effect: &AttackEffect| {
|
|
|
|
matches!(attack_effect.target, Some(GroupTarget::OutOfGroup))
|
2021-07-31 17:53:09 +00:00
|
|
|
&& (target_dodging || !may_harm)
|
2021-07-26 13:52:43 +00:00
|
|
|
};
|
2023-10-22 21:00:09 +00:00
|
|
|
let precision_mult = attacker
|
|
|
|
.and_then(|a| a.stats)
|
|
|
|
.and_then(|s| s.precision_multiplier_override)
|
|
|
|
.or(precision_mult);
|
2021-07-26 13:52:43 +00:00
|
|
|
let mut is_applied = false;
|
2021-01-26 03:53:52 +00:00
|
|
|
let mut accumulated_damage = 0.0;
|
2022-12-19 18:26:03 +00:00
|
|
|
let damage_modifier = attacker
|
|
|
|
.and_then(|a| a.stats)
|
|
|
|
.map_or(1.0, |s| s.attack_damage_modifier);
|
2021-01-26 03:53:52 +00:00
|
|
|
for damage in self
|
|
|
|
.damages
|
|
|
|
.iter()
|
|
|
|
.filter(|d| d.target.map_or(true, |t| t == target_group))
|
2021-07-26 13:52:43 +00:00
|
|
|
.filter(|d| !avoid_damage(d))
|
2021-01-26 03:53:52 +00:00
|
|
|
{
|
2023-04-24 23:55:14 +00:00
|
|
|
let damage_instance = damage.instance + damage_instance_offset;
|
2021-07-08 19:05:32 +00:00
|
|
|
is_applied = true;
|
2021-05-06 18:50:16 +00:00
|
|
|
let damage_reduction = Attack::compute_damage_reduction(
|
2022-03-05 18:52:43 +00:00
|
|
|
attacker.as_ref(),
|
2023-04-24 01:31:29 +00:00
|
|
|
target,
|
2021-05-06 18:50:16 +00:00
|
|
|
attack_source,
|
|
|
|
dir,
|
2022-01-07 05:30:28 +00:00
|
|
|
damage.damage,
|
2022-05-28 23:41:31 +00:00
|
|
|
msm,
|
2021-11-28 17:24:57 +00:00
|
|
|
&mut emit,
|
|
|
|
&mut emit_outcome,
|
2021-05-06 18:50:16 +00:00
|
|
|
);
|
2021-02-02 18:02:40 +00:00
|
|
|
let change = damage.damage.calculate_health_change(
|
2021-02-28 20:02:03 +00:00
|
|
|
damage_reduction,
|
2021-11-13 20:46:45 +00:00
|
|
|
attacker.map(|x| x.into()),
|
2023-10-22 21:00:09 +00:00
|
|
|
precision_mult,
|
2023-11-11 19:20:38 +00:00
|
|
|
self.precision_multiplier,
|
2022-12-19 18:26:03 +00:00
|
|
|
strength_modifier * damage_modifier,
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-04-24 23:55:14 +00:00
|
|
|
damage_instance,
|
2021-01-28 01:44:49 +00:00
|
|
|
);
|
2021-10-28 22:37:51 +00:00
|
|
|
let applied_damage = -change.amount;
|
2021-01-31 17:33:22 +00:00
|
|
|
accumulated_damage += applied_damage;
|
2022-01-21 07:39:31 +00:00
|
|
|
|
2021-09-10 19:20:14 +00:00
|
|
|
if change.amount.abs() > Health::HEALTH_EPSILON {
|
2021-09-09 04:07:17 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-01-26 03:53:52 +00:00
|
|
|
change,
|
|
|
|
});
|
2022-01-07 05:30:28 +00:00
|
|
|
match damage.damage.kind {
|
|
|
|
DamageKind::Slashing => {
|
|
|
|
// For slashing damage, reduce target energy by some fraction of applied
|
|
|
|
// damage. When target would lose more energy than they have, deal an
|
|
|
|
// equivalent amount of damage
|
|
|
|
if let Some(target_energy) = target.energy {
|
|
|
|
let energy_change = applied_damage * SLASHING_ENERGY_FRACTION;
|
|
|
|
if energy_change > target_energy.current() {
|
2022-03-05 06:35:57 +00:00
|
|
|
let health_damage = energy_change - target_energy.current();
|
|
|
|
accumulated_damage += health_damage;
|
2022-01-07 05:30:28 +00:00
|
|
|
let health_change = HealthChange {
|
2022-03-05 06:35:57 +00:00
|
|
|
amount: -health_damage,
|
2022-01-07 05:30:28 +00:00
|
|
|
by: attacker.map(|x| x.into()),
|
|
|
|
cause: Some(damage.damage.source),
|
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: precision_mult.is_some(),
|
2023-04-24 23:55:14 +00:00
|
|
|
instance: damage_instance,
|
2022-01-07 05:30:28 +00:00
|
|
|
};
|
|
|
|
emit(ServerEvent::HealthChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change: health_change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
emit(ServerEvent::EnergyChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change: -energy_change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
DamageKind::Crushing => {
|
|
|
|
// For crushing damage, reduce target poise by some fraction of the amount
|
|
|
|
// of damage that was reduced by target's protection
|
|
|
|
// Damage reduction should never equal 1 here as otherwise the check above
|
|
|
|
// that health change amount is greater than 0 would fail.
|
|
|
|
let reduced_damage =
|
|
|
|
applied_damage * damage_reduction / (1.0 - damage_reduction);
|
2022-12-16 02:51:03 +00:00
|
|
|
let poise = reduced_damage
|
|
|
|
* CRUSHING_POISE_FRACTION
|
|
|
|
* attacker
|
|
|
|
.and_then(|a| a.stats)
|
|
|
|
.map_or(1.0, |s| s.poise_damage_modifier);
|
2022-03-05 20:51:41 +00:00
|
|
|
let change = -Poise::apply_poise_reduction(
|
|
|
|
poise,
|
|
|
|
target.inventory,
|
|
|
|
msm,
|
|
|
|
target.char_state,
|
|
|
|
target.stats,
|
|
|
|
);
|
2022-01-07 05:30:28 +00:00
|
|
|
let poise_change = PoiseChange {
|
|
|
|
amount: change,
|
|
|
|
impulse: *dir,
|
|
|
|
by: attacker.map(|x| x.into()),
|
|
|
|
cause: Some(damage.damage.source),
|
2022-01-08 05:36:15 +00:00
|
|
|
time,
|
2022-01-07 05:30:28 +00:00
|
|
|
};
|
|
|
|
if change.abs() > Poise::POISE_EPSILON {
|
2022-06-18 01:13:45 +00:00
|
|
|
// If target is in a stunned state, apply extra poise damage as health
|
|
|
|
// damage instead
|
|
|
|
if let Some(CharacterState::Stunned(data)) = target.char_state {
|
|
|
|
let health_change =
|
|
|
|
change * data.static_data.poise_state.damage_multiplier();
|
|
|
|
let health_change = HealthChange {
|
|
|
|
amount: health_change,
|
|
|
|
by: attacker.map(|x| x.into()),
|
|
|
|
cause: Some(damage.damage.source),
|
2023-04-24 23:55:14 +00:00
|
|
|
instance: damage_instance,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: precision_mult.is_some(),
|
2022-06-18 01:13:45 +00:00
|
|
|
time,
|
|
|
|
};
|
|
|
|
emit(ServerEvent::HealthChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change: health_change,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
emit(ServerEvent::PoiseChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change: poise_change,
|
|
|
|
});
|
|
|
|
}
|
2022-01-07 05:30:28 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
// Piercing damage ignores some penetration, and is handled when damage
|
|
|
|
// reduction is computed Energy is a placeholder damage type
|
|
|
|
DamageKind::Piercing | DamageKind::Energy => {},
|
|
|
|
}
|
2021-01-26 03:53:52 +00:00
|
|
|
for effect in damage.effects.iter() {
|
|
|
|
match effect {
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Knockback(kb) => {
|
2022-10-24 01:12:59 +00:00
|
|
|
let impulse =
|
|
|
|
kb.calculate_impulse(dir, target.char_state) * strength_modifier;
|
2021-01-26 03:53:52 +00:00
|
|
|
if !impulse.is_approx_zero() {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Knockback {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-01-26 03:53:52 +00:00
|
|
|
impulse,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::EnergyReward(ec) => {
|
2021-05-19 01:42:14 +00:00
|
|
|
if let Some(attacker) = attacker {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::EnergyChange {
|
2021-05-19 01:42:14 +00:00
|
|
|
entity: attacker.entity,
|
2021-10-28 22:37:51 +00:00
|
|
|
change: *ec
|
2022-05-28 23:41:31 +00:00
|
|
|
* compute_energy_reward_mod(attacker.inventory, msm)
|
2023-07-03 00:50:25 +00:00
|
|
|
* strength_modifier
|
|
|
|
* attacker.stats.map_or(1.0, |s| s.energy_reward_modifier),
|
2021-02-01 02:43:26 +00:00
|
|
|
});
|
|
|
|
}
|
2021-01-26 17:58:52 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Buff(b) => {
|
2022-11-16 00:46:34 +00:00
|
|
|
if rng.gen::<f32>() < b.chance {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Buff {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-10-28 22:37:51 +00:00
|
|
|
buff_change: BuffChange::Add(b.to_buff(
|
2023-03-08 03:22:54 +00:00
|
|
|
time,
|
2021-10-28 22:37:51 +00:00
|
|
|
attacker.map(|a| a.uid),
|
2023-03-09 04:10:24 +00:00
|
|
|
target.stats,
|
2022-12-16 02:51:03 +00:00
|
|
|
target.health,
|
2021-10-28 22:37:51 +00:00
|
|
|
applied_damage,
|
|
|
|
strength_modifier,
|
|
|
|
)),
|
2021-01-28 01:44:49 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Lifesteal(l) => {
|
2022-07-15 16:59:37 +00:00
|
|
|
// Not modified by strength_modifier as damage already is
|
2021-02-28 20:02:03 +00:00
|
|
|
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
|
2021-02-01 02:43:26 +00:00
|
|
|
let change = HealthChange {
|
2021-09-09 04:07:17 +00:00
|
|
|
amount: applied_damage * l,
|
2021-11-13 20:46:45 +00:00
|
|
|
by: attacker.map(|a| a.into()),
|
2021-09-09 04:07:17 +00:00
|
|
|
cause: None,
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance: rand::random(),
|
2021-02-01 02:43:26 +00:00
|
|
|
};
|
2021-09-10 19:20:14 +00:00
|
|
|
if change.amount.abs() > Health::HEALTH_EPSILON {
|
2021-09-09 04:07:17 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
2021-02-02 18:02:40 +00:00
|
|
|
entity: attacker_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
CombatEffect::Poise(p) => {
|
2022-03-05 20:51:41 +00:00
|
|
|
let change = -Poise::apply_poise_reduction(
|
|
|
|
*p,
|
|
|
|
target.inventory,
|
|
|
|
msm,
|
|
|
|
target.char_state,
|
|
|
|
target.stats,
|
2022-12-16 02:51:03 +00:00
|
|
|
) * strength_modifier
|
|
|
|
* attacker
|
|
|
|
.and_then(|a| a.stats)
|
|
|
|
.map_or(1.0, |s| s.poise_damage_modifier);
|
2021-09-25 18:07:47 +00:00
|
|
|
if change.abs() > Poise::POISE_EPSILON {
|
2022-01-07 05:30:28 +00:00
|
|
|
let poise_change = PoiseChange {
|
|
|
|
amount: change,
|
|
|
|
impulse: *dir,
|
|
|
|
by: attacker.map(|x| x.into()),
|
|
|
|
cause: Some(damage.damage.source),
|
2022-01-08 05:36:15 +00:00
|
|
|
time,
|
2022-01-07 05:30:28 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::PoiseChange {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2022-01-07 05:30:28 +00:00
|
|
|
change: poise_change,
|
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 {
|
2021-10-28 22:37:51 +00:00
|
|
|
amount: *h * strength_modifier,
|
2021-11-13 20:46:45 +00:00
|
|
|
by: attacker.map(|a| a.into()),
|
2021-09-09 04:07:17 +00:00
|
|
|
cause: None,
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance: rand::random(),
|
2021-01-30 05:03:23 +00:00
|
|
|
};
|
2021-09-10 19:20:14 +00:00
|
|
|
if change.amount.abs() > Health::HEALTH_EPSILON {
|
2021-09-09 04:07:17 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-02-02 18:02:40 +00:00
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
2021-01-30 05:03:23 +00:00
|
|
|
},
|
2021-02-27 19:55:06 +00:00
|
|
|
CombatEffect::Combo(c) => {
|
2021-10-28 22:37:51 +00:00
|
|
|
// Not affected by strength modifier as integer
|
2021-02-28 20:02:03 +00:00
|
|
|
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
|
2021-02-27 19:55:06 +00:00
|
|
|
emit(ServerEvent::ComboChange {
|
|
|
|
entity: attacker_entity,
|
|
|
|
change: *c,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2022-11-27 21:11:19 +00:00
|
|
|
CombatEffect::StageVulnerable(damage, section) => {
|
|
|
|
if target
|
|
|
|
.char_state
|
|
|
|
.map_or(false, |cs| cs.stage_section() == Some(*section))
|
|
|
|
{
|
|
|
|
let change = {
|
|
|
|
let mut change = change;
|
|
|
|
change.amount *= damage;
|
|
|
|
change
|
|
|
|
};
|
2022-09-07 01:23:12 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2022-11-16 00:46:34 +00:00
|
|
|
CombatEffect::RefreshBuff(chance, b) => {
|
|
|
|
if rng.gen::<f32>() < *chance {
|
|
|
|
emit(ServerEvent::Buff {
|
|
|
|
entity: target.entity,
|
|
|
|
buff_change: BuffChange::Refresh(*b),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2022-11-27 21:11:19 +00:00
|
|
|
CombatEffect::BuffsVulnerable(damage, buff) => {
|
|
|
|
if target.buffs.map_or(false, |b| b.contains(*buff)) {
|
|
|
|
let change = {
|
|
|
|
let mut change = change;
|
|
|
|
change.amount *= damage;
|
|
|
|
change
|
|
|
|
};
|
|
|
|
emit(ServerEvent::HealthChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2023-02-11 00:31:07 +00:00
|
|
|
CombatEffect::StunnedVulnerable(damage) => {
|
|
|
|
if target.char_state.map_or(false, |cs| cs.is_stunned()) {
|
|
|
|
let change = {
|
|
|
|
let mut change = change;
|
|
|
|
change.amount *= damage;
|
|
|
|
change
|
|
|
|
};
|
|
|
|
emit(ServerEvent::HealthChange {
|
|
|
|
entity: target.entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-01-26 03:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-28 02:15:25 +00:00
|
|
|
for effect in self
|
|
|
|
.effects
|
|
|
|
.iter()
|
2023-05-19 03:07:44 +00:00
|
|
|
.chain(
|
|
|
|
attacker
|
|
|
|
.and_then(|attacker| attacker.stats)
|
|
|
|
.iter()
|
2023-07-03 00:50:25 +00:00
|
|
|
.flat_map(|stats| stats.effects_on_attack.iter()),
|
2023-05-19 03:07:44 +00:00
|
|
|
)
|
2021-01-28 02:15:25 +00:00
|
|
|
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
2021-07-26 13:52:43 +00:00
|
|
|
.filter(|e| !avoid_effect(e))
|
2021-01-28 02:15:25 +00:00
|
|
|
{
|
2023-04-07 06:10:24 +00:00
|
|
|
let requirements_met = effect.requirements.iter().all(|req| match req {
|
2021-03-20 20:26:10 +00:00
|
|
|
CombatRequirement::AnyDamage => accumulated_damage > 0.0 && target.health.is_some(),
|
2021-03-09 00:23:00 +00:00
|
|
|
CombatRequirement::Energy(r) => {
|
2021-02-02 18:02:40 +00:00
|
|
|
if let Some(AttackerInfo {
|
|
|
|
entity,
|
|
|
|
energy: Some(e),
|
|
|
|
..
|
2021-02-28 20:02:03 +00:00
|
|
|
}) = attacker
|
2021-02-02 18:02:40 +00:00
|
|
|
{
|
2021-09-14 02:16:01 +00:00
|
|
|
let sufficient_energy = e.current() >= *r;
|
2021-02-02 18:02:40 +00:00
|
|
|
if sufficient_energy {
|
|
|
|
emit(ServerEvent::EnergyChange {
|
|
|
|
entity,
|
2021-09-14 02:16:01 +00:00
|
|
|
change: -*r,
|
2021-02-01 02:43:26 +00:00
|
|
|
});
|
|
|
|
}
|
2021-02-02 18:02:40 +00:00
|
|
|
|
|
|
|
sufficient_energy
|
2021-01-30 05:17:40 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
2021-03-09 00:23:00 +00:00
|
|
|
CombatRequirement::Combo(r) => {
|
2021-03-02 22:19:38 +00:00
|
|
|
if let Some(AttackerInfo {
|
|
|
|
entity,
|
|
|
|
combo: Some(c),
|
|
|
|
..
|
|
|
|
}) = attacker
|
|
|
|
{
|
|
|
|
let sufficient_combo = c.counter() >= *r;
|
|
|
|
if sufficient_combo {
|
|
|
|
emit(ServerEvent::ComboChange {
|
|
|
|
entity,
|
|
|
|
change: -(*r as i32),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
sufficient_combo
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
2023-07-03 00:50:25 +00:00
|
|
|
CombatRequirement::TargetHasBuff(buff) => {
|
|
|
|
target.buffs.map_or(false, |buffs| buffs.contains(*buff))
|
|
|
|
},
|
2023-04-07 06:10:24 +00:00
|
|
|
});
|
|
|
|
if requirements_met {
|
2021-07-08 19:05:32 +00:00
|
|
|
is_applied = true;
|
2021-01-28 18:50:56 +00:00
|
|
|
match effect.effect {
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Knockback(kb) => {
|
2022-10-24 01:12:59 +00:00
|
|
|
let impulse =
|
|
|
|
kb.calculate_impulse(dir, target.char_state) * strength_modifier;
|
2021-01-28 18:50:56 +00:00
|
|
|
if !impulse.is_approx_zero() {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Knockback {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-01-28 18:50:56 +00:00
|
|
|
impulse,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::EnergyReward(ec) => {
|
2021-05-19 01:42:14 +00:00
|
|
|
if let Some(attacker) = attacker {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::EnergyChange {
|
2021-05-19 01:42:14 +00:00
|
|
|
entity: attacker.entity,
|
2021-10-28 22:37:51 +00:00
|
|
|
change: ec
|
2022-05-28 23:41:31 +00:00
|
|
|
* compute_energy_reward_mod(attacker.inventory, msm)
|
2023-07-03 00:50:25 +00:00
|
|
|
* strength_modifier
|
|
|
|
* attacker.stats.map_or(1.0, |s| s.energy_reward_modifier),
|
2021-02-01 02:43:26 +00:00
|
|
|
});
|
|
|
|
}
|
2021-01-28 18:50:56 +00:00
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Buff(b) => {
|
2022-11-16 00:46:34 +00:00
|
|
|
if rng.gen::<f32>() < b.chance {
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::Buff {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-10-28 22:37:51 +00:00
|
|
|
buff_change: BuffChange::Add(b.to_buff(
|
2023-03-08 03:22:54 +00:00
|
|
|
time,
|
2021-10-28 22:37:51 +00:00
|
|
|
attacker.map(|a| a.uid),
|
2023-03-09 04:10:24 +00:00
|
|
|
target.stats,
|
2022-12-16 02:51:03 +00:00
|
|
|
target.health,
|
2021-10-28 22:37:51 +00:00
|
|
|
accumulated_damage,
|
|
|
|
strength_modifier,
|
|
|
|
)),
|
2021-01-28 18:50:56 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2021-02-02 18:02:40 +00:00
|
|
|
CombatEffect::Lifesteal(l) => {
|
2022-07-15 16:59:37 +00:00
|
|
|
// Not modified by strength_modifier as damage already is
|
2021-02-28 20:02:03 +00:00
|
|
|
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
|
2021-02-01 02:43:26 +00:00
|
|
|
let change = HealthChange {
|
2021-09-09 04:07:17 +00:00
|
|
|
amount: accumulated_damage * l,
|
2021-11-13 20:46:45 +00:00
|
|
|
by: attacker.map(|a| a.into()),
|
2021-09-09 04:07:17 +00:00
|
|
|
cause: None,
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance: rand::random(),
|
2021-02-01 02:43:26 +00:00
|
|
|
};
|
2021-09-10 19:20:14 +00:00
|
|
|
if change.amount.abs() > Health::HEALTH_EPSILON {
|
2021-09-09 04:07:17 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
2021-02-02 18:02:40 +00:00
|
|
|
entity: attacker_entity,
|
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
CombatEffect::Poise(p) => {
|
2022-03-05 20:51:41 +00:00
|
|
|
let change = -Poise::apply_poise_reduction(
|
|
|
|
p,
|
|
|
|
target.inventory,
|
|
|
|
msm,
|
|
|
|
target.char_state,
|
|
|
|
target.stats,
|
2022-12-16 02:51:03 +00:00
|
|
|
) * strength_modifier
|
|
|
|
* attacker
|
|
|
|
.and_then(|a| a.stats)
|
|
|
|
.map_or(1.0, |s| s.poise_damage_modifier);
|
2021-09-25 18:07:47 +00:00
|
|
|
if change.abs() > Poise::POISE_EPSILON {
|
2022-01-07 05:30:28 +00:00
|
|
|
let poise_change = PoiseChange {
|
|
|
|
amount: change,
|
|
|
|
impulse: *dir,
|
|
|
|
by: attacker.map(|x| x.into()),
|
|
|
|
cause: Some(attack_source.into()),
|
2022-01-08 05:36:15 +00:00
|
|
|
time,
|
2022-01-07 05:30:28 +00:00
|
|
|
};
|
2021-02-02 18:02:40 +00:00
|
|
|
emit(ServerEvent::PoiseChange {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2022-01-07 05:30:28 +00:00
|
|
|
change: poise_change,
|
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 {
|
2021-10-28 22:37:51 +00:00
|
|
|
amount: h * strength_modifier,
|
2021-11-13 20:46:45 +00:00
|
|
|
by: attacker.map(|a| a.into()),
|
2021-09-09 04:07:17 +00:00
|
|
|
cause: None,
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance: rand::random(),
|
2021-01-30 05:03:23 +00:00
|
|
|
};
|
2021-09-10 19:20:14 +00:00
|
|
|
if change.amount.abs() > Health::HEALTH_EPSILON {
|
2021-09-09 04:07:17 +00:00
|
|
|
emit(ServerEvent::HealthChange {
|
2021-02-28 20:02:03 +00:00
|
|
|
entity: target.entity,
|
2021-02-02 18:02:40 +00:00
|
|
|
change,
|
|
|
|
});
|
|
|
|
}
|
2021-01-30 05:03:23 +00:00
|
|
|
},
|
2021-02-27 19:55:06 +00:00
|
|
|
CombatEffect::Combo(c) => {
|
2021-10-28 22:37:51 +00:00
|
|
|
// Not affected by strength modifier as integer
|
2021-02-28 20:02:03 +00:00
|
|
|
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
|
2021-02-27 19:55:06 +00:00
|
|
|
emit(ServerEvent::ComboChange {
|
|
|
|
entity: attacker_entity,
|
|
|
|
change: c,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2022-09-07 01:23:12 +00:00
|
|
|
// Only has an effect when attached to a damage
|
2022-11-27 21:11:19 +00:00
|
|
|
CombatEffect::StageVulnerable(_, _) => {},
|
2022-11-16 00:46:34 +00:00
|
|
|
CombatEffect::RefreshBuff(chance, b) => {
|
|
|
|
if rng.gen::<f32>() < chance {
|
|
|
|
emit(ServerEvent::Buff {
|
|
|
|
entity: target.entity,
|
|
|
|
buff_change: BuffChange::Refresh(b),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2022-11-27 21:11:19 +00:00
|
|
|
// Only has an effect when attached to a damage
|
|
|
|
CombatEffect::BuffsVulnerable(_, _) => {},
|
2023-02-11 00:31:07 +00:00
|
|
|
CombatEffect::StunnedVulnerable(_) => {},
|
2021-01-28 18:50:56 +00:00
|
|
|
}
|
2021-01-28 02:15:25 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-29 01:55:01 +00:00
|
|
|
// Emits event to handle things that should happen for any successful attack,
|
|
|
|
// regardless of if the attack had any damages or effects in it
|
|
|
|
if is_applied {
|
|
|
|
emit(ServerEvent::EntityAttackedHook {
|
|
|
|
entity: target.entity,
|
2023-05-15 01:38:11 +00:00
|
|
|
attacker: attacker.map(|a| a.entity),
|
2021-08-29 01:55:01 +00:00
|
|
|
});
|
|
|
|
}
|
2021-07-08 19:05:32 +00:00
|
|
|
is_applied
|
2021-01-26 02:50:16 +00:00
|
|
|
}
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-08-27 18:49:58 +00:00
|
|
|
/// Function that checks for unintentional PvP between players.
|
2021-08-27 12:52:52 +00:00
|
|
|
///
|
2021-08-27 18:49:58 +00:00
|
|
|
/// Returns `false` if attack will create unintentional conflict,
|
|
|
|
/// e.g. if player with PvE mode will harm pets of other players
|
|
|
|
/// or other players will do the same to such player.
|
|
|
|
///
|
|
|
|
/// If both players have PvP mode enabled, interact with NPC and
|
|
|
|
/// in any other case, this function will return `true`
|
|
|
|
// TODO: add parameter for doing self-harm?
|
|
|
|
pub fn may_harm(
|
|
|
|
alignments: &ReadStorage<Alignment>,
|
|
|
|
players: &ReadStorage<Player>,
|
2023-04-26 04:31:14 +00:00
|
|
|
id_maps: &IdMaps,
|
2021-08-27 18:49:58 +00:00
|
|
|
attacker: Option<EcsEntity>,
|
|
|
|
target: EcsEntity,
|
|
|
|
) -> bool {
|
|
|
|
// Return owner entity if pet,
|
|
|
|
// or just return entity back otherwise
|
|
|
|
let owner_if_pet = |entity| {
|
|
|
|
let alignment = alignments.get(entity).copied();
|
|
|
|
if let Some(Alignment::Owned(uid)) = alignment {
|
|
|
|
// return original entity
|
|
|
|
// if can't get owner
|
2023-04-26 04:31:14 +00:00
|
|
|
id_maps.uid_entity(uid).unwrap_or(entity)
|
2021-08-27 18:49:58 +00:00
|
|
|
} else {
|
|
|
|
entity
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Just return ok if attacker is unknown, it's probably
|
|
|
|
// environment or command.
|
|
|
|
let attacker = match attacker {
|
|
|
|
Some(attacker) => attacker,
|
|
|
|
None => return true,
|
|
|
|
};
|
|
|
|
|
|
|
|
// "Dereference" to owner if this is a pet.
|
|
|
|
let attacker = owner_if_pet(attacker);
|
|
|
|
let target = owner_if_pet(target);
|
|
|
|
|
|
|
|
// Get player components
|
|
|
|
let attacker_info = players.get(attacker);
|
|
|
|
let target_info = players.get(target);
|
|
|
|
|
|
|
|
// Return `true` if not players.
|
|
|
|
attacker_info
|
|
|
|
.zip(target_info)
|
|
|
|
.map_or(true, |(a, t)| a.may_harm(t))
|
2021-07-27 21:57:48 +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>,
|
2022-01-29 19:03:04 +00:00
|
|
|
/// A random ID, used to group up attacks
|
2022-01-27 17:27:21 +00:00
|
|
|
instance: u64,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
impl AttackDamage {
|
2022-01-27 17:27:21 +00:00
|
|
|
pub fn new(damage: Damage, target: Option<GroupTarget>, instance: u64) -> Self {
|
2021-01-26 00:01:07 +00:00
|
|
|
Self {
|
|
|
|
damage,
|
|
|
|
target,
|
|
|
|
effects: Vec::new(),
|
2022-01-27 17:27:21 +00:00
|
|
|
instance,
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
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-03-09 00:23:00 +00:00
|
|
|
requirements: Vec<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,
|
2021-03-09 00:23:00 +00:00
|
|
|
requirements: Vec::new(),
|
2021-01-28 18:50:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
2021-01-28 18:50:56 +00:00
|
|
|
pub fn with_requirement(mut self, requirement: CombatRequirement) -> Self {
|
2021-03-09 00:23:00 +00:00
|
|
|
self.requirements.push(requirement);
|
2021-01-28 18:50:56 +00:00
|
|
|
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-03-20 17:29:57 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
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-02-05 01:39:12 +00:00
|
|
|
EnergyReward(f32),
|
2021-01-29 01:55:43 +00:00
|
|
|
Lifesteal(f32),
|
2021-01-30 04:40:03 +00:00
|
|
|
Poise(f32),
|
2021-02-27 19:55:06 +00:00
|
|
|
Combo(i32),
|
2022-11-27 21:11:19 +00:00
|
|
|
/// If the attack hits the target while they are in the buildup portion of a
|
|
|
|
/// character state, deal increased damage
|
|
|
|
/// Only has an effect when attached to a damage, otherwise does nothing if
|
|
|
|
/// only attached to the attack
|
2022-11-16 00:46:34 +00:00
|
|
|
// TODO: Maybe try to make it do something if tied to
|
2022-09-07 01:23:12 +00:00
|
|
|
// attack, not sure if it should double count in that instance?
|
2022-11-27 21:11:19 +00:00
|
|
|
StageVulnerable(f32, StageSection),
|
|
|
|
/// Resets duration of all buffs of this buffkind, with some probability
|
2022-11-16 00:46:34 +00:00
|
|
|
RefreshBuff(f32, BuffKind),
|
2022-11-27 21:11:19 +00:00
|
|
|
/// If the target hit by an attack has this buff, they will take increased
|
2023-02-11 00:31:07 +00:00
|
|
|
/// damage.
|
|
|
|
/// Only has an effect when attached to a damage, otherwise does nothing if
|
|
|
|
/// only attached to the attack
|
2022-11-27 21:11:19 +00:00
|
|
|
// TODO: Maybe try to make it do something if tied to attack, not sure if it should double
|
|
|
|
// count in that instance?
|
|
|
|
BuffsVulnerable(f32, BuffKind),
|
2023-02-11 00:31:07 +00:00
|
|
|
/// If the target hit by an attack is in a stunned state, they will take
|
|
|
|
/// increased damage.
|
|
|
|
/// Only has an effect when attached to a damage, otherwise does nothing if
|
|
|
|
/// only attached to the attack
|
|
|
|
// TODO: Maybe try to make it do something if tied to attack, not sure if it should double
|
|
|
|
// count in that instance?
|
|
|
|
StunnedVulnerable(f32),
|
2021-01-26 00:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-02-14 02:43:52 +00:00
|
|
|
impl CombatEffect {
|
|
|
|
pub fn adjusted_by_stats(self, stats: tool::Stats) -> Self {
|
|
|
|
match self {
|
|
|
|
CombatEffect::Heal(h) => CombatEffect::Heal(h * stats.effect_power),
|
|
|
|
CombatEffect::Buff(CombatBuff {
|
|
|
|
kind,
|
|
|
|
dur_secs,
|
|
|
|
strength,
|
|
|
|
chance,
|
|
|
|
}) => CombatEffect::Buff(CombatBuff {
|
|
|
|
kind,
|
|
|
|
dur_secs,
|
|
|
|
strength: strength * stats.buff_strength,
|
|
|
|
chance,
|
|
|
|
}),
|
|
|
|
CombatEffect::Knockback(Knockback {
|
|
|
|
direction,
|
|
|
|
strength,
|
|
|
|
}) => CombatEffect::Knockback(Knockback {
|
|
|
|
direction,
|
2023-05-27 22:00:04 +00:00
|
|
|
strength: strength * stats.effect_power,
|
2023-02-14 02:43:52 +00:00
|
|
|
}),
|
|
|
|
CombatEffect::EnergyReward(e) => CombatEffect::EnergyReward(e),
|
|
|
|
CombatEffect::Lifesteal(l) => CombatEffect::Lifesteal(l * stats.effect_power),
|
|
|
|
CombatEffect::Poise(p) => CombatEffect::Poise(p * stats.effect_power),
|
|
|
|
CombatEffect::Combo(c) => CombatEffect::Combo(c),
|
|
|
|
CombatEffect::StageVulnerable(v, s) => {
|
|
|
|
CombatEffect::StageVulnerable(v * stats.effect_power, s)
|
|
|
|
},
|
|
|
|
CombatEffect::RefreshBuff(c, b) => CombatEffect::RefreshBuff(c, b),
|
|
|
|
CombatEffect::BuffsVulnerable(v, b) => {
|
|
|
|
CombatEffect::BuffsVulnerable(v * stats.effect_power, b)
|
|
|
|
},
|
|
|
|
CombatEffect::StunnedVulnerable(v) => {
|
|
|
|
CombatEffect::StunnedVulnerable(v * stats.effect_power)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-03-02 22:19:38 +00:00
|
|
|
Energy(f32),
|
|
|
|
Combo(u32),
|
2023-07-03 00:50:25 +00:00
|
|
|
TargetHasBuff(BuffKind),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum DamagedEffect {
|
|
|
|
Combo(i32),
|
2021-01-28 18:50:56 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 20:46:45 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub enum DamageContributor {
|
|
|
|
Solo(Uid),
|
|
|
|
Group { entity_uid: Uid, group: Group },
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DamageContributor {
|
|
|
|
pub fn new(uid: Uid, group: Option<Group>) -> Self {
|
|
|
|
if let Some(group) = group {
|
|
|
|
DamageContributor::Group {
|
|
|
|
entity_uid: uid,
|
|
|
|
group,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DamageContributor::Solo(uid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn uid(&self) -> Uid {
|
|
|
|
match self {
|
|
|
|
DamageContributor::Solo(uid) => *uid,
|
|
|
|
DamageContributor::Group {
|
|
|
|
entity_uid,
|
|
|
|
group: _,
|
|
|
|
} => *entity_uid,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<AttackerInfo<'_>> for DamageContributor {
|
|
|
|
fn from(attacker_info: AttackerInfo) -> Self {
|
|
|
|
DamageContributor::new(attacker_info.uid, attacker_info.group.copied())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-01-07 05:30:28 +00:00
|
|
|
impl From<AttackSource> for DamageSource {
|
|
|
|
fn from(attack: AttackSource) -> Self {
|
|
|
|
match attack {
|
|
|
|
AttackSource::Melee => DamageSource::Melee,
|
|
|
|
AttackSource::Projectile => DamageSource::Projectile,
|
|
|
|
AttackSource::Explosion => DamageSource::Explosion,
|
2023-10-08 11:35:01 +00:00
|
|
|
AttackSource::AirShockwave
|
|
|
|
| AttackSource::GroundShockwave
|
|
|
|
| AttackSource::UndodgeableShockwave => DamageSource::Shockwave,
|
2022-01-07 05:30:28 +00:00
|
|
|
AttackSource::Beam => DamageSource::Energy,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-06 18:50:16 +00:00
|
|
|
/// DamageKind for the purpose of differentiating damage reduction
|
|
|
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
pub enum DamageKind {
|
2022-01-07 05:30:28 +00:00
|
|
|
/// Bypasses some protection from armor
|
2021-05-06 18:50:16 +00:00
|
|
|
Piercing,
|
2022-01-07 05:30:28 +00:00
|
|
|
/// Reduces energy of target, dealing additional damage when target energy
|
|
|
|
/// is 0
|
2021-05-06 18:50:16 +00:00
|
|
|
Slashing,
|
2022-01-07 05:30:28 +00:00
|
|
|
/// Deals additional poise damage the more armored the target is
|
2021-05-06 18:50:16 +00:00
|
|
|
Crushing,
|
2022-01-07 05:30:28 +00:00
|
|
|
/// Catch all for remaining damage kinds (TODO: differentiate further with
|
|
|
|
/// staff/sceptre reworks
|
2021-05-06 18:50:16 +00:00
|
|
|
Energy,
|
|
|
|
}
|
|
|
|
|
2022-07-26 23:44:49 +00:00
|
|
|
const PIERCING_PENETRATION_FRACTION: f32 = 1.5;
|
2022-02-22 23:17:23 +00:00
|
|
|
const SLASHING_ENERGY_FRACTION: f32 = 0.5;
|
2022-01-07 05:30:28 +00:00
|
|
|
const CRUSHING_POISE_FRACTION: f32 = 1.0;
|
|
|
|
|
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,
|
2021-05-06 18:50:16 +00:00
|
|
|
pub kind: DamageKind,
|
2020-10-30 20:41:21 +00:00
|
|
|
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
|
2021-05-06 18:50:16 +00:00
|
|
|
pub fn compute_damage_reduction(
|
2022-01-07 05:30:28 +00:00
|
|
|
damage: Option<Self>,
|
2021-05-06 18:50:16 +00:00
|
|
|
inventory: Option<&Inventory>,
|
|
|
|
stats: Option<&Stats>,
|
2022-05-28 23:41:31 +00:00
|
|
|
msm: &MaterialStatManifest,
|
2021-05-06 18:50:16 +00:00
|
|
|
) -> f32 {
|
2022-05-28 23:41:31 +00:00
|
|
|
let protection = compute_protection(inventory, msm);
|
2021-12-03 18:21:17 +00:00
|
|
|
|
|
|
|
let penetration = if let Some(damage) = damage {
|
|
|
|
if let DamageKind::Piercing = damage.kind {
|
2022-11-28 11:24:27 +00:00
|
|
|
(damage.value * PIERCING_PENETRATION_FRACTION).clamp(0.0, protection.unwrap_or(0.0))
|
2021-05-06 18:50:16 +00:00
|
|
|
} else {
|
2022-01-07 05:30:28 +00:00
|
|
|
0.0
|
2021-02-28 20:02:03 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2021-12-03 18:21:17 +00:00
|
|
|
|
|
|
|
let protection = protection.map(|p| p - penetration);
|
|
|
|
|
|
|
|
const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0;
|
|
|
|
|
|
|
|
let inventory_dr = match protection {
|
|
|
|
Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()),
|
|
|
|
None => 1.0,
|
|
|
|
};
|
|
|
|
|
2021-02-28 20:02:03 +00:00
|
|
|
let stats_dr = if let Some(stats) = stats {
|
|
|
|
stats.damage_reduction
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2021-02-28 23:14:59 +00:00
|
|
|
1.0 - (1.0 - inventory_dr) * (1.0 - stats_dr)
|
2021-01-08 19:12:09 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:02:40 +00:00
|
|
|
pub fn calculate_health_change(
|
2021-01-26 03:53:52 +00:00
|
|
|
self,
|
2021-02-28 20:02:03 +00:00
|
|
|
damage_reduction: f32,
|
2021-11-13 20:46:45 +00:00
|
|
|
damage_contributor: Option<DamageContributor>,
|
2023-10-22 21:00:09 +00:00
|
|
|
precision_mult: Option<f32>,
|
2023-11-11 19:20:38 +00:00
|
|
|
precision_power: f32,
|
2021-01-31 16:40:04 +00:00
|
|
|
damage_modifier: f32,
|
2021-11-13 20:46:45 +00:00
|
|
|
time: Time,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance: u64,
|
2021-01-26 03:53:52 +00:00
|
|
|
) -> HealthChange {
|
2021-01-31 16:40:04 +00:00
|
|
|
let mut damage = self.value * damage_modifier;
|
2023-11-11 19:20:38 +00:00
|
|
|
let precise_damage = damage * precision_mult.unwrap_or(0.0) * (precision_power - 1.0);
|
2020-10-30 20:41:21 +00:00
|
|
|
match self.source {
|
2021-06-30 00:13:28 +00:00
|
|
|
DamageSource::Melee
|
|
|
|
| DamageSource::Projectile
|
|
|
|
| DamageSource::Explosion
|
|
|
|
| DamageSource::Shockwave
|
|
|
|
| DamageSource::Energy => {
|
2023-11-11 19:20:38 +00:00
|
|
|
// Precise hit
|
|
|
|
damage += precise_damage;
|
2020-10-17 22:42:43 +00:00
|
|
|
// Armor
|
|
|
|
damage *= 1.0 - damage_reduction;
|
|
|
|
|
2020-12-06 02:29:46 +00:00
|
|
|
HealthChange {
|
2021-09-09 04:07:17 +00:00
|
|
|
amount: -damage,
|
2021-11-13 20:46:45 +00:00
|
|
|
by: damage_contributor,
|
2021-09-09 04:07:17 +00:00
|
|
|
cause: Some(self.source),
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: precision_mult.is_some(),
|
2022-01-26 10:16:45 +00:00
|
|
|
instance,
|
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 {
|
2021-09-09 04:07:17 +00:00
|
|
|
amount: -damage,
|
|
|
|
by: None,
|
|
|
|
cause: Some(self.source),
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance,
|
2021-01-15 03:32:12 +00:00
|
|
|
}
|
2020-10-17 22:42:43 +00:00
|
|
|
},
|
2021-06-30 00:13:28 +00:00
|
|
|
DamageSource::Buff(_) | DamageSource::Other => HealthChange {
|
2021-09-09 04:07:17 +00:00
|
|
|
amount: -damage,
|
|
|
|
by: None,
|
|
|
|
cause: Some(self.source),
|
2021-11-13 20:46:45 +00:00
|
|
|
time,
|
2023-11-11 19:20:38 +00:00
|
|
|
precise: false,
|
2022-01-26 10:16:45 +00:00
|
|
|
instance,
|
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,
|
|
|
|
}
|
|
|
|
|
2022-09-08 19:51:02 +00:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2021-01-25 22:46:43 +00:00
|
|
|
pub enum KnockbackDir {
|
|
|
|
Away,
|
|
|
|
Towards,
|
|
|
|
Up,
|
|
|
|
TowardsUp,
|
2020-10-18 18:21:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Knockback {
|
2022-10-24 01:12:59 +00:00
|
|
|
pub fn calculate_impulse(self, dir: Dir, char_state: Option<&CharacterState>) -> Vec3<f32> {
|
|
|
|
let from_char = {
|
|
|
|
let resistant = char_state
|
|
|
|
.and_then(|cs| cs.ability_info())
|
2022-10-30 18:40:20 +00:00
|
|
|
.map(|a| a.ability_meta)
|
2022-10-24 01:12:59 +00:00
|
|
|
.map_or(false, |a| {
|
|
|
|
a.capabilities.contains(Capability::KNOCKBACK_RESISTANT)
|
|
|
|
});
|
|
|
|
if resistant { 0.5 } else { 1.0 }
|
|
|
|
};
|
|
|
|
// TEMP: 50.0 multiplication kept until source knockback values have been
|
|
|
|
// updated
|
|
|
|
50.0 * self.strength
|
|
|
|
* from_char
|
|
|
|
* match self.direction {
|
|
|
|
KnockbackDir::Away => *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5),
|
|
|
|
KnockbackDir::Towards => *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5),
|
|
|
|
KnockbackDir::Up => Vec3::unit_z(),
|
|
|
|
KnockbackDir::TowardsUp => *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
|
|
|
|
2021-12-21 20:19:23 +00:00
|
|
|
#[must_use]
|
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-03-20 17:29:57 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
2021-01-28 01:44:49 +00:00
|
|
|
pub struct CombatBuff {
|
|
|
|
pub kind: BuffKind,
|
|
|
|
pub dur_secs: f32,
|
|
|
|
pub strength: CombatBuffStrength,
|
|
|
|
pub chance: f32,
|
|
|
|
}
|
|
|
|
|
2021-03-20 17:29:57 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
|
2021-01-28 01:44:49 +00:00
|
|
|
pub enum CombatBuffStrength {
|
|
|
|
DamageFraction(f32),
|
|
|
|
Value(f32),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CombatBuffStrength {
|
2021-10-28 22:37:51 +00:00
|
|
|
fn to_strength(self, damage: f32, strength_modifier: f32) -> f32 {
|
2021-01-28 01:44:49 +00:00
|
|
|
match self {
|
2021-10-28 22:37:51 +00:00
|
|
|
// Not affected by strength modifier as damage already is
|
2021-01-28 01:44:49 +00:00
|
|
|
CombatBuffStrength::DamageFraction(f) => damage * f,
|
2021-10-28 22:37:51 +00:00
|
|
|
CombatBuffStrength::Value(v) => v * strength_modifier,
|
2021-01-28 01:44:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-10 16:04:12 +00:00
|
|
|
impl MulAssign<f32> for CombatBuffStrength {
|
2023-02-14 02:43:52 +00:00
|
|
|
fn mul_assign(&mut self, mul: f32) { *self = *self * mul; }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Mul<f32> for CombatBuffStrength {
|
|
|
|
type Output = Self;
|
|
|
|
|
|
|
|
fn mul(self, mult: f32) -> Self {
|
2021-07-10 16:04:12 +00:00
|
|
|
match self {
|
2023-02-14 02:43:52 +00:00
|
|
|
Self::DamageFraction(val) => Self::DamageFraction(val * mult),
|
|
|
|
Self::Value(val) => Self::Value(val * mult),
|
2021-07-10 16:04:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-28 01:44:49 +00:00
|
|
|
impl CombatBuff {
|
2023-03-09 04:10:24 +00:00
|
|
|
fn to_buff(
|
|
|
|
self,
|
|
|
|
time: Time,
|
|
|
|
uid: Option<Uid>,
|
2022-12-16 02:51:03 +00:00
|
|
|
tgt_stats: Option<&Stats>,
|
|
|
|
tgt_health: Option<&Health>,
|
2023-03-09 04:10:24 +00:00
|
|
|
damage: f32,
|
|
|
|
strength_modifier: 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(
|
2021-10-28 22:37:51 +00:00
|
|
|
self.strength.to_strength(damage, strength_modifier),
|
2023-03-11 21:44:57 +00:00
|
|
|
Some(Secs(self.dur_secs as f64)),
|
2021-01-28 01:44:49 +00:00
|
|
|
),
|
|
|
|
Vec::new(),
|
2021-02-01 02:43:26 +00:00
|
|
|
source,
|
2023-03-08 03:22:54 +00:00
|
|
|
time,
|
2022-12-16 02:51:03 +00:00
|
|
|
tgt_stats,
|
|
|
|
tgt_health,
|
2021-01-28 01:44:49 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 15:41:08 +00:00
|
|
|
pub fn get_weapon_kinds(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
|
2020-11-15 02:05:18 +00:00
|
|
|
(
|
2021-11-18 15:41:08 +00:00
|
|
|
inv.equipped(EquipSlot::ActiveMainhand).and_then(|i| {
|
|
|
|
if let ItemKind::Tool(tool) = &*i.kind() {
|
|
|
|
Some(tool.kind)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
inv.equipped(EquipSlot::ActiveOffhand).and_then(|i| {
|
|
|
|
if let ItemKind::Tool(tool) = &*i.kind() {
|
|
|
|
Some(tool.kind)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}),
|
2020-11-15 02:05:18 +00:00
|
|
|
)
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|
|
|
|
|
2021-11-18 15:41:08 +00:00
|
|
|
// TODO: Either remove msm or use it as argument in fn kind
|
2022-07-28 23:19:20 +00:00
|
|
|
fn weapon_rating<T: ItemDesc>(item: &T, _msm: &MaterialStatManifest) -> f32 {
|
2022-05-08 03:28:20 +00:00
|
|
|
const POWER_WEIGHT: f32 = 2.0;
|
2022-01-18 05:18:27 +00:00
|
|
|
const SPEED_WEIGHT: f32 = 3.0;
|
2022-05-08 03:28:20 +00:00
|
|
|
const RANGE_WEIGHT: f32 = 0.8;
|
|
|
|
const EFFECT_WEIGHT: f32 = 1.5;
|
|
|
|
const EQUIP_TIME_WEIGHT: f32 = 0.0;
|
|
|
|
const ENERGY_EFFICIENCY_WEIGHT: f32 = 1.5;
|
|
|
|
const BUFF_STRENGTH_WEIGHT: f32 = 1.5;
|
2021-03-30 16:16:20 +00:00
|
|
|
|
2022-05-10 02:45:43 +00:00
|
|
|
let rating = if let ItemKind::Tool(tool) = &*item.kind() {
|
2022-06-06 21:48:39 +00:00
|
|
|
let stats = tool.stats(item.stats_durability_multiplier());
|
2021-03-30 16:16:20 +00:00
|
|
|
|
2021-06-06 23:48:47 +00:00
|
|
|
// TODO: Look into changing the 0.5 to reflect armor later maybe?
|
|
|
|
// Since it is only for weapon though, it probably makes sense to leave
|
|
|
|
// independent for now
|
2021-03-30 16:16:20 +00:00
|
|
|
|
2022-05-08 03:28:20 +00:00
|
|
|
let power_rating = stats.power;
|
2022-01-18 05:18:27 +00:00
|
|
|
let speed_rating = stats.speed - 1.0;
|
2022-05-08 03:28:20 +00:00
|
|
|
let range_rating = stats.range - 1.0;
|
2022-01-18 05:18:27 +00:00
|
|
|
let effect_rating = stats.effect_power - 1.0;
|
2022-01-18 12:39:49 +00:00
|
|
|
let equip_time_rating = 0.5 - stats.equip_time_secs;
|
2022-05-08 03:28:20 +00:00
|
|
|
let energy_efficiency_rating = stats.energy_efficiency - 1.0;
|
|
|
|
let buff_strength_rating = stats.buff_strength - 1.0;
|
|
|
|
|
|
|
|
power_rating * POWER_WEIGHT
|
|
|
|
+ speed_rating * SPEED_WEIGHT
|
|
|
|
+ range_rating * RANGE_WEIGHT
|
|
|
|
+ effect_rating * EFFECT_WEIGHT
|
|
|
|
+ equip_time_rating * EQUIP_TIME_WEIGHT
|
|
|
|
+ energy_efficiency_rating * ENERGY_EFFICIENCY_WEIGHT
|
|
|
|
+ buff_strength_rating * BUFF_STRENGTH_WEIGHT
|
2021-03-30 16:16:20 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
2022-05-10 02:45:43 +00:00
|
|
|
};
|
|
|
|
rating.max(0.0)
|
2021-03-30 16:16:20 +00:00
|
|
|
}
|
|
|
|
|
2021-04-14 15:35:34 +00:00
|
|
|
fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
|
2021-11-18 15:41:08 +00:00
|
|
|
let (mainhand, offhand) = get_weapon_kinds(inventory);
|
2021-03-30 16:16:20 +00:00
|
|
|
let mainhand_skills = if let Some(tool) = mainhand {
|
2021-04-14 15:35:34 +00:00
|
|
|
skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32
|
2021-03-30 16:16:20 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
let offhand_skills = if let Some(tool) = offhand {
|
2021-04-14 15:35:34 +00:00
|
|
|
skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32
|
2021-03-30 16:16:20 +00:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
mainhand_skills.max(offhand_skills)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_weapon_rating(inventory: &Inventory, msm: &MaterialStatManifest) -> f32 {
|
2021-11-18 15:41:08 +00:00
|
|
|
let mainhand_rating = if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
|
|
|
|
weapon_rating(item, msm)
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2021-03-30 16:16:20 +00:00
|
|
|
|
2021-11-18 15:41:08 +00:00
|
|
|
let offhand_rating = if let Some(item) = inventory.equipped(EquipSlot::ActiveOffhand) {
|
|
|
|
weapon_rating(item, msm)
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2021-03-30 16:16:20 +00:00
|
|
|
|
|
|
|
mainhand_rating.max(offhand_rating)
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 20:29:27 +00:00
|
|
|
pub fn combat_rating(
|
|
|
|
inventory: &Inventory,
|
|
|
|
health: &Health,
|
2021-08-16 13:12:22 +00:00
|
|
|
energy: &Energy,
|
2021-09-17 05:45:30 +00:00
|
|
|
poise: &Poise,
|
2021-04-14 15:35:34 +00:00
|
|
|
skill_set: &SkillSet,
|
2021-02-23 20:29:27 +00:00
|
|
|
body: Body,
|
|
|
|
msm: &MaterialStatManifest,
|
|
|
|
) -> f32 {
|
2021-03-30 16:16:20 +00:00
|
|
|
const WEAPON_WEIGHT: f32 = 1.0;
|
2021-09-17 05:45:30 +00:00
|
|
|
const HEALTH_WEIGHT: f32 = 1.5;
|
2021-08-16 13:12:22 +00:00
|
|
|
const ENERGY_WEIGHT: f32 = 0.5;
|
2021-03-30 16:16:20 +00:00
|
|
|
const SKILLS_WEIGHT: f32 = 1.0;
|
2021-08-16 13:12:22 +00:00
|
|
|
const POISE_WEIGHT: f32 = 0.5;
|
2023-11-11 19:20:38 +00:00
|
|
|
const PRECISION_WEIGHT: f32 = 0.5;
|
2021-11-13 20:46:45 +00:00
|
|
|
// Normalized with a standard max health of 100
|
2021-09-17 05:45:30 +00:00
|
|
|
let health_rating = health.base_max()
|
2021-03-30 16:16:20 +00:00
|
|
|
/ 100.0
|
2022-05-28 23:41:31 +00:00
|
|
|
/ (1.0 - Damage::compute_damage_reduction(None, Some(inventory), None, msm)).max(0.00001);
|
2021-03-30 16:16:20 +00:00
|
|
|
|
2021-11-13 20:46:45 +00:00
|
|
|
// Normalized with a standard max energy of 100 and energy reward multiplier of
|
2021-09-17 05:45:30 +00:00
|
|
|
// x1
|
2022-05-28 23:41:31 +00:00
|
|
|
let energy_rating = (energy.base_max() + compute_max_energy_mod(Some(inventory), msm)) / 100.0
|
|
|
|
* compute_energy_reward_mod(Some(inventory), msm);
|
2021-08-16 13:12:22 +00:00
|
|
|
|
2021-11-13 20:46:45 +00:00
|
|
|
// Normalized with a standard max poise of 100
|
2022-11-28 11:24:27 +00:00
|
|
|
let poise_rating = poise.base_max()
|
2021-09-17 05:45:30 +00:00
|
|
|
/ 100.0
|
2022-03-05 20:51:41 +00:00
|
|
|
/ (1.0 - Poise::compute_poise_damage_reduction(Some(inventory), msm, None, None))
|
|
|
|
.max(0.00001);
|
2021-08-16 13:12:22 +00:00
|
|
|
|
2023-11-11 19:20:38 +00:00
|
|
|
// Normalized with a standard precision multiplier of 1.2
|
|
|
|
let precision_rating = compute_precision_mult(Some(inventory), msm) / 1.2;
|
2021-08-16 13:12:22 +00:00
|
|
|
|
2021-03-30 16:16:20 +00:00
|
|
|
// Assumes a standard person has earned 20 skill points in the general skill
|
2021-04-02 21:37:08 +00:00
|
|
|
// tree and 10 skill points for the weapon skill tree
|
2021-04-14 15:35:34 +00:00
|
|
|
let skills_rating = (skill_set.earned_sp(SkillGroupKind::General) as f32 / 20.0
|
|
|
|
+ weapon_skills(inventory, skill_set) / 10.0)
|
2021-03-30 16:16:20 +00:00
|
|
|
/ 2.0;
|
|
|
|
|
2021-09-17 05:45:30 +00:00
|
|
|
let weapon_rating = get_weapon_rating(inventory, msm);
|
2021-03-30 16:16:20 +00:00
|
|
|
|
|
|
|
let combined_rating = (health_rating * HEALTH_WEIGHT
|
2021-08-16 13:12:22 +00:00
|
|
|
+ energy_rating * ENERGY_WEIGHT
|
|
|
|
+ poise_rating * POISE_WEIGHT
|
2023-11-11 19:20:38 +00:00
|
|
|
+ precision_rating * PRECISION_WEIGHT
|
2021-03-30 16:16:20 +00:00
|
|
|
+ skills_rating * SKILLS_WEIGHT
|
|
|
|
+ weapon_rating * WEAPON_WEIGHT)
|
2021-08-16 13:12:22 +00:00
|
|
|
/ (HEALTH_WEIGHT
|
|
|
|
+ ENERGY_WEIGHT
|
|
|
|
+ POISE_WEIGHT
|
2023-11-11 19:20:38 +00:00
|
|
|
+ PRECISION_WEIGHT
|
2021-08-16 13:12:22 +00:00
|
|
|
+ SKILLS_WEIGHT
|
|
|
|
+ WEAPON_WEIGHT);
|
2021-03-30 16:16:20 +00:00
|
|
|
|
|
|
|
// Body multiplier meant to account for an enemy being harder than equipment and
|
|
|
|
// skills would account for. It should only not be 1.0 for non-humanoids
|
2021-01-22 21:12:16 +00:00
|
|
|
combined_rating * body.combat_multiplier()
|
2021-01-07 05:23:24 +00:00
|
|
|
}
|
2021-05-21 02:08:42 +00:00
|
|
|
|
2023-11-11 19:20:38 +00:00
|
|
|
pub fn compute_precision_mult(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
|
2023-11-11 17:53:14 +00:00
|
|
|
// Starts with a value of 0.1 when summing the stats from each armor piece, and
|
2023-11-11 19:20:38 +00:00
|
|
|
// defaults to a value of 0.1 if no inventory is equipped. Precision multiplier
|
2023-11-11 17:53:14 +00:00
|
|
|
// cannot go below 1
|
|
|
|
1.0 + inventory
|
|
|
|
.map_or(0.1, |inv| {
|
|
|
|
inv.equipped_items()
|
|
|
|
.filter_map(|item| {
|
|
|
|
if let ItemKind::Armor(armor) = &*item.kind() {
|
|
|
|
armor
|
|
|
|
.stats(msm, item.stats_durability_multiplier())
|
2023-11-11 19:20:38 +00:00
|
|
|
.precision_power
|
2023-11-11 17:53:14 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fold(0.1, |a, b| a + b)
|
|
|
|
})
|
|
|
|
.max(0.0)
|
2021-05-21 02:08:42 +00:00
|
|
|
}
|
2021-06-06 23:48:47 +00:00
|
|
|
|
2022-07-15 16:59:37 +00:00
|
|
|
/// Computes the energy reward modifier from worn armor
|
2022-05-28 23:41:31 +00:00
|
|
|
pub fn compute_energy_reward_mod(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
|
2021-06-06 23:48:47 +00:00
|
|
|
// Starts with a value of 1.0 when summing the stats from each armor piece, and
|
|
|
|
// defaults to a value of 1.0 if no inventory is present
|
|
|
|
inventory.map_or(1.0, |inv| {
|
|
|
|
inv.equipped_items()
|
|
|
|
.filter_map(|item| {
|
2021-11-18 15:41:08 +00:00
|
|
|
if let ItemKind::Armor(armor) = &*item.kind() {
|
2022-06-06 21:48:39 +00:00
|
|
|
armor
|
|
|
|
.stats(msm, item.stats_durability_multiplier())
|
|
|
|
.energy_reward
|
2021-06-06 23:48:47 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fold(1.0, |a, b| a + b)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-14 02:16:01 +00:00
|
|
|
/// Computes the additive modifier that should be applied to max energy from the
|
2021-06-06 23:48:47 +00:00
|
|
|
/// currently equipped items
|
2022-05-28 23:41:31 +00:00
|
|
|
pub fn compute_max_energy_mod(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
|
2021-06-06 23:48:47 +00:00
|
|
|
// Defaults to a value of 0 if no inventory is present
|
2021-09-14 02:16:01 +00:00
|
|
|
inventory.map_or(0.0, |inv| {
|
2021-06-06 23:48:47 +00:00
|
|
|
inv.equipped_items()
|
|
|
|
.filter_map(|item| {
|
2021-11-18 15:41:08 +00:00
|
|
|
if let ItemKind::Armor(armor) = &*item.kind() {
|
2022-06-06 21:48:39 +00:00
|
|
|
armor
|
|
|
|
.stats(msm, item.stats_durability_multiplier())
|
|
|
|
.energy_max
|
2021-06-06 23:48:47 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.sum()
|
2021-09-14 02:16:01 +00:00
|
|
|
})
|
2021-06-06 23:48:47 +00:00
|
|
|
}
|
2021-10-24 05:31:49 +00:00
|
|
|
|
2022-05-04 13:24:19 +00:00
|
|
|
/// Returns a value to be included as a multiplicative factor in perception
|
|
|
|
/// distance checks.
|
|
|
|
pub fn perception_dist_multiplier_from_stealth(
|
|
|
|
inventory: Option<&Inventory>,
|
|
|
|
character_state: Option<&CharacterState>,
|
2022-05-28 23:41:31 +00:00
|
|
|
msm: &MaterialStatManifest,
|
2022-05-04 13:24:19 +00:00
|
|
|
) -> f32 {
|
|
|
|
const SNEAK_MULTIPLIER: f32 = 0.7;
|
|
|
|
|
2022-05-28 23:41:31 +00:00
|
|
|
let item_stealth_multiplier = stealth_multiplier_from_items(inventory, msm);
|
2022-05-04 13:24:19 +00:00
|
|
|
let is_sneaking = character_state.map_or(false, |state| state.is_stealthy());
|
|
|
|
|
2022-05-10 03:23:08 +00:00
|
|
|
let multiplier = item_stealth_multiplier * if is_sneaking { SNEAK_MULTIPLIER } else { 1.0 };
|
2022-05-04 13:24:19 +00:00
|
|
|
|
2022-05-06 20:41:55 +00:00
|
|
|
multiplier.clamp(0.0, 1.0)
|
2022-05-04 13:24:19 +00:00
|
|
|
}
|
|
|
|
|
2022-05-28 23:41:31 +00:00
|
|
|
pub fn stealth_multiplier_from_items(
|
|
|
|
inventory: Option<&Inventory>,
|
|
|
|
msm: &MaterialStatManifest,
|
|
|
|
) -> f32 {
|
2022-05-06 20:41:55 +00:00
|
|
|
let stealth_sum = inventory.map_or(0.0, |inv| {
|
2021-10-24 05:31:49 +00:00
|
|
|
inv.equipped_items()
|
|
|
|
.filter_map(|item| {
|
2021-11-18 15:41:08 +00:00
|
|
|
if let ItemKind::Armor(armor) = &*item.kind() {
|
2022-06-06 21:48:39 +00:00
|
|
|
armor.stats(msm, item.stats_durability_multiplier()).stealth
|
2021-10-24 05:31:49 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2022-05-06 20:46:09 +00:00
|
|
|
.sum()
|
2022-05-06 20:41:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
(1.0 / (1.0 + stealth_sum)).clamp(0.0, 1.0)
|
2021-10-24 05:31:49 +00:00
|
|
|
}
|
2021-12-03 18:21:17 +00:00
|
|
|
|
|
|
|
/// Computes the total protection provided from armor. Is used to determine the
|
|
|
|
/// damage reduction applied to damage received by an entity None indicates that
|
|
|
|
/// the armor equipped makes the entity invulnerable
|
2022-05-28 23:41:31 +00:00
|
|
|
pub fn compute_protection(
|
|
|
|
inventory: Option<&Inventory>,
|
|
|
|
msm: &MaterialStatManifest,
|
|
|
|
) -> Option<f32> {
|
2021-12-03 18:21:17 +00:00
|
|
|
inventory.map_or(Some(0.0), |inv| {
|
|
|
|
inv.equipped_items()
|
|
|
|
.filter_map(|item| {
|
2021-11-18 15:41:08 +00:00
|
|
|
if let ItemKind::Armor(armor) = &*item.kind() {
|
2022-06-06 21:48:39 +00:00
|
|
|
armor
|
|
|
|
.stats(msm, item.stats_durability_multiplier())
|
|
|
|
.protection
|
2021-12-03 18:21:17 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map(|protection| match protection {
|
|
|
|
Protection::Normal(protection) => Some(protection),
|
|
|
|
Protection::Invincible => None,
|
|
|
|
})
|
|
|
|
.sum::<Option<f32>>()
|
|
|
|
})
|
|
|
|
}
|
2023-11-11 17:40:46 +00:00
|
|
|
|
|
|
|
/// Used to compute the precision multiplier achieved by flanking a target
|
|
|
|
pub fn precision_mult_from_flank(attack_dir: Vec3<f32>, target_ori: Option<&Ori>) -> Option<f32> {
|
|
|
|
let angle = target_ori.map(|t_ori| t_ori.look_dir().angle_between(attack_dir));
|
|
|
|
match angle {
|
2023-11-11 17:53:14 +00:00
|
|
|
Some(angle) if angle < FULL_FLANK_ANGLE => Some(MAX_BACK_FLANK_PRECISION),
|
|
|
|
Some(angle) if angle < PARTIAL_FLANK_ANGLE => Some(MAX_SIDE_FLANK_PRECISION),
|
2023-11-11 17:40:46 +00:00
|
|
|
Some(_) | None => None,
|
|
|
|
}
|
|
|
|
}
|