diff --git a/CHANGELOG.md b/CHANGELOG.md index 846e7ad2f3..68eea077e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Balance changes; Smoother entity progression consisting of larger variety in (effective) health pools, dps, entity flee_health alterations, and minor weight distribution changes for entity spawns. - Made power of weapon tiers scale non-linearly. - Sword Changes; Pommel Strike has been nerfed -> increased energy cost, increased _durations, and decreased poise damage. Heavy Sweep has been nerfed -> decreased poise damage and stun vulnerability damage. Pillar Thrust has been altered -> decreased maximum base damage with an increase in stun vulnerability damage. +- Weapons block are based on poise +- Wooden Shield recipe ### Removed - Medium and large potions from all loot tables diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index f7b907f25b..a142621547 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -351,8 +351,9 @@ abilities: [], ), Tool(Shield): ( - primary: Simple(None, "common.abilities.shield.tempbasic"), - secondary: Simple(None, "common.abilities.shield.block"), + guard: Some(Simple(None, "common.abilities.shield.basic_guard")), + primary: Simple(None, "common.abilities.shield.singlestrike"), + secondary: Simple(None, "common.abilities.shield.power_guard"), abilities: [], ), Custom("Stone Golem"): ( diff --git a/assets/common/abilities/axe/basic_guard.ron b/assets/common/abilities/axe/basic_guard.ron index 8d6bf65b4e..79e67f1046 100644 --- a/assets/common/abilities/axe/basic_guard.ron +++ b/assets/common/abilities/axe/basic_guard.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.25, recover_duration: 0.2, max_angle: 60.0, - block_strength: 0.5, + block_strength: 5.0, parry_window: ( buildup: true, recover: false, diff --git a/assets/common/abilities/hammer/basic_guard.ron b/assets/common/abilities/hammer/basic_guard.ron index 8d6bf65b4e..79e67f1046 100644 --- a/assets/common/abilities/hammer/basic_guard.ron +++ b/assets/common/abilities/hammer/basic_guard.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.25, recover_duration: 0.2, max_angle: 60.0, - block_strength: 0.5, + block_strength: 5.0, parry_window: ( buildup: true, recover: false, diff --git a/assets/common/abilities/haniwa/soldier/guard.ron b/assets/common/abilities/haniwa/soldier/guard.ron index 91c4e5ad61..81d0cbb112 100644 --- a/assets/common/abilities/haniwa/soldier/guard.ron +++ b/assets/common/abilities/haniwa/soldier/guard.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.25, recover_duration: 0.25, max_angle: 90.0, - block_strength: 0.5, + block_strength: 5.0, parry_window: ( buildup: false, recover: false, diff --git a/assets/common/abilities/haniwa/soldier/riposte.ron b/assets/common/abilities/haniwa/soldier/riposte.ron index 7885865b1f..78f9c1fcca 100644 --- a/assets/common/abilities/haniwa/soldier/riposte.ron +++ b/assets/common/abilities/haniwa/soldier/riposte.ron @@ -3,6 +3,7 @@ RiposteMelee( buildup_duration: 0.7, swing_duration: 0.3, recover_duration: 0.2, + block_strength: 5.0, melee_constructor: ( kind: Slash( damage: 30, diff --git a/assets/common/abilities/shield/block.ron b/assets/common/abilities/shield/basic_guard.ron similarity index 71% rename from assets/common/abilities/shield/block.ron rename to assets/common/abilities/shield/basic_guard.ron index 3fb5439393..5d8ac8f34a 100644 --- a/assets/common/abilities/shield/block.ron +++ b/assets/common/abilities/shield/basic_guard.ron @@ -1,14 +1,14 @@ BasicBlock( - buildup_duration: 0.1, - recover_duration: 0.1, + buildup_duration: 0.45, + recover_duration: 0.2, max_angle: 90.0, - block_strength: 0.8, + block_strength: 10.0, parry_window: ( buildup: true, recover: false, ), - energy_cost: 0.0, - energy_regen: 0.0, + energy_cost: 5.0, + energy_regen: 2.5, can_hold: true, blocked_attacks: ( melee: true, diff --git a/assets/common/abilities/shield/power_guard.ron b/assets/common/abilities/shield/power_guard.ron new file mode 100644 index 0000000000..fbbd23d127 --- /dev/null +++ b/assets/common/abilities/shield/power_guard.ron @@ -0,0 +1,21 @@ +BasicBlock( + buildup_duration: 0.5, + recover_duration: 0.25, + max_angle: 90.0, + block_strength: 15.0, + parry_window: ( + buildup: true, + recover: false, + ), + energy_cost: 15.0, + energy_regen: 2.5, + can_hold: true, + blocked_attacks: ( + melee: true, + projectiles: true, + beams: true, + ground_shockwaves: false, + air_shockwaves: true, + explosions: true, + ), +) \ No newline at end of file diff --git a/assets/common/abilities/shield/singlestrike.ron b/assets/common/abilities/shield/singlestrike.ron new file mode 100644 index 0000000000..9d5ec124d9 --- /dev/null +++ b/assets/common/abilities/shield/singlestrike.ron @@ -0,0 +1,32 @@ +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Bash( + damage: 6, + poise: 5, + knockback: 0, + energy_regen: 5, + ), + range: 1.5, + angle: 45.0, + damage_effect: Some(BuffsVulnerable(0.5, Parried)), + ), + buildup_duration: 0.15, + swing_duration: 0.2, + hit_timing: 0.1, + recover_duration: 0.15, + ori_modifier: 0.1, + movement: ( + buildup: Some(Forward(0.1)), + swing: None, + recover: None, + ), + ), + ], + energy_cost_per_strike: 0, + meta: ( + // The ability will parry all blockable attacks in the buildup portion + capabilities: ("PARRIES"), + ), +) \ No newline at end of file diff --git a/assets/common/abilities/shield/tempbasic.ron b/assets/common/abilities/shield/tempbasic.ron deleted file mode 100644 index bb79339a46..0000000000 --- a/assets/common/abilities/shield/tempbasic.ron +++ /dev/null @@ -1,18 +0,0 @@ -BasicMelee( - energy_cost: 0, - buildup_duration: 0.1, - swing_duration: 0.1, - hit_timing: 0.0, - recover_duration: 0.3, - melee_constructor: ( - kind: Bash( - damage: 4.0, - poise: 0.0, - knockback: 0.0, - energy_regen: 0.0, - ), - range: 3.0, - angle: 90.0, - ), - ori_modifier: 1.0, -) diff --git a/assets/common/abilities/sword/basic_guard.ron b/assets/common/abilities/sword/basic_guard.ron index 8d6bf65b4e..79e67f1046 100644 --- a/assets/common/abilities/sword/basic_guard.ron +++ b/assets/common/abilities/sword/basic_guard.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.25, recover_duration: 0.2, max_angle: 60.0, - block_strength: 0.5, + block_strength: 5.0, parry_window: ( buildup: true, recover: false, diff --git a/assets/common/abilities/sword/defensive_cascade.ron b/assets/common/abilities/sword/defensive_cascade.ron index 37fc225cbc..2394dcb05a 100644 --- a/assets/common/abilities/sword/defensive_cascade.ron +++ b/assets/common/abilities/sword/defensive_cascade.ron @@ -22,6 +22,6 @@ ComboMelee2( energy_cost_per_strike: 5, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/abilities/sword/defensive_crescent_slash.ron b/assets/common/abilities/sword/defensive_crescent_slash.ron index 53fb375a65..24ee90e179 100644 --- a/assets/common/abilities/sword/defensive_crescent_slash.ron +++ b/assets/common/abilities/sword/defensive_crescent_slash.ron @@ -22,6 +22,6 @@ ComboMelee2( energy_cost_per_strike: 5, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/abilities/sword/defensive_cross_cut.ron b/assets/common/abilities/sword/defensive_cross_cut.ron index 5a18563bf5..66660945d9 100644 --- a/assets/common/abilities/sword/defensive_cross_cut.ron +++ b/assets/common/abilities/sword/defensive_cross_cut.ron @@ -41,6 +41,6 @@ ComboMelee2( auto_progress: true, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/abilities/sword/defensive_deflect.ron b/assets/common/abilities/sword/defensive_deflect.ron index 7c0fe8398f..916f822650 100644 --- a/assets/common/abilities/sword/defensive_deflect.ron +++ b/assets/common/abilities/sword/defensive_deflect.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.4, recover_duration: 0.2, max_angle: 45.0, - block_strength: 0.75, + block_strength: 7.5, parry_window: ( buildup: true, recover: false, diff --git a/assets/common/abilities/sword/defensive_dual_cross_cut.ron b/assets/common/abilities/sword/defensive_dual_cross_cut.ron index 50803ab50c..f633edf259 100644 --- a/assets/common/abilities/sword/defensive_dual_cross_cut.ron +++ b/assets/common/abilities/sword/defensive_dual_cross_cut.ron @@ -23,6 +23,6 @@ ComboMelee2( energy_cost_per_strike: 5, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/abilities/sword/defensive_fell_strike.ron b/assets/common/abilities/sword/defensive_fell_strike.ron index a5ecbdc8c9..7342f8f188 100644 --- a/assets/common/abilities/sword/defensive_fell_strike.ron +++ b/assets/common/abilities/sword/defensive_fell_strike.ron @@ -22,6 +22,6 @@ ComboMelee2( energy_cost_per_strike: 5, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/abilities/sword/defensive_guard.ron b/assets/common/abilities/sword/defensive_guard.ron index d2ff1157cb..1bc159d36b 100644 --- a/assets/common/abilities/sword/defensive_guard.ron +++ b/assets/common/abilities/sword/defensive_guard.ron @@ -2,7 +2,7 @@ BasicBlock( buildup_duration: 0.4, recover_duration: 0.15, max_angle: 60.0, - block_strength: 0.75, + block_strength: 7.5, parry_window: ( buildup: true, recover: false, diff --git a/assets/common/abilities/sword/defensive_riposte.ron b/assets/common/abilities/sword/defensive_riposte.ron index 289c276879..4c655cdb39 100644 --- a/assets/common/abilities/sword/defensive_riposte.ron +++ b/assets/common/abilities/sword/defensive_riposte.ron @@ -3,6 +3,7 @@ RiposteMelee( buildup_duration: 0.4, swing_duration: 0.1, recover_duration: 0.2, + block_strength: 5.0, melee_constructor: ( kind: Slash( damage: 18, diff --git a/assets/common/abilities/sword/defensive_skewer.ron b/assets/common/abilities/sword/defensive_skewer.ron index dd8ad2ab29..8cdfd7f73b 100644 --- a/assets/common/abilities/sword/defensive_skewer.ron +++ b/assets/common/abilities/sword/defensive_skewer.ron @@ -27,6 +27,6 @@ ComboMelee2( energy_cost_per_strike: 5, meta: ( // The ability will parry melee attacks in the buildup & swing portion - capabilities: ("PARRIES"), + capabilities: ("PARRIES_MELEE"), ), ) \ No newline at end of file diff --git a/assets/common/item_i18n_manifest.ron b/assets/common/item_i18n_manifest.ron index 6290c6e935..7611850fb3 100644 --- a/assets/common/item_i18n_manifest.ron +++ b/assets/common/item_i18n_manifest.ron @@ -708,6 +708,9 @@ Simple( "common.items.weapons.shield.shield_1", ): "weapon-shield-wood-0", + Simple( + "common.items.weapons.shield.starter_shield", + ): "weapon-shield-starter", Simple( "common.items.weapons.dagger.basic_0", ): "weapon-dagger-dagger_basic-0", diff --git a/assets/common/items/weapons/shield/starter_shield.ron b/assets/common/items/weapons/shield/starter_shield.ron new file mode 100644 index 0000000000..97c8cf4946 --- /dev/null +++ b/assets/common/items/weapons/shield/starter_shield.ron @@ -0,0 +1,20 @@ +ItemDef( + legacy_name: "Wooden Shield", + legacy_description: "May not withstand fire", + kind: Tool(( + kind: Shield, + hands: One, + stats: ( + equip_time_secs: 0.3, + power: 0.5, + effect_power: 1.0, + speed: 1.0, + range: 1.0, + energy_efficiency: 1.0, + buff_strength: 1.0, + ), + )), + quality: Common, + tags: [], + ability_spec: None, +) \ No newline at end of file diff --git a/assets/common/loot_tables/weapons/starter.ron b/assets/common/loot_tables/weapons/starter.ron index 6cd133e0aa..4acef796e8 100644 --- a/assets/common/loot_tables/weapons/starter.ron +++ b/assets/common/loot_tables/weapons/starter.ron @@ -5,4 +5,5 @@ (1.0, Item("common.items.weapons.axe.starter_axe")), (1.0, Item("common.items.weapons.staff.starter_staff")), (1.0, Item("common.items.weapons.sceptre.starter_sceptre")), + (1.0, Item("common.items.weapons.shield.starter_shield")), ] \ No newline at end of file diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index 0a74079573..a7f0dc8a30 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -2357,4 +2357,12 @@ ], craft_sprite: Some(Cauldron), ), + "shield": ( + output: ("common.items.weapons.shield.starter_shield", 1), + inputs: [ + (Item("common.items.log.wood"), 5, false), + (Item("common.items.crafting_ing.leather.leather_strips"), 2, false) + ], + craft_sprite: Some(CraftingBench), + ), } diff --git a/assets/common/repair_recipe_book.ron b/assets/common/repair_recipe_book.ron index 21e11ba9ff..c2b1416385 100644 --- a/assets/common/repair_recipe_book.ron +++ b/assets/common/repair_recipe_book.ron @@ -8,6 +8,7 @@ recipes: { ItemDefId("common.items.weapons.staff.starter_staff"): ( inputs: [], ), ItemDefId("common.items.weapons.sceptre.starter_sceptre"): ( inputs: [], ), ItemDefId("common.items.weapons.sword_1h.starter"): ( inputs: [], ), + ItemDefId("common.items.weapons.shield.starter_shield"): ( inputs: [], ), ModularWeapon(material: "common.items.mineral.ingot.bronze"): ( inputs: [ (Item("common.items.mineral.ingot.bronze"), 1), diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 9e97651176..1f4ec0f08f 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -954,7 +954,7 @@ threshold: 0.5, subtitle: "subtitle-unwield_shield", ), - Attack(BasicMelee(Action), Shield): ( + Attack(ComboMelee2(Action), Shield): ( files: [ "voxygen.audio.sfx.abilities.swing", ], diff --git a/assets/voxygen/i18n/en/item/weapon/weapon.ftl b/assets/voxygen/i18n/en/item/weapon/weapon.ftl index 62ce2f98e1..55f9aee027 100644 --- a/assets/voxygen/i18n/en/item/weapon/weapon.ftl +++ b/assets/voxygen/i18n/en/item/weapon/weapon.ftl @@ -1,6 +1,9 @@ weapon-shield-wood-0 = A Tattered Targe .desc = Should withstand a few more hits, hopefully... +weapon-shield-starter = Wooden Shield + .desc = May not withstand fire + weapon-dagger-dagger_basic-0 = Suspicious Paper Knife .desc = Opens letters quickly. diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index aa31dcece6..a2eca4b5d2 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -1554,7 +1554,11 @@ // Shields Simple("common.items.weapons.shield.shield_1"): VoxTrans( "voxel.weapon.shield.wood-0", - (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 2.4, + (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0, + ), + Simple("common.items.weapons.shield.starter_shield"): VoxTrans( + "voxel.weapon.shield.wood-1", + (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0, ), // Lanterns Simple("common.items.lantern.black_0"): VoxTrans( diff --git a/assets/voxygen/voxel/biped_weapon_manifest.ron b/assets/voxygen/voxel/biped_weapon_manifest.ron index d5a69c5045..322f2d14bb 100644 --- a/assets/voxygen/voxel/biped_weapon_manifest.ron +++ b/assets/voxygen/voxel/biped_weapon_manifest.ron @@ -899,7 +899,11 @@ ), // Shields Tool("common.items.weapons.shield.shield_1"): ( - vox_spec: ("weapon.shield.wood-0", (-2.5, -5.5, -5.5)), + vox_spec: ("weapon.shield.wood-0", (-2.5, -7.5, -5.5)), + color: None + ), + Tool("common.items.weapons.shield.starter_shield"): ( + vox_spec: ("weapon.shield.wood-1", (-3.5, -7.5, -5.5)), color: None ), // Bows diff --git a/assets/voxygen/voxel/item_drop_manifest.ron b/assets/voxygen/voxel/item_drop_manifest.ron index 50a619428d..83e8f1b225 100644 --- a/assets/voxygen/voxel/item_drop_manifest.ron +++ b/assets/voxygen/voxel/item_drop_manifest.ron @@ -376,6 +376,7 @@ Simple("common.items.weapons.dagger.cultist_0"): "voxel.weapon.dagger.dagger_cult-0", // Shields Simple("common.items.weapons.shield.shield_1"): "voxel.weapon.shield.wood-0", + Simple("common.items.weapons.shield.starter_shield"): "voxel.weapon.shield.wood-1", // Lanterns Simple("common.items.lantern.black_0"): "voxel.lantern.black-0", Simple("common.items.lantern.green_0"): "voxel.lantern.green-0", diff --git a/assets/voxygen/voxel/weapon/shield/wood-1.vox b/assets/voxygen/voxel/weapon/shield/wood-1.vox new file mode 100644 index 0000000000..f46ecc6d62 --- /dev/null +++ b/assets/voxygen/voxel/weapon/shield/wood-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e84ac8c3d9ed4198bec57342fc5f82cfbbe5a5b3ba59702bfb1280732804c34 +size 26086 diff --git a/common/src/combat.rs b/common/src/combat.rs index 41dc42ede0..eb420fc00e 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -58,6 +58,9 @@ pub const MAX_HEADSHOT_PRECISION: f32 = 1.0; pub const MAX_TOP_HEADSHOT_PRECISION: f32 = 0.5; pub const MAX_BEAM_DUR_PRECISION: f32 = 0.25; pub const MAX_MELEE_POISE_PRECISION: f32 = 0.5; +pub const MAX_BLOCK_POISE_COST: f32 = 50.0; +pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0; +pub const FALLBACK_BLOCK_STRENGTH: f32 = 5.0; #[derive(Copy, Clone)] pub struct AttackerInfo<'a> { @@ -140,15 +143,84 @@ impl Attack { pub fn effects(&self) -> impl Iterator { self.effects.iter() } - pub fn compute_damage_reduction( + pub fn compute_block_damage_decrement( attacker: Option<&AttackerInfo>, + damage_reduction: f32, target: &TargetInfo, source: AttackSource, dir: Dir, damage: Damage, msm: &MaterialStatManifest, - emitters: &mut impl EmitExt, + time: Time, + emitters: &mut (impl EmitExt + EmitExt), mut emit_outcome: impl FnMut(Outcome), + ) -> f32 { + if damage.value > 0.0 { + if let (Some(char_state), Some(ori), Some(inventory)) = + (target.char_state, target.ori, target.inventory) + { + let is_parry = char_state.is_parry(source); + let is_block = char_state.is_block(source); + let damage_value = damage.value * (1.0 - damage_reduction); + let mut block_strength = block_strength(inventory, char_state); + + if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle() + && (is_parry || is_block) + && block_strength > 0.0 + { + if is_parry { + block_strength *= PARRY_BONUS_MULTIPLIER; + + emitters.emit(ParryHookEvent { + defender: target.entity, + attacker: attacker.map(|a| a.entity), + source, + }); + } + + let poise_cost = + (damage_value / block_strength).min(1.0) * MAX_BLOCK_POISE_COST; + + let poise_change = Poise::apply_poise_reduction( + poise_cost, + target.inventory, + msm, + target.char_state, + target.stats, + ); + + emit_outcome(Outcome::Block { + parry: is_parry, + pos: target.pos, + uid: target.uid, + }); + emitters.emit(PoiseChangeEvent { + entity: target.entity, + change: PoiseChange { + amount: -poise_change, + impulse: *dir, + by: attacker.map(|x| (*x).into()), + cause: Some(damage.source), + time, + }, + }); + block_strength + } else { + 0.0 + } + } else { + 0.0 + } + } else { + 0.0 + } + } + + pub fn compute_damage_reduction( + attacker: Option<&AttackerInfo>, + target: &TargetInfo, + damage: Damage, + msm: &MaterialStatManifest, ) -> f32 { if damage.value > 0.0 { let attacker_penetration = attacker @@ -157,39 +229,7 @@ impl Attack { .clamp(0.0, 1.0); let raw_damage_reduction = Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm); - let damage_reduction = (1.0 - attacker_penetration) * raw_damage_reduction; - let block_reduction = - if let (Some(char_state), Some(ori)) = (target.char_state, target.ori) { - if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle() { - if char_state.is_parry(source) { - emit_outcome(Outcome::Block { - parry: true, - pos: target.pos, - uid: target.uid, - }); - emitters.emit(ParryHookEvent { - defender: target.entity, - attacker: attacker.map(|a| a.entity), - source, - }); - 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 - } else { - 0.0 - } - } else { - 0.0 - } - } else { - 0.0 - }; - 1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction) + (1.0 - attacker_penetration) * raw_damage_reduction } else { 0.0 } @@ -260,18 +300,26 @@ impl Attack { { let damage_instance = damage.instance + damage_instance_offset; is_applied = true; - let damage_reduction = Attack::compute_damage_reduction( + + let damage_reduction = + Attack::compute_damage_reduction(attacker.as_ref(), target, damage.damage, msm); + + let block_damage_decrement = Attack::compute_block_damage_decrement( attacker.as_ref(), + damage_reduction, target, attack_source, dir, damage.damage, msm, + time, emitters, &mut emit_outcome, ); + let change = damage.damage.calculate_health_change( damage_reduction, + block_damage_decrement, attacker.map(|x| x.into()), precision_mult, self.precision_multiplier, @@ -1046,6 +1094,7 @@ impl Damage { pub fn calculate_health_change( self, damage_reduction: f32, + block_damage_decrement: f32, damage_contributor: Option, precision_mult: Option, precision_power: f32, @@ -1065,6 +1114,8 @@ impl Damage { damage += precise_damage; // Armor damage *= 1.0 - damage_reduction; + // Block + damage = f32::max(damage - block_damage_decrement, 0.0); HealthChange { amount: -damage, @@ -1496,3 +1547,55 @@ pub fn precision_mult_from_flank(attack_dir: Vec3, target_ori: Option<&Ori> Some(_) | None => None, } } + +pub fn block_strength(inventory: &Inventory, char_state: &CharacterState) -> f32 { + match char_state { + CharacterState::BasicBlock(data) => data.static_data.block_strength, + CharacterState::RiposteMelee(data) => data.static_data.block_strength, + _ => char_state + .ability_info() + .map(|ability| (ability.ability_meta.capabilities, ability.hand)) + .map(|(capabilities, hand)| { + ( + if capabilities.contains(Capability::PARRIES) + || capabilities.contains(Capability::PARRIES_MELEE) + || capabilities.contains(Capability::BLOCKS) + { + FALLBACK_BLOCK_STRENGTH + } else { + 0.0 + }, + hand.and_then(|hand| inventory.equipped(hand.to_equip_slot())) + .map_or(1.0, |item| match &*item.kind() { + ItemKind::Tool(tool) => { + tool.stats(item.stats_durability_multiplier()).power + }, + _ => 1.0, + }), + ) + }) + .map_or(0.0, |(capability_strength, tool_block_strength)| { + capability_strength * tool_block_strength + }), + } +} + +pub fn get_equip_slot_by_block_priority(inventory: Option<&Inventory>) -> EquipSlot { + inventory + .map(get_weapon_kinds) + .map_or( + EquipSlot::ActiveMainhand, + |weapon_kinds| match weapon_kinds { + (Some(mainhand), Some(offhand)) => { + if mainhand.block_priority() >= offhand.block_priority() { + EquipSlot::ActiveMainhand + } else { + EquipSlot::ActiveOffhand + } + }, + (Some(_), None) => EquipSlot::ActiveMainhand, + (None, Some(_)) => EquipSlot::ActiveOffhand, + (None, None) => EquipSlot::ActiveMainhand, + }, + ) +} diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 48291a545f..46167a7d5c 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -195,34 +195,22 @@ impl ActiveAbilities { }; match ability { - Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| { - abilities - .guard(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveMainhand), - true, - spec_ability(i), - ) - }) - .or_else(|| { - ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| { - abilities - .guard(Some(skill_set), context) - .map(|(a, i)| (a.ability.clone(), i)) - }) - .map(|(ability, i)| { - ( - scale_ability(ability, EquipSlot::ActiveOffhand), - false, - spec_ability(i), - ) - }) - }), + Ability::ToolGuard => { + let equip_slot = combat::get_equip_slot_by_block_priority(inv); + ability_set(equip_slot) + .and_then(|abilities| { + abilities + .guard(Some(skill_set), context) + .map(|(a, i)| (a.ability.clone(), i)) + }) + .map(|(ability, i)| { + ( + scale_ability(ability, equip_slot), + matches!(equip_slot, EquipSlot::ActiveOffhand), + spec_ability(i), + ) + }) + }, Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) .and_then(|abilities| { abilities @@ -396,7 +384,7 @@ impl Ability { }; match self { - Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) + Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) .and_then(|abilities| { abilities .guard(skillset, context) @@ -407,19 +395,6 @@ impl Ability { .as_ref() .and_then(|g| contextual_id(Some(g))) }) - }) - .or_else(|| { - ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| { - abilities - .guard(skillset, context) - .map(|a| a.0.id.as_str()) - .or_else(|| { - abilities - .guard - .as_ref() - .and_then(|g| contextual_id(Some(g))) - }) - }) }), Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { abilities @@ -524,12 +499,8 @@ impl SpecifiedAbility { ability_set(EquipSlot::ActiveMainhand) .map(|abilities| ability_id(self, &abilities.secondary)) }), - Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) - .and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))) - .or_else(|| { - ability_set(EquipSlot::ActiveOffhand) - .and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))) - }), + Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv)) + .and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))), Ability::SpeciesMovement => None, // TODO: Make not None Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand) .and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))), @@ -1006,6 +977,7 @@ pub enum CharacterAbility { buildup_duration: f32, swing_duration: f32, recover_duration: f32, + block_strength: f32, melee_constructor: MeleeConstructor, #[serde(default)] meta: AbilityMeta, @@ -1279,8 +1251,7 @@ impl CharacterAbility { ref mut recover_duration, // Do we want angle to be adjusted by range? max_angle: _, - // Block strength explicitly not modified by power, that will be a separate stat - block_strength: _, + ref mut block_strength, parry_window: _, ref mut energy_cost, energy_regen: _, @@ -1291,6 +1262,7 @@ impl CharacterAbility { *buildup_duration /= stats.speed; *recover_duration /= stats.speed; *energy_cost /= stats.energy_efficiency; + *block_strength *= stats.power; }, Roll { ref mut energy_cost, @@ -1660,6 +1632,7 @@ impl CharacterAbility { ref mut buildup_duration, ref mut swing_duration, ref mut recover_duration, + ref mut block_strength, ref mut melee_constructor, meta: _, } => { @@ -1667,6 +1640,7 @@ impl CharacterAbility { *swing_duration /= stats.speed; *recover_duration /= stats.speed; *energy_cost /= stats.energy_efficiency; + *block_strength *= stats.power; *melee_constructor = melee_constructor.adjusted_by_stats(stats); }, RapidMelee { @@ -2914,6 +2888,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { buildup_duration, swing_duration, recover_duration, + block_strength, melee_constructor, meta: _, } => CharacterState::RiposteMelee(riposte_melee::Data { @@ -2921,6 +2896,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { buildup_duration: Duration::from_secs_f32(*buildup_duration), swing_duration: Duration::from_secs_f32(*swing_duration), recover_duration: Duration::from_secs_f32(*recover_duration), + block_strength: *block_strength, melee_constructor: *melee_constructor, ability_info, }, @@ -3003,18 +2979,18 @@ bitflags::bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] // If more are ever needed, first check if any not used anymore, as some were only used in intermediary stages so may be free pub struct Capability: u8 { - // There used to be a capability here, to keep ordering the same below this is now a placeholder - const PLACEHOLDER = 0b00000001; + // The ability will parry all blockable attacks in the buildup portion + const PARRIES = 0b00000001; // Allows blocking to interrupt the ability at any point const BLOCK_INTERRUPT = 0b00000010; - // When the ability is in the buildup section, it counts as a block with 50% DR + // The ability will block melee attacks in the buildup portion const BLOCKS = 0b00000100; // When in the ability, an entity only receives half as much poise damage const POISE_RESISTANT = 0b00001000; // WHen in the ability, an entity only receives half as much knockback const KNOCKBACK_RESISTANT = 0b00010000; // The ability will parry melee attacks in the buildup portion - const PARRIES = 0b00100000; + const PARRIES_MELEE = 0b00100000; } } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index ee7fc1092c..9aa88a8251 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -308,53 +308,36 @@ impl CharacterState { ) } - pub fn block_strength(&self, attack_source: AttackSource) -> Option { - let from_capability = if let AttackSource::Melee = attack_source { - if let Some(capabilities) = self - .ability_info() - .map(|a| a.ability_meta) - .map(|m| m.capabilities) - { - (capabilities.contains(Capability::BLOCKS) - && matches!( - self.stage_section(), - Some(StageSection::Buildup | StageSection::Action) - )) - .then_some(0.5) - } else { - None - } - } else { - None - }; - let from_state = match self { - CharacterState::BasicBlock(c) => c - .static_data - .blocked_attacks - .applies(attack_source) - .then_some(c.static_data.block_strength), - _ => None, - }; - match (from_capability, from_state) { - (Some(a), Some(b)) => Some(a.max(b)), - (Some(a), None) | (None, Some(a)) => Some(a), - (None, None) => None, - } - } - pub fn is_parry(&self, attack_source: AttackSource) -> bool { let melee = matches!(attack_source, AttackSource::Melee); - let from_capability = melee + let from_capability_melee = melee && self .ability_info() .map(|a| a.ability_meta.capabilities) .map_or(false, |c| { - c.contains(Capability::PARRIES) + c.contains(Capability::PARRIES_MELEE) && matches!( self.stage_section(), Some(StageSection::Buildup | StageSection::Action) ) }); + let from_capability = matches!( + attack_source, + AttackSource::Melee + | AttackSource::Projectile + | AttackSource::Beam + | AttackSource::AirShockwave + | AttackSource::Explosion + ) && self + .ability_info() + .map(|a| a.ability_meta.capabilities) + .map_or(false, |c| { + c.contains(Capability::PARRIES) + && matches!( + self.stage_section(), + Some(StageSection::Buildup | StageSection::Action) + ) + }); let from_state = match self { CharacterState::BasicBlock(c) => c.is_parry(attack_source), CharacterState::RiposteMelee(c) => { @@ -366,7 +349,30 @@ impl CharacterState { }, _ => false, }; - from_capability || from_state + from_capability_melee || from_capability || from_state + } + + pub fn is_block(&self, attack_source: AttackSource) -> bool { + match self { + CharacterState::BasicBlock(data) => { + data.static_data.blocked_attacks.applies(attack_source) + && matches!( + self.stage_section(), + Some(StageSection::Buildup | StageSection::Action) + ) + }, + _ => self + .ability_info() + .map(|ability| ability.ability_meta.capabilities) + .map_or(false, |capabilities| { + capabilities.contains(Capability::BLOCKS) + && matches!( + self.stage_section(), + Some(StageSection::Buildup | StageSection::Action) + ) + && matches!(attack_source, AttackSource::Melee) + }), + } } /// In radians diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index d52bdc74ed..744192f56a 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -97,6 +97,28 @@ impl ToolKind { | ToolKind::Dagger ) } + + pub fn block_priority(&self) -> i32 { + match self { + ToolKind::Debug => 0, + ToolKind::Blowgun => 1, + ToolKind::Bow => 2, + ToolKind::Staff => 3, + ToolKind::Sceptre => 4, + ToolKind::Empty => 5, + ToolKind::Natural => 6, + ToolKind::Instrument => 7, + ToolKind::Farming => 8, + ToolKind::Shovel => 9, + ToolKind::Pick => 10, + ToolKind::Dagger => 11, + ToolKind::Spear => 12, + ToolKind::Hammer => 13, + ToolKind::Axe => 14, + ToolKind::Sword => 15, + ToolKind::Shield => 16, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/src/states/basic_block.rs b/common/src/states/basic_block.rs index c29d1b3ee9..b21984f42b 100644 --- a/common/src/states/basic_block.rs +++ b/common/src/states/basic_block.rs @@ -25,7 +25,8 @@ pub struct StaticData { pub recover_duration: Duration, /// Max angle (45.0 will give you a 90.0 angle window) pub max_angle: f32, - /// What percentage incoming damage is reduced by + /// Base value that incoming damage is reduced by and converted to poise + /// damage pub block_strength: f32, /// What durations are considered a parry pub parry_window: ParryWindow, diff --git a/common/src/states/riposte_melee.rs b/common/src/states/riposte_melee.rs index 5c915f0859..c9bb66ea60 100644 --- a/common/src/states/riposte_melee.rs +++ b/common/src/states/riposte_melee.rs @@ -18,6 +18,9 @@ pub struct StaticData { pub swing_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, + /// Base value that incoming damage is reduced by and converted to poise + /// damage + pub block_strength: f32, /// Used to construct the Melee attack pub melee_constructor: MeleeConstructor, /// What key is used to press ability diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 45577217a5..5001619aa3 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -1411,10 +1411,7 @@ pub fn get_hands(data: &JoinData<'_>) -> (Option, Option) { pub fn get_tool_stats(data: &JoinData<'_>, ai: AbilityInfo) -> tool::Stats { ai.hand - .map(|hand| match hand { - HandInfo::TwoHanded | HandInfo::MainHand => EquipSlot::ActiveMainhand, - HandInfo::OffHand => EquipSlot::ActiveOffhand, - }) + .map(|hand| hand.to_equip_slot()) .and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot))) .and_then(|item| { if let ItemKind::Tool(tool) = &*item.kind() { @@ -1616,6 +1613,13 @@ impl HandInfo { }, } } + + pub fn to_equip_slot(&self) -> EquipSlot { + match self { + HandInfo::TwoHanded | HandInfo::MainHand => EquipSlot::ActiveMainhand, + HandInfo::OffHand => EquipSlot::ActiveOffhand, + } + } } pub fn leave_stance(data: &JoinData<'_>, output_events: &mut OutputEvents) { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 0e004e07bd..1581a857c0 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -819,6 +819,7 @@ impl ServerEvent for LandOnGroundEvent { ); let change = damage.calculate_health_change( damage_reduction, + 0.0, None, None, 0.0, @@ -1401,6 +1402,7 @@ pub fn emit_effect_events( common::effect::Effect::Damage(damage) => { let change = damage.calculate_health_change( combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm), + 0.0, damage_contributor, None, 0.0, @@ -1677,7 +1679,7 @@ impl ServerEvent for ParryHookEvent { entity: ev.defender, change: c.static_data.energy_regen, }); - true + false }, _ => false, }; diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 1419123eaa..405bd2e2e8 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -202,6 +202,7 @@ impl StateExt for State { stats.get(entity), &msm, ), + 0.0, damage_contributor, None, 0.0, diff --git a/voxygen/anim/src/character/block.rs b/voxygen/anim/src/character/block.rs index 256661a7dd..2fec758d54 100644 --- a/voxygen/anim/src/character/block.rs +++ b/voxygen/anim/src/character/block.rs @@ -55,7 +55,9 @@ impl Animation for BlockAnimation { | Some("common.abilities.sword.basic_guard") | Some("common.abilities.axe.basic_guard") | Some("common.abilities.hammer.basic_guard") - | Some("common.abilities.sword.defensive_guard") => { + | Some("common.abilities.sword.defensive_guard") + | Some("common.abilities.shield.basic_guard") + | Some("common.abilities.shield.power_guard") => { let speed = Vec2::::from(velocity).magnitude(); let (movement1base, move2, movement3) = match stage_section { @@ -215,53 +217,95 @@ impl Animation for BlockAnimation { * Quaternion::rotation_y(0.6) * Quaternion::rotation_z(0.0); }, + Some(ToolKind::Shield) => { + next.control.position = Vec3::new(2.0, 9.0, 8.0); + next.control.orientation = + Quaternion::rotation_x(0.25) * Quaternion::rotation_z(-1.5); + + next.hand_l.position = Vec3::new(0.0, -2.0, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.hand_r.position = Vec3::new(0.0, 0.0, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0) + * Quaternion::rotation_y(PI / 2.0); + }, _ => {}, } }, + ((Some(Hands::One), Some(Hands::One)), _, Some(ToolKind::Shield)) => { + next.control_r.position = Vec3::new(-1.5, 8.0, 4.0 + move1 * 3.0); + next.control_r.orientation = Quaternion::rotation_x(0.25) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(1.5); + next.hand_r.position = Vec3::new(0.0, -2.0, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0); + + next.control_l.position = Vec3::new(-9.0, -5.0, 0.0); + next.control_l.orientation = + Quaternion::rotation_x(-1.75) * Quaternion::rotation_y(-0.3); + next.hand_l.position = Vec3::new(0.0, -0.5, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + }, + ((Some(Hands::One), _), Some(ToolKind::Shield), _) => { + next.control_l.position = Vec3::new(1.5, 8.0, 4.0 + move1 * 3.0); + next.control_l.orientation = Quaternion::rotation_x(0.25) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(-1.5); + next.hand_l.position = Vec3::new(0.0, -2.0, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.control_r.position = Vec3::new(9.0, -5.0, 0.0); + next.control_r.orientation = + Quaternion::rotation_x(-1.75) * Quaternion::rotation_y(0.3); + next.hand_r.position = Vec3::new(0.0, -0.5, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0); + }, + ((Some(Hands::One), _), _, _) => { + match hands { + (Some(Hands::One), _) => { + next.control_l.position = + Vec3::new(-7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0); + next.control_l.orientation = Quaternion::rotation_x(-0.3) + * Quaternion::rotation_y(move1 * 1.0); + next.hand_l.position = Vec3::new(0.0, -0.5, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0) + }, + (_, _) => {}, + }; + match hands { + (None | Some(Hands::One), Some(Hands::One)) => { + next.control_r.position = + Vec3::new(7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0); + next.control_r.orientation = Quaternion::rotation_x(-0.3) + * Quaternion::rotation_y(move1 * -1.0); + next.hand_r.position = Vec3::new(0.0, -0.5, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0) + }, + (_, _) => {}, + }; + match hands { + (None, None) | (None, Some(Hands::One)) => { + next.hand_l.position = Vec3::new(-4.5, 8.0, 5.0); + next.hand_l.orientation = + Quaternion::rotation_x(1.9) * Quaternion::rotation_y(-0.5) + }, + (_, _) => {}, + }; + match hands { + (None, None) | (Some(Hands::One), None) => { + next.hand_r.position = Vec3::new(4.5, 8.0, 5.0); + next.hand_r.orientation = + Quaternion::rotation_x(1.9) * Quaternion::rotation_y(0.5) + }, + (_, _) => {}, + }; + + if let (None, Some(Hands::Two)) = hands { + next.second = next.main; + } + }, ((_, _), _, _) => {}, }; - match hands { - (Some(Hands::One), _) => { - next.control_l.position = - Vec3::new(-7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0); - next.control_l.orientation = - Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(move1 * 1.0); - next.hand_l.position = Vec3::new(0.0, -0.5, 0.0); - next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0) - }, - (_, _) => {}, - }; - match hands { - (None | Some(Hands::One), Some(Hands::One)) => { - next.control_r.position = - Vec3::new(7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0); - next.control_r.orientation = - Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(move1 * -1.0); - next.hand_r.position = Vec3::new(0.0, -0.5, 0.0); - next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0) - }, - (_, _) => {}, - }; - match hands { - (None, None) | (None, Some(Hands::One)) => { - next.hand_l.position = Vec3::new(-4.5, 8.0, 5.0); - next.hand_l.orientation = - Quaternion::rotation_x(1.9) * Quaternion::rotation_y(-0.5) - }, - (_, _) => {}, - }; - match hands { - (None, None) | (Some(Hands::One), None) => { - next.hand_r.position = Vec3::new(4.5, 8.0, 5.0); - next.hand_r.orientation = - Quaternion::rotation_x(1.9) * Quaternion::rotation_y(0.5) - }, - (_, _) => {}, - }; - - if let (None, Some(Hands::Two)) = hands { - next.second = next.main; - } }, Some("common.abilities.sword.defensive_deflect") => { let (move1, move2, move3) = match stage_section { diff --git a/voxygen/anim/src/character/combomelee.rs b/voxygen/anim/src/character/combomelee.rs index d4fb7be588..af4dbcfbd8 100644 --- a/voxygen/anim/src/character/combomelee.rs +++ b/voxygen/anim/src/character/combomelee.rs @@ -2,7 +2,7 @@ use super::{ super::{vek::*, Animation}, CharacterSkeleton, SkeletonAttr, }; -use common::states::utils::{AbilityInfo, StageSection}; +use common::states::utils::{AbilityInfo, HandInfo, StageSection}; use core::f32::consts::{PI, TAU}; pub struct ComboAnimation; @@ -1133,6 +1133,81 @@ impl Animation for ComboAnimation { next.control.orientation.rotate_x(move2 * -1.2); next.control.position += Vec3::new(move2 * 8.0, 0.0, 0.0); }, + Some("common.abilities.shield.singlestrike") => { + let move1 = if strike == current_strike { + match stage_section { + Some(StageSection::Buildup) => anim_time, + Some(StageSection::Action) => 1.0, + Some(StageSection::Recover) => 1.0, + _ => 0.0, + } + } else { + 1.0 + }; + let move1 = move1 * multi_strike_pullback; + + if let Some(ability_info) = _ability_info { + match ability_info.hand { + Some(HandInfo::TwoHanded) => { + next.main.orientation = Quaternion::rotation_x(0.0); + next.chest.orientation = Quaternion::rotation_z(move1 * -0.3); + next.torso.orientation = Quaternion::rotation_z(move1 * -1.0); + next.head.orientation = Quaternion::rotation_z(move1 * 0.75); + next.head.position = Vec3::new(0.5, s_a.head.0 + 0.5, s_a.head.1); + + next.control.position = Vec3::new(move1 * -10.0, 6.0, move1 * 6.0); + next.control.orientation = Quaternion::rotation_z(-0.25); + + next.hand_l.position = Vec3::new(0.0, -2.0, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.hand_r.position = Vec3::new(0.0, 0.0, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0) + * Quaternion::rotation_y(PI / 2.0); + }, + Some(HandInfo::MainHand) => { + next.main.orientation = Quaternion::rotation_x(0.0); + next.chest.orientation = Quaternion::rotation_z(move1 * -0.3); + next.torso.orientation = Quaternion::rotation_z(move1 * -1.2); + next.head.orientation = Quaternion::rotation_z(move1 * 0.75); + next.head.position = Vec3::new(0.5, s_a.head.0 + 0.5, s_a.head.1); + + next.control_l.position = + Vec3::new(move1 * -12.0, 4.0, move1 * 6.0); + next.control_l.orientation = Quaternion::rotation_x(move1 * 0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(-0.25); + next.hand_l.position = Vec3::new(0.0, -1.5, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.control_r.position = Vec3::new(9.0, -1.0, 0.0); + next.control_r.orientation = Quaternion::rotation_x(-1.75); + next.hand_r.position = Vec3::new(0.0, 0.5, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0); + }, + Some(HandInfo::OffHand) => { + next.main.orientation = Quaternion::rotation_x(0.0); + next.chest.orientation = Quaternion::rotation_z(move1 * 0.3); + next.torso.orientation = Quaternion::rotation_z(move1 * 1.2); + next.head.orientation = Quaternion::rotation_z(move1 * -0.75); + next.head.position = Vec3::new(-0.5, s_a.head.0 + -0.5, s_a.head.1); + + next.control_r.position = Vec3::new(move1 * 12.0, 4.0, move1 * 6.0); + next.control_r.orientation = Quaternion::rotation_x(move1 * 0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.25); + next.hand_r.position = Vec3::new(0.0, -1.5, 0.0); + next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0); + + next.control_l.position = Vec3::new(-9.0, -1.0, 0.0); + next.control_l.orientation = Quaternion::rotation_x(-1.75); + next.hand_l.position = Vec3::new(0.0, 0.5, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + }, + _ => {}, + } + } + }, _ => {}, } } diff --git a/voxygen/anim/src/character/mod.rs b/voxygen/anim/src/character/mod.rs index 661b6e97c4..22e448671a 100644 --- a/voxygen/anim/src/character/mod.rs +++ b/voxygen/anim/src/character/mod.rs @@ -360,6 +360,11 @@ impl CharacterSkeleton { self.main.orientation = Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(PI / 2.0); }, + Some(ToolKind::Shield) => { + self.main.position = Vec3::new(-2.0, -3.0 - self.back_carry_offset, 1.0); + self.main.orientation = + Quaternion::rotation_y(-0.75) * Quaternion::rotation_z(PI / 2.0); + }, _ => { self.main.position = Vec3::new(-7.0, -5.0 - self.back_carry_offset, 15.0); self.main.orientation = @@ -382,7 +387,7 @@ impl CharacterSkeleton { Quaternion::rotation_y(2.5) * Quaternion::rotation_z(PI / 2.0); }, Some(ToolKind::Shield) => { - self.main.position = Vec3::new(-0.0, -4.0 - self.back_carry_offset, 3.0); + self.main.position = Vec3::new(-2.0, -4.0 - self.back_carry_offset, 3.0); self.main.orientation = Quaternion::rotation_y(0.25 * PI) * Quaternion::rotation_z(-1.5 * PI); }, @@ -403,7 +408,7 @@ impl CharacterSkeleton { Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(-PI / 2.0); }, Some(ToolKind::Shield) => { - self.second.position = Vec3::new(0.0, -4.0 - self.back_carry_offset, 3.0); + self.second.position = Vec3::new(1.5, -4.0 - self.back_carry_offset, 3.0); self.second.orientation = Quaternion::rotation_y(-0.25 * PI) * Quaternion::rotation_z(1.5 * PI); }, diff --git a/voxygen/anim/src/character/roll.rs b/voxygen/anim/src/character/roll.rs index a9e09b7b16..c1dea9080b 100644 --- a/voxygen/anim/src/character/roll.rs +++ b/voxygen/anim/src/character/roll.rs @@ -167,6 +167,18 @@ impl Animation for RollAnimation { next.control.position = Vec3::new(-11.0, 1.8, 4.0); }, + Some(ToolKind::Shield) => { + next.hand_l.position = Vec3::new(0.0, -1.5, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.hand_r.position = Vec3::new(0.0, 0.0, 0.0); + next.hand_r.orientation = + Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(2.0); + + next.control.position = Vec3::new(0.0, 7.0, 4.0); + next.control.orientation = + Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(-1.25); + }, _ => {}, }, (_, _) => {}, diff --git a/voxygen/anim/src/character/wield.rs b/voxygen/anim/src/character/wield.rs index 899da75366..2a1e28b98b 100644 --- a/voxygen/anim/src/character/wield.rs +++ b/voxygen/anim/src/character/wield.rs @@ -497,6 +497,18 @@ impl Animation for WieldAnimation { } } }, + Some(ToolKind::Shield) => { + next.hand_l.position = Vec3::new(0.0, -2.0, 0.0); + next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0); + + next.hand_r.position = Vec3::new(0.0, 0.0, 0.0); + next.hand_r.orientation = + Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(2.0); + + next.control.position = Vec3::new(0.0, 7.0, 4.0); + next.control.orientation = + Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(-1.25); + }, _ => {}, }, ((_, _), _, _) => {}, diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs index 0494a51781..edc86365a8 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs @@ -145,7 +145,12 @@ impl CombatEventMapper { previous_state: &PreviousEntityState, inventory: &Inventory, ) -> SfxEvent { - if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) { + let equip_slot = character_state + .ability_info() + .and_then(|ability| ability.hand) + .map_or(EquipSlot::ActiveMainhand, |hand| hand.to_equip_slot()); + + if let Some(item) = inventory.equipped(equip_slot) { if let ItemKind::Tool(data) = &*item.kind() { if character_state.is_attack() { return SfxEvent::Attack( diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index 6f81791c84..dad5fc506f 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -1131,6 +1131,7 @@ impl<'a> Widget for Crafting<'a> { RecipeKind::Component(ToolKind::Bow) => self.imgs.icon_log, RecipeKind::Component(ToolKind::Staff) => self.imgs.icon_log, RecipeKind::Component(ToolKind::Sceptre) => self.imgs.icon_log, + RecipeKind::Component(ToolKind::Shield) => self.imgs.icon_ingot, _ => self.imgs.not_found, }; diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 90a8ec52b3..805742c70a 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -563,8 +563,8 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id { "common.abilities.sceptre.healingaura" => imgs.skill_sceptre_heal, "common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura, // Shield - "common.abilities.shield.tempbasic" => imgs.onehshield_m1, - "common.abilities.shield.block" => imgs.onehshield_m2, + "common.abilities.shield.singlestrike" => imgs.onehshield_m1, + "common.abilities.shield.power_guard" => imgs.onehshield_m1, // Dagger "common.abilities.dagger.tempbasic" => imgs.onehdagger_m1, // Pickaxe