mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Input handling changes
This commit is contained in:
parent
37ec191021
commit
6ba158b7e1
@ -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));
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user