Input handling changes

This commit is contained in:
Imbris 2020-03-24 03:38:16 -04:00
parent 37ec191021
commit 6ba158b7e1
15 changed files with 426 additions and 178 deletions

View File

@ -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::<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 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::<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
.send_message(ClientMsg::ControllerInputs(inputs));
}

View File

@ -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<Climb>,
pub move_dir: Vec2<f32>,
pub look_dir: Vec3<f32>,
}
@ -126,25 +152,46 @@ pub struct Controller {
pub inputs: ControllerInputs,
// TODO: consider SmallVec
pub events: Vec<ControlEvent>,
pub actions: Vec<ControlAction>,
}
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 {

View File

@ -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;

View File

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

View File

@ -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::<f32>::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),
);
},
}
}

View File

@ -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() {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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)),
}
}
}

View File

@ -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

View File

@ -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<f32>,
}
@ -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<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 {
/// Tick the session (and the client attached to it).
fn tick(&mut self, dt: Duration) -> Result<TickAction, Error> {
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()