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), _ => return Err(Error::ServerWentMad),
}; };
// Initialize ecs components the client has actions over
state.write_component(entity, comp::Inputs::default());
Ok(Self { Ok(Self {
client_state, client_state,
thread_pool: threadpool::Builder::new() 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) { pub fn register(&mut self, player: comp::Player) {
self.postbox.send_message(ClientMsg::Register { player }); self.postbox.send_message(ClientMsg::Register { player });
self.client_state = ClientState::Pending; self.client_state = ClientState::Pending;
} }
/// Request a state transition to `ClientState::Character`.
pub fn request_character(&mut self, name: String, body: comp::Body) { pub fn request_character(&mut self, name: String, body: comp::Body) {
self.postbox self.postbox
.send_message(ClientMsg::Character { name, body }); .send_message(ClientMsg::Character { name, body });
@ -111,24 +110,56 @@ impl Client {
.send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap())); // Can't fail .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 /// Send a chat message to the server.
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
#[allow(dead_code)] #[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool { pub fn send_chat(&mut self, msg: String) {
&self.thread_pool 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)] #[allow(dead_code)]
pub fn state(&self) -> &State { pub fn jump(&mut self) {
&self.state 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)] #[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State { pub fn glide(&mut self, state: bool) {
&mut self.state 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 /// Remove all cached terrain
@ -138,38 +169,9 @@ impl Client {
self.pending_chunks.clear(); 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. /// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)] #[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 // 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 // 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 // the core developers before making significant changes to this code. Here is the
@ -187,11 +189,9 @@ impl Client {
// 1) Handle input from frontend. // 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity. // Pass character actions from frontend input to the player's entity.
// TODO: Only do this if the entity already has a Inputs component! // TODO: Only do this if the entity already has a Inputs component!
self.state.write_component(self.entity, input.clone()); if self.client_state == ClientState::Character {
self.state.write_component(self.entity, control.clone());
// Tell the server about the inputs. }
self.postbox
.send_message(ClientMsg::PlayerInputs(input.clone()));
// 2) Build up a list of events for this frame, to be passed to the frontend. // 2) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new(); let mut frontend_events = Vec::new();
@ -288,6 +288,25 @@ impl Client {
} }
// 7) Finish the tick, pass control back to the frontend. // 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; self.tick += 1;
Ok(frontend_events) Ok(frontend_events)
} }
@ -378,6 +397,49 @@ impl Client {
Ok(frontend_events) 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 { impl Drop for Client {

View File

@ -1,33 +1,49 @@
use specs::{Component, FlaggedStorage, VecStorage}; use specs::{Component, FlaggedStorage, NullStorage, VecStorage};
use vek::*; use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum InputEvent { pub struct Control {
Jump, pub move_dir: Vec2<f32>,
Attack,
RequestRespawn,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Respawning;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Inputs { pub struct Attacking {
// Held down pub time: f32,
pub move_dir: Vec2<f32>, pub applied: bool,
pub jumping: bool,
pub gliding: bool,
// Event based
pub events: Vec<InputEvent>,
} }
#[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>; type Storage = VecStorage<Self>;
} }
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] impl Component for Respawning {
pub struct Actions { type Storage = NullStorage<Self>;
pub attack_time: Option<f32>,
} }
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>>; 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 agent::Agent;
pub use animation::Animation; pub use animation::Animation;
pub use animation::AnimationInfo; pub use animation::AnimationInfo;
pub use inputs::Actions; pub use inputs::Attacking;
pub use inputs::InputEvent; pub use inputs::Control;
pub use inputs::Inputs; pub use inputs::Gliding;
pub use inputs::Jumping;
pub use inputs::Respawning;
pub use player::Player; pub use player::Player;
pub use player::Respawn;
pub use stats::Dying; pub use stats::Dying;
pub use stats::Stats; pub use stats::Stats;

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ use vek::*;
use crate::{ use crate::{
comp::{ comp::{
phys::{Dir, Pos, Vel}, phys::{Dir, Pos, Vel},
Actions, Animation, AnimationInfo, Animation, AnimationInfo, Attacking,
}, },
state::DeltaTime, state::DeltaTime,
terrain::TerrainMap, terrain::TerrainMap,
@ -17,18 +17,27 @@ use crate::{
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for 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) { fn run(&mut self, (entities, dt, mut attackings): Self::SystemData) {
for (entity, mut action) in (&entities, &mut actions).join() { for (entity, attacking) in (&entities, &mut attackings).join() {
let should_end = action.attack_time.as_mut().map_or(false, |mut time| { attacking.time += dt.0;
*time += dt.0;
*time > 0.5 // TODO: constant
});
if should_end {
action.attack_time = None;
} }
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 // Library
use specs::{Join, Read, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*; use vek::*;
// Crate // Crate
use crate::comp::{phys::Pos, Agent, Inputs}; use crate::comp::{phys::Pos, Agent, Control, Jumping};
// Basic ECS AI agent system // Basic ECS AI agent system
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
WriteStorage<'a, Inputs>, WriteStorage<'a, Control>,
WriteStorage<'a, Jumping>,
); );
fn run(&mut self, (mut agents, positions, mut inputs): Self::SystemData) { fn run(
for (mut agent, pos, mut input) in (&mut agents, &positions, &mut inputs).join() { &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 { match agent {
Agent::Wanderer(bearing) => { Agent::Wanderer(bearing) => {
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5) *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; - pos.0 * 0.0002;
if bearing.magnitude_squared() != 0.0 { if bearing.magnitude_squared() != 0.0 {
input.move_dir = bearing.normalized(); control.move_dir = bearing.normalized();
} }
} }
Agent::Pet { target, offset } => { Agent::Pet { target, offset } => {
@ -34,12 +41,13 @@ impl<'a> System<'a> for Sys {
Some(tgt_pos) => { Some(tgt_pos) => {
let tgt_pos = tgt_pos.0 + *offset; let tgt_pos = tgt_pos.0 + *offset;
// Jump with target. if tgt_pos.z > pos.0.z + 1.0 {
input.jumping = tgt_pos.z > pos.0.z + 1.0; jumpings.insert(entity, Jumping);
}
// Move towards the target. // Move towards the target.
let dist = tgt_pos.distance(pos.0); 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() Vec2::from(tgt_pos - pos.0).normalized()
} else if dist < 1.5 && dist > 0.0 { } else if dist < 1.5 && dist > 0.0 {
Vec2::from(pos.0 - tgt_pos).normalized() Vec2::from(pos.0 - tgt_pos).normalized()
@ -47,7 +55,7 @@ impl<'a> System<'a> for Sys {
Vec2::zero() Vec2::zero()
}; };
} }
_ => input.move_dir = Vec2::zero(), _ => control.move_dir = Vec2::zero(),
} }
// Change offset occasionally. // Change offset occasionally.

View File

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

View File

@ -6,7 +6,11 @@ use crate::{
dyna::{Dyna, DynaErr}, 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::*; use vek::*;
#[derive(Debug)] #[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) { fn handle_kill(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
server server
.state .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) { 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 .state
.read_component_cloned::<comp::phys::Pos>(entity) .read_component_cloned::<comp::phys::Pos>(entity)
{ {
Some(pos) => { Some(mut pos) => {
pos.0.x += 1.0;
server server
.create_npc( .create_npc(
pos,
"Bungo".to_owned(), "Bungo".to_owned(),
comp::Body::Quadruped(comp::QuadrupedBody::random()), comp::Body::Quadruped(comp::QuadrupedBody::random()),
) )
@ -218,7 +223,6 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
target: entity, target: entity,
offset: Vec2::zero(), offset: Vec2::zero(),
}) })
.with(pos)
.build(); .build();
server server
.clients .clients

View File

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

View File

@ -5,8 +5,6 @@ pub struct KeyState {
pub left: bool, pub left: bool,
pub up: bool, pub up: bool,
pub down: bool, pub down: bool,
pub jump: bool,
pub glide: bool,
} }
impl KeyState { impl KeyState {
@ -16,8 +14,6 @@ impl KeyState {
left: false, left: false,
up: false, up: false,
down: 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). // Tick the client (currently only to keep the connection alive).
self.client self.client
.borrow_mut() .borrow_mut()
.tick(comp::Inputs::default(), clock.get_last_delta()) .tick(comp::Control::default(), clock.get_last_delta())
.expect("Failed to tick the client"); .expect("Failed to tick the client");
self.client.borrow_mut().cleanup(); self.client.borrow_mut().cleanup();

View File

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

View File

@ -19,7 +19,6 @@ pub struct SessionState {
scene: Scene, scene: Scene,
client: Rc<RefCell<Client>>, client: Rc<RefCell<Client>>,
key_state: KeyState, key_state: KeyState,
input_events: Vec<comp::InputEvent>,
hud: Hud, hud: Hud,
} }
@ -34,7 +33,6 @@ impl SessionState {
client, client,
key_state: KeyState::new(), key_state: KeyState::new(),
hud: Hud::new(window), hud: Hud::new(window),
input_events: Vec::new(),
} }
} }
} }
@ -60,19 +58,11 @@ impl SessionState {
let dir_vec = self.key_state.dir_vec(); let dir_vec = self.key_state.dir_vec();
let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1]; let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
// Take the input events. for event in self
let mut input_events = Vec::new(); .client
mem::swap(&mut self.input_events, &mut input_events); .borrow_mut()
.tick(comp::Control { move_dir }, dt)?
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,
)? {
match event { match event {
client::Event::Chat(msg) => { client::Event::Chat(msg) => {
self.hud.new_message(msg); self.hud.new_message(msg);
@ -136,21 +126,19 @@ impl PlayState for SessionState {
return PlayStateResult::Shutdown; return PlayStateResult::Shutdown;
} }
// Attack key pressed // Attack key pressed
Event::InputUpdate(GameInput::Attack, state) => { Event::InputUpdate(GameInput::Attack, true) => {
self.input_events.push(comp::InputEvent::Attack); self.client.borrow_mut().attack();
//self.input_events.push(comp::InputEvent::RequestRespawn); 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::MoveForward, state) => self.key_state.up = state,
Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state, Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state,
Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = state, Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = state,
Event::InputUpdate(GameInput::MoveRight, state) => self.key_state.right = state, Event::InputUpdate(GameInput::MoveRight, state) => self.key_state.right = state,
Event::InputUpdate(GameInput::Glide, state) => self.key_state.glide = state, Event::InputUpdate(GameInput::Glide, state) => {
Event::InputUpdate(GameInput::Jump, true) => { self.client.borrow_mut().glide(state)
self.input_events.push(comp::InputEvent::Jump);
self.key_state.jump = true;
}
Event::InputUpdate(GameInput::Jump, false) => {
self.key_state.jump = false;
} }
// Pass all other events to the scene // 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.map, GameInput::Map);
key_map.insert(settings.controls.bag, GameInput::Bag); key_map.insert(settings.controls.bag, GameInput::Bag);
key_map.insert(settings.controls.quest_log, GameInput::QuestLog); 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.social, GameInput::Social);
key_map.insert(settings.controls.spellbook, GameInput::Spellbook); key_map.insert(settings.controls.spellbook, GameInput::Spellbook);
key_map.insert(settings.controls.settings, GameInput::Settings); key_map.insert(settings.controls.settings, GameInput::Settings);
key_map.insert(settings.controls.help, GameInput::Help); 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.toggle_debug, GameInput::ToggleDebug);
key_map.insert(settings.controls.fullscreen, GameInput::Fullscreen); key_map.insert(settings.controls.fullscreen, GameInput::Fullscreen);
key_map.insert(settings.controls.screenshot, GameInput::Screenshot); 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))); events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
} }
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::WindowEvent::MouseInput { button, state, .. } glutin::WindowEvent::MouseInput { button, state, .. } if cursor_grabbed => {
if cursor_grabbed =>
{
if let Some(&game_input) = key_map.get(&KeyMouse::Mouse(button)) { 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 glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode
@ -204,7 +211,10 @@ impl Window {
glutin::ElementState::Pressed => take_screenshot = true, 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,
)),
_ => {} _ => {}
}, },
_ => {} _ => {}