mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add better inputs, input validation and more
Former-commit-id: 3227221b12a674f66b011ce0ba734e226f223f34
This commit is contained in:
parent
3c0121538b
commit
4696cd2c8b
@ -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 {
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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<_>>();
|
||||
|
@ -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
|
||||
});
|
||||
fn run(&mut self, (entities, dt, mut attackings): Self::SystemData) {
|
||||
for (entity, attacking) in (&entities, &mut attackings).join() {
|
||||
attacking.time += dt.0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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,42 +139,27 @@ 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 (b, pos_b, mut stat_b, mut vel_b) in
|
||||
(&entities, &positions, &mut stats, &mut velocities).join()
|
||||
{
|
||||
// Check if it is a hit
|
||||
if entity != b
|
||||
&& 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;
|
||||
force_updates.insert(b, ForceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
{
|
||||
// Check if it is a hit
|
||||
if entity != b
|
||||
&& pos.0.distance_squared(pos_b.0) < 50.0
|
||||
&& dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
|
||||
{
|
||||
// Deal damage
|
||||
stat_b.hp.change_by(-10); // TODO: variable damage
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
Loading…
Reference in New Issue
Block a user