diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a23e1566f..2c1f1b9c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Missing translations can be displayed in English. - New large birds npcs - Day period dependant wildlife spawns +- You can now block and parry with melee weapons ### Changed diff --git a/assets/common/abilities/shield/block.ron b/assets/common/abilities/shield/block.ron index ca0309ede7..f586128082 100644 --- a/assets/common/abilities/shield/block.ron +++ b/assets/common/abilities/shield/block.ron @@ -1 +1,7 @@ -BasicBlock \ No newline at end of file +BasicBlock( + buildup_duration: 0.1, + recover_duration: 0.1, + max_angle: 90.0, + block_strength: 0.8, + energy_cost: 0.0, +) \ No newline at end of file diff --git a/assets/common/abilities/sword/spin.ron b/assets/common/abilities/sword/spin.ron index a0cef50ca7..171d2ec2d2 100644 --- a/assets/common/abilities/sword/spin.ron +++ b/assets/common/abilities/sword/spin.ron @@ -1,5 +1,5 @@ SpinMelee( - buildup_duration: 0.6, + buildup_duration: 0.35, swing_duration: 0.4, recover_duration: 0.5, base_damage: 160, diff --git a/assets/voxygen/audio/ambient/wind.ogg b/assets/voxygen/audio/ambient/wind.ogg index a351e79110..b8509325e9 100644 --- a/assets/voxygen/audio/ambient/wind.ogg +++ b/assets/voxygen/audio/ambient/wind.ogg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70f1ce7d6f20d94e66fa5ad45eb71f35f09312e32741772d6ff04008b1314f99 -size 257478 +oid sha256:4416db6944a76b35c8fb9994c671681970853aa521bbbf86b4f2de25638ec46a +size 351351 diff --git a/assets/voxygen/audio/sfx/character/block_1.ogg b/assets/voxygen/audio/sfx/character/block_1.ogg new file mode 100644 index 0000000000..bc8f726218 --- /dev/null +++ b/assets/voxygen/audio/sfx/character/block_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02b8f554bd7b0a4c86ef4dcf40933edc0f5548a39db9caf40c71f8f659d4e424 +size 20008 diff --git a/assets/voxygen/audio/sfx/character/block_2.ogg b/assets/voxygen/audio/sfx/character/block_2.ogg new file mode 100644 index 0000000000..f24b07d24f --- /dev/null +++ b/assets/voxygen/audio/sfx/character/block_2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ac1d38cf8aeec45834c57309596fe7d0d16bcc55d06a1a7fcb44e825e2aa563 +size 14764 diff --git a/assets/voxygen/audio/sfx/character/block_3.ogg b/assets/voxygen/audio/sfx/character/block_3.ogg new file mode 100644 index 0000000000..163c7f48cf --- /dev/null +++ b/assets/voxygen/audio/sfx/character/block_3.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70f63b6e8dad4dadcc1d42b3045ebe49718302b4850e2cd9432cd500b5a1619b +size 21549 diff --git a/assets/voxygen/audio/sfx/character/parry_1.ogg b/assets/voxygen/audio/sfx/character/parry_1.ogg new file mode 100644 index 0000000000..c30b040265 --- /dev/null +++ b/assets/voxygen/audio/sfx/character/parry_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23f15dcad6a34c473fe623278a31153acc938d65ce2990cc7f69e1cd4e03fb29 +size 23921 diff --git a/assets/voxygen/audio/sfx/character/parry_2.ogg b/assets/voxygen/audio/sfx/character/parry_2.ogg new file mode 100644 index 0000000000..38fd748211 --- /dev/null +++ b/assets/voxygen/audio/sfx/character/parry_2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2f7cf45b1fd57c4941e7b699dca41ea7d6f85b5f4a122f8b96bf413ebef8896 +size 31716 diff --git a/assets/voxygen/i18n/en/gameinput.ron b/assets/voxygen/i18n/en/gameinput.ron index bdec336735..331da4aae6 100644 --- a/assets/voxygen/i18n/en/gameinput.ron +++ b/assets/voxygen/i18n/en/gameinput.ron @@ -4,7 +4,8 @@ ( string_map: { "gameinput.primary": "Basic Attack", - "gameinput.secondary": "Secondary Attack/Block/Aim", + "gameinput.secondary": "Secondary Attack", + "gameinput.block": "Block", "gameinput.slot1": "Hotbar Slot 1", "gameinput.slot2": "Hotbar Slot 2", "gameinput.slot3": "Hotbar Slot 3", diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index a2c8a89ee4..dd3ca7b466 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -202,7 +202,7 @@ void main() { attr = Attr( linear_motion( normalize(vec3(rand0, rand1, rand3)) * 0.3, - normalize(vec3(rand4, rand5, rand6)) * 2.0 + grav_vel(earth_gravity) + normalize(vec3(rand4, rand5, rand6)) * 4.0 + grav_vel(earth_gravity) ), vec3(1.0), vec4(3.5, 3 + rand7, 0, 1), diff --git a/common/src/combat.rs b/common/src/combat.rs index e2de8da7db..b79c837e1e 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,11 +12,12 @@ use crate::{ }, poise::PoiseChange, skills::SkillGroupKind, - Body, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource, - Inventory, SkillSet, Stats, + Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, + HealthSource, Inventory, Ori, SkillSet, Stats, }, event::ServerEvent, outcome::Outcome, + states::utils::StageSection, uid::Uid, util::Dir, }; @@ -39,6 +40,15 @@ pub enum GroupTarget { OutOfGroup, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum AttackSource { + Melee, + Projectile, + Beam, + Shockwave, + Explosion, +} + #[cfg(not(target_arch = "wasm32"))] #[derive(Copy, Clone)] pub struct AttackerInfo<'a> { @@ -51,10 +61,13 @@ pub struct AttackerInfo<'a> { #[cfg(not(target_arch = "wasm32"))] pub struct TargetInfo<'a> { pub entity: EcsEntity, + pub uid: Uid, pub inventory: Option<&'a Inventory>, pub stats: Option<&'a Stats>, pub health: Option<&'a Health>, pub pos: Vec3, + pub ori: Option<&'a Ori>, + pub char_state: Option<&'a CharacterState>, } #[cfg(not(target_arch = "wasm32"))] @@ -105,6 +118,43 @@ impl Attack { pub fn effects(&self) -> impl Iterator { self.effects.iter() } + pub fn compute_damage_reduction( + target: &TargetInfo, + source: AttackSource, + dir: Dir, + mut emit_outcome: impl FnMut(Outcome), + ) -> f32 { + let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats); + let block_reduction = match source { + AttackSource::Melee => { + if let (Some(CharacterState::BasicBlock(data)), Some(ori)) = + (target.char_state, target.ori) + { + if ori.look_vec().angle_between(-*dir) < data.static_data.max_angle.to_radians() + { + let parry = matches!(data.stage_section, StageSection::Buildup); + emit_outcome(Outcome::Block { + parry, + pos: target.pos, + uid: target.uid, + }); + if parry { + 1.0 + } else { + data.static_data.block_strength + } + } else { + 0.0 + } + } else { + 0.0 + } + }, + _ => 0.0, + }; + 1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction) + } + #[allow(clippy::too_many_arguments)] pub fn apply_attack( &self, @@ -115,6 +165,7 @@ impl Attack { target_dodging: bool, // Currently just modifies damage, maybe look into modifying strength of other effects? strength_modifier: f32, + attack_source: AttackSource, mut emit: impl FnMut(ServerEvent), mut emit_outcome: impl FnMut(Outcome), ) { @@ -126,7 +177,8 @@ impl Attack { .filter(|d| d.target.map_or(true, |t| t == target_group)) .filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging)) { - let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats); + let damage_reduction = + Attack::compute_damage_reduction(&target, attack_source, dir, |o| emit_outcome(o)); let change = damage.damage.calculate_health_change( damage_reduction, attacker.map(|a| a.uid), diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index e29b21021d..aecb1775dd 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -39,7 +39,7 @@ impl From<&CharacterState> for CharacterAbilityType { CharacterState::BasicRanged(_) => Self::BasicRanged, CharacterState::Boost(_) => Self::Boost, CharacterState::DashMelee(data) => Self::DashMelee(data.stage_section), - CharacterState::BasicBlock => Self::BasicBlock, + CharacterState::BasicBlock(_) => Self::BasicBlock, CharacterState::LeapMelee(data) => Self::LeapMelee(data.stage_section), CharacterState::ComboMelee(data) => Self::ComboMelee(data.stage_section, data.stage), CharacterState::SpinMelee(data) => Self::SpinMelee(data.stage_section), @@ -114,7 +114,13 @@ pub enum CharacterAbility { charge_through: bool, is_interruptible: bool, }, - BasicBlock, + BasicBlock { + buildup_duration: f32, + recover_duration: f32, + max_angle: f32, + block_strength: f32, + energy_cost: f32, + }, Roll { energy_cost: f32, buildup_duration: f32, @@ -305,7 +311,8 @@ impl CharacterAbility { | CharacterAbility::ChargedRanged { energy_cost, .. } | CharacterAbility::ChargedMelee { energy_cost, .. } | CharacterAbility::Shockwave { energy_cost, .. } - | CharacterAbility::BasicAura { energy_cost, .. } => update + | CharacterAbility::BasicAura { energy_cost, .. } + | CharacterAbility::BasicBlock { energy_cost, .. } => update .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), @@ -341,6 +348,16 @@ impl CharacterAbility { } } + pub fn default_block() -> CharacterAbility { + CharacterAbility::BasicBlock { + buildup_duration: 0.35, + recover_duration: 0.3, + max_angle: 60.0, + block_strength: 0.5, + energy_cost: 50.0, + } + } + pub fn adjusted_by_stats(mut self, power: f32, poise_strength: f32, speed: f32) -> Self { use CharacterAbility::*; match self { @@ -408,7 +425,15 @@ impl CharacterAbility { *swing_duration /= speed; *recover_duration /= speed; }, - BasicBlock => {}, + BasicBlock { + ref mut buildup_duration, + ref mut recover_duration, + // Block strength explicitly not modified by power, that will be a separate stat + .. + } => { + *buildup_duration /= speed; + *recover_duration /= speed; + }, Roll { ref mut buildup_duration, ref mut movement_duration, @@ -578,7 +603,8 @@ impl CharacterAbility { | ChargedRanged { energy_cost, .. } | Shockwave { energy_cost, .. } | HealingBeam { energy_cost, .. } - | BasicAura { energy_cost, .. } => *energy_cost as u32, + | BasicAura { energy_cost, .. } + | BasicBlock { energy_cost, .. } => *energy_cost as u32, BasicBeam { energy_drain, .. } => { if *energy_drain > f32::EPSILON { 1 @@ -586,7 +612,7 @@ impl CharacterAbility { 0 } }, - BasicBlock | Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0, + Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0, } } @@ -1235,7 +1261,23 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { stage_section: StageSection::Buildup, exhausted: false, }), - CharacterAbility::BasicBlock => CharacterState::BasicBlock, + CharacterAbility::BasicBlock { + buildup_duration, + recover_duration, + max_angle, + block_strength, + energy_cost: _, + } => CharacterState::BasicBlock(basic_block::Data { + static_data: basic_block::StaticData { + buildup_duration: Duration::from_secs_f32(*buildup_duration), + recover_duration: Duration::from_secs_f32(*recover_duration), + max_angle: *max_angle, + block_strength: *block_strength, + ability_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }), CharacterAbility::Roll { energy_cost: _, buildup_duration, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 40414c39ad..6f7a375e65 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -55,7 +55,7 @@ pub enum CharacterState { /// A stunned state Stunned(stunned::Data), /// A basic blocking state - BasicBlock, + BasicBlock(basic_block::Data), /// Player is busy equipping or unequipping weapons Equipping(equipping::Data), /// Player is holding a weapon and can perform other actions @@ -110,7 +110,7 @@ impl CharacterState { | CharacterState::BasicRanged(_) | CharacterState::DashMelee(_) | CharacterState::ComboMelee(_) - | CharacterState::BasicBlock + | CharacterState::BasicBlock(_) | CharacterState::LeapMelee(_) | CharacterState::SpinMelee(_) | CharacterState::ChargedMelee(_) @@ -153,7 +153,7 @@ impl CharacterState { | CharacterState::BasicRanged(_) | CharacterState::DashMelee(_) | CharacterState::ComboMelee(_) - | CharacterState::BasicBlock + | CharacterState::BasicBlock(_) | CharacterState::LeapMelee(_) | CharacterState::ChargedMelee(_) | CharacterState::ChargedRanged(_) @@ -180,7 +180,7 @@ impl CharacterState { ) } - pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock) } + pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock(_)) } pub fn is_dodge(&self) -> bool { matches!(self, CharacterState::Roll(_)) } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 62d7f0befd..9e13b4e327 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -149,15 +149,19 @@ impl ControlAction { pub enum InputKind { Primary = 0, Secondary = 1, - Ability(usize) = 2, - Roll = 3, - Jump = 4, - Fly = 5, + Block = 2, + Ability(usize) = 3, + Roll = 4, + Jump = 5, + Fly = 6, } impl InputKind { pub fn is_ability(self) -> bool { - matches!(self, Self::Primary | Self::Secondary | Self::Ability(_)) + matches!( + self, + Self::Primary | Self::Secondary | Self::Ability(_) | Self::Block + ) } } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 9bf489e6e8..cd85d3b4f0 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -326,6 +326,17 @@ impl Tool { Default::default() } } + + pub fn can_block(&self) -> bool { + matches!( + self.kind, + ToolKind::Sword + | ToolKind::Axe + | ToolKind::Hammer + | ToolKind::Shield + | ToolKind::Dagger + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/common/src/outcome.rs b/common/src/outcome.rs index ecc1e08b05..1125094511 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -59,6 +59,11 @@ pub enum Outcome { Damage { pos: Vec3, }, + Block { + pos: Vec3, + parry: bool, + uid: Uid, + }, } impl Outcome { @@ -70,7 +75,8 @@ impl Outcome { | Outcome::Beam { pos, .. } | Outcome::SkillPointGain { pos, .. } | Outcome::SummonedCreature { pos, .. } - | Outcome::Damage { pos, .. } => Some(*pos), + | Outcome::Damage { pos, .. } + | Outcome::Block { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)), Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None, } diff --git a/common/src/states/basic_block.rs b/common/src/states/basic_block.rs index 6db22268cf..dffe356fe8 100644 --- a/common/src/states/basic_block.rs +++ b/common/src/states/basic_block.rs @@ -1,15 +1,36 @@ use super::utils::*; use crate::{ - comp::StateUpdate, + comp::{CharacterState, InputKind, StateUpdate}, states::behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; +use std::time::Duration; -// const BLOCK_ACCEL: f32 = 30.0; -// const BLOCK_SPEED: f32 = 75.0; +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should deal damage + pub buildup_duration: Duration, + /// How long the state has until exiting + 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 + pub block_strength: f32, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] -pub struct Data; +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, +} impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { @@ -17,6 +38,71 @@ impl CharacterBehavior for Data { handle_move(&data, &mut update, 0.4); + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::BasicBlock(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to swing section of stage + update.character = CharacterState::BasicBlock(Data { + timer: Duration::default(), + stage_section: StageSection::Swing, + ..*self + }); + } + }, + StageSection::Swing => { + if input_is_pressed(data, InputKind::Block) { + // Block + update.character = CharacterState::BasicBlock(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to recover section of stage + update.character = CharacterState::BasicBlock(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovery + update.character = CharacterState::BasicBlock(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + }, + } + + // At end of state logic so an interrupt isn't overwritten + if !input_is_pressed(data, self.static_data.ability_info.input) { + handle_state_interrupt(data, &mut update, false); + } + update } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 245169fc3b..76dd3d1ac7 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -538,21 +538,17 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate, strength: f32) -> } fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) { - let hands = |equip_slot| match data.inventory.equipped(equip_slot).map(|i| i.kind()) { - Some(ItemKind::Tool(tool)) => Some(tool.hands), - _ => None, - }; + let hands = get_hands(data); // Mouse1 and Skill1 always use the MainHand slot let always_main_hand = matches!(input, InputKind::Primary | InputKind::Ability(0)); - let no_main_hand = hands(EquipSlot::Mainhand).is_none(); + let no_main_hand = hands.0.is_none(); // skill_index used to select ability for the AbilityKey::Skill2 input let (equip_slot, skill_index) = if no_main_hand { (Some(EquipSlot::Offhand), 1) } else if always_main_hand { (Some(EquipSlot::Mainhand), 0) } else { - let hands = (hands(EquipSlot::Mainhand), hands(EquipSlot::Offhand)); match hands { (Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1), (_, Some(Hands::One)) => (Some(EquipSlot::Offhand), 0), @@ -579,7 +575,7 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) { .get(skill_index) .cloned() .and_then(unlocked), - InputKind::Roll | InputKind::Jump | InputKind::Fly => None, + InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None, }) .map(|a| { let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind); @@ -615,6 +611,7 @@ pub fn handle_input(data: &JoinData, update: &mut StateUpdate, input: InputKind) InputKind::Jump => { handle_jump(data, update, 1.0); }, + InputKind::Block => handle_block_input(data, update), InputKind::Fly => {}, } } @@ -626,6 +623,24 @@ pub fn attempt_input(data: &JoinData, update: &mut StateUpdate) { } } +/// Checks that player can block, then attempts to block +pub fn handle_block_input(data: &JoinData, update: &mut StateUpdate) { + let can_block = + |equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some(tool) if tool.can_block()); + let hands = get_hands(data); + if input_is_pressed(data, InputKind::Block) + && (can_block(EquipSlot::Mainhand) || (hands.0.is_none() && can_block(EquipSlot::Offhand))) + { + let ability = CharacterAbility::default_block(); + if ability.requirements_paid(data, update) { + update.character = CharacterState::from(( + &ability, + AbilityInfo::from_input(data, false, InputKind::Roll), + )); + } + } +} + /// Checks that player can perform a dodge, then /// attempts to perform their dodge ability pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { @@ -662,6 +677,17 @@ pub fn unwrap_tool_data<'a>(data: &'a JoinData, equip_slot: EquipSlot) -> Option } } +pub fn get_hands(data: &JoinData) -> (Option, Option) { + let hand = |slot| { + if let Some(ItemKind::Tool(tool)) = data.inventory.equipped(slot).map(|i| i.kind()) { + Some(tool.hands) + } else { + None + } + }; + (hand(EquipSlot::Mainhand), hand(EquipSlot::Offhand)) +} + pub fn get_crit_data(data: &JoinData, ai: AbilityInfo) -> (f32, f32) { const DEFAULT_CRIT_DATA: (f32, f32) = (0.5, 1.3); use HandInfo::*; diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index b91ca2c94d..5c0aa482c7 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -1,8 +1,8 @@ use common::{ - combat::{AttackerInfo, TargetInfo}, + combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ - Beam, BeamSegment, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, Pos, - Scale, Stats, + Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, + Inventory, Ori, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, @@ -40,6 +40,7 @@ pub struct ReadData<'a> { energies: ReadStorage<'a, Energy>, stats: ReadStorage<'a, Stats>, combos: ReadStorage<'a, Combo>, + character_states: ReadStorage<'a, CharacterState>, } /// This system is responsible for handling beams that heal or do damage @@ -184,10 +185,13 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), health: read_data.healths.get(target), - pos: pos.0, + pos: pos_b.0, + ori: read_data.orientations.get(target), + char_state: read_data.character_states.get(target), }; beam_segment.properties.attack.apply_attack( @@ -197,6 +201,7 @@ impl<'a> System<'a> for Sys { ori.look_dir(), false, 1.0, + AttackSource::Beam, |e| server_events.push(e), |o| outcomes.push(o), ); diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 251df59873..b2336bffdb 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -301,9 +301,7 @@ impl<'a> System<'a> for Sys { CharacterState::Sneak => { states::sneak::Data::handle_event(&states::sneak::Data, &j, action) }, - CharacterState::BasicBlock => { - states::basic_block::Data.handle_event(&j, action) - }, + CharacterState::BasicBlock(data) => data.handle_event(&j, action), CharacterState::Roll(data) => data.handle_event(&j, action), CharacterState::Wielding => states::wielding::Data.handle_event(&j, action), CharacterState::Equipping(data) => data.handle_event(&j, action), @@ -357,7 +355,7 @@ impl<'a> System<'a> for Sys { CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j), CharacterState::Dance => states::dance::Data::behavior(&states::dance::Data, &j), CharacterState::Sneak => states::sneak::Data::behavior(&states::sneak::Data, &j), - CharacterState::BasicBlock => states::basic_block::Data.behavior(&j), + CharacterState::BasicBlock(data) => data.behavior(&j), CharacterState::Roll(data) => data.behavior(&j), CharacterState::Wielding => states::wielding::Data.behavior(&j), CharacterState::Equipping(data) => data.behavior(&j), diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 0809f0d965..abb5aa703d 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -1,5 +1,5 @@ use common::{ - combat::{AttackerInfo, TargetInfo}, + combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats, @@ -87,11 +87,12 @@ impl<'a> System<'a> for Sys { } // Go through all other entities - for (target, pos_b, health_b, body_b) in ( + for (target, pos_b, health_b, body_b, uid_b) in ( &read_data.entities, &read_data.positions, &read_data.healths, &read_data.bodies, + &read_data.uids, ) .join() { @@ -143,10 +144,13 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), health: read_data.healths.get(target), - pos: pos.0, + pos: pos_b.0, + ori: read_data.orientations.get(target), + char_state: read_data.char_states.get(target), }; melee_attack.attack.apply_attack( @@ -156,6 +160,7 @@ impl<'a> System<'a> for Sys { dir, is_dodge, 1.0, + AttackSource::Melee, |e| server_emitter.emit(e), |o| outcomes.push(o), ); diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index f780f7f967..096f0f512d 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -1,8 +1,8 @@ use common::{ - combat::{AttackerInfo, TargetInfo}, + combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ - projectile, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, - Pos, Projectile, Stats, Vel, + projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, + Ori, PhysicsState, Pos, Projectile, Stats, Vel, }, event::{EventBus, ServerEvent}, outcome::Outcome, @@ -36,6 +36,7 @@ pub struct ReadData<'a> { combos: ReadStorage<'a, Combo>, healths: ReadStorage<'a, Health>, bodies: ReadStorage<'a, Body>, + character_states: ReadStorage<'a, CharacterState>, } /// This system is responsible for handling projectile effect triggers @@ -60,11 +61,11 @@ impl<'a> System<'a> for Sys { let mut server_emitter = read_data.server_bus.emitter(); // Attacks - 'projectile_loop: for (entity, pos, physics, ori, mut projectile) in ( + 'projectile_loop: for (entity, pos, physics, vel, mut projectile) in ( &read_data.entities, &read_data.positions, &read_data.physics_states, - &mut orientations, + &read_data.velocities, &mut projectiles, ) .join() @@ -110,51 +111,60 @@ impl<'a> System<'a> for Sys { .uid_allocator .retrieve_entity_internal(other.into()) { - let owner_entity = projectile.owner.and_then(|u| { - read_data.uid_allocator.retrieve_entity_internal(u.into()) - }); - - let attacker_info = - owner_entity.zip(projectile.owner).map(|(entity, uid)| { - AttackerInfo { - entity, - uid, - energy: read_data.energies.get(entity), - combo: read_data.combos.get(entity), - } + if let (Some(pos), Some(dir)) = ( + read_data.positions.get(target), + Dir::from_unnormalized(vel.0), + ) { + let owner_entity = projectile.owner.and_then(|u| { + read_data.uid_allocator.retrieve_entity_internal(u.into()) }); - let target_info = TargetInfo { - entity: target, - inventory: read_data.inventories.get(target), - stats: read_data.stats.get(target), - health: read_data.healths.get(target), - pos: pos.0, - }; + let attacker_info = + owner_entity.zip(projectile.owner).map(|(entity, uid)| { + AttackerInfo { + entity, + uid, + energy: read_data.energies.get(entity), + combo: read_data.combos.get(entity), + } + }); - if let Some(&body) = read_data.bodies.get(entity) { - outcomes.push(Outcome::ProjectileHit { + let target_info = TargetInfo { + entity: target, + uid: other, + inventory: read_data.inventories.get(target), + stats: read_data.stats.get(target), + health: read_data.healths.get(target), pos: pos.0, - body, - vel: read_data - .velocities - .get(entity) - .map_or(Vec3::zero(), |v| v.0), - source: projectile.owner, - target: read_data.uids.get(target).copied(), - }); - } + ori: orientations.get(target), + char_state: read_data.character_states.get(target), + }; - attack.apply_attack( - target_group, - attacker_info, - target_info, - ori.look_dir(), - false, - 1.0, - |e| server_emitter.emit(e), - |o| outcomes.push(o), - ); + if let Some(&body) = read_data.bodies.get(entity) { + outcomes.push(Outcome::ProjectileHit { + pos: pos.0, + body, + vel: read_data + .velocities + .get(entity) + .map_or(Vec3::zero(), |v| v.0), + source: projectile.owner, + target: read_data.uids.get(target).copied(), + }); + } + + attack.apply_attack( + target_group, + attacker_info, + target_info, + dir, + false, + 1.0, + AttackSource::Projectile, + |e| server_emitter.emit(e), + |o| outcomes.push(o), + ); + } } }, projectile::Effect::Explode(e) => { @@ -213,12 +223,10 @@ impl<'a> System<'a> for Sys { if projectile_vanished { continue 'projectile_loop; } - } else if let Some(dir) = read_data - .velocities - .get(entity) - .and_then(|vel| Dir::from_unnormalized(vel.0)) - { - *ori = dir.into(); + } else if let Some(ori) = orientations.get_mut(entity) { + if let Some(dir) = Dir::from_unnormalized(vel.0) { + *ori = dir.into(); + } } if projectile.time_left == Duration::default() { diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index fe6ab8ed48..0c6e1131c8 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -1,8 +1,8 @@ use common::{ - combat::{AttackerInfo, TargetInfo}, + combat::{AttackSource, AttackerInfo, TargetInfo}, comp::{ - Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos, Scale, - Shockwave, ShockwaveHitEntities, Stats, + Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, + PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, }, event::{EventBus, ServerEvent}, outcome::Outcome, @@ -37,6 +37,7 @@ pub struct ReadData<'a> { energies: ReadStorage<'a, Energy>, stats: ReadStorage<'a, Stats>, combos: ReadStorage<'a, Combo>, + character_states: ReadStorage<'a, CharacterState>, } /// This system is responsible for handling accepted inputs like moving or @@ -189,10 +190,13 @@ impl<'a> System<'a> for Sys { let target_info = TargetInfo { entity: target, + uid: *uid_b, inventory: read_data.inventories.get(target), stats: read_data.stats.get(target), health: read_data.healths.get(target), - pos: pos.0, + pos: pos_b.0, + ori: read_data.orientations.get(target), + char_state: read_data.character_states.get(target), }; shockwave.properties.attack.apply_attack( @@ -202,6 +206,7 @@ impl<'a> System<'a> for Sys { dir, false, 1.0, + AttackSource::Shockwave, |e| server_emitter.emit(e), |o| outcomes.push(o), ); diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index c7da3ffd1a..d56129c3f7 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -253,26 +253,11 @@ impl<'a> System<'a> for Sys { energy.get_mut_unchecked().regen_rate = 0.0 } }, - // recover small amount of passive energy from blocking, and bonus energy from - // blocking attacks? - CharacterState::BasicBlock => { - let res = { - let energy = energy.get_unchecked(); - energy.current() < energy.maximum() - }; - - if res { - energy.get_mut_unchecked().change_by(EnergyChange { - amount: -3, - source: EnergySource::Regen, - }); - } - }, - // Non-combat abilities that consume energy; - // temporarily stall energy gain, but preserve regen_rate. + // Abilities that temporarily stall energy gain, but preserve regen_rate. CharacterState::Roll { .. } | CharacterState::Climb { .. } - | CharacterState::Stunned { .. } => {}, + | CharacterState::Stunned { .. } + | CharacterState::BasicBlock { .. } => {}, } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d046405393..1151f79638 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -672,16 +672,33 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o RadiusEffect::Attack(attack) => { let energies = &ecs.read_storage::(); let combos = &ecs.read_storage::(); - for (entity_b, pos_b, health_b, inventory_b_maybe, stats_b_maybe, body_b_maybe) in ( + for ( + entity_b, + pos_b, + health_b, + ( + body_b_maybe, + inventory_b_maybe, + stats_b_maybe, + ori_b_maybe, + char_state_b_maybe, + uid_b, + ), + ) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), + ( + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + &ecs.read_storage::(), + ), ) .join() - .filter(|(_, _, h, _, _, _)| !h.is_dead) + .filter(|(_, _, h, _)| !h.is_dead) { // Check if it is a hit let strength = if let Some(body) = body_b_maybe { @@ -721,10 +738,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o let target_info = combat::TargetInfo { entity: entity_b, + uid: *uid_b, inventory: inventory_b_maybe, stats: stats_b_maybe, health: Some(health_b), - pos, + pos: pos_b.0, + ori: ori_b_maybe, + char_state: char_state_b_maybe, }; let server_eventbus = ecs.read_resource::>(); @@ -736,6 +756,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3, explosion: Explosion, o dir, false, strength, + combat::AttackSource::Explosion, |e| server_eventbus.emit_now(e), |o| outcomes.push(o), ); diff --git a/voxygen/anim/src/character/alpha.rs b/voxygen/anim/src/character/alpha.rs index 0004398591..2ab3349da2 100644 --- a/voxygen/anim/src/character/alpha.rs +++ b/voxygen/anim/src/character/alpha.rs @@ -166,11 +166,13 @@ impl Animation for AlphaAnimation { let moveret2 = move2 * pullback; next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2 + moveret2 * -7.0); - next.hand_l.orientation = - Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4); + next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3) + * Quaternion::rotation_y(s_a.hhl.4) + * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); - next.hand_r.orientation = - Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4); + next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); next.control.position = Vec3::new( s_a.hc.0 + moveret1 * -13.0 + moveret2 * 3.0, diff --git a/voxygen/anim/src/character/block.rs b/voxygen/anim/src/character/block.rs index c4ceb474ca..8b582c6da9 100644 --- a/voxygen/anim/src/character/block.rs +++ b/voxygen/anim/src/character/block.rs @@ -2,12 +2,24 @@ use super::{ super::{vek::*, Animation}, CharacterSkeleton, SkeletonAttr, }; -use common::comp::item::ToolKind; +use common::{ + comp::item::{Hands, ToolKind}, + states::utils::StageSection, +}; +use std::f32::consts::PI; pub struct BlockAnimation; +type BlockAnimationDependency = ( + (Option, Option), + Option, + Option, + Vec3, + f32, + Option, +); impl Animation for BlockAnimation { - type Dependency = (Option, Option, f32); + type Dependency = BlockAnimationDependency; type Skeleton = CharacterSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -16,24 +28,206 @@ impl Animation for BlockAnimation { #[cfg_attr(feature = "be-dyn-lib", export_name = "character_block")] fn update_skeleton_inner( skeleton: &Self::Skeleton, - (_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency, - _anim_time: f32, - _rate: &mut f32, + (hands, active_tool_kind, second_tool_kind,velocity, _global_time, stage_section): Self::Dependency, + anim_time: f32, + rate: &mut f32, s_a: &SkeletonAttr, ) -> Self::Skeleton { + *rate = 1.0; let mut next = (*skeleton).clone(); - next.head.position = Vec3::new(0.0, -1.0 + s_a.head.0, s_a.head.1 + 19.5); - next.head.orientation = Quaternion::rotation_x(-0.25); - - next.hand_l.position = Vec3::new(s_a.hand.0 - 6.0, s_a.hand.1 + 3.5, s_a.hand.2 + 0.0); - next.hand_l.orientation = Quaternion::rotation_x(-0.3); - next.hand_r.position = Vec3::new(s_a.hand.0 - 6.0, s_a.hand.1 + 3.0, s_a.hand.2 - 2.0); - next.hand_r.orientation = Quaternion::rotation_x(-0.3); next.main.position = Vec3::new(0.0, 0.0, 0.0); - next.main.orientation = Quaternion::rotation_x(-0.3); + next.main.orientation = Quaternion::rotation_z(0.0); + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_z(0.0); - next.torso.position = Vec3::new(0.0, -0.2, 0.1) * s_a.scaler; + let speed = Vec2::::from(velocity).magnitude(); + + let (movement1base, move2, movement3) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), + Some(StageSection::Swing) => (1.0, (anim_time * 10.0).sin(), 0.0), + + Some(StageSection::Recover) => (1.0, 1.0, anim_time.powf(4.0)), + _ => (0.0, 0.0, 0.0), + }; + let pullback = 1.0 - movement3; + let move1 = movement1base * pullback; + + if speed > 0.5 { + } else { + next.chest.position = + Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + move1 * -1.0 + move2 * 0.2); + next.chest.orientation = Quaternion::rotation_x(move1 * -0.15); + next.head.orientation = Quaternion::rotation_x(move1 * 0.25); + + next.belt.position = Vec3::new(0.0, s_a.belt.0 + move1 * 0.5, s_a.belt.1 + move1 * 0.5); + next.shorts.position = + Vec3::new(0.0, s_a.shorts.0 + move1 * 1.3, s_a.shorts.1 + move1 * 1.0); + + next.belt.orientation = Quaternion::rotation_x(move1 * 0.15); + next.shorts.orientation = Quaternion::rotation_x(move1 * 0.25); + + next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1 + move1 * 2.0, s_a.foot.2); + next.foot_l.orientation = Quaternion::rotation_z(move1 * -0.5); + + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1 + move1 * -2.0, s_a.foot.2); + next.foot_r.orientation = Quaternion::rotation_x(move1 * -0.5); + }; + + match (hands, active_tool_kind, second_tool_kind) { + ((Some(Hands::Two), _), tool, _) | ((None, Some(Hands::Two)), _, tool) => match tool { + Some(ToolKind::Sword) | Some(ToolKind::SwordSimple) => { + next.hand_l.position = Vec3::new(s_a.shl.0, s_a.shl.1, s_a.shl.2); + next.hand_l.orientation = + Quaternion::rotation_x(s_a.shl.3) * Quaternion::rotation_y(s_a.shl.4); + next.hand_r.position = Vec3::new( + s_a.shr.0 + move1 * -2.0, + s_a.shr.1, + s_a.shr.2 + move1 * 20.0, + ); + next.hand_r.orientation = Quaternion::rotation_x(s_a.shr.3) + * Quaternion::rotation_y(s_a.shr.4) + * Quaternion::rotation_z(move1 * 1.5); + + next.control.position = + Vec3::new(s_a.sc.0 + move1 * -3.0, s_a.sc.1, s_a.sc.2 + move1 * 4.0); + next.control.orientation = Quaternion::rotation_x(s_a.sc.3) + * Quaternion::rotation_y(move1 * 1.1) + * Quaternion::rotation_z(move1 * 1.7); + }, + + Some(ToolKind::Axe) => { + next.main.position = Vec3::new(0.0, 0.0, 0.0); + next.main.orientation = Quaternion::rotation_x(0.0); + + next.hand_l.position = Vec3::new(s_a.ahl.0, s_a.ahl.1, s_a.ahl.2); + next.hand_l.orientation = + Quaternion::rotation_x(s_a.ahl.3) * Quaternion::rotation_y(s_a.ahl.4); + next.hand_r.position = Vec3::new(s_a.ahr.0, s_a.ahr.1, s_a.ahr.2); + next.hand_r.orientation = + Quaternion::rotation_x(s_a.ahr.3) * Quaternion::rotation_z(s_a.ahr.5); + + next.control.position = Vec3::new( + s_a.ac.0 + move1 * 13.0, + s_a.ac.1 + move1 * -3.0, + s_a.ac.2 + move1 * 8.0, + ); + next.control.orientation = Quaternion::rotation_x(s_a.ac.3 + move1 * -2.0) + * Quaternion::rotation_y(s_a.ac.4 + move1 * -1.8) + * Quaternion::rotation_z(s_a.ac.5 + move1 * 4.0); + }, + Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => { + next.hand_l.position = + Vec3::new(s_a.hhl.0, s_a.hhl.1 + move1 * 6.0, s_a.hhl.2 + move1 * 6.0); + next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3 + move1 * -0.5) + * Quaternion::rotation_y(s_a.hhl.4 + move1 * 1.5) + * Quaternion::rotation_z(s_a.hhl.5 + move1 * PI); + next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); + next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); + + next.control.position = Vec3::new( + s_a.hc.0 + move1 * 3.0, + s_a.hc.1 + move1 * 3.0, + s_a.hc.2 + move1 * 10.0, + ); + next.control.orientation = Quaternion::rotation_x(s_a.hc.3) + * Quaternion::rotation_y(s_a.hc.4) + * Quaternion::rotation_z(s_a.hc.5 + move1 * -1.0); + }, + Some(ToolKind::Staff) | Some(ToolKind::Sceptre) => { + next.hand_r.position = Vec3::new(s_a.sthr.0, s_a.sthr.1, s_a.sthr.2); + next.hand_r.orientation = + Quaternion::rotation_x(s_a.sthr.3) * Quaternion::rotation_y(s_a.sthr.4); + + next.control.position = Vec3::new(s_a.stc.0, s_a.stc.1, s_a.stc.2); + + next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2); + next.hand_l.orientation = Quaternion::rotation_x(s_a.sthl.3); + + next.control.orientation = Quaternion::rotation_x(s_a.stc.3) + * Quaternion::rotation_y(s_a.stc.4) + * Quaternion::rotation_z(s_a.stc.5); + }, + Some(ToolKind::Bow) => { + next.main.position = Vec3::new(0.0, 0.0, 0.0); + next.main.orientation = Quaternion::rotation_x(0.0); + next.hand_l.position = Vec3::new(s_a.bhl.0, s_a.bhl.1, s_a.bhl.2); + next.hand_l.orientation = Quaternion::rotation_x(s_a.bhl.3); + next.hand_r.position = Vec3::new(s_a.bhr.0, s_a.bhr.1, s_a.bhr.2); + next.hand_r.orientation = Quaternion::rotation_x(s_a.bhr.3); + + next.hold.position = Vec3::new(0.0, -1.0, -5.2); + next.hold.orientation = Quaternion::rotation_x(-1.57); + next.hold.scale = Vec3::one() * 1.0; + + next.control.position = Vec3::new(s_a.bc.0, s_a.bc.1, s_a.bc.2); + next.control.orientation = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(s_a.bc.4) + * Quaternion::rotation_z(s_a.bc.5); + }, + Some(ToolKind::Debug) => { + next.hand_l.position = Vec3::new(-7.0, 4.0, 3.0); + next.hand_l.orientation = Quaternion::rotation_x(1.27); + next.main.position = Vec3::new(-5.0, 5.0, 23.0); + next.main.orientation = Quaternion::rotation_x(PI); + }, + Some(ToolKind::Farming) => { + next.hand_l.position = Vec3::new(9.0, 1.0, 1.0); + next.hand_l.orientation = Quaternion::rotation_x(1.57); + next.hand_r.position = Vec3::new(9.0, 1.0, 11.0); + next.hand_r.orientation = Quaternion::rotation_x(1.57); + next.main.position = Vec3::new(7.5, 7.5, 13.2); + next.main.orientation = Quaternion::rotation_y(PI); + + next.control.position = Vec3::new(-11.0, 1.8, 4.0); + next.control.orientation = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.6) + * Quaternion::rotation_z(0.0); + }, + _ => {}, + }, + ((_, _), _, _) => {}, + }; + 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(1.57) + }, + (_, _) => {}, + }; + 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(1.57) + }, + (_, _) => {}, + }; + 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; + } next } diff --git a/voxygen/anim/src/character/chargeswing.rs b/voxygen/anim/src/character/chargeswing.rs index 7a19349913..4be709117e 100644 --- a/voxygen/anim/src/character/chargeswing.rs +++ b/voxygen/anim/src/character/chargeswing.rs @@ -87,11 +87,13 @@ impl Animation for ChargeswingAnimation { Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) => { next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2 + (move2 * -8.0)); - next.hand_l.orientation = - Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4); + next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3) + * Quaternion::rotation_y(s_a.hhl.4) + * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); - next.hand_r.orientation = - Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4); + next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); next.control.position = Vec3::new( s_a.hc.0 + (move1 * -2.0 + move2 * -8.0), diff --git a/voxygen/anim/src/character/leapmelee.rs b/voxygen/anim/src/character/leapmelee.rs index 272cde65ec..116851439c 100644 --- a/voxygen/anim/src/character/leapmelee.rs +++ b/voxygen/anim/src/character/leapmelee.rs @@ -119,9 +119,11 @@ impl Animation for LeapAnimation { match ability_info.and_then(|a| a.tool) { Some(ToolKind::Hammer) => { next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2); - next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3); + next.hand_l.orientation = + Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); - next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3); + next.hand_r.orientation = + Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_z(s_a.hhr.5); next.main.position = Vec3::new(0.0, 0.0, 0.0); next.main.orientation = Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); diff --git a/voxygen/anim/src/character/mod.rs b/voxygen/anim/src/character/mod.rs index 7e01dcde4a..ad446fcff1 100644 --- a/voxygen/anim/src/character/mod.rs +++ b/voxygen/anim/src/character/mod.rs @@ -291,10 +291,10 @@ impl<'a> From<&'a Body> for SkeletonAttr { _ => (-7.0, 7.0, 2.0, -0.1, 0.0, 0.0), }, hhl: match (body.species, body.body_type) { - _ => (-0.5, -1.0, 10.0, 4.71, 0.0, 0.0), + _ => (0.1, 0.0, 11.0, 4.71, 0.0, PI), }, hhr: match (body.species, body.body_type) { - _ => (0.0, 0.0, 0.0, 4.71, 0.0, 0.0), + _ => (0.0, 0.0, 0.0, 4.71, 0.0, PI), }, hc: match (body.species, body.body_type) { _ => (6.0, 7.0, 1.0, -0.3, -1.57, 3.64), diff --git a/voxygen/anim/src/character/staggered.rs b/voxygen/anim/src/character/staggered.rs index 644ab23884..c738a88fc9 100644 --- a/voxygen/anim/src/character/staggered.rs +++ b/voxygen/anim/src/character/staggered.rs @@ -106,10 +106,12 @@ impl Animation for StaggeredAnimation { Some(ToolKind::Hammer | ToolKind::Pick) => { next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2); next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3) - * Quaternion::rotation_y(s_a.hhl.4); + * Quaternion::rotation_y(s_a.hhl.4) + * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) - * Quaternion::rotation_y(s_a.hhr.4); + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); next.control.position = Vec3::new(s_a.hc.0, s_a.hc.1, s_a.hc.2); next.control.orientation = Quaternion::rotation_x(s_a.hc.3) diff --git a/voxygen/anim/src/character/stunned.rs b/voxygen/anim/src/character/stunned.rs index c88a001e91..fa12b7a934 100644 --- a/voxygen/anim/src/character/stunned.rs +++ b/voxygen/anim/src/character/stunned.rs @@ -101,11 +101,13 @@ impl Animation for StunnedAnimation { }, Some(ToolKind::Hammer | ToolKind::Pick) => { next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2); - next.hand_l.orientation = - Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4); + next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3) + * Quaternion::rotation_y(s_a.hhl.4) + * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); - next.hand_r.orientation = - Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4); + next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); next.control.position = Vec3::new(s_a.hc.0, s_a.hc.1, s_a.hc.2); next.control.orientation = Quaternion::rotation_x(s_a.hc.3) diff --git a/voxygen/anim/src/character/wield.rs b/voxygen/anim/src/character/wield.rs index 66c91a7d73..cc4751481b 100644 --- a/voxygen/anim/src/character/wield.rs +++ b/voxygen/anim/src/character/wield.rs @@ -166,19 +166,6 @@ impl Animation for WieldAnimation { next.control.orientation = Quaternion::rotation_x(s_a.sc.3 + u_slow * 0.15) * Quaternion::rotation_z(u_slowalt * 0.08); }, - Some(ToolKind::Dagger) => { - next.control.position = Vec3::new(0.0, 0.0, 0.0); - - next.hand_l.position = Vec3::new(0.0, 0.0, 0.0); - next.hand_l.orientation = Quaternion::rotation_x(0.0); - - next.control_l.position = Vec3::new(-7.0, 0.0, 0.0); - - next.hand_r.position = Vec3::new(0.0, 0.0, 0.0); - next.hand_r.orientation = Quaternion::rotation_x(0.0); - - next.control_r.position = Vec3::new(7.0, 0.0, 0.0); - }, Some(ToolKind::Axe) => { next.main.position = Vec3::new(0.0, 0.0, 0.0); next.main.orientation = Quaternion::rotation_x(0.0); @@ -215,11 +202,13 @@ impl Animation for WieldAnimation { }, Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => { next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2); - next.hand_l.orientation = - Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4); + next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3) + * Quaternion::rotation_y(s_a.hhl.4) + * Quaternion::rotation_z(s_a.hhl.5); next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2); - next.hand_r.orientation = - Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4); + next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3) + * Quaternion::rotation_y(s_a.hhr.4) + * Quaternion::rotation_z(s_a.hhr.5); next.control.position = Vec3::new( s_a.hc.0, diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 96ea404ba2..fe2d0ceb7c 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -407,9 +407,6 @@ impl SfxMgr { let file_ref = "voxygen.audio.sfx.footsteps.stone_step_1"; audio.play_sfx(file_ref, pos.map(|e| e as f32 + 0.5), Some(3.0)); }, - Outcome::ExpChange { .. } - | Outcome::ComboChange { .. } - | Outcome::SummonedCreature { .. } => {}, Outcome::Damage { pos, .. } => { let file_ref = vec![ "voxygen.audio.sfx.character.hit_1", @@ -419,6 +416,27 @@ impl SfxMgr { ][rand::thread_rng().gen_range(1..4)]; audio.play_sfx(file_ref, *pos, None); }, + Outcome::Block { pos, parry, .. } => { + let block_sfx = vec![ + "voxygen.audio.sfx.character.block_1", + "voxygen.audio.sfx.character.block_2", + "voxygen.audio.sfx.character.block_3", + ]; + let parry_sfx = vec![ + "voxygen.audio.sfx.character.parry_1", + "voxygen.audio.sfx.character.parry_2", + ]; + if *parry { + let file_ref = parry_sfx[rand::thread_rng().gen_range(1..parry_sfx.len())]; + audio.play_sfx(file_ref, *pos, Some(2.0)); + } else { + let file_ref = block_sfx[rand::thread_rng().gen_range(1..block_sfx.len())]; + audio.play_sfx(file_ref, *pos, Some(2.0)); + } + }, + Outcome::ExpChange { .. } + | Outcome::ComboChange { .. } + | Outcome::SummonedCreature { .. } => {}, } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f96a46586f..48ee2b7816 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -329,6 +329,11 @@ pub struct ComboFloater { pub timer: f64, } +pub struct BlockFloater { + pub owner: Uid, + pub timer: f32, +} + pub struct DebugInfo { pub tps: f64, pub frame_time: Duration, @@ -739,6 +744,13 @@ impl PromptDialogSettings { } } +pub struct Floaters { + pub exp_floaters: Vec, + pub skill_point_displays: Vec, + pub combo_floaters: VecDeque, + pub block_floaters: Vec, +} + pub struct Hud { ui: Ui, ids: Ids, @@ -765,9 +777,7 @@ pub struct Hud { hotbar: hotbar::State, events: Vec, crosshair_opacity: f32, - exp_floaters: Vec, - skill_point_displays: Vec, - combo_floaters: VecDeque, + floaters: Floaters, } impl Hud { @@ -878,9 +888,12 @@ impl Hud { hotbar: hotbar_state, events: Vec::new(), crosshair_opacity: 0.0, - exp_floaters: Vec::new(), - skill_point_displays: Vec::new(), - combo_floaters: VecDeque::new(), + floaters: Floaters { + exp_floaters: Vec::new(), + skill_point_displays: Vec::new(), + combo_floaters: VecDeque::new(), + block_floaters: Vec::new(), + }, } } @@ -1174,9 +1187,18 @@ impl Hud { } } // EXP Numbers - self.exp_floaters.retain(|f| f.timer > 0_f32); + self.floaters + .exp_floaters + .iter_mut() + .for_each(|f| f.timer -= dt.as_secs_f32()); + self.floaters.exp_floaters.retain(|f| f.timer > 0_f32); if let Some(uid) = uids.get(me) { - for floater in self.exp_floaters.iter_mut().filter(|f| f.owner == *uid) { + for floater in self + .floaters + .exp_floaters + .iter_mut() + .filter(|f| f.owner == *uid) + { let number_speed = 50.0; // Number Speed for Single EXP let player_sct_bg_id = player_sct_bg_id_walker.next( &mut self.ids.player_sct_bgs, @@ -1221,13 +1243,19 @@ impl Hud { ) .set(player_sct_id, ui_widgets); } - floater.timer -= dt.as_secs_f32(); } } // Skill points - self.skill_point_displays.retain(|d| d.timer > 0_f32); + self.floaters + .skill_point_displays + .iter_mut() + .for_each(|f| f.timer -= dt.as_secs_f32()); + self.floaters + .skill_point_displays + .retain(|d| d.timer > 0_f32); if let Some(uid) = uids.get(me) { if let Some(display) = self + .floaters .skill_point_displays .iter_mut() .find(|d| d.owner == *uid) @@ -1311,8 +1339,54 @@ impl Hud { .left_from(self.ids.player_rank_up_txt_1_bg, 5.0) .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade))) .set(self.ids.player_rank_up_icon, ui_widgets); + } + } + // Scrolling Combat Text for Parrying an attack + self.floaters + .block_floaters + .iter_mut() + .for_each(|f| f.timer -= dt.as_secs_f32()); + self.floaters.block_floaters.retain(|f| f.timer > 0_f32); + if let Some(uid) = uids.get(me) { + for floater in self + .floaters + .block_floaters + .iter_mut() + .filter(|f| f.owner == *uid) + { + let number_speed = 50.0; + let player_sct_bg_id = player_sct_bg_id_walker.next( + &mut self.ids.player_sct_bgs, + &mut ui_widgets.widget_id_generator(), + ); + let player_sct_id = player_sct_id_walker.next( + &mut self.ids.player_scts, + &mut ui_widgets.widget_id_generator(), + ); + let font_size = 30; + let y = floater.timer as f64 * number_speed; // Timer sets the widget offset + // text transparency + let fade = if floater.timer < 0.25 { + floater.timer as f32 / 0.25 + } else { + 1.0 + }; - display.timer -= dt.as_secs_f32(); + Text::new(&i18n.get("hud.sct.block")) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, fade)) + .x_y( + ui_widgets.win_w * (0.0), + ui_widgets.win_h * (-0.3) + y - 3.0, + ) + .set(player_sct_bg_id, ui_widgets); + Text::new(&i18n.get("hud.sct.block")) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.69, 0.82, 0.88, fade)) + .x_y(ui_widgets.win_w * 0.0, ui_widgets.win_h * -0.3 + y) + .set(player_sct_id, ui_widgets); } } } @@ -2273,12 +2347,14 @@ impl Hud { let ability_map = ecs.fetch::(); let bodies = ecs.read_storage::(); // Combo floater stuffs - for combo_floater in self.combo_floaters.iter_mut() { - combo_floater.timer -= dt.as_secs_f64(); - } - self.combo_floaters.retain(|f| f.timer > 0_f64); + self.floaters + .combo_floaters + .iter_mut() + .for_each(|f| f.timer -= dt.as_secs_f64()); + self.floaters.combo_floaters.retain(|f| f.timer > 0_f64); let combo = if let Some(uid) = ecs.read_storage::().get(entity) { - self.combo_floaters + self.floaters + .combo_floaters .iter() .find(|c| c.owner == *uid) .copied() @@ -3425,7 +3501,7 @@ impl Hud { pub fn handle_outcome(&mut self, outcome: &Outcome) { match outcome { - Outcome::ExpChange { uid, exp } => self.exp_floaters.push(ExpFloater { + Outcome::ExpChange { uid, exp } => self.floaters.exp_floaters.push(ExpFloater { owner: *uid, exp_change: *exp, timer: 4.0, @@ -3436,17 +3512,25 @@ impl Hud { skill_tree, total_points, .. - } => self.skill_point_displays.push(SkillPointGain { + } => self.floaters.skill_point_displays.push(SkillPointGain { owner: *uid, skill_tree: *skill_tree, total_points: *total_points, timer: 5.0, }), - Outcome::ComboChange { uid, combo } => self.combo_floaters.push_front(ComboFloater { - owner: *uid, - combo: *combo, - timer: comp::combo::COMBO_DECAY_START, - }), + Outcome::ComboChange { uid, combo } => { + self.floaters.combo_floaters.push_front(ComboFloater { + owner: *uid, + combo: *combo, + timer: comp::combo::COMBO_DECAY_START, + }) + }, + Outcome::Block { uid, parry, .. } if *parry => { + self.floaters.block_floaters.push(BlockFloater { + owner: *uid, + timer: 1.0, + }) + }, _ => {}, } } @@ -3568,8 +3652,8 @@ pub fn get_buff_desc(buff: BuffKind, localized_strings: &Localization) -> &str { pub fn get_buff_time(buff: BuffInfo) -> String { if let Some(dur) = buff.dur { - format!("Remaining: {:.0}s", dur.as_secs_f32()) + format!("{:.0}s", dur.as_secs_f32()) } else { - "Permanent".to_string() + "".to_string() } } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index ff3845c199..07dae79cbe 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1385,11 +1385,29 @@ impl FigureMgr { ), } }, - CharacterState::BasicBlock { .. } => { + CharacterState::BasicBlock(s) => { + let stage_time = s.timer.as_secs_f32(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f32() + }, + StageSection::Swing => stage_time, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; anim::character::BlockAnimation::update_skeleton( - &CharacterSkeleton::new(holding_lantern), - (active_tool_kind, second_tool_kind, time), - state.state_time, + &target_base, + ( + hands, + active_tool_kind, + second_tool_kind, + rel_vel, + time, + Some(s.stage_section), + ), + stage_progress, &mut state_animation_rate, skeleton_attr, ) diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index b58bbff27f..c9a349d96e 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -203,6 +203,18 @@ impl ParticleMgr { }); } }, + Outcome::Block { pos, parry, .. } => { + if *parry { + self.particles.resize_with(self.particles.len() + 10, || { + Particle::new( + Duration::from_millis(200), + time, + ParticleMode::GunPowderSpark, + *pos + Vec3::unit_z(), + ) + }); + } + }, Outcome::ProjectileShot { .. } | Outcome::Beam { .. } | Outcome::ExpChange { .. } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index fa1b98732c..57f293f0d7 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -418,6 +418,15 @@ impl PlayState for SessionState { ); } }, + GameInput::Block => { + let mut client = self.client.borrow_mut(); + client.handle_input( + InputKind::Block, + state, + select_pos, + target_entity.map(|t| t.0), + ); + }, GameInput::Roll => { let mut client = self.client.borrow_mut(); if can_build { diff --git a/voxygen/src/settings/control.rs b/voxygen/src/settings/control.rs index b70e30ebe2..67b4defb63 100644 --- a/voxygen/src/settings/control.rs +++ b/voxygen/src/settings/control.rs @@ -111,6 +111,7 @@ impl ControlSettings { match game_input { GameInput::Primary => KeyMouse::Mouse(MouseButton::Left), GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right), + GameInput::Block => KeyMouse::Key(VirtualKeyCode::LAlt), GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Comma), GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape), GameInput::Chat => KeyMouse::Key(VirtualKeyCode::Return), diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index e6c1581402..d75cc87785 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -21,6 +21,7 @@ use winit::monitor::VideoMode; pub enum GameInput { Primary, Secondary, + Block, Slot1, Slot2, Slot3, @@ -85,6 +86,7 @@ impl GameInput { match *self { GameInput::Primary => "gameinput.primary", GameInput::Secondary => "gameinput.secondary", + GameInput::Block => "gameinput.block", GameInput::ToggleCursor => "gameinput.togglecursor", GameInput::MoveForward => "gameinput.moveforward", GameInput::MoveLeft => "gameinput.moveleft", @@ -149,6 +151,7 @@ impl GameInput { [ GameInput::Primary, GameInput::Secondary, + GameInput::Block, GameInput::ToggleCursor, GameInput::MoveForward, GameInput::MoveLeft,