Input handling changes

This commit is contained in:
Imbris 2020-03-24 03:38:16 -04:00
parent 4bfbdf7c84
commit a1cf04fb22
15 changed files with 426 additions and 178 deletions

View File

@ -15,7 +15,8 @@ pub use specs::{
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use common::{ use common::{
comp::{ comp::{
self, ControlEvent, Controller, ControllerInputs, InventoryManip, InventoryUpdateEvent, self, ControlAction, ControlEvent, Controller, ControllerInputs, InventoryManip,
InventoryUpdateEvent,
}, },
event::{EventBus, SfxEvent, SfxEventItem}, event::{EventBus, SfxEvent, SfxEventItem},
msg::{ msg::{
@ -31,7 +32,7 @@ use common::{
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use image::DynamicImage; use image::DynamicImage;
use log::warn; use log::{error, warn};
use std::{ use std::{
net::SocketAddr, net::SocketAddr,
sync::Arc, sync::Arc,
@ -288,6 +289,38 @@ impl Client {
.send_message(ClientMsg::ControlEvent(ControlEvent::Unmount)); .send_message(ClientMsg::ControlEvent(ControlEvent::Unmount));
} }
pub fn respawn(&mut self) {
if self
.state
.ecs()
.read_storage::<comp::Stats>()
.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::<Controller>()
.get_mut(self.entity)
{
controller.actions.push(control_action);
}
self.postbox
.send_message(ClientMsg::ControlAction(control_action));
}
pub fn view_distance(&self) -> Option<u32> { self.view_distance } pub fn view_distance(&self) -> Option<u32> { self.view_distance }
pub fn loaded_distance(&self) -> f32 { self.loaded_distance } pub fn loaded_distance(&self) -> f32 { self.loaded_distance }
@ -371,10 +404,26 @@ impl Client {
// 1) Handle input from frontend. // 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity. // Pass character actions from frontend input to the player's entity.
if let ClientState::Character = self.client_state { if let ClientState::Character = self.client_state {
self.state.write_component(self.entity, Controller { if let Err(err) = self
inputs: inputs.clone(), .state
events: Vec::new(), .ecs()
}); .write_storage::<Controller>()
.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 self.postbox
.send_message(ClientMsg::ControllerInputs(inputs)); .send_message(ClientMsg::ControllerInputs(inputs));
} }

View File

@ -12,7 +12,21 @@ pub enum ControlEvent {
Mount(Uid), Mount(Uid),
Unmount, Unmount,
InventoryManip(InventoryManip), 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 /// Whether a key is pressed or unpressed
@ -21,41 +35,50 @@ pub enum ControlEvent {
pub struct Input { pub struct Input {
/// Should not be pub because duration should /// Should not be pub because duration should
/// always be reset when state is updated /// always be reset when state is updated
state: bool, pressed: bool,
/// Should only be updated by npc agents /// Should only be updated by npc agents
/// through appropriate fn /// through appropriate fn
duration: Duration, duration: Duration,
/// How many update ticks the button has been in its current state for /// How fresh is the last change to the input state
ticks_held: u32, freshness: Freshness,
} }
impl Input { 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 // Increase how long input has been in current state
self.duration = self.duration.checked_add(dt).unwrap_or_default(); self.duration = self.duration.checked_add(dt).unwrap_or_default();
self.tick_freshness();
}
match (self.is_pressed(), old.is_pressed()) { fn tick_freshness(&mut self) {
(false, true) | (true, false) => { self.freshness = match self.freshness {
println!("{:?}", self); Freshness::New => Freshness::TickedOnce,
self.duration = Duration::default(); Freshness::TickedOnce => Freshness::Old,
self.ticks_held = 1; Freshness::Old => Freshness::Old,
println!("{:?}", self);
},
(_, _) => {
self.ticks_held += 1;
println!("____");
},
}; };
} }
/// 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 /// 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 /// 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 /// 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 /// Whether input has been pressed longer than
/// `DEFAULT_HOLD_DURATION` /// `DEFAULT_HOLD_DURATION`
@ -74,49 +97,52 @@ impl Input {
self.is_pressed() && self.duration >= threshold self.is_pressed() && self.duration >= threshold
} }
/// Whether input has been pressed for longer than `count` number of ticks /// Handles logic of updating state of Input
pub fn held_for_ticks(&self, count: u32) -> bool { pub fn set_state(&mut self, pressed: bool) {
self.is_pressed() && self.ticks_held >= count if self.pressed != pressed {
self.pressed = pressed;
self.duration = Duration::default();
self.freshness = Freshness::New;
}
} }
/// Handles logic of updating state of Input /// Increases `input.duration` by `dur`
pub fn set_state(&mut self, new_state: bool) { self.state = new_state; }
/// Increases `input::duration` by `dur`
pub fn inc_dur(&mut self, dur: Duration) { pub fn inc_dur(&mut self, dur: Duration) {
self.duration = self.duration.checked_add(dur).unwrap_or_default(); self.duration = self.duration.checked_add(dur).unwrap_or_default();
} }
/// Returns `input::duration` /// Returns `input.duration`
pub fn get_dur(&self) -> Duration { self.duration } pub fn get_dur(&self) -> Duration { self.duration }
} }
impl Default for Input { impl Default for Input {
fn default() -> Self { fn default() -> Self {
Self { Self {
state: false, pressed: false,
duration: Duration::default(), 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)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ControllerInputs { pub struct ControllerInputs {
pub primary: Input, pub primary: Input,
pub secondary: Input, pub secondary: Input,
pub ability3: Input, pub ability3: Input,
pub sit: Input,
pub jump: Input, pub jump: Input,
pub roll: Input, pub roll: Input,
pub glide: Input, pub glide: Input,
pub climb: Input,
pub climb_down: Input,
pub wall_leap: Input, pub wall_leap: Input,
pub respawn: Input,
pub toggle_wield: Input,
pub swap_loadout: Input,
pub charge: Input, pub charge: Input,
pub climb: Option<Climb>,
pub move_dir: Vec2<f32>, pub move_dir: Vec2<f32>,
pub look_dir: Vec3<f32>, pub look_dir: Vec3<f32>,
} }
@ -126,25 +152,46 @@ pub struct Controller {
pub inputs: ControllerInputs, pub inputs: ControllerInputs,
// TODO: consider SmallVec // TODO: consider SmallVec
pub events: Vec<ControlEvent>, pub events: Vec<ControlEvent>,
pub actions: Vec<ControlAction>,
} }
impl ControllerInputs { impl ControllerInputs {
/// Updates all inputs, accounting for delta time /// Updates all inputs, accounting for delta time
pub fn calculate_change(&mut self, old: ControllerInputs, dt: Duration) { pub fn tick(&mut self, dt: Duration) {
self.primary.tick(old.primary, dt); self.primary.tick(dt);
self.secondary.tick(old.secondary, dt); self.secondary.tick(dt);
self.ability3.tick(old.ability3, dt); self.ability3.tick(dt);
self.sit.tick(old.sit, dt); self.jump.tick(dt);
self.jump.tick(old.jump, dt); self.roll.tick(dt);
self.roll.tick(old.roll, dt); self.glide.tick(dt);
self.glide.tick(old.glide, dt); self.wall_leap.tick(dt);
self.climb.tick(old.climb, dt); self.charge.tick(dt);
self.climb_down.tick(old.climb_down, dt); }
self.wall_leap.tick(old.wall_leap, dt);
self.respawn.tick(old.respawn, dt); pub fn tick_freshness(&mut self) {
self.toggle_wield.tick(old.toggle_wield, dt); self.primary.tick_freshness();
self.swap_loadout.tick(old.swap_loadout, dt); self.secondary.tick_freshness();
self.charge.tick(old.charge, dt); 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 { pub fn holding_ability_key(&self) -> bool {

View File

@ -25,7 +25,8 @@ pub use body::{
}; };
pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use character_state::{Attacking, CharacterState, StateUpdate};
pub use controller::{ 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 energy::{Energy, EnergySource};
pub use inputs::CanBuild; pub use inputs::CanBuild;

View File

@ -18,6 +18,7 @@ pub enum ClientMsg {
Spectate, Spectate,
ControllerInputs(comp::ControllerInputs), ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent), ControlEvent(comp::ControlEvent),
ControlAction(comp::ControlAction),
SetViewDistance(u32), SetViewDistance(u32),
BreakBlock(Vec3<i32>), BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block), PlaceBlock(Vec3<i32>, Block),

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{CharacterState, EnergySource, StateUpdate}, comp::{CharacterState, Climb, EnergySource, StateUpdate},
event::LocalEvent, event::LocalEvent,
sys::{ sys::{
character_behavior::{CharacterBehavior, JoinData}, character_behavior::{CharacterBehavior, JoinData},
@ -22,15 +22,8 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
if let Err(_) = update.energy.try_change_by(-8, EnergySource::Climb) { // If no wall is in front of character or we stopped climbing;
update.character = CharacterState::Idle {}; if data.physics.on_wall.is_none() || data.physics.on_ground || data.inputs.climb.is_none() {
}
// 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 data.inputs.jump.is_pressed() { if data.inputs.jump.is_pressed() {
// They've climbed atop something, give them a boost // They've climbed atop something, give them a boost
update update
@ -50,13 +43,35 @@ impl CharacterBehavior for Data {
0.0 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 // Set orientation direction based on wall direction
let ori_dir = if let Some(wall_dir) = data.physics.on_wall { let ori_dir = if let Some(wall_dir) = data.physics.on_wall {
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 { Vec2::from(wall_dir)
Vec2::from(wall_dir).normalized()
} else {
Vec2::from(update.vel.0)
}
} else { } else {
Vec2::from(update.vel.0) Vec2::from(update.vel.0)
}; };
@ -69,23 +84,27 @@ impl CharacterBehavior for Data {
); );
// Apply Vertical Climbing Movement // Apply Vertical Climbing Movement
if let (true, Some(_wall_dir)) = ( if let (Some(climb), true, Some(_wall_dir)) = (
(data.inputs.climb.is_pressed() | data.inputs.climb_down.is_pressed()) data.inputs.climb,
&& update.vel.0.z <= CLIMB_SPEED, update.vel.0.z <= CLIMB_SPEED,
data.physics.on_wall, data.physics.on_wall,
) { ) {
if data.inputs.climb_down.is_pressed() && !data.inputs.climb.is_pressed() { match climb {
update.vel.0 -= Climb::Down => {
data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0); update.vel.0 -=
} else if data.inputs.climb.is_pressed() && !data.inputs.climb_down.is_pressed() { data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED); },
} else { Climb::Up => {
update.vel.0.z = update.vel.0.z + data.dt.0 * GRAVITY * 1.5; update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
update.vel.0 = Lerp::lerp( },
update.vel.0, Climb::Hold => {
Vec3::zero(), update.vel.0.z = update.vel.0.z + data.dt.0 * GRAVITY * 1.5;
30.0 * data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0), 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),
);
},
} }
} }

View File

@ -28,7 +28,7 @@ impl CharacterBehavior for Data {
if self.initialize { if self.initialize {
update.vel.0 = data.inputs.look_dir * 20.0; 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() { if self.buildup_duration != Duration::default() && data.physics.touch_entity.is_none() {

View File

@ -12,12 +12,29 @@ impl CharacterBehavior for Data {
handle_move(data, &mut update); handle_move(data, &mut update);
handle_jump(data, &mut update); handle_jump(data, &mut update);
handle_wield(data, &mut update); handle_primary_wield(data, &mut update);
handle_sit(data, &mut update);
handle_climb(data, &mut update); handle_climb(data, &mut update);
handle_glide(data, &mut update); handle_glide(data, &mut update);
handle_dodge_input(data, &mut update); handle_dodge_input(data, &mut update);
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
}
} }

View File

@ -11,16 +11,26 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate { fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
handle_wield(data, &mut update); handle_primary_wield(data, &mut update);
// Try to Fall/Stand up/Move // Try to Fall/Stand up/Move
if !data.physics.on_ground if !data.physics.on_ground || data.inputs.move_dir.magnitude_squared() > 0.0 {
|| data.inputs.sit.is_just_pressed()
|| data.inputs.move_dir.magnitude_squared() > 0.0
{
update.character = CharacterState::Idle; update.character = CharacterState::Idle;
} }
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);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle;
update
}
} }

View File

@ -90,7 +90,7 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate) {
/// First checks whether `primary` input is pressed, then /// First checks whether `primary` input is pressed, then
/// attempts to go into Equipping state, otherwise Idle /// 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() { if data.inputs.primary.is_pressed() {
attempt_wield(data, update); 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 /// Checks that player can `Sit` and updates `CharacterState` if so
pub fn handle_sit(data: &JoinData, update: &mut StateUpdate) { pub fn attempt_sit(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.sit.is_pressed() && data.physics.on_ground && data.body.is_humanoid() { if data.physics.on_ground && data.body.is_humanoid() {
update.character = CharacterState::Sit; update.character = CharacterState::Sit;
} }
} }
/// Checks that player can `Climb` and updates `CharacterState` if so /// Checks that player can `Climb` and updates `CharacterState` if so
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) { 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_wall.is_some()
&& !data.physics.on_ground && !data.physics.on_ground
//&& update.vel.0.z < 0.0 //&& 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 /// Checks that player can Swap Weapons and updates `Loadout` if so
pub fn handle_swap_loadout(data: &JoinData, update: &mut StateUpdate) { pub fn attempt_swap_loadout(data: &JoinData, update: &mut StateUpdate) {
if let CharacterState::Wielding { .. } = update.character { let mut new_loadout = data.loadout.clone();
if data.inputs.swap_loadout.is_just_pressed() { new_loadout.active_item = data.loadout.second_item.clone();
let mut new_loadout = data.loadout.clone(); new_loadout.second_item = data.loadout.active_item.clone();
new_loadout.active_item = data.loadout.second_item.clone(); update.loadout = new_loadout;
new_loadout.second_item = data.loadout.active_item.clone();
update.loadout = new_loadout;
}
}
} }
/// Checks that player can glide and updates `CharacterState` if so /// Checks that player can glide and updates `CharacterState` if so

View File

@ -1,6 +1,6 @@
use super::utils::*; use super::utils::*;
use crate::{ use crate::{
comp::StateUpdate, comp::{CharacterState, StateUpdate},
sys::character_behavior::{CharacterBehavior, JoinData}, sys::character_behavior::{CharacterBehavior, JoinData},
}; };
@ -12,11 +12,8 @@ impl CharacterBehavior for Data {
handle_move(&data, &mut update); handle_move(&data, &mut update);
handle_jump(&data, &mut update); handle_jump(&data, &mut update);
handle_sit(&data, &mut update);
handle_climb(&data, &mut update); handle_climb(&data, &mut update);
handle_glide(&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_ability1_input(&data, &mut update);
handle_ability2_input(&data, &mut update); handle_ability2_input(&data, &mut update);
handle_ability3_input(&data, &mut update); handle_ability3_input(&data, &mut update);
@ -24,4 +21,22 @@ impl CharacterBehavior for Data {
update 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
}
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
Attacking, Body, CharacterState, Controller, ControllerInputs, Energy, Loadout, Mounting, Attacking, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy,
Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
state::DeltaTime, state::DeltaTime,
@ -15,6 +15,17 @@ use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, Write
pub trait CharacterBehavior { pub trait CharacterBehavior {
fn behavior(&self, data: &JoinData) -> StateUpdate; 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; // fn init(data: &JoinData) -> CharacterState;
} }
@ -47,13 +58,22 @@ pub type JoinTuple<'a> = (
&'a mut Ori, &'a mut Ori,
&'a mut Energy, &'a mut Energy,
&'a mut Loadout, &'a mut Loadout,
&'a Controller, &'a mut Controller,
&'a Stats, &'a Stats,
&'a Body, &'a Body,
&'a PhysicsState, &'a PhysicsState,
Option<&'a Attacking>, 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> { impl<'a> JoinData<'a> {
fn new(j: &'a JoinTuple<'a>, updater: &'a LazyUpdate, dt: &'a DeltaTime) -> Self { fn new(j: &'a JoinTuple<'a>, updater: &'a LazyUpdate, dt: &'a DeltaTime) -> Self {
Self { Self {
@ -96,7 +116,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
WriteStorage<'a, Energy>, WriteStorage<'a, Energy>,
WriteStorage<'a, Loadout>, WriteStorage<'a, Loadout>,
ReadStorage<'a, Controller>, WriteStorage<'a, Controller>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, PhysicsState>, ReadStorage<'a, PhysicsState>,
@ -120,7 +140,7 @@ impl<'a> System<'a> for Sys {
mut orientations, mut orientations,
mut energies, mut energies,
mut loadouts, mut loadouts,
controllers, mut controllers,
stats, stats,
bodies, bodies,
physics_states, physics_states,
@ -141,7 +161,7 @@ impl<'a> System<'a> for Sys {
&mut orientations, &mut orientations,
&mut energies, &mut energies,
&mut loadouts, &mut loadouts,
&controllers, &mut controllers,
&stats, &stats,
&bodies, &bodies,
&physics_states, &physics_states,
@ -149,28 +169,49 @@ impl<'a> System<'a> for Sys {
) )
.join(); .join();
while let Some(tuple) = join_iter.next() { while let Some(mut tuple) = join_iter.next() {
let j = JoinData::new(&tuple, &updater, &dt);
let inputs = &j.inputs;
// Being dead overrides all other states // Being dead overrides all other states
if j.stats.is_dead { if tuple.9.is_dead {
// Only options: click respawn // Do nothing
// prevent instant-respawns (i.e. player was holding attack) continue;
// 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 mounted, character state is controlled by mount // If mounted, character state is controlled by mount
// TODO: Make mounting a state // 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 {}; *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 { let mut state_update = match j.character {
CharacterState::Idle => states::idle::Data.behavior(&j), CharacterState::Idle => states::idle::Data.behavior(&j),
CharacterState::Climb => states::climb::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), 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); local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events); server_emitter.append(&mut state_update.server_events);
incorporate_update(&mut tuple, state_update);
} }
} }
} }

View File

@ -8,7 +8,6 @@ use specs::{
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadStorage, System, WriteStorage, Entities, Join, Read, ReadStorage, System, WriteStorage,
}; };
use std::time::Duration;
// const CHARGE_COST: i32 = 200; // const CHARGE_COST: i32 = 200;
// const ROLL_COST: i32 = 30; // const ROLL_COST: i32 = 30;
@ -34,25 +33,27 @@ impl<'a> System<'a> for Sys {
uid_allocator, uid_allocator,
server_bus, server_bus,
_local_bus, _local_bus,
read_dt, _dt,
mut controllers, mut controllers,
mut character_states, mut character_states,
uids, uids,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
let dt = Duration::from_secs_f32(read_dt.0);
for (entity, _uid, controller, character_state) in ( for (entity, _uid, controller, character_state) in
&entities, (&entities, &uids, &mut controllers, &mut character_states).join()
&uids,
&mut controllers,
// &last_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`. // Update `inputs.move_dir`.
inputs.move_dir = if inputs.move_dir.magnitude_squared() > 1.0 { inputs.move_dir = if inputs.move_dir.magnitude_squared() > 1.0 {
@ -82,8 +83,8 @@ impl<'a> System<'a> for Sys {
ControlEvent::InventoryManip(manip) => { ControlEvent::InventoryManip(manip) => {
*character_state = CharacterState::Idle; *character_state = CharacterState::Idle;
server_emitter.emit(ServerEvent::InventoryManip(entity, manip)) server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
}, /*ControlEvent::Respawn => },
* server_emitter.emit(ServerEvent::Unmount(entity)), */ ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use super::SysTimer; use super::SysTimer;
use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT}; use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{ 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}, event::{EventBus, ServerEvent},
msg::{ msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate, validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
@ -208,7 +208,7 @@ impl<'a> System<'a> for Sys {
}, },
ClientState::Character => { ClientState::Character => {
if let Some(controller) = controllers.get_mut(entity) { if let Some(controller) = controllers.get_mut(entity) {
controller.inputs = inputs; controller.inputs.update_with_new(inputs);
} }
}, },
ClientState::Pending => {}, ClientState::Pending => {},
@ -220,12 +220,31 @@ impl<'a> System<'a> for Sys {
client.error_state(RequestStateError::Impossible) client.error_state(RequestStateError::Impossible)
}, },
ClientState::Character => { 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) { if let Some(controller) = controllers.get_mut(entity) {
controller.events.push(event); controller.events.push(event);
} }
}, },
ClientState::Pending => {}, 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 { ClientMsg::ChatMsg { message } => match client.client_state {
ClientState::Connected => client.error_state(RequestStateError::Impossible), ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered ClientState::Registered

View File

@ -5,6 +5,12 @@ pub struct KeyState {
pub left: bool, pub left: bool,
pub up: bool, pub up: bool,
pub down: 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<f32>, pub analog_matrix: Vec2<f32>,
} }
@ -15,6 +21,12 @@ impl KeyState {
left: false, left: false,
up: false, up: false,
down: false, down: false,
climb_up: false,
climb_down: false,
toggle_wield: false,
toggle_sit: false,
swap_loadout: false,
respawn: false,
analog_matrix: Vec2::zero(), analog_matrix: Vec2::zero(),
} }
} }
@ -35,4 +47,14 @@ impl KeyState {
dir.normalized() dir.normalized()
} }
} }
pub fn climb(&self) -> Option<common::comp::Climb> {
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,
}
}
} }

View File

@ -75,6 +75,8 @@ impl SessionState {
impl SessionState { impl SessionState {
/// Tick the session (and the client attached to it). /// Tick the session (and the client attached to it).
fn tick(&mut self, dt: Duration) -> Result<TickAction, Error> { fn tick(&mut self, dt: Duration) -> Result<TickAction, Error> {
self.inputs.tick(dt);
for event in self.client.borrow_mut().tick( for event in self.client.borrow_mut().tick(
self.inputs.clone(), self.inputs.clone(),
dt, dt,
@ -297,15 +299,25 @@ impl PlayState for SessionState {
self.inputs.roll.set_state(state); self.inputs.roll.set_state(state);
} }
}, },
Event::InputUpdate(GameInput::Respawn, state) => { Event::InputUpdate(GameInput::Respawn, state)
self.inputs.respawn.set_state(state); if state != self.key_state.respawn =>
}, {
self.key_state.respawn = state;
if state {
self.client.borrow_mut().respawn();
}
}
Event::InputUpdate(GameInput::Jump, state) => { Event::InputUpdate(GameInput::Jump, state) => {
self.inputs.jump.set_state(state); self.inputs.jump.set_state(state);
}, },
Event::InputUpdate(GameInput::Sit, state) => { Event::InputUpdate(GameInput::Sit, state)
self.inputs.sit.set_state(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::MoveForward, state) => self.key_state.up = state,
Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state, Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state,
Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = 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); self.inputs.glide.set_state(state);
}, },
Event::InputUpdate(GameInput::Climb, state) => { Event::InputUpdate(GameInput::Climb, state) => {
self.inputs.climb.set_state(state) self.key_state.climb_up = state;
}, },
Event::InputUpdate(GameInput::ClimbDown, state) => { Event::InputUpdate(GameInput::ClimbDown, state) => {
self.inputs.climb_down.set_state(state) self.key_state.climb_down = state;
}, },
Event::InputUpdate(GameInput::WallLeap, state) => { Event::InputUpdate(GameInput::WallLeap, state) => {
self.inputs.wall_leap.set_state(state) self.inputs.wall_leap.set_state(state)
}, },
Event::InputUpdate(GameInput::ToggleWield, state) => { Event::InputUpdate(GameInput::ToggleWield, state)
self.inputs.toggle_wield.set_state(state); if state != self.key_state.toggle_wield =>
}, {
Event::InputUpdate(GameInput::SwapLoadout, state) => { self.key_state.toggle_wield = state;
self.inputs.swap_loadout.set_state(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) => { Event::InputUpdate(GameInput::Mount, true) => {
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if client.is_mounted() { if client.is_mounted() {
@ -427,6 +449,8 @@ impl PlayState for SessionState {
self.inputs.look_dir = cam_dir; 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 // Runs if either in a multiplayer server or the singleplayer server is unpaused
if global_state.singleplayer.is_none() if global_state.singleplayer.is_none()
|| !global_state.singleplayer.as_ref().unwrap().is_paused() || !global_state.singleplayer.as_ref().unwrap().is_paused()