diff --git a/assets/voxygen/i18n/be/common.ftl b/assets/voxygen/i18n/be/common.ftl index 25a89f2d36..6c2a61da9b 100644 --- a/assets/voxygen/i18n/be/common.ftl +++ b/assets/voxygen/i18n/be/common.ftl @@ -86,7 +86,6 @@ common-rand_name = Выпадковае імя common-stats-combat_rating = БР common-stats-power = Моц common-stats-speed = Хуткасць -common-stats-crit_chance = Крыт. шанец common-stats-crit_mult = Крыт. множнік common-stats-armor = Браня common-stats-poise_res = Супраціўленне аглушэнню diff --git a/assets/voxygen/i18n/ca/common.ftl b/assets/voxygen/i18n/ca/common.ftl index 4f3045710f..93878844ab 100644 --- a/assets/voxygen/i18n/ca/common.ftl +++ b/assets/voxygen/i18n/ca/common.ftl @@ -86,7 +86,6 @@ common-rand_name = Nom Aleatori common-stats-combat_rating = PC common-stats-power = Potència common-stats-speed = Velocitat -common-stats-crit_chance = Probabilitat de Crític common-stats-crit_mult = Multiplicador de Crític common-stats-armor = Armadura common-stats-poise_res = Resistència a l'Atordiment diff --git a/assets/voxygen/i18n/cs/common.ftl b/assets/voxygen/i18n/cs/common.ftl index f388c9d3a8..397ad02e5f 100644 --- a/assets/voxygen/i18n/cs/common.ftl +++ b/assets/voxygen/i18n/cs/common.ftl @@ -83,7 +83,6 @@ common-rand_name = Náhodné jméno common-stats-combat_rating = CR common-stats-power = Síla common-stats-speed = Rychlost -common-stats-crit_chance = Kritická šance common-stats-crit_mult = Krit Násobek common-stats-armor = Zbroj common-stats-poise_res = Odolnost omráčení diff --git a/assets/voxygen/i18n/de/common.ftl b/assets/voxygen/i18n/de/common.ftl index 82be3da263..b99ea0ef51 100644 --- a/assets/voxygen/i18n/de/common.ftl +++ b/assets/voxygen/i18n/de/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = Schnelligkeit common-stats-range = Reichweite common-stats-energy_efficiency = Ausdauereffizienz common-stats-buff_strength = Buff/Debuff Stärke -common-stats-crit_chance = Krit. Trefferchance common-stats-crit_mult = Krit. Multiplikator common-stats-armor = Rüstung common-stats-poise_res = Betäubungsresistenz diff --git a/assets/voxygen/i18n/en/common.ftl b/assets/voxygen/i18n/en/common.ftl index 0b69c4d703..a8324b14ee 100644 --- a/assets/voxygen/i18n/en/common.ftl +++ b/assets/voxygen/i18n/en/common.ftl @@ -97,7 +97,6 @@ common-stats-effect-power = Effect Power common-stats-range = Range common-stats-energy_efficiency = Energy Efficiency common-stats-buff_strength = Buff/Debuff Strength -common-stats-crit_chance = Crit Chance common-stats-crit_mult = Crit Mult common-stats-armor = Armor common-stats-poise_res = Stun Res diff --git a/assets/voxygen/i18n/es-419/common.ftl b/assets/voxygen/i18n/es-419/common.ftl index 7bd540bc0d..85f3289bb8 100644 --- a/assets/voxygen/i18n/es-419/common.ftl +++ b/assets/voxygen/i18n/es-419/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = Velocidad common-stats-range = Rango common-stats-energy_efficiency = Eficiencia de energía common-stats-buff_strength = Fuerza de Mejora/Debilitación -common-stats-crit_chance = Probabilidad de Crítico common-stats-crit_mult = Multiplicador de Crítico common-stats-armor = Armadura common-stats-poise_res = Resistencia al aturdimiento diff --git a/assets/voxygen/i18n/es/common.ftl b/assets/voxygen/i18n/es/common.ftl index e6d605a0e5..a3f2d1b752 100644 --- a/assets/voxygen/i18n/es/common.ftl +++ b/assets/voxygen/i18n/es/common.ftl @@ -94,7 +94,6 @@ common-stats-effect-power = Potencia de estados alterados common-stats-range = Rango common-stats-energy_efficiency = Eficiencia de aguante common-stats-buff_strength = Potencia de estados beneficiosos -common-stats-crit_chance = Probabilidad de daño crítico common-stats-crit_mult = Multiplicador de crítico common-stats-armor = Armadura common-stats-poise_res = Resistencia al aturdimiento diff --git a/assets/voxygen/i18n/eu/common.ftl b/assets/voxygen/i18n/eu/common.ftl index ec99a548bf..5bc88819c3 100644 --- a/assets/voxygen/i18n/eu/common.ftl +++ b/assets/voxygen/i18n/eu/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = Abiadura common-stats-range = Maila common-stats-energy_efficiency = Energia eraginkortasuna common-stats-buff_strength = Buff/Debuff indarra -common-stats-crit_chance = Kritiko probabilitatea common-stats-crit_mult = Kritiko biderkatzailea common-stats-armor = Armadura common-stats-poise_res = Orekarako gaitasuna diff --git a/assets/voxygen/i18n/fr/common.ftl b/assets/voxygen/i18n/fr/common.ftl index c08d05c787..7d843992f1 100644 --- a/assets/voxygen/i18n/fr/common.ftl +++ b/assets/voxygen/i18n/fr/common.ftl @@ -95,7 +95,6 @@ common-stats-speed = Vitesse common-stats-range = Portée common-stats-energy_efficiency = Efficacité du coût d'Endurance common-stats-buff_strength = Montant de l'augmentation -common-stats-crit_chance = Chance de Crit common-stats-crit_mult = Multiplicateur de Crit common-stats-armor = Armure common-stats-poise_res = Résistance à l'étourdissement diff --git a/assets/voxygen/i18n/hu/common.ftl b/assets/voxygen/i18n/hu/common.ftl index 90d002785d..194fe51d3a 100644 --- a/assets/voxygen/i18n/hu/common.ftl +++ b/assets/voxygen/i18n/hu/common.ftl @@ -81,7 +81,6 @@ common-rand_name = Véletlenszerű név common-stats-combat_rating = KÉ common-stats-power = Erő common-stats-speed = Gyorsaság -common-stats-crit_chance = Kritikus találat esélye common-stats-crit_mult = Kritikus találat szorzója common-stats-armor = Páncélzat common-stats-poise_res = Megszédíthetőség diff --git a/assets/voxygen/i18n/it/common.ftl b/assets/voxygen/i18n/it/common.ftl index b71df0c51d..53c3d19f4c 100644 --- a/assets/voxygen/i18n/it/common.ftl +++ b/assets/voxygen/i18n/it/common.ftl @@ -97,7 +97,6 @@ common-stats-effect-power = Potenza effetto common-stats-range = Intervallo common-stats-energy_efficiency = Efficienza energia common-stats-buff_strength = Quantità di aumento/diminuzione -common-stats-crit_chance = Probabilità di critico common-stats-crit_mult = Moltiplicatore del critico common-stats-armor = Armatura common-stats-poise_res = Resistenza allo stordimento diff --git a/assets/voxygen/i18n/ja/common.ftl b/assets/voxygen/i18n/ja/common.ftl index 7cde6e04fa..92cb61c3fb 100644 --- a/assets/voxygen/i18n/ja/common.ftl +++ b/assets/voxygen/i18n/ja/common.ftl @@ -77,7 +77,6 @@ common-rand_appearance = ランダムに見た目を選択 common-rand_name = ランダムに名前を選ぶ common-stats-power = Power common-stats-speed = Speed -common-stats-crit_chance = Crit Chance common-stats-crit_mult = Crit Mult common-stats-armor = Armor common-stats-poise_res = Poise res diff --git a/assets/voxygen/i18n/ko/common.ftl b/assets/voxygen/i18n/ko/common.ftl index 21ee0dd011..26b3cab1b9 100644 --- a/assets/voxygen/i18n/ko/common.ftl +++ b/assets/voxygen/i18n/ko/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = 속도 common-stats-range = 사거리 common-stats-energy_efficiency = 기력 효율 common-stats-buff_strength = 힘 버프/디버프 -common-stats-crit_chance = 치명타 확률 common-stats-crit_mult = 치명타 배수 common-stats-armor = 방어력 common-stats-poise_res = 기절 저항 diff --git a/assets/voxygen/i18n/pl/common.ftl b/assets/voxygen/i18n/pl/common.ftl index 0f9e897401..ce80c5ce44 100644 --- a/assets/voxygen/i18n/pl/common.ftl +++ b/assets/voxygen/i18n/pl/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = Prędkość common-stats-range = Zasięg common-stats-energy_efficiency = Efektywność Energii common-stats-buff_strength = Siła Efektów Wzmacniających -common-stats-crit_chance = % na cios kryt. common-stats-crit_mult = Mnożnik ciosu kryt. common-stats-armor = Obrona common-stats-poise_res = Odp. na ogłuszenie diff --git a/assets/voxygen/i18n/pt-BR/common.ftl b/assets/voxygen/i18n/pt-BR/common.ftl index fc01916215..163def67d3 100644 --- a/assets/voxygen/i18n/pt-BR/common.ftl +++ b/assets/voxygen/i18n/pt-BR/common.ftl @@ -96,7 +96,6 @@ common-stats-effect-power = Poder de Efeito common-stats-range = Alcance common-stats-energy_efficiency = Eficiência Energética common-stats-buff_strength = Buff/Debuff de força -common-stats-crit_chance = Chance de Crítico common-stats-crit_mult = Multiplicador de Crítico common-stats-armor = Armadura common-stats-poise_res = Resistência a Atordoamento diff --git a/assets/voxygen/i18n/ro/common.ftl b/assets/voxygen/i18n/ro/common.ftl index e704c04cd7..465c12b1a6 100644 --- a/assets/voxygen/i18n/ro/common.ftl +++ b/assets/voxygen/i18n/ro/common.ftl @@ -90,7 +90,6 @@ common-stats-speed = Viteză common-stats-range = Distanță common-stats-energy_efficiency = Eficiența energiei common-stats-buff_strength = Buff/Debuff Strength -common-stats-crit_chance = Șansă Crit common-stats-crit_mult = Multiplicator Crit common-stats-armor = Armură common-stats-poise_res = Stun Res diff --git a/assets/voxygen/i18n/ru/common.ftl b/assets/voxygen/i18n/ru/common.ftl index ff038bc0cb..a8a10a601d 100644 --- a/assets/voxygen/i18n/ru/common.ftl +++ b/assets/voxygen/i18n/ru/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = Скорость common-stats-range = Дистанция common-stats-energy_efficiency = Энергоэффективность common-stats-buff_strength = Увеличение силы -common-stats-crit_chance = Шанс крита common-stats-crit_mult = Множитель крита common-stats-armor = Броня common-stats-poise_res = Оглушение diff --git a/assets/voxygen/i18n/sr/common.ftl b/assets/voxygen/i18n/sr/common.ftl index 96f6bceca2..3b65a9e050 100644 --- a/assets/voxygen/i18n/sr/common.ftl +++ b/assets/voxygen/i18n/sr/common.ftl @@ -83,7 +83,6 @@ common-rand_name = Насумично име common-stats-combat_rating = CR common-stats-power = Снага common-stats-speed = Брзина -common-stats-crit_chance = Крит Шанса common-stats-crit_mult = Мулти Крит common-stats-armor = Оклоп common-stats-poise_res = Отпорност на Омаму diff --git a/assets/voxygen/i18n/sv/common.ftl b/assets/voxygen/i18n/sv/common.ftl index 648f8a3d03..8e5ade34a1 100644 --- a/assets/voxygen/i18n/sv/common.ftl +++ b/assets/voxygen/i18n/sv/common.ftl @@ -97,7 +97,6 @@ common-stats-effect-power = Effektstyrka common-stats-range = Räckvidd common-stats-energy_efficiency = Energieffektivitet common-stats-buff_strength = Buff/Debuff-styrka -common-stats-crit_chance = Kritisk chans common-stats-crit_mult = Kritisk multi common-stats-armor = Rustning common-stats-poise_res = Motståndskraft diff --git a/assets/voxygen/i18n/th/common.ftl b/assets/voxygen/i18n/th/common.ftl index 101d9332fa..43f36c8ebb 100644 --- a/assets/voxygen/i18n/th/common.ftl +++ b/assets/voxygen/i18n/th/common.ftl @@ -93,7 +93,6 @@ common-stats-speed = ความเร็ว common-stats-range = ระยะโจมตี common-stats-energy_efficiency = ประสิทธิภาพพลังงาน common-stats-buff_strength = เสริมพลังความแข็งแกร่ง -common-stats-crit_chance = โอกาสคริติคอล common-stats-crit_mult = ความรุนแรงคริติคอล common-stats-armor = เกราะ common-stats-poise_res = ความคงทน diff --git a/assets/voxygen/i18n/tr/common.ftl b/assets/voxygen/i18n/tr/common.ftl index 3a5b07d3ee..09c56d5f00 100644 --- a/assets/voxygen/i18n/tr/common.ftl +++ b/assets/voxygen/i18n/tr/common.ftl @@ -83,7 +83,6 @@ common-rand_appearance = Rastgele görünüm common-stats-combat_rating = DP common-stats-power = Güç common-stats-speed = Hız -common-stats-crit_chance = Kritik Şansı common-stats-crit_mult = Kritik Çarpanı common-stats-armor = Zırh common-stats-energy_max = Maksimum Enerji diff --git a/assets/voxygen/i18n/uk/common.ftl b/assets/voxygen/i18n/uk/common.ftl index ac4ba9d875..1d0800a827 100644 --- a/assets/voxygen/i18n/uk/common.ftl +++ b/assets/voxygen/i18n/uk/common.ftl @@ -97,7 +97,6 @@ common-stats-effect-power = Сила ефекту common-stats-range = Дистанція common-stats-energy_efficiency = Енергоощадливість common-stats-buff_strength = Сила бафу/дебафу -common-stats-crit_chance = Крит. шанс common-stats-crit_mult = Крит. множник common-stats-armor = Броня common-stats-poise_res = Супротив приголомшенню diff --git a/assets/voxygen/i18n/vi/common.ftl b/assets/voxygen/i18n/vi/common.ftl index 1656af3afe..2e059cb1e7 100644 --- a/assets/voxygen/i18n/vi/common.ftl +++ b/assets/voxygen/i18n/vi/common.ftl @@ -81,7 +81,6 @@ common-rand_name = Tên ngẫu nhiên common-stats-combat_rating = CR common-stats-power = Sức Mạnh common-stats-speed = Tốc Độ -common-stats-crit_chance = Tỉ Lệ Chí Mạng common-stats-armor = Giáp common-stats-energy_max = Năng Lượng Tối Đa common-stats-energy_reward = Thưởng Năng Lượng diff --git a/assets/voxygen/i18n/zh-Hans/common.ftl b/assets/voxygen/i18n/zh-Hans/common.ftl index d7190560e2..b4335c1626 100644 --- a/assets/voxygen/i18n/zh-Hans/common.ftl +++ b/assets/voxygen/i18n/zh-Hans/common.ftl @@ -95,7 +95,6 @@ common-stats-effect-power = 效果威力 common-stats-range = 范围 common-stats-energy_efficiency = 耐力消耗 common-stats-buff_strength = 增幅 -common-stats-crit_chance = 暴击率 common-stats-crit_mult = 暴击倍率 common-stats-armor = 护甲 common-stats-poise_res = 韧性 diff --git a/common/src/combat.rs b/common/src/combat.rs index 79fc586c4e..bc46c73fb2 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -44,6 +44,12 @@ pub enum AttackSource { Explosion, } +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; + #[derive(Copy, Clone)] pub struct AttackerInfo<'a> { pub entity: EcsEntity, @@ -73,6 +79,7 @@ pub struct AttackOptions { pub target_dodging: bool, pub may_harm: bool, pub target_group: GroupTarget, + pub precision_mult: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive @@ -202,6 +209,7 @@ impl Attack { target_dodging, may_harm, target_group, + precision_mult, } = options; // target == OutOfGroup is basic heuristic that this @@ -217,7 +225,10 @@ impl Attack { matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) && (target_dodging || !may_harm) }; - let is_crit = false; + let precision_mult = attacker + .and_then(|a| a.stats) + .and_then(|s| s.precision_multiplier_override) + .or(precision_mult); let mut is_applied = false; let mut accumulated_damage = 0.0; let damage_modifier = attacker @@ -244,7 +255,7 @@ impl Attack { let change = damage.damage.calculate_health_change( damage_reduction, attacker.map(|x| x.into()), - is_crit, + precision_mult, self.crit_multiplier, strength_modifier * damage_modifier, time, @@ -273,7 +284,7 @@ impl Attack { by: attacker.map(|x| x.into()), cause: Some(damage.damage.source), time, - crit: is_crit, + crit: precision_mult.is_some(), instance: damage_instance, }; emit(ServerEvent::HealthChange { @@ -324,7 +335,7 @@ impl Attack { by: attacker.map(|x| x.into()), cause: Some(damage.damage.source), instance: damage_instance, - crit: is_crit, + crit: precision_mult.is_some(), time, }; emit(ServerEvent::HealthChange { @@ -1014,18 +1025,14 @@ impl Damage { self, damage_reduction: f32, damage_contributor: Option, - is_crit: bool, + precision_mult: Option, crit_mult: f32, damage_modifier: f32, time: Time, instance: u64, ) -> HealthChange { let mut damage = self.value * damage_modifier; - let critdamage = if is_crit { - damage * (crit_mult - 1.0) - } else { - 0.0 - }; + let critdamage = damage * precision_mult.unwrap_or(0.0) * (crit_mult - 1.0); match self.source { DamageSource::Melee | DamageSource::Projectile @@ -1042,7 +1049,7 @@ impl Damage { by: damage_contributor, cause: Some(self.source), time, - crit: is_crit, + crit: precision_mult.is_some(), instance, } }, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 20f5147996..431a099792 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -1,4 +1,5 @@ use crate::{combat::Attack, resources::Secs}; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage, Entity as EcsEntity}; use vek::*; @@ -14,6 +15,16 @@ pub struct Beam { pub bezier: QuadraticBezier3, #[serde(skip)] pub hit_entities: Vec, + #[serde(skip)] + pub hit_durations: HashMap, +} + +impl Beam { + pub fn hit_entities_and_durations( + &mut self, + ) -> (&Vec, &mut HashMap) { + (&self.hit_entities, &mut self.hit_durations) + } } impl Component for Beam { diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 3ea218d601..08d9f43f2f 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -333,10 +333,7 @@ impl BuffKind { BuffKind::Hastened => vec![ BuffEffect::MovementSpeed(1.0 + data.strength), BuffEffect::AttackSpeed(1.0 + data.strength), - BuffEffect::CriticalChance { - kind: ModifierKind::Multiplicative, - val: 0.0, - }, + BuffEffect::PrecisionOverride(0.0), ], BuffKind::Fortitude => vec![ BuffEffect::PoiseReduction(nn_scaling(data.strength)), @@ -388,10 +385,7 @@ impl BuffKind { AttackEffect::new(None, CombatEffect::Lifesteal(data.strength)) .with_requirement(CombatRequirement::TargetHasBuff(BuffKind::Bleeding)), )], - BuffKind::ImminentCritical => vec![BuffEffect::CriticalChance { - kind: ModifierKind::Additive, - val: 1.0, - }], + BuffKind::ImminentCritical => vec![BuffEffect::PrecisionOverride(1.0)], BuffKind::Fury => vec![BuffEffect::AttackEffect( AttackEffect::new(None, CombatEffect::Combo(data.strength.round() as i32)) .with_requirement(CombatRequirement::AnyDamage), @@ -558,11 +552,8 @@ pub enum BuffEffect { }, /// Modifier to the amount of damage dealt with attacks AttackDamage(f32), - /// Multiplies crit chance of attacks - CriticalChance { - kind: ModifierKind, - val: f32, - }, + /// Overrides the precision multiplier applied to an attack + PrecisionOverride(f32), /// Changes body. BodyChange(Body), BuffImmunity(BuffKind), diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index 4fb69b8932..1db1736a24 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -134,7 +134,7 @@ impl PoiseState { } /// Returns the multiplier on poise damage to health damage for when the - /// target is in a poise state + /// target is in a poise state, also is used for precision pub fn damage_multiplier(&self) -> f32 { match self { Self::Interrupted => 0.1, diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 30abed94bb..c30f06a981 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -63,7 +63,7 @@ pub struct Stats { pub max_energy_modifiers: StatsModifier, pub poise_damage_modifier: f32, pub attack_damage_modifier: f32, - pub crit_chance_modifier: StatsModifier, + pub precision_multiplier_override: Option, pub swim_speed_modifier: f32, /// This adds effects to any attacks that the entity makes pub effects_on_attack: Vec, @@ -90,7 +90,7 @@ impl Stats { max_energy_modifiers: StatsModifier::default(), poise_damage_modifier: 1.0, attack_damage_modifier: 1.0, - crit_chance_modifier: StatsModifier::default(), + precision_multiplier_override: None, swim_speed_modifier: 1.0, effects_on_attack: Vec::new(), mitigations_penetration: 0.0, diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index ad58284c59..e2dcf8fc38 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -17,6 +17,7 @@ use crate::{ terrain::Block, util::Dir, }; +use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::time::Duration; use vek::*; @@ -132,6 +133,7 @@ impl CharacterBehavior for Data { duration: self.static_data.beam_duration, tick_dur: Secs(1.0 / self.static_data.tick_rate as f64), hit_entities: Vec::new(), + hit_durations: HashMap::new(), specifier: self.static_data.specifier, bezier: QuadraticBezier3 { start: data.pos.0, diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index 13725403c2..470c02fd49 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -73,6 +73,11 @@ impl<'a> System<'a> for Sys { .for_each(|(pos, ori, char_state, mut beam)| { // Clear hit entities list if list should be cleared if read_data.time.0 % beam.tick_dur.0 < read_data.dt.0 as f64 { + let (hit_entities, hit_durations) = beam.hit_entities_and_durations(); + hit_durations.retain(|e, _| hit_entities.contains(e)); + for entity in hit_entities { + *hit_durations.entry(*entity).or_insert(0) += 1; + } beam.hit_entities.clear(); } // Update start, end, and control positions of beam bezier @@ -227,10 +232,45 @@ impl<'a> System<'a> for Sys { Some(entity), target, ); + + let precision_from_flank = { + let beam_dir = beam.bezier.ctrl - beam.bezier.start; + let angle = target_info.ori.map_or(std::f32::consts::PI, |t_ori| { + t_ori.look_dir().angle_between(beam_dir) + }); + if angle < combat::FULL_FLANK_ANGLE { + Some(1.0) + } else if angle < combat::PARTIAL_FLANK_ANGLE { + Some(0.5) + } else { + None + } + }; + + let precision_from_time = { + if let Some(ticks) = beam.hit_durations.get(&target) { + let dur = *ticks as f32 * beam.tick_dur.0 as f32; + let mult = + (dur / combat::BEAM_DURATION_PRECISION).clamp(0.0, 1.0); + Some(mult) + } else { + None + } + }; + + // Is there a more idiomatic way to do this (taking the max of 2 + // options)? + let precision_mult = precision_from_flank + .map(|flank| { + precision_from_time.map_or(flank, |head: f32| head.max(flank)) + }) + .or(precision_from_time); + let attack_options = AttackOptions { target_dodging, may_harm, target_group, + precision_mult, }; beam.attack.apply_attack( diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index aa6ef8a521..41f5fc5b00 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -674,9 +674,12 @@ fn execute_effect( BuffEffect::AttackDamage(dam) => { stat.attack_damage_modifier *= *dam; }, - BuffEffect::CriticalChance { kind, val } => match kind { - ModifierKind::Additive => stat.crit_chance_modifier.add_mod += val, - ModifierKind::Multiplicative => stat.crit_chance_modifier.mult_mod *= val, + BuffEffect::PrecisionOverride(val) => { + // Use lower of precision multiplier overrides + stat.precision_multiplier_override = stat + .precision_multiplier_override + .map(|mult| mult.min(*val)) + .or(Some(*val)); }, BuffEffect::BodyChange(b) => { // For when an entity is under the effects of multiple de/buffs that change the diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 0558dd6a5f..b8dd7516c8 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -196,6 +196,8 @@ impl<'a> System<'a> for Sys { stats: read_data.stats.get(attacker), }); + let target_ori = read_data.orientations.get(target); + let target_char_state = read_data.char_states.get(target); let target_info = TargetInfo { entity: target, uid: *uid_b, @@ -203,8 +205,8 @@ impl<'a> System<'a> for Sys { stats: read_data.stats.get(target), health: read_data.healths.get(target), pos: pos_b.0, - ori: read_data.orientations.get(target), - char_state: read_data.char_states.get(target), + ori: target_ori, + char_state: target_char_state, energy: read_data.energies.get(target), buffs: read_data.buffs.get(target), }; @@ -218,10 +220,39 @@ impl<'a> System<'a> for Sys { target, ); + let precision_from_flank = { + let angle = target_ori.map_or(std::f32::consts::PI, |t_ori| { + t_ori.look_dir().angle_between(*ori.look_dir()) + }); + if angle < combat::FULL_FLANK_ANGLE { + Some(1.0) + } else if angle < combat::PARTIAL_FLANK_ANGLE { + Some(0.5) + } else { + None + } + }; + + let precision_from_poise = { + if let Some(CharacterState::Stunned(data)) = target_char_state { + Some(data.static_data.poise_state.damage_multiplier()) + } else { + None + } + }; + + // Is there a more idiomatic way to do this (taking the max of 2 options)? + let precision_mult = precision_from_flank + .map(|flank| { + precision_from_poise.map_or(flank, |head: f32| head.max(flank)) + }) + .or(precision_from_poise); + let attack_options = AttackOptions { target_dodging, may_harm, target_group, + precision_mult, }; let strength = diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 2357c313ae..79fc5ec785 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -157,6 +157,7 @@ impl<'a> System<'a> for Sys { owner, ori: orientations.get(entity), pos, + vel, }; let target = entity_of(other); @@ -247,6 +248,7 @@ struct ProjectileInfo<'a> { owner: Option, ori: Option<&'a Ori>, pos: &'a Pos, + vel: &'a Vel, } struct ProjectileTargetInfo<'a> { @@ -343,10 +345,76 @@ fn dispatch_hit( .get(target) .and_then(|cs| cs.attack_immunities()) .map_or(false, |i| i.projectiles); + + let precision_from_flank = { + let angle = target_info.ori.map_or(std::f32::consts::PI, |t_ori| { + t_ori.look_dir().angle_between(*projectile_dir) + }); + if angle < combat::FULL_FLANK_ANGLE { + Some(1.0) + } else if angle < combat::PARTIAL_FLANK_ANGLE { + Some(0.5) + } else { + None + } + }; + + let precision_from_head = { + let curr_pos = projectile_info.pos.0; + let last_pos = projectile_info.pos.0 - projectile_info.vel.0 * read_data.dt.0; + let vel = projectile_info.vel.0; + let (target_height, target_radius) = read_data + .bodies + .get(target) + .map_or((0.0, 0.0), |b| (b.height(), b.max_radius())); + let head_top_pos = target_pos.with_z(target_pos.z + target_height); + let head_bottom_pos = head_top_pos.with_z( + head_top_pos.z - target_height * combat::PROJECTILE_HEADSHOT_PROPORTION, + ); + let headshot = if (curr_pos.z < head_bottom_pos.z && last_pos.z < head_bottom_pos.z) + || (curr_pos.z > head_top_pos.z && last_pos.z > head_top_pos.z) + { + false + } else if curr_pos.z > head_top_pos.z + || curr_pos.z < head_bottom_pos.z + || last_pos.z > head_top_pos.z + || last_pos.z < head_bottom_pos.z + { + let proj_top_intersection = { + let t = (head_top_pos.z - last_pos.z) / vel.z; + last_pos + vel * t + }; + let proj_bottom_intersection = { + let t = (head_bottom_pos.z - last_pos.z) / vel.z; + last_pos + vel * t + }; + head_top_pos.distance_squared(proj_top_intersection) < target_radius.powi(2) + || head_bottom_pos.distance_squared(proj_bottom_intersection) + < target_radius.powi(2) + } else { + let trajectory = LineSegment3 { + start: last_pos, + end: curr_pos, + }; + let head_middle_pos = head_bottom_pos.with_z( + head_bottom_pos.z + + target_height * combat::PROJECTILE_HEADSHOT_PROPORTION * 0.5, + ); + trajectory.distance_to_point(head_middle_pos) < target_radius + }; + if headshot { Some(1.0) } else { None } + }; + + // Is there a more idiomatic way to do this (taking the max of 2 options)? + let precision_mult = precision_from_flank + .map(|flank| precision_from_head.map_or(flank, |head: f32| head.max(flank))) + .or(precision_from_head); + let attack_options = AttackOptions { target_dodging, may_harm, target_group: projectile_target_info.target_group, + precision_mult, }; attack.apply_attack( diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 837647ebf4..9ddcdc140c 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -234,10 +234,13 @@ impl<'a> System<'a> for Sys { shockwave_owner, target, ); + // Shockwaves aren't precise, and thus cannot be a precise strike + let precision_mult = None; let attack_options = AttackOptions { target_dodging, may_harm, target_group, + precision_mult, }; shockwave.properties.attack.apply_attack( diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 0f949802d4..b20e3ec2a1 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -670,7 +670,7 @@ pub fn handle_land_on_ground( let change = damage.calculate_health_change( damage_reduction, None, - false, + None, 0.0, 1.0, *time, @@ -1049,10 +1049,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o // PvP check let may_harm = combat::may_harm(alignments, players, id_maps, owner_entity, entity_b); + // Explosions aren't precise, and thus cannot be a precise strike + let precision_mult = None; let attack_options = combat::AttackOptions { target_dodging, may_harm, target_group, + precision_mult, }; let time = server.state.ecs().read_resource::