From 6ba158b7e1848ee1dc3041e076d710495eaf3ea0 Mon Sep 17 00:00:00 2001 From: Imbris Date: Tue, 24 Mar 2020 03:38:16 -0400 Subject: [PATCH] Input handling changes --- client/src/lib.rs | 61 +++++++++-- common/src/comp/controller.rs | 147 ++++++++++++++++++--------- common/src/comp/mod.rs | 3 +- common/src/msg/client.rs | 1 + common/src/states/climb.rs | 79 ++++++++------ common/src/states/dash_melee.rs | 2 +- common/src/states/idle.rs | 21 +++- common/src/states/sit.rs | 20 +++- common/src/states/utils.rs | 31 ++---- common/src/states/wielding.rs | 23 ++++- common/src/sys/character_behavior.rs | 90 +++++++++++----- common/src/sys/controller.rs | 29 +++--- server/src/sys/message.rs | 23 ++++- voxygen/src/key_state.rs | 22 ++++ voxygen/src/session.rs | 52 +++++++--- 15 files changed, 426 insertions(+), 178 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 9d2e4846d2..5606d415ab 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -15,7 +15,8 @@ pub use specs::{ use byteorder::{ByteOrder, LittleEndian}; use common::{ comp::{ - self, ControlEvent, Controller, ControllerInputs, InventoryManip, InventoryUpdateEvent, + self, ControlAction, ControlEvent, Controller, ControllerInputs, InventoryManip, + InventoryUpdateEvent, }, event::{EventBus, SfxEvent, SfxEventItem}, msg::{ @@ -31,7 +32,7 @@ use common::{ }; use hashbrown::HashMap; use image::DynamicImage; -use log::warn; +use log::{error, warn}; use std::{ net::SocketAddr, sync::Arc, @@ -288,6 +289,38 @@ impl Client { .send_message(ClientMsg::ControlEvent(ControlEvent::Unmount)); } + pub fn respawn(&mut self) { + if self + .state + .ecs() + .read_storage::() + .get(self.entity) + .map_or(false, |s| s.is_dead) + { + self.postbox + .send_message(ClientMsg::ControlEvent(ControlEvent::Respawn)); + } + } + + pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapLoadout); } + + pub fn toggle_wield(&mut self) { self.control_action(ControlAction::ToggleWield); } + + pub fn toggle_sit(&mut self) { self.control_action(ControlAction::ToggleSit); } + + fn control_action(&mut self, control_action: ControlAction) { + if let Some(controller) = self + .state + .ecs() + .write_storage::() + .get_mut(self.entity) + { + controller.actions.push(control_action); + } + self.postbox + .send_message(ClientMsg::ControlAction(control_action)); + } + pub fn view_distance(&self) -> Option { self.view_distance } pub fn loaded_distance(&self) -> f32 { self.loaded_distance } @@ -371,10 +404,26 @@ impl Client { // 1) Handle input from frontend. // Pass character actions from frontend input to the player's entity. if let ClientState::Character = self.client_state { - self.state.write_component(self.entity, Controller { - inputs: inputs.clone(), - events: Vec::new(), - }); + if let Err(err) = self + .state + .ecs() + .write_storage::() + .entry(self.entity) + .map(|entry| { + entry + .or_insert_with(|| Controller { + inputs: inputs.clone(), + events: Vec::new(), + actions: Vec::new(), + }) + .inputs = inputs.clone(); + }) + { + error!( + "Couldn't access controller component on client entity: {:?}", + err + ); + } self.postbox .send_message(ClientMsg::ControllerInputs(inputs)); } diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 2a0b8b966d..87d9e1577f 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -12,7 +12,21 @@ pub enum ControlEvent { Mount(Uid), Unmount, InventoryManip(InventoryManip), - //Respawn, + Respawn, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum ControlAction { + SwapLoadout, + ToggleWield, + ToggleSit, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +enum Freshness { + New, + TickedOnce, + Old, } /// Whether a key is pressed or unpressed @@ -21,41 +35,50 @@ pub enum ControlEvent { pub struct Input { /// Should not be pub because duration should /// always be reset when state is updated - state: bool, + pressed: bool, /// Should only be updated by npc agents /// through appropriate fn duration: Duration, - /// How many update ticks the button has been in its current state for - ticks_held: u32, + /// How fresh is the last change to the input state + freshness: Freshness, } impl Input { - fn tick(&mut self, old: Input, dt: Duration) { + fn tick(&mut self, dt: Duration) { // Increase how long input has been in current state self.duration = self.duration.checked_add(dt).unwrap_or_default(); + self.tick_freshness(); + } - match (self.is_pressed(), old.is_pressed()) { - (false, true) | (true, false) => { - println!("{:?}", self); - self.duration = Duration::default(); - self.ticks_held = 1; - println!("{:?}", self); - }, - (_, _) => { - self.ticks_held += 1; - println!("____"); - }, + fn tick_freshness(&mut self) { + self.freshness = match self.freshness { + Freshness::New => Freshness::TickedOnce, + Freshness::TickedOnce => Freshness::Old, + Freshness::Old => Freshness::Old, }; } + /// Update input with newer version + /// Used to update inputs with input recieved from clients + pub fn update_with_new(&mut self, new: Self) { + if self.pressed != new.pressed { + self.freshness = Freshness::New; + } + + self.pressed = new.pressed; + self.duration = new.duration; + } + /// Whether input is being pressed down - pub fn is_pressed(&self) -> bool { self.state == true } + pub fn is_pressed(&self) -> bool { self.pressed } /// Whether it's the first frame this input has been pressed - pub fn is_just_pressed(&self) -> bool { self.is_pressed() && self.ticks_held == 1 } + pub fn is_just_pressed(&self) -> bool { self.is_pressed() && self.freshness != Freshness::Old } /// Whether it's the first frame this input has been unpressed - pub fn is_just_unpressed(&self) -> bool { !self.is_pressed() && self.ticks_held == 1 } + pub fn is_just_unpressed(&self) -> bool { + !self.is_pressed() && self.freshness != Freshness::Old + } /// Whether input has been pressed longer than /// `DEFAULT_HOLD_DURATION` @@ -74,49 +97,52 @@ impl Input { self.is_pressed() && self.duration >= threshold } - /// Whether input has been pressed for longer than `count` number of ticks - pub fn held_for_ticks(&self, count: u32) -> bool { - self.is_pressed() && self.ticks_held >= count + /// Handles logic of updating state of Input + pub fn set_state(&mut self, pressed: bool) { + if self.pressed != pressed { + self.pressed = pressed; + self.duration = Duration::default(); + self.freshness = Freshness::New; + } } - /// Handles logic of updating state of Input - pub fn set_state(&mut self, new_state: bool) { self.state = new_state; } - - /// Increases `input::duration` by `dur` + /// Increases `input.duration` by `dur` pub fn inc_dur(&mut self, dur: Duration) { self.duration = self.duration.checked_add(dur).unwrap_or_default(); } - /// Returns `input::duration` + /// Returns `input.duration` pub fn get_dur(&self) -> Duration { self.duration } } impl Default for Input { fn default() -> Self { Self { - state: false, + pressed: false, duration: Duration::default(), - ticks_held: 0, + freshness: Freshness::New, } } } +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum Climb { + Up, + Down, + Hold, +} + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ControllerInputs { pub primary: Input, pub secondary: Input, pub ability3: Input, - pub sit: Input, pub jump: Input, pub roll: Input, pub glide: Input, - pub climb: Input, - pub climb_down: Input, pub wall_leap: Input, - pub respawn: Input, - pub toggle_wield: Input, - pub swap_loadout: Input, pub charge: Input, + pub climb: Option, pub move_dir: Vec2, pub look_dir: Vec3, } @@ -126,25 +152,46 @@ pub struct Controller { pub inputs: ControllerInputs, // TODO: consider SmallVec pub events: Vec, + pub actions: Vec, } impl ControllerInputs { /// Updates all inputs, accounting for delta time - pub fn calculate_change(&mut self, old: ControllerInputs, dt: Duration) { - self.primary.tick(old.primary, dt); - self.secondary.tick(old.secondary, dt); - self.ability3.tick(old.ability3, dt); - self.sit.tick(old.sit, dt); - self.jump.tick(old.jump, dt); - self.roll.tick(old.roll, dt); - self.glide.tick(old.glide, dt); - self.climb.tick(old.climb, dt); - self.climb_down.tick(old.climb_down, dt); - self.wall_leap.tick(old.wall_leap, dt); - self.respawn.tick(old.respawn, dt); - self.toggle_wield.tick(old.toggle_wield, dt); - self.swap_loadout.tick(old.swap_loadout, dt); - self.charge.tick(old.charge, dt); + pub fn tick(&mut self, dt: Duration) { + self.primary.tick(dt); + self.secondary.tick(dt); + self.ability3.tick(dt); + self.jump.tick(dt); + self.roll.tick(dt); + self.glide.tick(dt); + self.wall_leap.tick(dt); + self.charge.tick(dt); + } + + pub fn tick_freshness(&mut self) { + self.primary.tick_freshness(); + self.secondary.tick_freshness(); + self.ability3.tick_freshness(); + self.jump.tick_freshness(); + self.roll.tick_freshness(); + self.glide.tick_freshness(); + self.wall_leap.tick_freshness(); + self.charge.tick_freshness(); + } + + /// Updates Controller inputs with new version received from the client + pub fn update_with_new(&mut self, new: Self) { + self.primary.update_with_new(new.primary); + self.secondary.update_with_new(new.secondary); + self.ability3.update_with_new(new.ability3); + self.jump.update_with_new(new.jump); + self.roll.update_with_new(new.roll); + self.glide.update_with_new(new.glide); + self.wall_leap.update_with_new(new.wall_leap); + self.charge.update_with_new(new.charge); + self.climb = new.climb; + self.move_dir = new.move_dir; + self.look_dir = new.look_dir; } pub fn holding_ability_key(&self) -> bool { diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index f053f5f5cc..63ee953011 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -25,7 +25,8 @@ pub use body::{ }; pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use controller::{ - ControlEvent, Controller, ControllerInputs, Input, InventoryManip, MountState, Mounting, + Climb, ControlAction, ControlEvent, Controller, ControllerInputs, Input, InventoryManip, + MountState, Mounting, }; pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index 2b2ec63f33..970208cc23 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -18,6 +18,7 @@ pub enum ClientMsg { Spectate, ControllerInputs(comp::ControllerInputs), ControlEvent(comp::ControlEvent), + ControlAction(comp::ControlAction), SetViewDistance(u32), BreakBlock(Vec3), PlaceBlock(Vec3, Block), diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 91fbaf87fb..5beb746345 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{CharacterState, EnergySource, StateUpdate}, + comp::{CharacterState, Climb, EnergySource, StateUpdate}, event::LocalEvent, sys::{ character_behavior::{CharacterBehavior, JoinData}, @@ -22,15 +22,8 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); - if let Err(_) = update.energy.try_change_by(-8, EnergySource::Climb) { - update.character = CharacterState::Idle {}; - } - - // If no wall is in front of character or we stopped holding space: - if data.physics.on_wall.is_none() - || data.physics.on_ground - || !data.inputs.jump.is_pressed() - { + // If no wall is in front of character or we stopped climbing; + if data.physics.on_wall.is_none() || data.physics.on_ground || data.inputs.climb.is_none() { if data.inputs.jump.is_pressed() { // They've climbed atop something, give them a boost update @@ -50,13 +43,35 @@ impl CharacterBehavior for Data { 0.0 }; + // Expend energy if climbing + let energy_use = match data.inputs.climb { + Some(Climb::Up) | Some(Climb::Down) => 8, + Some(Climb::Hold) => data + .physics + .on_wall + .map(|wall_dir| { + // Calculate velocity perpendicular to the wall + let vel = update.vel.0; + let perp_vel = + vel - wall_dir * vel.dot(wall_dir) / wall_dir.magnitude_squared(); + // Enegry cost based on magnitude of this velocity + (perp_vel.magnitude() * 8.0) as i32 + }) + // Note: this is currently unreachable + .unwrap_or(0), + // Note: this is currently unreachable + None => 0, + }; + if let Err(_) = update + .energy + .try_change_by(-energy_use, EnergySource::Climb) + { + update.character = CharacterState::Idle {}; + } + // Set orientation direction based on wall direction let ori_dir = if let Some(wall_dir) = data.physics.on_wall { - if Vec2::::from(wall_dir).magnitude_squared() > 0.001 { - Vec2::from(wall_dir).normalized() - } else { - Vec2::from(update.vel.0) - } + Vec2::from(wall_dir) } else { Vec2::from(update.vel.0) }; @@ -69,23 +84,27 @@ impl CharacterBehavior for Data { ); // Apply Vertical Climbing Movement - if let (true, Some(_wall_dir)) = ( - (data.inputs.climb.is_pressed() | data.inputs.climb_down.is_pressed()) - && update.vel.0.z <= CLIMB_SPEED, + if let (Some(climb), true, Some(_wall_dir)) = ( + data.inputs.climb, + update.vel.0.z <= CLIMB_SPEED, data.physics.on_wall, ) { - if data.inputs.climb_down.is_pressed() && !data.inputs.climb.is_pressed() { - update.vel.0 -= - data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0); - } else if data.inputs.climb.is_pressed() && !data.inputs.climb_down.is_pressed() { - update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED); - } else { - update.vel.0.z = update.vel.0.z + data.dt.0 * GRAVITY * 1.5; - update.vel.0 = Lerp::lerp( - update.vel.0, - Vec3::zero(), - 30.0 * data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0), - ); + match climb { + Climb::Down => { + update.vel.0 -= + data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0); + }, + Climb::Up => { + update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED); + }, + Climb::Hold => { + update.vel.0.z = update.vel.0.z + data.dt.0 * GRAVITY * 1.5; + update.vel.0 = Lerp::lerp( + update.vel.0, + Vec3::zero(), + 30.0 * data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0), + ); + }, } } diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 2783b8278f..1b69eeb987 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -28,7 +28,7 @@ impl CharacterBehavior for Data { if self.initialize { update.vel.0 = data.inputs.look_dir * 20.0; - update.ori.0 = data.vel.0.normalized(); + update.ori.0 = Vec3::from(data.vel.0.xy()).normalized(); } if self.buildup_duration != Duration::default() && data.physics.touch_entity.is_none() { diff --git a/common/src/states/idle.rs b/common/src/states/idle.rs index 17b07ebc5a..0355ddbe5a 100644 --- a/common/src/states/idle.rs +++ b/common/src/states/idle.rs @@ -12,12 +12,29 @@ impl CharacterBehavior for Data { handle_move(data, &mut update); handle_jump(data, &mut update); - handle_wield(data, &mut update); - handle_sit(data, &mut update); + handle_primary_wield(data, &mut update); handle_climb(data, &mut update); handle_glide(data, &mut update); handle_dodge_input(data, &mut update); update } + + fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_wield(data, &mut update); + update + } + + fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_sit(data, &mut update); + update + } + + fn swap_loadout(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_swap_loadout(data, &mut update); + update + } } diff --git a/common/src/states/sit.rs b/common/src/states/sit.rs index 576a05fb88..ad919a3993 100644 --- a/common/src/states/sit.rs +++ b/common/src/states/sit.rs @@ -11,16 +11,26 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); - handle_wield(data, &mut update); + handle_primary_wield(data, &mut update); // Try to Fall/Stand up/Move - if !data.physics.on_ground - || data.inputs.sit.is_just_pressed() - || data.inputs.move_dir.magnitude_squared() > 0.0 - { + if !data.physics.on_ground || data.inputs.move_dir.magnitude_squared() > 0.0 { update.character = CharacterState::Idle; } update } + + fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_wield(data, &mut update); + update + } + + fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + // Try to Fall/Stand up/Move + update.character = CharacterState::Idle; + update + } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 2f3d1b2857..d886081424 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -90,7 +90,7 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate) { /// First checks whether `primary` input is pressed, then /// attempts to go into Equipping state, otherwise Idle -pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) { +pub fn handle_primary_wield(data: &JoinData, update: &mut StateUpdate) { if data.inputs.primary.is_pressed() { attempt_wield(data, update); } @@ -108,15 +108,15 @@ pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) { } /// Checks that player can `Sit` and updates `CharacterState` if so -pub fn handle_sit(data: &JoinData, update: &mut StateUpdate) { - if data.inputs.sit.is_pressed() && data.physics.on_ground && data.body.is_humanoid() { +pub fn attempt_sit(data: &JoinData, update: &mut StateUpdate) { + if data.physics.on_ground && data.body.is_humanoid() { update.character = CharacterState::Sit; } } /// Checks that player can `Climb` and updates `CharacterState` if so pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) { - if (data.inputs.climb.is_pressed() || data.inputs.climb_down.is_pressed()) + if data.inputs.climb.is_some() && data.physics.on_wall.is_some() && !data.physics.on_ground //&& update.vel.0.z < 0.0 @@ -127,25 +127,12 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) { } } -/// Checks that player can Unwield and updates `CharacterState` if so -pub fn handle_unwield(data: &JoinData, update: &mut StateUpdate) { - if let CharacterState::Wielding { .. } = update.character { - if data.inputs.toggle_wield.is_pressed() { - update.character = CharacterState::Idle; - } - } -} - /// Checks that player can Swap Weapons and updates `Loadout` if so -pub fn handle_swap_loadout(data: &JoinData, update: &mut StateUpdate) { - if let CharacterState::Wielding { .. } = update.character { - if data.inputs.swap_loadout.is_just_pressed() { - let mut new_loadout = data.loadout.clone(); - new_loadout.active_item = data.loadout.second_item.clone(); - new_loadout.second_item = data.loadout.active_item.clone(); - update.loadout = new_loadout; - } - } +pub fn attempt_swap_loadout(data: &JoinData, update: &mut StateUpdate) { + let mut new_loadout = data.loadout.clone(); + new_loadout.active_item = data.loadout.second_item.clone(); + new_loadout.second_item = data.loadout.active_item.clone(); + update.loadout = new_loadout; } /// Checks that player can glide and updates `CharacterState` if so diff --git a/common/src/states/wielding.rs b/common/src/states/wielding.rs index 7675f212da..74ca337cda 100644 --- a/common/src/states/wielding.rs +++ b/common/src/states/wielding.rs @@ -1,6 +1,6 @@ use super::utils::*; use crate::{ - comp::StateUpdate, + comp::{CharacterState, StateUpdate}, sys::character_behavior::{CharacterBehavior, JoinData}, }; @@ -12,11 +12,8 @@ impl CharacterBehavior for Data { handle_move(&data, &mut update); handle_jump(&data, &mut update); - handle_sit(&data, &mut update); handle_climb(&data, &mut update); handle_glide(&data, &mut update); - handle_unwield(&data, &mut update); - handle_swap_loadout(&data, &mut update); handle_ability1_input(&data, &mut update); handle_ability2_input(&data, &mut update); handle_ability3_input(&data, &mut update); @@ -24,4 +21,22 @@ impl CharacterBehavior for Data { update } + + fn toggle_sit(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_sit(data, &mut update); + update + } + + fn toggle_wield(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + update.character = CharacterState::Idle; + update + } + + fn swap_loadout(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + attempt_swap_loadout(data, &mut update); + update + } } diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index ebccef6258..729e4be506 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - Attacking, Body, CharacterState, Controller, ControllerInputs, Energy, Loadout, Mounting, - Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, + Attacking, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, + Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, state::DeltaTime, @@ -15,6 +15,17 @@ use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, Write pub trait CharacterBehavior { fn behavior(&self, data: &JoinData) -> StateUpdate; + // Impl these to provide behavior for these inputs + fn swap_loadout(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn toggle_wield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn toggle_sit(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) } + fn handle_event(&self, data: &JoinData, event: ControlAction) -> StateUpdate { + match event { + ControlAction::SwapLoadout => self.swap_loadout(data), + ControlAction::ToggleWield => self.toggle_wield(data), + ControlAction::ToggleSit => self.toggle_sit(data), + } + } // fn init(data: &JoinData) -> CharacterState; } @@ -47,13 +58,22 @@ pub type JoinTuple<'a> = ( &'a mut Ori, &'a mut Energy, &'a mut Loadout, - &'a Controller, + &'a mut Controller, &'a Stats, &'a Body, &'a PhysicsState, Option<&'a Attacking>, ); +fn incorporate_update(tuple: &mut JoinTuple, state_update: StateUpdate) { + *tuple.2 = state_update.character; + *tuple.3 = state_update.pos; + *tuple.4 = state_update.vel; + *tuple.5 = state_update.ori; + *tuple.6 = state_update.energy; + *tuple.7 = state_update.loadout; +} + impl<'a> JoinData<'a> { fn new(j: &'a JoinTuple<'a>, updater: &'a LazyUpdate, dt: &'a DeltaTime) -> Self { Self { @@ -96,7 +116,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Ori>, WriteStorage<'a, Energy>, WriteStorage<'a, Loadout>, - ReadStorage<'a, Controller>, + WriteStorage<'a, Controller>, ReadStorage<'a, Stats>, ReadStorage<'a, Body>, ReadStorage<'a, PhysicsState>, @@ -120,7 +140,7 @@ impl<'a> System<'a> for Sys { mut orientations, mut energies, mut loadouts, - controllers, + mut controllers, stats, bodies, physics_states, @@ -141,7 +161,7 @@ impl<'a> System<'a> for Sys { &mut orientations, &mut energies, &mut loadouts, - &controllers, + &mut controllers, &stats, &bodies, &physics_states, @@ -149,28 +169,49 @@ impl<'a> System<'a> for Sys { ) .join(); - while let Some(tuple) = join_iter.next() { - let j = JoinData::new(&tuple, &updater, &dt); - let inputs = &j.inputs; - + while let Some(mut tuple) = join_iter.next() { // Being dead overrides all other states - if j.stats.is_dead { - // Only options: click respawn - // prevent instant-respawns (i.e. player was holding attack) - // by disallowing while input is held down - if inputs.respawn.is_pressed() && !inputs.respawn.is_held_down() { - server_bus.emitter().emit(ServerEvent::Respawn(j.entity)); - } - // Or do nothing - return; + if tuple.9.is_dead { + // Do nothing + continue; } // If mounted, character state is controlled by mount // TODO: Make mounting a state - if let Some(Mounting(_)) = mountings.get(j.entity) { + if let Some(Mounting(_)) = mountings.get(tuple.0) { *tuple.2 = CharacterState::Sit {}; - return; + continue; } + let actions = std::mem::replace(&mut tuple.8.actions, Vec::new()); + for action in actions { + let j = JoinData::new(&tuple, &updater, &dt); + let mut state_update = match j.character { + CharacterState::Idle => states::idle::Data.handle_event(&j, action), + CharacterState::Climb => states::climb::Data.handle_event(&j, action), + CharacterState::Glide => states::glide::Data.handle_event(&j, action), + CharacterState::Sit => { + states::sit::Data::handle_event(&states::sit::Data, &j, action) + }, + CharacterState::BasicBlock => { + states::basic_block::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), + CharacterState::TripleStrike(data) => data.handle_event(&j, action), + CharacterState::BasicMelee(data) => data.handle_event(&j, action), + CharacterState::BasicRanged(data) => data.handle_event(&j, action), + CharacterState::Boost(data) => data.handle_event(&j, action), + CharacterState::DashMelee(data) => data.handle_event(&j, action), + CharacterState::TimedCombo(data) => data.handle_event(&j, action), + }; + local_emitter.append(&mut state_update.local_events); + server_emitter.append(&mut state_update.server_events); + incorporate_update(&mut tuple, state_update); + } + + let j = JoinData::new(&tuple, &updater, &dt); + let mut state_update = match j.character { CharacterState::Idle => states::idle::Data.behavior(&j), CharacterState::Climb => states::climb::Data.behavior(&j), @@ -188,14 +229,9 @@ impl<'a> System<'a> for Sys { CharacterState::TimedCombo(data) => data.behavior(&j), }; - *tuple.2 = state_update.character; - *tuple.3 = state_update.pos; - *tuple.4 = state_update.vel; - *tuple.5 = state_update.ori; - *tuple.6 = state_update.energy; - *tuple.7 = state_update.loadout; local_emitter.append(&mut state_update.local_events); server_emitter.append(&mut state_update.server_events); + incorporate_update(&mut tuple, state_update); } } } diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 8617f7cf7a..c92522b1b5 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -8,7 +8,6 @@ use specs::{ saveload::{Marker, MarkerAllocator}, Entities, Join, Read, ReadStorage, System, WriteStorage, }; -use std::time::Duration; // const CHARGE_COST: i32 = 200; // const ROLL_COST: i32 = 30; @@ -34,25 +33,27 @@ impl<'a> System<'a> for Sys { uid_allocator, server_bus, _local_bus, - read_dt, + _dt, mut controllers, mut character_states, uids, ): Self::SystemData, ) { let mut server_emitter = server_bus.emitter(); - let dt = Duration::from_secs_f32(read_dt.0); - for (entity, _uid, controller, character_state) in ( - &entities, - &uids, - &mut controllers, - // &last_controllers, - &mut character_states, - ) - .join() + for (entity, _uid, controller, character_state) in + (&entities, &uids, &mut controllers, &mut character_states).join() { - let inputs = &mut controller.inputs; + let mut inputs = &mut controller.inputs; + + // Note(imbris): I avoided incrementing the duration with inputs.tick() because + // this is being done manually in voxygen right now so it would double up on + // speed of time. + // Perhaphs the duration aspects of inputs could be + // calculated exclusively on the server (since the client can't be + // trusted anyway). It needs to be considered if these calculations + // being on the client are critical for responsiveness/client-side prediction. + inputs.tick_freshness(); // Update `inputs.move_dir`. inputs.move_dir = if inputs.move_dir.magnitude_squared() > 1.0 { @@ -82,8 +83,8 @@ impl<'a> System<'a> for Sys { ControlEvent::InventoryManip(manip) => { *character_state = CharacterState::Idle; server_emitter.emit(ServerEvent::InventoryManip(entity, manip)) - }, /*ControlEvent::Respawn => - * server_emitter.emit(ServerEvent::Unmount(entity)), */ + }, + ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), } } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index f6d5e95435..c43d565dbf 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -1,7 +1,7 @@ use super::SysTimer; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use common::{ - comp::{Admin, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, + comp::{Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, event::{EventBus, ServerEvent}, msg::{ validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate, @@ -208,7 +208,7 @@ impl<'a> System<'a> for Sys { }, ClientState::Character => { if let Some(controller) = controllers.get_mut(entity) { - controller.inputs = inputs; + controller.inputs.update_with_new(inputs); } }, ClientState::Pending => {}, @@ -220,12 +220,31 @@ impl<'a> System<'a> for Sys { client.error_state(RequestStateError::Impossible) }, ClientState::Character => { + // Skip respawn if client entity is alive + if let &ControlEvent::Respawn = &event { + if stats.get(entity).map_or(true, |s| !s.is_dead) { + continue; + } + } if let Some(controller) = controllers.get_mut(entity) { controller.events.push(event); } }, ClientState::Pending => {}, }, + ClientMsg::ControlAction(event) => match client.client_state { + ClientState::Connected + | ClientState::Registered + | ClientState::Spectator => { + client.error_state(RequestStateError::Impossible) + }, + ClientState::Character => { + if let Some(controller) = controllers.get_mut(entity) { + controller.actions.push(event); + } + }, + ClientState::Pending => {}, + }, ClientMsg::ChatMsg { message } => match client.client_state { ClientState::Connected => client.error_state(RequestStateError::Impossible), ClientState::Registered diff --git a/voxygen/src/key_state.rs b/voxygen/src/key_state.rs index e07474e0db..61b86128ae 100644 --- a/voxygen/src/key_state.rs +++ b/voxygen/src/key_state.rs @@ -5,6 +5,12 @@ pub struct KeyState { pub left: bool, pub up: bool, pub down: bool, + pub climb_up: bool, + pub climb_down: bool, + pub toggle_wield: bool, + pub toggle_sit: bool, + pub swap_loadout: bool, + pub respawn: bool, pub analog_matrix: Vec2, } @@ -15,6 +21,12 @@ impl KeyState { left: false, up: false, down: false, + climb_up: false, + climb_down: false, + toggle_wield: false, + toggle_sit: false, + swap_loadout: false, + respawn: false, analog_matrix: Vec2::zero(), } } @@ -35,4 +47,14 @@ impl KeyState { dir.normalized() } } + + pub fn climb(&self) -> Option { + use common::comp::Climb; + match (self.climb_up, self.climb_down) { + (true, false) => Some(Climb::Up), + (false, true) => Some(Climb::Down), + (true, true) => Some(Climb::Hold), + (false, false) => None, + } + } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 00e5bceb2b..3964a5778c 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -75,6 +75,8 @@ impl SessionState { impl SessionState { /// Tick the session (and the client attached to it). fn tick(&mut self, dt: Duration) -> Result { + self.inputs.tick(dt); + for event in self.client.borrow_mut().tick( self.inputs.clone(), dt, @@ -297,15 +299,25 @@ impl PlayState for SessionState { self.inputs.roll.set_state(state); } }, - Event::InputUpdate(GameInput::Respawn, state) => { - self.inputs.respawn.set_state(state); - }, + Event::InputUpdate(GameInput::Respawn, state) + if state != self.key_state.respawn => + { + self.key_state.respawn = state; + if state { + self.client.borrow_mut().respawn(); + } + } Event::InputUpdate(GameInput::Jump, state) => { self.inputs.jump.set_state(state); }, - Event::InputUpdate(GameInput::Sit, state) => { - self.inputs.sit.set_state(state); - }, + Event::InputUpdate(GameInput::Sit, state) + if state != self.key_state.toggle_sit => + { + self.key_state.toggle_sit = state; + if state { + self.client.borrow_mut().toggle_sit(); + } + } Event::InputUpdate(GameInput::MoveForward, state) => self.key_state.up = state, Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state, Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = state, @@ -314,20 +326,30 @@ impl PlayState for SessionState { self.inputs.glide.set_state(state); }, Event::InputUpdate(GameInput::Climb, state) => { - self.inputs.climb.set_state(state) + self.key_state.climb_up = state; }, Event::InputUpdate(GameInput::ClimbDown, state) => { - self.inputs.climb_down.set_state(state) + self.key_state.climb_down = state; }, Event::InputUpdate(GameInput::WallLeap, state) => { self.inputs.wall_leap.set_state(state) }, - Event::InputUpdate(GameInput::ToggleWield, state) => { - self.inputs.toggle_wield.set_state(state); - }, - Event::InputUpdate(GameInput::SwapLoadout, state) => { - self.inputs.swap_loadout.set_state(state); - }, + Event::InputUpdate(GameInput::ToggleWield, state) + if state != self.key_state.toggle_wield => + { + self.key_state.toggle_wield = state; + if state { + self.client.borrow_mut().toggle_wield(); + } + } + Event::InputUpdate(GameInput::SwapLoadout, state) + if state != self.key_state.swap_loadout => + { + self.key_state.swap_loadout = state; + if state { + self.client.borrow_mut().swap_loadout(); + } + } Event::InputUpdate(GameInput::Mount, true) => { let mut client = self.client.borrow_mut(); if client.is_mounted() { @@ -427,6 +449,8 @@ impl PlayState for SessionState { self.inputs.look_dir = cam_dir; + self.inputs.climb = self.key_state.climb(); + // Runs if either in a multiplayer server or the singleplayer server is unpaused if global_state.singleplayer.is_none() || !global_state.singleplayer.as_ref().unwrap().is_paused()