Add better inputs, input validation and more

Former-commit-id: 3227221b12a674f66b011ce0ba734e226f223f34
This commit is contained in:
timokoesters 2019-05-25 23:13:38 +02:00
parent 3c0121538b
commit 4696cd2c8b
18 changed files with 330 additions and 227 deletions

View File

@ -70,9 +70,6 @@ impl Client {
_ => return Err(Error::ServerWentMad),
};
// Initialize ecs components the client has actions over
state.write_component(entity, comp::Inputs::default());
Ok(Self {
client_state,
thread_pool: threadpool::Builder::new()
@ -94,11 +91,13 @@ impl Client {
})
}
/// Request a state transition to `ClientState::Registered`.
pub fn register(&mut self, player: comp::Player) {
self.postbox.send_message(ClientMsg::Register { player });
self.client_state = ClientState::Pending;
}
/// Request a state transition to `ClientState::Character`.
pub fn request_character(&mut self, name: String, body: comp::Body) {
self.postbox
.send_message(ClientMsg::Character { name, body });
@ -111,24 +110,56 @@ impl Client {
.send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap())); // Can't fail
}
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool {
&self.thread_pool
pub fn send_chat(&mut self, msg: String) {
self.postbox.send_message(ClientMsg::Chat(msg))
}
/// Get a reference to the client's game state.
/// Jump locally, the new positions will be synced to the server
#[allow(dead_code)]
pub fn state(&self) -> &State {
&self.state
pub fn jump(&mut self) {
if self.client_state != ClientState::Character {
return;
}
self.state.write_component(self.entity, comp::Jumping);
}
/// Get a mutable reference to the client's game state.
/// Start to glide locally, animation will be synced
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State {
&mut self.state
pub fn glide(&mut self, state: bool) {
if self.client_state != ClientState::Character {
return;
}
if state {
self.state.write_component(self.entity, comp::Gliding);
} else {
self.state
.ecs_mut()
.write_storage::<comp::Gliding>()
.remove(self.entity);
}
}
/// Start to attack
#[allow(dead_code)]
pub fn attack(&mut self) {
if self.client_state != ClientState::Character {
return;
}
// TODO: Test if attack is possible using timeout
self.state
.write_component(self.entity, comp::Attacking::start());
self.postbox.send_message(ClientMsg::Attack);
}
/// Tell the server the client wants to respawn.
#[allow(dead_code)]
pub fn respawn(&mut self) {
if self.client_state != ClientState::Dead {
return;
}
self.postbox.send_message(ClientMsg::Respawn)
}
/// Remove all cached terrain
@ -138,38 +169,9 @@ impl Client {
self.pending_chunks.clear();
}
/// Get the player's entity.
#[allow(dead_code)]
pub fn entity(&self) -> EcsEntity {
self.entity
}
/// Get the client state
#[allow(dead_code)]
pub fn get_client_state(&self) -> ClientState {
self.client_state
}
/// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 {
self.tick
}
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) {
self.postbox.send_message(ClientMsg::Chat(msg))
}
#[allow(dead_code)]
pub fn get_ping_ms(&self) -> f64 {
self.last_ping_delta * 1000.0
}
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(&mut self, input: comp::Inputs, dt: Duration) -> Result<Vec<Event>, Error> {
pub fn tick(&mut self, control: comp::Control, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the
@ -187,11 +189,9 @@ impl Client {
// 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity.
// TODO: Only do this if the entity already has a Inputs component!
self.state.write_component(self.entity, input.clone());
// Tell the server about the inputs.
self.postbox
.send_message(ClientMsg::PlayerInputs(input.clone()));
if self.client_state == ClientState::Character {
self.state.write_component(self.entity, control.clone());
}
// 2) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new();
@ -288,6 +288,25 @@ impl Client {
}
// 7) Finish the tick, pass control back to the frontend.
// Cleanup
self.state
.ecs_mut()
.write_storage::<comp::Jumping>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Gliding>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Dying>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Respawning>()
.remove(self.entity);
self.tick += 1;
Ok(frontend_events)
}
@ -378,6 +397,49 @@ impl Client {
Ok(frontend_events)
}
/// Get the player's entity.
#[allow(dead_code)]
pub fn entity(&self) -> EcsEntity {
self.entity
}
/// Get the client state
#[allow(dead_code)]
pub fn get_client_state(&self) -> ClientState {
self.client_state
}
/// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 {
self.tick
}
#[allow(dead_code)]
pub fn get_ping_ms(&self) -> f64 {
self.last_ping_delta * 1000.0
}
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool {
&self.thread_pool
}
/// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State {
&self.state
}
/// Get a mutable reference to the client's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State {
&mut self.state
}
}
impl Drop for Client {

View File

@ -1,33 +1,49 @@
use specs::{Component, FlaggedStorage, VecStorage};
use specs::{Component, FlaggedStorage, NullStorage, VecStorage};
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InputEvent {
Jump,
Attack,
RequestRespawn,
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Control {
pub move_dir: Vec2<f32>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Respawning;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Inputs {
// Held down
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
// Event based
pub events: Vec<InputEvent>,
pub struct Attacking {
pub time: f32,
pub applied: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Jumping;
impl Component for Inputs {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Gliding;
impl Component for Control {
type Storage = VecStorage<Self>;
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Actions {
pub attack_time: Option<f32>,
impl Component for Respawning {
type Storage = NullStorage<Self>;
}
impl Component for Actions {
impl Attacking {
pub fn start() -> Self {
Self {
time: 0.0,
applied: false,
}
}
}
impl Component for Attacking {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Component for Jumping {
type Storage = NullStorage<Self>;
}
impl Component for Gliding {
type Storage = NullStorage<Self>;
}

View File

@ -14,10 +14,11 @@ pub use actor::QuadrupedBody;
pub use agent::Agent;
pub use animation::Animation;
pub use animation::AnimationInfo;
pub use inputs::Actions;
pub use inputs::InputEvent;
pub use inputs::Inputs;
pub use inputs::Attacking;
pub use inputs::Control;
pub use inputs::Gliding;
pub use inputs::Jumping;
pub use inputs::Respawning;
pub use player::Player;
pub use player::Respawn;
pub use stats::Dying;
pub use stats::Stats;

View File

@ -21,6 +21,12 @@ pub struct Stats {
pub xp: u32,
}
impl Stats {
pub fn is_dead(&self) -> bool {
self.hp.current == 0
}
}
impl Default for Stats {
fn default() -> Self {
Self {

View File

@ -11,12 +11,13 @@ pub enum ClientMsg {
name: String,
body: comp::Body,
},
Attack,
Respawn,
RequestState(ClientState),
SetViewDistance(u32),
Ping,
Pong,
Chat(String),
PlayerInputs(comp::Inputs),
PlayerAnimation(comp::AnimationInfo),
PlayerPhysics {
pos: comp::phys::Pos,

View File

@ -23,7 +23,7 @@ sphynx::sum_type! {
Actor(comp::Actor),
Player(comp::Player),
Stats(comp::Stats),
Actions(comp::Actions),
Attacking(comp::Attacking),
}
}
// Automatically derive From<T> for EcsCompPhantom
@ -37,7 +37,7 @@ sphynx::sum_type! {
Actor(PhantomData<comp::Actor>),
Player(PhantomData<comp::Player>),
Stats(PhantomData<comp::Stats>),
Actions(PhantomData<comp::Actions>),
Attacking(PhantomData<comp::Attacking>),
}
}
impl sphynx::CompPacket for EcsCompPacket {

View File

@ -103,7 +103,7 @@ impl State {
ecs.register_synced::<comp::Actor>();
ecs.register_synced::<comp::Player>();
ecs.register_synced::<comp::Stats>();
ecs.register_synced::<comp::Actions>();
ecs.register_synced::<comp::Attacking>();
ecs.register::<comp::phys::ForceUpdate>();
// Register unsynced (or synced by other means) components.
@ -111,9 +111,12 @@ impl State {
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Dir>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Inputs>();
ecs.register::<comp::Attacking>();
ecs.register::<comp::Control>();
ecs.register::<comp::Jumping>();
ecs.register::<comp::Respawning>();
ecs.register::<comp::Gliding>();
ecs.register::<comp::Dying>();
ecs.register::<comp::Respawn>();
ecs.register::<comp::Agent>();
ecs.register::<inventory::Inventory>();
@ -192,7 +195,8 @@ impl State {
/// Removes every chunk of the terrain.
pub fn clear_terrain(&mut self) {
let keys = self.terrain_mut()
let keys = self
.terrain_mut()
.drain()
.map(|(key, _)| key)
.collect::<Vec<_>>();

View File

@ -6,7 +6,7 @@ use vek::*;
use crate::{
comp::{
phys::{Dir, Pos, Vel},
Actions, Animation, AnimationInfo,
Animation, AnimationInfo, Attacking,
},
state::DeltaTime,
terrain::TerrainMap,
@ -17,18 +17,27 @@ use crate::{
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (Entities<'a>, Read<'a, DeltaTime>, WriteStorage<'a, Actions>);
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
WriteStorage<'a, Attacking>,
);
fn run(&mut self, (entities, dt, mut actions): Self::SystemData) {
for (entity, mut action) in (&entities, &mut actions).join() {
let should_end = action.attack_time.as_mut().map_or(false, |mut time| {
*time += dt.0;
*time > 0.5 // TODO: constant
});
if should_end {
action.attack_time = None;
fn run(&mut self, (entities, dt, mut attackings): Self::SystemData) {
for (entity, attacking) in (&entities, &mut attackings).join() {
attacking.time += dt.0;
}
let finished_attack = (&entities, &mut attackings)
.join()
.filter(|(e, a)| {
a.time > 0.5 // TODO: constant
})
.map(|(e, a)| e)
.collect::<Vec<_>>();
for entity in finished_attack {
attackings.remove(entity);
}
}
}

View File

@ -1,22 +1,29 @@
// Library
use specs::{Join, Read, ReadStorage, System, WriteStorage};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::comp::{phys::Pos, Agent, Inputs};
use crate::comp::{phys::Pos, Agent, Control, Jumping};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
WriteStorage<'a, Agent>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Inputs>,
WriteStorage<'a, Control>,
WriteStorage<'a, Jumping>,
);
fn run(&mut self, (mut agents, positions, mut inputs): Self::SystemData) {
for (mut agent, pos, mut input) in (&mut agents, &positions, &mut inputs).join() {
fn run(
&mut self,
(entities, mut agents, positions, mut controls, mut jumpings): Self::SystemData,
) {
for (entity, agent, pos, control) in
(&entities, &mut agents, &positions, &mut controls).join()
{
match agent {
Agent::Wanderer(bearing) => {
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
@ -25,7 +32,7 @@ impl<'a> System<'a> for Sys {
- pos.0 * 0.0002;
if bearing.magnitude_squared() != 0.0 {
input.move_dir = bearing.normalized();
control.move_dir = bearing.normalized();
}
}
Agent::Pet { target, offset } => {
@ -34,12 +41,13 @@ impl<'a> System<'a> for Sys {
Some(tgt_pos) => {
let tgt_pos = tgt_pos.0 + *offset;
// Jump with target.
input.jumping = tgt_pos.z > pos.0.z + 1.0;
if tgt_pos.z > pos.0.z + 1.0 {
jumpings.insert(entity, Jumping);
}
// Move towards the target.
let dist = tgt_pos.distance(pos.0);
input.move_dir = if dist > 5.0 {
control.move_dir = if dist > 5.0 {
Vec2::from(tgt_pos - pos.0).normalized()
} else if dist < 1.5 && dist > 0.0 {
Vec2::from(pos.0 - tgt_pos).normalized()
@ -47,7 +55,7 @@ impl<'a> System<'a> for Sys {
Vec2::zero()
};
}
_ => input.move_dir = Vec2::zero(),
_ => control.move_dir = Vec2::zero(),
}
// Change offset occasionally.

View File

@ -6,7 +6,7 @@ use vek::*;
use crate::{
comp::{
phys::{Dir, ForceUpdate, Pos, Vel},
Actions, Animation, AnimationInfo, InputEvent, Inputs, Respawn, Stats,
Animation, AnimationInfo, Attacking, Control, Gliding, Jumping, Respawning, Stats,
},
state::{DeltaTime, Time},
terrain::TerrainMap,
@ -22,14 +22,16 @@ impl<'a> System<'a> for Sys {
Read<'a, Time>,
Read<'a, DeltaTime>,
ReadExpect<'a, TerrainMap>,
WriteStorage<'a, Inputs>,
WriteStorage<'a, Actions>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Dir>,
WriteStorage<'a, AnimationInfo>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Respawn>,
ReadStorage<'a, Control>,
ReadStorage<'a, Jumping>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Gliding>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, ForceUpdate>,
);
@ -40,27 +42,29 @@ impl<'a> System<'a> for Sys {
time,
dt,
terrain,
mut inputs,
mut actions,
positions,
mut velocities,
mut directions,
mut animation_infos,
mut stats,
mut respawns,
mut controls,
mut jumpings,
mut respawnings,
mut glidings,
mut attackings,
mut force_updates,
): Self::SystemData,
) {
for (entity, inputs, pos, mut dir, mut vel) in (
for (entity, pos, control, mut dir, mut vel) in (
&entities,
&mut inputs,
&positions,
&controls,
&mut directions,
&mut velocities,
)
.join()
{
// Handle held-down inputs
// Handle held-down control
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
@ -70,9 +74,9 @@ impl<'a> System<'a> for Sys {
let (gliding, friction) = if on_ground {
// TODO: Don't hard-code this.
// Apply physics to the player: acceleration and non-linear deceleration.
vel.0 += Vec2::broadcast(dt.0) * inputs.move_dir * 200.0;
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 200.0;
if inputs.jumping {
if jumpings.get(entity).is_some() {
vel.0.z += 16.0;
}
@ -80,9 +84,9 @@ impl<'a> System<'a> for Sys {
} else {
// TODO: Don't hard-code this.
// Apply physics to the player: acceleration and non-linear deceleration.
vel.0 += Vec2::broadcast(dt.0) * inputs.move_dir * 10.0;
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 10.0;
if inputs.gliding && vel.0.z < 0.0 {
if glidings.get(entity).is_some() && vel.0.z < 0.0 {
// TODO: Don't hard-code this.
let anti_grav = 9.81 * 3.95 + vel.0.z.powf(2.0) * 0.2;
vel.0.z +=
@ -109,12 +113,12 @@ impl<'a> System<'a> for Sys {
}
let animation = if on_ground {
if inputs.move_dir.magnitude() > 0.01 {
if control.move_dir.magnitude() > 0.01 {
Animation::Run
} else {
Animation::Idle
}
} else if gliding {
} else if glidings.get(entity).is_some() {
Animation::Gliding
} else {
Animation::Jump
@ -135,17 +139,11 @@ impl<'a> System<'a> for Sys {
},
);
}
for (entity, inputs) in (&entities, &mut inputs).join() {
// Handle event-based inputs
for event in inputs.events.drain(..) {
match event {
InputEvent::Attack => {
// Attack delay
if let (Some(pos), Some(dir), Some(action)) = (
positions.get(entity),
directions.get(entity),
actions.get_mut(entity),
) {
for (entity, pos, dir, attacking) in
(&entities, &positions, &directions, &mut attackings).join()
{
if !attacking.applied {
for (b, pos_b, mut stat_b, mut vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
@ -154,23 +152,14 @@ impl<'a> System<'a> for Sys {
&& pos.0.distance_squared(pos_b.0) < 50.0
&& dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Set action
action.attack_time = Some(0.0);
// Deal damage
stat_b.hp.change_by(-10); // TODO: variable damage
vel_b.0 += (pos_b.0 - pos.0).normalized() * 20.0;
vel_b.0.z = 20.0;
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
force_updates.insert(b, ForceUpdate);
}
}
}
}
InputEvent::RequestRespawn => {
respawns.insert(entity, Respawn);
}
InputEvent::Jump => {}
}
attacking.applied = true;
}
}
}

View File

@ -6,7 +6,11 @@ use crate::{
dyna::{Dyna, DynaErr},
},
};
use std::{collections::{hash_map, HashMap}, marker::PhantomData, sync::Arc};
use std::{
collections::{hash_map, HashMap},
marker::PhantomData,
sync::Arc,
};
use vek::*;
#[derive(Debug)]

View File

@ -141,7 +141,10 @@ fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
fn handle_kill(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
server
.state
.write_component::<comp::Dying>(entity, comp::Dying)
.ecs_mut()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|s| s.hp.current = 0);
}
fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
@ -208,9 +211,11 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
.state
.read_component_cloned::<comp::phys::Pos>(entity)
{
Some(pos) => {
Some(mut pos) => {
pos.0.x += 1.0;
server
.create_npc(
pos,
"Bungo".to_owned(),
comp::Body::Quadruped(comp::QuadrupedBody::random()),
)
@ -218,7 +223,6 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
target: entity,
offset: Vec2::zero(),
})
.with(pos)
.build();
server
.clients

View File

@ -126,15 +126,19 @@ impl Server {
/// Build a non-player character.
#[allow(dead_code)]
pub fn create_npc(&mut self, name: String, body: comp::Body) -> EcsEntityBuilder {
pub fn create_npc(
&mut self,
pos: comp::phys::Pos,
name: String,
body: comp::Body,
) -> EcsEntityBuilder {
self.state
.ecs_mut()
.create_entity_synced()
.with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)))
.with(pos)
.with(comp::Control::default())
.with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y()))
.with(comp::Inputs::default())
.with(comp::Actions::default())
.with(comp::Actor::Character { name, body })
.with(comp::Stats::default())
}
@ -150,8 +154,7 @@ impl Server {
state.write_component(entity, comp::Actor::Character { name, body });
state.write_component(entity, comp::Stats::default());
state.write_component(entity, comp::Inputs::default());
state.write_component(entity, comp::Actions::default());
state.write_component(entity, comp::Control::default());
state.write_component(entity, comp::AnimationInfo::new());
state.write_component(entity, comp::phys::Pos(spawn_point));
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
@ -210,25 +213,17 @@ impl Server {
.collect::<Vec<_>>();
for entity in todo_kill {
self.state
.ecs_mut()
.write_storage::<comp::Dying>()
.remove(entity);
self.state
.ecs_mut()
.write_storage::<comp::Actions>()
.remove(entity);
if let Some(client) = self.clients.get_mut(&entity) {
client.force_state(ClientState::Dead);
} else {
//self.state.ecs_mut().delete_entity_synced(entity);
self.state.ecs_mut().delete_entity_synced(entity);
}
}
// Handle respawns
let todo_respawn = (
&self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Respawn>(),
&self.state.ecs().read_storage::<comp::Respawning>(),
)
.join()
.map(|(entity, _)| entity)
@ -237,12 +232,7 @@ impl Server {
for entity in todo_respawn {
if let Some(client) = self.clients.get_mut(&entity) {
client.allow_state(ClientState::Character);
self.state
.ecs_mut()
.write_storage::<comp::Respawn>()
.remove(entity);
self.state.write_component(entity, comp::Stats::default());
self.state.write_component(entity, comp::Actions::default());
self.state
.ecs_mut()
.write_storage::<comp::phys::Pos>()
@ -319,6 +309,16 @@ impl Server {
self.sync_clients();
// 7) Finish the tick, pass control back to the frontend.
// Cleanup
let ecs = self.state.ecs_mut();
for entity in ecs.entities().join() {
ecs.write_storage::<comp::Jumping>().remove(entity);
ecs.write_storage::<comp::Gliding>().remove(entity);
ecs.write_storage::<comp::Dying>().remove(entity);
ecs.write_storage::<comp::Respawning>().remove(entity);
}
Ok(frontend_events)
}
@ -442,6 +442,18 @@ impl Server {
}
ClientState::Pending => {}
},
ClientMsg::Attack => match client.client_state {
ClientState::Character => {
state.write_component(entity, comp::Attacking::start());
}
_ => client.error_state(RequestStateError::Impossible),
},
ClientMsg::Respawn => match client.client_state {
ClientState::Dead => {
state.write_component(entity, comp::Respawning);
}
_ => client.error_state(RequestStateError::Impossible),
},
ClientMsg::Chat(msg) => match client.client_state {
ClientState::Connected => {
client.error_state(RequestStateError::Impossible)
@ -452,18 +464,6 @@ impl Server {
| ClientState::Character => new_chat_msgs.push((entity, msg)),
ClientState::Pending => {}
},
ClientMsg::PlayerInputs(mut inputs) => match client.client_state {
ClientState::Character | ClientState::Dead => {
state
.ecs_mut()
.write_storage::<comp::Inputs>()
.get_mut(entity)
.map(|s| {
s.events.append(&mut inputs.events);
});
}
_ => client.error_state(RequestStateError::Impossible),
},
ClientMsg::PlayerAnimation(animation_info) => {
match client.client_state {
ClientState::Character => {

View File

@ -5,8 +5,6 @@ pub struct KeyState {
pub left: bool,
pub up: bool,
pub down: bool,
pub jump: bool,
pub glide: bool,
}
impl KeyState {
@ -16,8 +14,6 @@ impl KeyState {
left: false,
up: false,
down: false,
jump: false,
glide: false,
}
}

View File

@ -109,7 +109,7 @@ impl PlayState for CharSelectionState {
// Tick the client (currently only to keep the connection alive).
self.client
.borrow_mut()
.tick(comp::Inputs::default(), clock.get_last_delta())
.tick(comp::Control::default(), clock.get_last_delta())
.expect("Failed to tick the client");
self.client.borrow_mut().cleanup();

View File

@ -366,7 +366,8 @@ impl FigureMgr {
.and_then(|stats| stats.hp.last_change)
.map(|(change_by, time)| {
Rgba::broadcast(1.0)
+ Rgba::new(0.0, -1.0, -1.0, 0.0).map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
+ Rgba::new(0.0, -1.0, -1.0, 0.0)
.map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
})
.unwrap_or(Rgba::broadcast(1.0));
@ -460,13 +461,17 @@ impl FigureMgr {
let tick = client.get_tick();
let ecs = client.state().ecs();
for (entity, actor, _) in (
for (entity, actor, stats) in (
&ecs.entities(),
&ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::Actions>(),
&ecs.read_storage::<comp::Stats>(), // Just to make sure the entity is alive
)
.join()
{
if stats.is_dead() {
return;
}
match actor {
comp::Actor::Character { body, .. } => {
if let Some((locals, bone_consts)) = match body {

View File

@ -19,7 +19,6 @@ pub struct SessionState {
scene: Scene,
client: Rc<RefCell<Client>>,
key_state: KeyState,
input_events: Vec<comp::InputEvent>,
hud: Hud,
}
@ -34,7 +33,6 @@ impl SessionState {
client,
key_state: KeyState::new(),
hud: Hud::new(window),
input_events: Vec::new(),
}
}
}
@ -60,19 +58,11 @@ impl SessionState {
let dir_vec = self.key_state.dir_vec();
let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
// Take the input events.
let mut input_events = Vec::new();
mem::swap(&mut self.input_events, &mut input_events);
for event in self.client.borrow_mut().tick(
comp::Inputs {
move_dir,
jumping: self.key_state.jump,
gliding: self.key_state.glide,
events: input_events,
},
dt,
)? {
for event in self
.client
.borrow_mut()
.tick(comp::Control { move_dir }, dt)?
{
match event {
client::Event::Chat(msg) => {
self.hud.new_message(msg);
@ -136,21 +126,19 @@ impl PlayState for SessionState {
return PlayStateResult::Shutdown;
}
// Attack key pressed
Event::InputUpdate(GameInput::Attack, state) => {
self.input_events.push(comp::InputEvent::Attack);
//self.input_events.push(comp::InputEvent::RequestRespawn);
},
Event::InputUpdate(GameInput::Attack, true) => {
self.client.borrow_mut().attack();
self.client.borrow_mut().respawn();
}
Event::InputUpdate(GameInput::Jump, true) => {
self.client.borrow_mut().jump();
}
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,
Event::InputUpdate(GameInput::MoveRight, state) => self.key_state.right = state,
Event::InputUpdate(GameInput::Glide, state) => self.key_state.glide = state,
Event::InputUpdate(GameInput::Jump, true) => {
self.input_events.push(comp::InputEvent::Jump);
self.key_state.jump = true;
}
Event::InputUpdate(GameInput::Jump, false) => {
self.key_state.jump = false;
Event::InputUpdate(GameInput::Glide, state) => {
self.client.borrow_mut().glide(state)
}
// Pass all other events to the scene

View File

@ -113,12 +113,18 @@ impl Window {
key_map.insert(settings.controls.map, GameInput::Map);
key_map.insert(settings.controls.bag, GameInput::Bag);
key_map.insert(settings.controls.quest_log, GameInput::QuestLog);
key_map.insert(settings.controls.character_window, GameInput::CharacterWindow);
key_map.insert(
settings.controls.character_window,
GameInput::CharacterWindow,
);
key_map.insert(settings.controls.social, GameInput::Social);
key_map.insert(settings.controls.spellbook, GameInput::Spellbook);
key_map.insert(settings.controls.settings, GameInput::Settings);
key_map.insert(settings.controls.help, GameInput::Help);
key_map.insert(settings.controls.toggle_interface, GameInput::ToggleInterface);
key_map.insert(
settings.controls.toggle_interface,
GameInput::ToggleInterface,
);
key_map.insert(settings.controls.toggle_debug, GameInput::ToggleDebug);
key_map.insert(settings.controls.fullscreen, GameInput::Fullscreen);
key_map.insert(settings.controls.screenshot, GameInput::Screenshot);
@ -184,11 +190,12 @@ impl Window {
events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
}
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::WindowEvent::MouseInput { button, state, .. }
if cursor_grabbed =>
{
glutin::WindowEvent::MouseInput { button, state, .. } if cursor_grabbed => {
if let Some(&game_input) = key_map.get(&KeyMouse::Mouse(button)) {
events.push(Event::InputUpdate(game_input, state == glutin::ElementState::Pressed))
events.push(Event::InputUpdate(
game_input,
state == glutin::ElementState::Pressed,
))
}
}
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode
@ -204,7 +211,10 @@ impl Window {
glutin::ElementState::Pressed => take_screenshot = true,
_ => {}
},
Some(&game_input) => events.push(Event::InputUpdate(game_input, input.state == glutin::ElementState::Pressed)),
Some(&game_input) => events.push(Event::InputUpdate(
game_input,
input.state == glutin::ElementState::Pressed,
)),
_ => {}
},
_ => {}