diff --git a/assets/common/abilities/sword/heavy_combo.ron b/assets/common/abilities/sword/heavy_combo.ron index d311941ad4..e8767b351b 100644 --- a/assets/common/abilities/sword/heavy_combo.ron +++ b/assets/common/abilities/sword/heavy_combo.ron @@ -1,25 +1,47 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 7, + poise: 0, + knockback: 0, + energy_regen: 5, + ), + range: 3.0, + angle: 45.0, + ), + buildup_duration: 0.4, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.7, + ori_modifier: 0.6, ), - range: 5.0, - angle: 10.0, - ), - ori_modifier: 1.0, + ( + melee_constructor: ( + kind: Slash( + damage: 10, + poise: 0, + knockback: 0, + energy_regen: 10, + ), + range: 3.0, + angle: 45.0, + ), + buildup_duration: 0.5, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.6, + ori_modifier: 0.6, + ), + ], + is_stance: true, + energy_cost_per_strike: 5, meta: ( - kind: Some(Sword(Balanced)), + kind: Some(Sword(Heavy)), capabilities: ( - // Block - bits: 0b00000010, + // Poise resistant during attack + bits: 0b00001000, ), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/heavy_finisher.ron b/assets/common/abilities/sword/heavy_finisher.ron index d311941ad4..27bbf2c7ca 100644 --- a/assets/common/abilities/sword/heavy_finisher.ron +++ b/assets/common/abilities/sword/heavy_finisher.ron @@ -1,25 +1,30 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, +FinisherMelee( + energy_cost: 40, + buildup_duration: 0.4, swing_duration: 0.1, - recover_duration: 0.2, + recover_duration: 0.4, melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, + kind: Slash( + damage: 20, + poise: 30, + knockback: 0, + energy_regen: 10, + ), + scaled: Some(Slash( + damage: 0, + poise: 20, knockback: 0, energy_regen: 0, - ), - range: 5.0, - angle: 10.0, + )), + range: 3.0, + angle: 15.0, ), - ori_modifier: 1.0, + scaling: Some(( + target: Attack, + kind: Linear, + )), + minimum_combo: 10, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), + kind: Some(Sword(Heavy)), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/heavy_fortitude.ron b/assets/common/abilities/sword/heavy_fortitude.ron index d311941ad4..9170b40712 100644 --- a/assets/common/abilities/sword/heavy_fortitude.ron +++ b/assets/common/abilities/sword/heavy_fortitude.ron @@ -1,25 +1,12 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, - ), - range: 5.0, - angle: 10.0, - ), - ori_modifier: 1.0, +SelfBuff( + buildup_duration: 0.2, + cast_duration: 0.2, + recover_duration: 0.6, + buff_kind: Fortitude, + buff_strength: 1.0, + buff_duration: Some(5.0), + energy_cost: 40, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), + kind: Some(Sword(Heavy)), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/heavy_pommelstrike.ron b/assets/common/abilities/sword/heavy_pommelstrike.ron index d311941ad4..57ac8ab658 100644 --- a/assets/common/abilities/sword/heavy_pommelstrike.ron +++ b/assets/common/abilities/sword/heavy_pommelstrike.ron @@ -1,25 +1,26 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 10, + poise: 20, + knockback: 0, + energy_regen: 10, + ), + range: 4.0, + angle: 5.0, + ), + buildup_duration: 0.2, + swing_duration: 0.1, + hit_timing: 0.6, + recover_duration: 0.4, + ori_modifier: 0.6, ), - range: 5.0, - angle: 10.0, - ), - ori_modifier: 1.0, + ], + is_stance: false, + energy_cost_per_strike: 10, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), + kind: Some(Sword(Heavy)), ), -) +) \ No newline at end of file diff --git a/assets/voxygen/i18n/en/buff.ftl b/assets/voxygen/i18n/en/buff.ftl index 3cd5c8464d..c4d1d9e1eb 100644 --- a/assets/voxygen/i18n/en/buff.ftl +++ b/assets/voxygen/i18n/en/buff.ftl @@ -61,6 +61,9 @@ buff-desc-wet = The ground rejects your feet, making it hard to stop. ## Ensnared buff-title-ensnared = Ensnared buff-desc-ensnared = Vines grasp at your legs, impeding your movement. +## Fortitude +buff-title-fortitude = Fortitude +buff-desc-fortitude = You can withstand staggers. ## 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 bd9e772a84..9af913f81f 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -153,6 +153,7 @@ lazy_static! { BuffKind::Ensnared => "ensnared", BuffKind::Poisoned => "poisoned", BuffKind::Hastened => "hastened", + BuffKind::Fortitude => "fortitude", }; let mut buff_parser = HashMap::new(); for kind in BuffKind::iter() { diff --git a/common/src/combat.rs b/common/src/combat.rs index 5b18087aa7..f15c8ee1d7 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -286,7 +286,13 @@ impl Attack { let reduced_damage = applied_damage * damage_reduction / (1.0 - damage_reduction); let poise = reduced_damage * CRUSHING_POISE_FRACTION; - let change = -Poise::apply_poise_reduction(poise, target.inventory, msm); + let change = -Poise::apply_poise_reduction( + poise, + target.inventory, + msm, + target.char_state, + target.stats, + ); let poise_change = PoiseChange { amount: change, impulse: *dir, @@ -377,8 +383,13 @@ impl Attack { } }, CombatEffect::Poise(p) => { - let change = -Poise::apply_poise_reduction(*p, target.inventory, msm) - * strength_modifier; + let change = -Poise::apply_poise_reduction( + *p, + target.inventory, + msm, + target.char_state, + target.stats, + ) * strength_modifier; if change.abs() > Poise::POISE_EPSILON { let poise_change = PoiseChange { amount: change, @@ -536,8 +547,13 @@ impl Attack { } }, CombatEffect::Poise(p) => { - let change = -Poise::apply_poise_reduction(p, target.inventory, msm) - * strength_modifier; + let change = -Poise::apply_poise_reduction( + p, + target.inventory, + msm, + target.char_state, + target.stats, + ) * strength_modifier; if change.abs() > Poise::POISE_EPSILON { let poise_change = PoiseChange { amount: change, @@ -1146,7 +1162,8 @@ pub fn combat_rating( // Normalized with a standard max poise of 100 let poise_rating = poise.base_max() as f32 / 100.0 - / (1.0 - Poise::compute_poise_damage_reduction(inventory, msm)).max(0.00001); + / (1.0 - Poise::compute_poise_damage_reduction(Some(inventory), msm, None, None)) + .max(0.00001); // Normalized with a standard crit multiplier of 1.2 let crit_rating = compute_crit_mult(Some(inventory), msm) / 1.2; diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 10c088014e..ea4e104880 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2544,5 +2544,6 @@ bitflags::bitflags! { const ROLL_INTERRUPT = 0b00000001; const BLOCK_INTERRUPT = 0b00000010; const BUILDUP_PARRIES = 0b00000100; + const POISE_RESISTANT = 0b00001000; } } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 1b19446da2..26088496df 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -51,6 +51,11 @@ pub enum BuffKind { /// Strength scales strength of both effects linearly. 0.5 is a 50% /// increase, 1.0 is a 100% increase. Hastened, + // TODO: Consider non linear scaling? + /// Increases resistance to incomin poise over time + /// Strength scales the resistance linearly, values over 1 will usually do + /// nothing. 0.5 is 50%, 1.0 is 100%. + Fortitude, // Debuffs /// Does damage to a creature over time /// Strength should be the DPS of the debuff @@ -99,7 +104,8 @@ impl BuffKind { | BuffKind::IncreaseMaxHealth | BuffKind::Invulnerability | BuffKind::ProtectingWard - | BuffKind::Hastened => true, + | BuffKind::Hastened + | BuffKind::Fortitude => true, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning @@ -181,6 +187,8 @@ pub enum BuffEffect { AttackSpeed(f32), /// Modifies ground friction of target GroundFriction(f32), + /// Reduces poise damage taken after armor is accounted for by this fraction + PoiseReduction(f32), } /// Actual de/buff. @@ -378,6 +386,10 @@ impl Buff { ], data.duration, ), + BuffKind::Fortitude => ( + vec![BuffEffect::PoiseReduction(data.strength)], + data.duration, + ), }; Buff { kind, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 8f30484fb0..b5bdad148b 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy, InputAttr, - InputKind, Ori, Pos, Vel, + ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, + Density, Energy, InputAttr, InputKind, Ori, Pos, Vel, }, event::{LocalEvent, ServerEvent}, states::{ diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index 09556c59d0..c4f55b6c3c 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -2,8 +2,9 @@ use crate::{ combat::{DamageContributor, DamageSource}, comp::{ self, + ability::Capability, inventory::item::{armor::Protection, ItemKind, MaterialStatManifest}, - CharacterState, Inventory, + CharacterState, Inventory, Stats, }, resources::Time, states, @@ -234,39 +235,58 @@ impl Poise { /// Returns the total poise damage reduction provided by all equipped items pub fn compute_poise_damage_reduction( - inventory: &Inventory, + inventory: Option<&Inventory>, msm: &MaterialStatManifest, + char_state: Option<&CharacterState>, + stats: Option<&Stats>, ) -> f32 { - let protection = inventory - .equipped_items() - .filter_map(|item| { - if let ItemKind::Armor(armor) = &*item.kind() { - armor.stats(msm).poise_resilience - } else { - None - } - }) - .map(|protection| match protection { - Protection::Normal(protection) => Some(protection), - Protection::Invincible => None, - }) - .sum::>(); - match protection { + let protection = inventory.map_or(Some(0.0), |inv| { + inv.equipped_items() + .filter_map(|item| { + if let ItemKind::Armor(armor) = &*item.kind() { + armor.stats(msm).poise_resilience + } else { + None + } + }) + .map(|protection| match protection { + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, + }) + .sum::>() + }); + let from_inventory = match protection { Some(dr) => dr / (60.0 + dr.abs()), None => 1.0, - } + }; + let from_char = { + let resistant = char_state + .and_then(|cs| cs.ability_info()) + .map_or(false, |a| { + a.ability_meta + .capabilities + .contains(Capability::POISE_RESISTANT) + }); + if resistant { 0.5 } else { 0.0 } + }; + let from_stats = if let Some(stats) = stats { + stats.poise_reduction + } else { + 0.0 + }; + 1.0 - (1.0 - from_inventory) * (1.0 - from_char) * (1.0 - from_stats) } - /// Modifies a poise change when optionally given an inventory to aid in - /// calculation of poise damage reduction + /// Modifies a poise change when optionally given an inventory and character + /// state to aid in calculation of poise damage reduction pub fn apply_poise_reduction( value: f32, inventory: Option<&Inventory>, msm: &MaterialStatManifest, + char_state: Option<&CharacterState>, + stats: Option<&Stats>, ) -> f32 { - inventory.map_or(value, |inv| { - value * (1.0 - Poise::compute_poise_damage_reduction(inv, msm)) - }) + value * (1.0 - Poise::compute_poise_damage_reduction(inventory, msm, char_state, stats)) } } diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 25b58effec..693083e1ea 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -49,6 +49,7 @@ impl Error for StatChangeError {} pub struct Stats { pub name: String, pub damage_reduction: f32, + pub poise_reduction: f32, pub max_health_modifiers: StatsModifier, pub move_speed_modifier: f32, pub attack_speed_modifier: f32, @@ -61,6 +62,7 @@ impl Stats { Self { name, damage_reduction: 0.0, + poise_reduction: 0.0, max_health_modifiers: StatsModifier::default(), move_speed_modifier: 1.0, attack_speed_modifier: 1.0, @@ -76,6 +78,7 @@ impl Stats { /// Resets temporary modifiers to default values pub fn reset_temp_modifiers(&mut self) { self.damage_reduction = 0.0; + self.poise_reduction = 0.0; self.max_health_modifiers = StatsModifier::default(); self.move_speed_modifier = 1.0; self.attack_speed_modifier = 1.0; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 8e38a41a37..c0c9805891 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -414,10 +414,8 @@ pub fn handle_forced_movement( // FRIC_GROUND temporarily used to normalize things around expected values data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND }) { - update.vel.0 += Vec2::broadcast(data.dt.0) - * accel - * Vec2::from(update.ori) - * strength; + update.vel.0 += + Vec2::broadcast(data.dt.0) * accel * Vec2::from(update.ori) * strength; } }, ForcedMovement::Reverse(strength) => { diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index fb70b7e122..a300b95ef9 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -466,6 +466,9 @@ fn execute_effect( BuffEffect::GroundFriction(gf) => { stat.friction_modifier *= *gf; }, + BuffEffect::PoiseReduction(pr) => { + stat.poise_reduction = stat.poise_reduction.max(*pr).min(1.0); + }, }; } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 53f6b3f1d8..9fd23f3911 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -552,6 +552,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) let falldmg = impact_energy / 1000.0; let inventories = ecs.read_storage::(); + let char_states = ecs.read_storage::(); let stats = ecs.read_storage::(); let time = ecs.read_resource::