diff --git a/assets/common/abilities/axe/savage_sense.ron b/assets/common/abilities/axe/savage_sense.ron index b4732b4a2f..d45a6098f9 100644 --- a/assets/common/abilities/axe/savage_sense.ron +++ b/assets/common/abilities/axe/savage_sense.ron @@ -1,22 +1,9 @@ -ComboMelee2( - strikes: [ - ( - melee_constructor: ( - kind: Slash( - damage: 4, - poise: 5, - knockback: 0, - energy_regen: 5, - ), - range: 3.0, - angle: 45.0, - ), - buildup_duration: 0.15, - swing_duration: 0.05, - hit_timing: 0.5, - recover_duration: 0.1, - ori_modifier: 0.6, - ), - ], - energy_cost_per_strike: 0, -) \ No newline at end of file +SelfBuff( + buildup_duration: 0.1, + cast_duration: 0.1, + recover_duration: 0.1, + buff_kind: ImminentCritical, + buff_strength: 1.0, + buff_duration: Some(30.0), + energy_cost: /*15*/0, +) diff --git a/assets/voxygen/i18n/en/buff.ftl b/assets/voxygen/i18n/en/buff.ftl index 4f50e530e8..34efb7ac4f 100644 --- a/assets/voxygen/i18n/en/buff.ftl +++ b/assets/voxygen/i18n/en/buff.ftl @@ -88,9 +88,12 @@ buff-desc-frigid = Freeze your foes. ## Lifesteal buff-title-lifesteal = Lifesteal buff-desc-lifesteal = Siphon your enemies life away. -## Polymorped +## Salamander's Aspect buff-title-salamanderaspect = Salamander's Aspect buff-desc-salamanderaspect = You cannot burn and move fast through lava. +## Imminent Critical +buff-title-imminentcritical = Imminent Critical +buff-desc-imminentcritical = Your next attack will critically hit the enemy. ## Util buff-text-over_seconds = over { $dur_secs } seconds buff-text-for_seconds = for { $dur_secs } seconds diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 9743f600e0..f275930a39 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -172,6 +172,7 @@ lazy_static! { BuffKind::Frigid => "frigid", BuffKind::Lifesteal => "lifesteal", // BuffKind::SalamanderAspect => "salamander_aspect", + BuffKind::ImminentCritical => "imminent_critical", }; let mut buff_parser = HashMap::new(); for kind in BuffKind::iter() { diff --git a/common/src/combat.rs b/common/src/combat.rs index d9ead06a66..6efd6b8acf 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -214,11 +214,14 @@ impl Attack { matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) && (target_dodging || !may_harm) }; - let is_crit = rng.gen::() - < self.crit_chance - * attacker - .and_then(|a| a.stats) - .map_or(1.0, |s| s.crit_chance_modifier); + let crit_chance = attacker + .and_then(|a| a.stats) + .map(|s| s.crit_chance_modifier) + .map_or(self.crit_chance, |cc_mod| { + self.crit_chance * cc_mod.mult_mod + cc_mod.add_mod + }) + .clamp(0.0, 1.0); + let is_crit = rng.gen::() < crit_chance; let mut is_applied = false; let mut accumulated_damage = 0.0; let damage_modifier = attacker @@ -687,6 +690,7 @@ impl Attack { if is_applied { emit(ServerEvent::EntityAttackedHook { entity: target.entity, + attacker: attacker.map(|a| a.entity), }); } is_applied diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index a6378e130e..a552aba455 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -74,6 +74,10 @@ pub enum BuffKind { /// Provides immunity to burning and increases movement speed in lava. /// Movement speed increases linearly with strength, 1.0 is a 100% increase. // SalamanderAspect, TODO: Readd in second dwarven mine MR + /// Guarantees that the next attack is a critical hit. Does this kind of + /// hackily by adding 100% to the crit, will need to be adjusted if we ever + /// allow double crits instead of treating 100 as a ceiling. + ImminentCritical, // Debuffs /// Does damage to a creature over time. /// Strength should be the DPS of the debuff. @@ -143,7 +147,7 @@ impl BuffKind { | BuffKind::Frigid | BuffKind::Lifesteal //| BuffKind::SalamanderAspect - => true, + | BuffKind::ImminentCritical => true, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning @@ -212,7 +216,7 @@ impl BuffKind { }, BuffKind::CampfireHeal => vec![BuffEffect::HealthChangeOverTime { rate: data.strength, - kind: ModifierKind::Fractional, + kind: ModifierKind::Multiplicative, instance, tick_dur: Secs(2.0), }], @@ -287,7 +291,10 @@ impl BuffKind { BuffKind::Hastened => vec![ BuffEffect::MovementSpeed(1.0 + data.strength), BuffEffect::AttackSpeed(1.0 + data.strength), - BuffEffect::CriticalChance(0.0), + BuffEffect::CriticalChance { + kind: ModifierKind::Multiplicative, + val: 0.0, + }, ], BuffKind::Fortitude => vec![ BuffEffect::PoiseReduction(nn_scaling(data.strength)), @@ -329,8 +336,24 @@ impl BuffKind { BuffEffect::BuffImmunity(BuffKind::Burning), BuffEffect::SwimSpeed(1.0 + data.strength), ],*/ + BuffKind::ImminentCritical => vec![BuffEffect::CriticalChance { + kind: ModifierKind::Additive, + val: 1.0, + }], } } + + fn extend_cat_ids(&self, mut cat_ids: Vec) -> Vec { + // TODO: Remove clippy allow after another buff needs this + #[allow(clippy::single_match)] + match self { + BuffKind::ImminentCritical => { + cat_ids.push(BuffCategory::RemoveOnAttack); + }, + _ => {}, + } + cat_ids + } } // Struct used to store data relevant to a buff @@ -362,12 +385,13 @@ pub enum BuffCategory { Divine, PersistOnDeath, FromActiveAura(Uid, AuraKey), + RemoveOnAttack, } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ModifierKind { Additive, - Fractional, + Multiplicative, } /// Data indicating and configuring behaviour of a de/buff. @@ -422,7 +446,10 @@ pub enum BuffEffect { /// Modifier to the amount of damage dealt with attacks AttackDamage(f32), /// Multiplies crit chance of attacks - CriticalChance(f32), + CriticalChance { + kind: ModifierKind, + val: f32, + }, /// Changes body. BodyChange(Body), /// Inflict buff to target @@ -489,6 +516,7 @@ impl Buff { health: Option<&Health>, ) -> Self { let effects = kind.effects(&data, stats, health); + let cat_ids = kind.extend_cat_ids(cat_ids); let start_time = Time(time.0 + data.delay.map_or(0.0, |delay| delay.0)); let end_time = if cat_ids .iter() diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 8846e814d8..1e65d591b8 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: f32, + pub crit_chance_modifier: StatsModifier, pub buffs_on_hit: Vec, pub swim_speed_modifier: f32, } @@ -83,7 +83,7 @@ impl Stats { max_energy_modifiers: StatsModifier::default(), poise_damage_modifier: 1.0, attack_damage_modifier: 1.0, - crit_chance_modifier: 1.0, + crit_chance_modifier: StatsModifier::default(), buffs_on_hit: Vec::new(), swim_speed_modifier: 1.0, } diff --git a/common/src/event.rs b/common/src/event.rs index b30b9a048d..cb2f31d864 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -297,6 +297,7 @@ pub enum ServerEvent { }, EntityAttackedHook { entity: EcsEntity, + attacker: Option, }, ChangeAbility { entity: EcsEntity, diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index 9e41d0c1a4..471e17eea2 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -535,7 +535,7 @@ fn execute_effect( }; let amount = match *kind { ModifierKind::Additive => amount, - ModifierKind::Fractional => health.maximum() * amount, + ModifierKind::Multiplicative => health.maximum() * amount, }; let damage_contributor = by.and_then(|uid| { read_data.id_maps.uid_entity(uid).map(|entity| { @@ -565,7 +565,7 @@ fn execute_effect( let amount = match *kind { ModifierKind::Additive => amount, - ModifierKind::Fractional => energy.maximum() * amount, + ModifierKind::Multiplicative => energy.maximum() * amount, }; server_emitter.emit(ServerEvent::EnergyChange { entity, @@ -577,7 +577,7 @@ fn execute_effect( ModifierKind::Additive => { stat.max_health_modifiers.add_mod += *value; }, - ModifierKind::Fractional => { + ModifierKind::Multiplicative => { stat.max_health_modifiers.mult_mod *= *value; }, }, @@ -585,7 +585,7 @@ fn execute_effect( ModifierKind::Additive => { stat.max_energy_modifiers.add_mod += *value; }, - ModifierKind::Fractional => { + ModifierKind::Multiplicative => { stat.max_energy_modifiers.mult_mod *= *value; }, }, @@ -608,7 +608,7 @@ fn execute_effect( // creates fraction potential_amount / health.base_max() }, - ModifierKind::Fractional => { + ModifierKind::Multiplicative => { // `rate * dt` is the fraction potential_amount }, @@ -664,8 +664,9 @@ fn execute_effect( BuffEffect::AttackDamage(dam) => { stat.attack_damage_modifier *= *dam; }, - BuffEffect::CriticalChance(cc) => { - stat.crit_chance_modifier *= *cc; + 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::BodyChange(b) => { // For when an entity is under the effects of multiple de/buffs that change the diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index a262a2fed8..02a7ef818a 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -659,7 +659,7 @@ impl<'a> AgentData<'a> { comp::BuffEffect::HealthChangeOverTime { rate, kind, .. } => { let amount = match kind { comp::ModifierKind::Additive => rate * duration.0 as f32, - comp::ModifierKind::Fractional => { + comp::ModifierKind::Multiplicative => { (1.0 + rate).powf(duration.0 as f32) }, }; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index db21c7bc86..431c6b80a7 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1445,11 +1445,25 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r /// Intended to handle things that should happen for any successful attack, /// regardless of the damages and effects specific to that attack -pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) { +pub fn handle_entity_attacked_hook( + server: &Server, + entity: EcsEntity, + attacker: Option, +) { let ecs = &server.state.ecs(); let server_eventbus = ecs.read_resource::>(); let time = ecs.read_resource::