Merge branch 'timo-rebase-slipped-on-midge' into 'master'

ECS organization + Rolling animation

See merge request veloren/veloren!235
This commit is contained in:
Joshua Barretto 2019-06-16 19:56:44 +00:00
commit ceff450233
38 changed files with 990 additions and 558 deletions

View File

@ -29,7 +29,7 @@ fn main() {
client.send_chat("Hello!".to_string()); client.send_chat("Hello!".to_string());
loop { loop {
let events = match client.tick(comp::Control::default(), clock.get_last_delta()) { let events = match client.tick(comp::Controller::default(), clock.get_last_delta()) {
Ok(events) => events, Ok(events) => events,
Err(err) => { Err(err) => {
error!("Error: {:?}", err); error!("Error: {:?}", err);

View File

@ -153,7 +153,7 @@ impl Client {
pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> { pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
let chunk_pos = Vec2::from( let chunk_pos = Vec2::from(
self.state self.state
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(self.entity) .get(self.entity)
.cloned()? .cloned()?
.0, .0,
@ -171,52 +171,6 @@ impl Client {
self.postbox.send_message(ClientMsg::Chat(msg)) self.postbox.send_message(ClientMsg::Chat(msg))
} }
/// Jump locally, the new positions will be synced to the server
#[allow(dead_code)]
pub fn jump(&mut self) {
if self.client_state != ClientState::Character {
return;
}
self.state.write_component(self.entity, comp::Jumping);
}
/// Start to glide locally, animation will be synced
#[allow(dead_code)]
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 /// Remove all cached terrain
#[allow(dead_code)] #[allow(dead_code)]
pub fn clear_terrain(&mut self) { pub fn clear_terrain(&mut self) {
@ -226,7 +180,11 @@ impl Client {
/// 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, control: comp::Control, dt: Duration) -> Result<Vec<Event>, Error> { pub fn tick(
&mut self,
controller: comp::Controller,
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
@ -243,10 +201,8 @@ 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! self.state.write_component(self.entity, controller.clone());
if self.client_state == ClientState::Character { self.postbox.send_message(ClientMsg::Controller(controller));
self.state.write_component(self.entity, control.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();
@ -262,7 +218,7 @@ impl Client {
// 5) Terrain // 5) Terrain
let pos = self let pos = self
.state .state
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(self.entity) .get(self.entity)
.cloned(); .cloned();
if let (Some(pos), Some(view_distance)) = (pos, self.view_distance) { if let (Some(pos), Some(view_distance)) = (pos, self.view_distance) {
@ -336,19 +292,6 @@ impl Client {
_ => {} _ => {}
} }
// Update the server about the player's current animation.
if let Some(animation_info) = self
.state
.ecs_mut()
.write_storage::<comp::AnimationInfo>()
.get_mut(self.entity)
{
if animation_info.changed {
self.postbox
.send_message(ClientMsg::PlayerAnimation(animation_info.clone()));
}
}
// Output debug metrics // Output debug metrics
if log_enabled!(log::Level::Info) && self.tick % 600 == 0 { if log_enabled!(log::Level::Info) && self.tick % 600 == 0 {
let metrics = self let metrics = self

View File

@ -7,6 +7,9 @@ pub enum Animation {
Jump, Jump,
Gliding, Gliding,
Attack, Attack,
Roll,
Crun,
Cidle,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]

View File

@ -0,0 +1,16 @@
use specs::{Component, FlaggedStorage, VecStorage};
use vek::*;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Controller {
pub move_dir: Vec2<f32>,
pub jump: bool,
pub attack: bool,
pub roll: bool,
pub glide: bool,
pub respawn: bool,
}
impl Component for Controller {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -1,29 +1,33 @@
use specs::{Component, FlaggedStorage, NullStorage, VecStorage}; use specs::{Component, FlaggedStorage, NullStorage, VecStorage};
use vek::*; use vek::*;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Control {
pub move_dir: Vec2<f32>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Respawning; pub struct Respawning;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MoveDir(pub Vec2<f32>);
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Attacking { pub struct Attacking {
pub time: f32, pub time: f32,
pub applied: bool, pub applied: bool,
} }
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Rolling {
pub time: f32,
pub applied: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct OnGround;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Jumping; pub struct Jumping;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Gliding; pub struct Gliding;
impl Component for Control {
type Storage = VecStorage<Self>;
}
impl Component for Respawning { impl Component for Respawning {
type Storage = NullStorage<Self>; type Storage = NullStorage<Self>;
} }
@ -36,14 +40,36 @@ impl Attacking {
} }
} }
} }
impl Rolling {
pub fn start() -> Self {
Self {
time: 0.0,
applied: false,
}
}
}
impl Component for MoveDir {
type Storage = VecStorage<Self>;
}
impl Component for Attacking { impl Component for Attacking {
type Storage = FlaggedStorage<Self, VecStorage<Self>>; type Storage = FlaggedStorage<Self, VecStorage<Self>>;
} }
impl Component for Rolling {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Component for OnGround {
type Storage = NullStorage<Self>;
}
impl Component for Jumping { impl Component for Jumping {
type Storage = NullStorage<Self>; type Storage = NullStorage<Self>;
} }
impl Component for Gliding { impl Component for Gliding {
type Storage = NullStorage<Self>; type Storage = FlaggedStorage<Self, NullStorage<Self>>;
} }

View File

@ -1,26 +1,18 @@
pub mod actor; pub mod actor;
pub mod agent; mod agent;
pub mod animation; mod animation;
pub mod inputs; mod controller;
pub mod phys; mod inputs;
pub mod player; mod phys;
pub mod stats; mod player;
mod stats;
// Reexports // Reexports
pub use actor::Actor; pub use actor::{Actor, Body, HumanoidBody, QuadrupedBody, QuadrupedMediumBody};
pub use actor::Body;
pub use actor::HumanoidBody;
pub use actor::QuadrupedBody;
pub use actor::QuadrupedMediumBody;
pub use agent::Agent; pub use agent::Agent;
pub use animation::Animation; pub use animation::{Animation, AnimationInfo};
pub use animation::AnimationInfo; pub use controller::Controller;
pub use inputs::Attacking; pub use inputs::{Attacking, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling};
pub use inputs::Control; pub use phys::{ForceUpdate, Ori, Pos, Vel};
pub use inputs::Gliding;
pub use inputs::Jumping;
pub use inputs::Respawning;
pub use player::Player; pub use player::Player;
pub use stats::Dying; pub use stats::{Dying, HealthSource, Stats};
pub use stats::HealthSource;
pub use stats::Stats;

View File

@ -2,7 +2,6 @@ use specs::{Component, NullStorage, VecStorage};
use vek::*; use vek::*;
// Position // Position
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Pos(pub Vec3<f32>); pub struct Pos(pub Vec3<f32>);
@ -11,7 +10,6 @@ impl Component for Pos {
} }
// Velocity // Velocity
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Vel(pub Vec3<f32>); pub struct Vel(pub Vec3<f32>);
@ -20,7 +18,6 @@ impl Component for Vel {
} }
// Orientation // Orientation
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Ori(pub Vec3<f32>); pub struct Ori(pub Vec3<f32>);
@ -29,7 +26,6 @@ impl Component for Ori {
} }
// ForceUpdate // ForceUpdate
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ForceUpdate; pub struct ForceUpdate;

View File

@ -11,18 +11,16 @@ pub enum ClientMsg {
name: String, name: String,
body: comp::Body, body: comp::Body,
}, },
Attack, Controller(comp::Controller),
Respawn,
RequestState(ClientState), RequestState(ClientState),
SetViewDistance(u32), SetViewDistance(u32),
Ping, Ping,
Pong, Pong,
Chat(String), Chat(String),
PlayerAnimation(comp::AnimationInfo),
PlayerPhysics { PlayerPhysics {
pos: comp::phys::Pos, pos: comp::Pos,
vel: comp::phys::Vel, vel: comp::Vel,
ori: comp::phys::Ori, ori: comp::Ori,
}, },
TerrainChunkRequest { TerrainChunkRequest {
key: Vec2<i32>, key: Vec2<i32>,

View File

@ -17,13 +17,15 @@ impl sphynx::ResPacket for EcsResPacket {}
sphynx::sum_type! { sphynx::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket { pub enum EcsCompPacket {
Pos(comp::phys::Pos), Pos(comp::Pos),
Vel(comp::phys::Vel), Vel(comp::Vel),
Ori(comp::phys::Ori), Ori(comp::Ori),
Actor(comp::Actor), Actor(comp::Actor),
Player(comp::Player), Player(comp::Player),
Stats(comp::Stats), Stats(comp::Stats),
Attacking(comp::Attacking), Attacking(comp::Attacking),
Rolling(comp::Rolling),
Gliding(comp::Gliding),
} }
} }
// Automatically derive From<T> for EcsCompPhantom // Automatically derive From<T> for EcsCompPhantom
@ -31,13 +33,15 @@ sphynx::sum_type! {
sphynx::sum_type! { sphynx::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom { pub enum EcsCompPhantom {
Pos(PhantomData<comp::phys::Pos>), Pos(PhantomData<comp::Pos>),
Vel(PhantomData<comp::phys::Vel>), Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::phys::Ori>), Ori(PhantomData<comp::Ori>),
Actor(PhantomData<comp::Actor>), Actor(PhantomData<comp::Actor>),
Player(PhantomData<comp::Player>), Player(PhantomData<comp::Player>),
Stats(PhantomData<comp::Stats>), Stats(PhantomData<comp::Stats>),
Attacking(PhantomData<comp::Attacking>), Attacking(PhantomData<comp::Attacking>),
Rolling(PhantomData<comp::Rolling>),
Gliding(PhantomData<comp::Gliding>),
} }
} }
impl sphynx::CompPacket for EcsCompPacket { impl sphynx::CompPacket for EcsCompPacket {

View File

@ -32,9 +32,9 @@ pub enum ServerMsg {
EcsSync(sphynx::SyncPackage<EcsCompPacket, EcsResPacket>), EcsSync(sphynx::SyncPackage<EcsCompPacket, EcsResPacket>),
EntityPhysics { EntityPhysics {
entity: u64, entity: u64,
pos: comp::phys::Pos, pos: comp::Pos,
vel: comp::phys::Vel, vel: comp::Vel,
ori: comp::phys::Ori, ori: comp::Ori,
}, },
EntityAnimation { EntityAnimation {
entity: u64, entity: u64,

View File

@ -98,28 +98,31 @@ impl State {
// Create a new Sphynx ECS world. // Create a new Sphynx ECS world.
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsCompPacket, EcsResPacket>) { fn setup_sphynx_world(ecs: &mut sphynx::World<EcsCompPacket, EcsResPacket>) {
// Register synced components. // Register server->client synced components.
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::Attacking>(); // TODO: Don't send this to the client? ecs.register_synced::<comp::Attacking>();
ecs.register::<comp::phys::ForceUpdate>(); ecs.register_synced::<comp::Rolling>();
ecs.register_synced::<comp::Gliding>();
// Register components synced by other means // Register components synced by other means
ecs.register::<comp::phys::Pos>(); ecs.register::<comp::Pos>();
ecs.register::<comp::phys::Vel>(); ecs.register::<comp::Vel>();
ecs.register::<comp::phys::Ori>(); ecs.register::<comp::Ori>();
ecs.register::<comp::MoveDir>();
ecs.register::<comp::OnGround>();
ecs.register::<comp::AnimationInfo>(); ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Controller>();
// Register client-local components // Register client-local components
ecs.register::<comp::Control>();
ecs.register::<comp::Jumping>(); ecs.register::<comp::Jumping>();
// Register server-local components // Register server-local components
ecs.register::<comp::Agent>(); ecs.register::<comp::Agent>();
ecs.register::<comp::Respawning>(); ecs.register::<comp::Respawning>();
ecs.register::<comp::Gliding>();
ecs.register::<comp::Dying>(); ecs.register::<comp::Dying>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<inventory::Inventory>(); ecs.register::<inventory::Inventory>();
// Register synced resources used by the ECS. // Register synced resources used by the ECS.

View File

@ -1,29 +0,0 @@
use crate::{comp::Attacking, state::DeltaTime};
use specs::{Entities, Join, Read, System, WriteStorage};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
WriteStorage<'a, Attacking>,
);
fn run(&mut self, (entities, dt, mut attacks): Self::SystemData) {
for attack in (&mut attacks).join() {
attack.time += dt.0;
}
let finished_attacks = (&entities, &mut attacks)
.join()
.filter(|(_, a)| a.time > 0.25) // TODO: constant
.map(|(e, _)| e)
.collect::<Vec<_>>();
for entity in finished_attacks {
attacks.remove(entity);
}
}
}

View File

@ -1,28 +1,27 @@
use crate::comp::{phys::Pos, Agent, Attacking, Control, Jumping}; use crate::comp::{Agent, Attacking, Controller, Jumping, Pos};
use log::warn; use log::warn;
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng};
use specs::{Entities, Join, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, ReadStorage, System, WriteStorage};
use vek::*; use vek::*;
// Basic ECS AI agent system /// This system will allow NPCs to modify their controller
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
WriteStorage<'a, Control>, WriteStorage<'a, Controller>,
WriteStorage<'a, Jumping>, WriteStorage<'a, Jumping>,
WriteStorage<'a, Attacking>, WriteStorage<'a, Attacking>,
); );
fn run( fn run(
&mut self, &mut self,
(entities, mut agents, positions, mut controls, mut jumps, mut attacks): Self::SystemData, (entities, mut agents, positions, mut controllers, mut jumps, mut attacks): Self::SystemData,
) { ) {
for (entity, agent, pos, control) in for (entity, agent, pos, controller) in
(&entities, &mut agents, &positions, &mut controls).join() (&entities, &mut agents, &positions, &mut controllers).join()
{ {
match agent { match agent {
Agent::Wanderer(bearing) => { Agent::Wanderer(bearing) => {
@ -32,7 +31,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 {
control.move_dir = bearing.normalized(); controller.move_dir = bearing.normalized();
} }
} }
Agent::Pet { target, offset } => { Agent::Pet { target, offset } => {
@ -49,7 +48,7 @@ impl<'a> System<'a> for Sys {
// Move towards the target. // Move towards the target.
let dist: f32 = Vec2::from(tgt_pos - pos.0).magnitude(); let dist: f32 = Vec2::from(tgt_pos - pos.0).magnitude();
control.move_dir = if dist > 5.0 { controller.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()
@ -57,7 +56,7 @@ impl<'a> System<'a> for Sys {
Vec2::zero() Vec2::zero()
}; };
} }
_ => control.move_dir = Vec2::zero(), _ => controller.move_dir = Vec2::zero(),
} }
// Change offset occasionally. // Change offset occasionally.
@ -72,7 +71,7 @@ impl<'a> System<'a> for Sys {
Some(tgt_pos) => { Some(tgt_pos) => {
let dist = Vec2::<f32>::from(tgt_pos.0 - pos.0).magnitude(); let dist = Vec2::<f32>::from(tgt_pos.0 - pos.0).magnitude();
if dist < 2.0 { if dist < 2.0 {
control.move_dir = Vec2::zero(); controller.move_dir = Vec2::zero();
if rand::random::<f32>() < 0.2 { if rand::random::<f32>() < 0.2 {
attacks attacks
@ -82,7 +81,7 @@ impl<'a> System<'a> for Sys {
false false
} else if dist < 60.0 { } else if dist < 60.0 {
control.move_dir = controller.move_dir =
Vec2::<f32>::from(tgt_pos.0 - pos.0).normalized() * 0.96; Vec2::<f32>::from(tgt_pos.0 - pos.0).normalized() * 0.96;
false false
@ -91,7 +90,7 @@ impl<'a> System<'a> for Sys {
} }
} }
None => { None => {
control.move_dir = Vec2::one(); controller.move_dir = Vec2::one();
true true
} }
}; };

View File

@ -1,15 +1,87 @@
use crate::{comp::AnimationInfo, state::DeltaTime}; use crate::{
use specs::{Join, Read, System, WriteStorage}; comp::{
Animation, AnimationInfo, Attacking, ForceUpdate, Gliding, Jumping, OnGround, Ori, Pos,
Rolling, Vel,
},
state::DeltaTime,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
// Basic ECS AI agent system /// This system will apply the animation that fits best to the users actions
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationInfo>); type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
ReadStorage<'a, Vel>,
ReadStorage<'a, OnGround>,
ReadStorage<'a, Jumping>,
ReadStorage<'a, Gliding>,
ReadStorage<'a, Attacking>,
ReadStorage<'a, Rolling>,
WriteStorage<'a, AnimationInfo>,
);
fn run(&mut self, (dt, mut animation_infos): Self::SystemData) { fn run(
for mut animation_info in (&mut animation_infos).join() { &mut self,
(
entities,
dt,
velocities,
on_grounds,
jumpings,
glidings,
attackings,
rollings,
mut animation_infos,
): Self::SystemData,
) {
for (entity, vel, on_ground, jumping, gliding, attacking, rolling, mut animation_info) in (
&entities,
&velocities,
on_grounds.maybe(),
jumpings.maybe(),
glidings.maybe(),
attackings.maybe(),
rollings.maybe(),
&mut animation_infos,
)
.join()
{
animation_info.time += dt.0 as f64; animation_info.time += dt.0 as f64;
fn impossible_animation(message: &str) -> Animation {
warn!("{}", message);
Animation::Idle
}
let animation = match (
on_ground.is_some(),
vel.0.magnitude() > 3.0, // Moving
attacking.is_some(),
gliding.is_some(),
rolling.is_some(),
) {
(_, _, true, true, _) => impossible_animation("Attack while gliding"),
(_, _, true, _, true) => impossible_animation("Roll while attacking"),
(_, _, _, true, true) => impossible_animation("Roll while gliding"),
(_, false, _, _, true) => impossible_animation("Roll without moving"),
(_, true, false, false, true) => Animation::Roll,
(true, false, false, false, false) => Animation::Idle,
(true, true, false, false, false) => Animation::Run,
(false, _, false, false, false) => Animation::Jump,
(_, _, false, true, false) => Animation::Gliding,
(_, _, true, false, false) => Animation::Attack,
};
let last = animation_info.clone();
let changed = last.animation != animation;
*animation_info = AnimationInfo {
animation,
time: if changed { 0.0 } else { last.time },
changed,
};
} }
} }
} }

77
common/src/sys/combat.rs Normal file
View File

@ -0,0 +1,77 @@
use crate::{
comp::{
Attacking, HealthSource, Stats, {ForceUpdate, Ori, Pos, Vel},
},
state::{DeltaTime, Uid},
};
use log::warn;
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
/// This system is responsible for handling accepted inputs like moving or attacking
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Uid>,
Read<'a, DeltaTime>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Stats>,
WriteStorage<'a, ForceUpdate>,
);
fn run(
&mut self,
(
entities,
uids,
dt,
positions,
orientations,
mut velocities,
mut attackings,
mut stats,
mut force_updates,
): Self::SystemData,
) {
// Attacks
(&entities, &uids, &positions, &orientations, &mut attackings)
.join()
.filter_map(|(entity, uid, pos, ori, mut attacking)| {
if !attacking.applied {
// Go through all other entities
for (b, pos_b, mut vel_b, mut stat_b) in
(&entities, &positions, &mut velocities, &mut stats).join()
{
// Check if it is a hit
if entity != b
&& !stat_b.is_dead
&& pos.0.distance_squared(pos_b.0) < 50.0
&& ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Deal damage
stat_b.hp.change_by(-10, HealthSource::Attack { by: *uid }); // TODO: variable damage and weapon
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
let _ = force_updates.insert(b, ForceUpdate);
}
}
attacking.applied = true;
}
if attacking.time > 0.5 {
Some(entity)
} else {
attacking.time += dt.0;
None
}
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|e| {
attackings.remove(e);
});
}
}

View File

@ -0,0 +1,117 @@
use crate::{
comp::{
Animation, AnimationInfo, Attacking, Controller, Gliding, HealthSource, Jumping, MoveDir,
OnGround, Respawning, Rolling, Stats, {ForceUpdate, Ori, Pos, Vel},
},
state::DeltaTime,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
/// This system is responsible for validating controller inputs
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
ReadStorage<'a, Controller>,
ReadStorage<'a, Stats>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Vel>,
ReadStorage<'a, Ori>,
ReadStorage<'a, OnGround>,
WriteStorage<'a, MoveDir>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Gliding>,
);
fn run(
&mut self,
(
entities,
dt,
controllers,
stats,
positions,
velocities,
orientations,
on_grounds,
mut move_dirs,
mut jumpings,
mut attackings,
mut rollings,
mut respawns,
mut glidings,
): Self::SystemData,
) {
for (entity, controller, stats, pos, vel, ori, on_ground) in (
&entities,
&controllers,
&stats,
&positions,
&velocities,
&orientations,
on_grounds.maybe(),
)
.join()
{
if stats.is_dead {
// Respawn
if controller.respawn {
respawns.insert(entity, Respawning);
}
continue;
}
// Move dir
if rollings.get(entity).is_none() {
move_dirs.insert(
entity,
MoveDir(if controller.move_dir.magnitude() > 1.0 {
controller.move_dir.normalized()
} else {
controller.move_dir
}),
);
}
// Glide
if controller.glide
&& on_ground.is_none()
&& attackings.get(entity).is_none()
&& rollings.get(entity).is_none()
{
glidings.insert(entity, Gliding);
} else {
glidings.remove(entity);
}
// Attack
if controller.attack
&& attackings.get(entity).is_none()
&& glidings.get(entity).is_none()
&& rollings.get(entity).is_none()
{
attackings.insert(entity, Attacking::start());
}
// Jump
if controller.jump && on_ground.is_some() && vel.0.z <= 0.0 {
jumpings.insert(entity, Jumping);
}
// Roll
if controller.roll
&& rollings.get(entity).is_none()
&& attackings.get(entity).is_none()
&& glidings.get(entity).is_none()
&& on_ground.is_some()
&& vel.0.magnitude() > 5.0
{
rollings.insert(entity, Rolling::start());
}
}
}
}

View File

@ -1,174 +0,0 @@
use crate::{
comp::{
phys::{ForceUpdate, Ori, Pos, Vel},
Animation, AnimationInfo, Attacking, Control, Gliding, HealthSource, Jumping, Stats,
},
state::{DeltaTime, Uid},
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
use log::warn;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
// Basic ECS AI agent system
pub struct Sys;
const HUMANOID_ACCEL: f32 = 100.0;
const HUMANOID_SPEED: f32 = 500.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
const GLIDE_ACCEL: f32 = 15.0;
const GLIDE_SPEED: f32 = 45.0;
// Gravity is 9.81 * 4, so this makes gravity equal to .15
const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Uid>,
Read<'a, DeltaTime>,
ReadExpect<'a, TerrainMap>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
WriteStorage<'a, AnimationInfo>,
WriteStorage<'a, Stats>,
ReadStorage<'a, Control>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Gliding>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, ForceUpdate>,
);
fn run(
&mut self,
(
entities,
uids,
dt,
terrain,
positions,
mut velocities,
mut orientations,
mut animation_infos,
mut stats,
controls,
mut jumps,
glides,
mut attacks,
mut force_updates,
): Self::SystemData,
) {
for (entity, pos, control, stats, mut ori, mut vel) in (
&entities,
&positions,
&controls,
&stats,
&mut orientations,
&mut velocities,
)
.join()
{
// Disable while dead TODO: Replace with client states
if stats.is_dead {
continue;
}
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0;
let gliding = glides.get(entity).is_some() && vel.0.z < 0.0;
let move_dir = if control.move_dir.magnitude() > 1.0 {
control.move_dir.normalized()
} else {
control.move_dir
};
if on_ground {
// Move player according to move_dir
if vel.0.magnitude() < HUMANOID_SPEED {
vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_ACCEL;
}
// Jump
if jumps.get(entity).is_some() && vel.0.z <= 0.0 {
vel.0.z = HUMANOID_JUMP_ACCEL;
jumps.remove(entity);
}
} else if gliding && vel.0.magnitude() < GLIDE_SPEED {
let anti_grav = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2;
vel.0.z += dt.0 * anti_grav * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
vel.0 += Vec2::broadcast(dt.0) * move_dir * GLIDE_ACCEL;
} else if vel.0.magnitude() < HUMANOID_AIR_SPEED {
vel.0 += Vec2::broadcast(dt.0) * move_dir * HUMANOID_AIR_ACCEL;
}
// Set direction based on velocity
if vel.0.magnitude_squared() != 0.0 {
ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
}
let animation = if on_ground {
if control.move_dir.magnitude() > 0.01 {
Animation::Run
} else if attacks.get(entity).is_some() {
Animation::Attack
} else {
Animation::Idle
}
} else if glides.get(entity).is_some() {
Animation::Gliding
} else {
Animation::Jump
};
let last = animation_infos
.get_mut(entity)
.cloned()
.unwrap_or(AnimationInfo::default());
let changed = last.animation != animation;
if let Err(err) = animation_infos.insert(
entity,
AnimationInfo {
animation,
time: if changed { 0.0 } else { last.time },
changed,
},
) {
warn!("Inserting AnimationInfo for an entity failed: {:?}", err);
}
}
for (entity, &uid, pos, ori, attacking) in
(&entities, &uids, &positions, &orientations, &mut attacks).join()
{
if !attacking.applied {
for (b, pos_b, stat_b, mut vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
// Check if it is a hit
if entity != b
&& !stat_b.is_dead
&& pos.0.distance_squared(pos_b.0) < 50.0
&& ori.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Deal damage
stat_b.hp.change_by(-10, HealthSource::Attack { by: uid }); // TODO: variable damage and weapon
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
if let Err(err) = force_updates.insert(b, ForceUpdate) {
warn!("Inserting ForceUpdate for an entity failed: {:?}", err);
}
}
}
attacking.applied = true;
}
}
}
}

View File

@ -1,7 +1,7 @@
pub mod actions;
pub mod agent; pub mod agent;
pub mod animation; pub mod animation;
pub mod inputs; pub mod combat;
pub mod controller;
pub mod phys; pub mod phys;
mod stats; mod stats;
@ -10,17 +10,17 @@ use specs::DispatcherBuilder;
// System names // System names
const AGENT_SYS: &str = "agent_sys"; const AGENT_SYS: &str = "agent_sys";
const INPUTS_SYS: &str = "inputs_sys"; const CONTROLLER_SYS: &str = "controller_sys";
const ACTIONS_SYS: &str = "actions_sys";
const PHYS_SYS: &str = "phys_sys"; const PHYS_SYS: &str = "phys_sys";
const COMBAT_SYS: &str = "combat_sys";
const ANIMATION_SYS: &str = "animation_sys"; const ANIMATION_SYS: &str = "animation_sys";
const STATS_SYS: &str = "stats_sys"; const STATS_SYS: &str = "stats_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[]); dispatch_builder.add(phys::Sys, PHYS_SYS, &[]);
dispatch_builder.add(actions::Sys, ACTIONS_SYS, &[]); dispatch_builder.add(combat::Sys, COMBAT_SYS, &[]);
dispatch_builder.add(inputs::Sys, INPUTS_SYS, &[]);
dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]); dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[INPUTS_SYS]); dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
} }

View File

@ -1,21 +1,26 @@
use crate::{ use crate::{
comp::{ comp::{Gliding, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Stats, Vel},
phys::{Pos, Vel},
Stats,
},
state::DeltaTime, state::DeltaTime,
terrain::TerrainMap, terrain::TerrainMap,
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
use specs::{Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*; use vek::*;
// Basic ECS physics system
pub struct Sys;
const GRAVITY: f32 = 9.81 * 4.0; const GRAVITY: f32 = 9.81 * 4.0;
const FRIC_GROUND: f32 = 0.15; const FRIC_GROUND: f32 = 0.15;
const FRIC_AIR: f32 = 0.015; const FRIC_AIR: f32 = 0.015;
const HUMANOID_ACCEL: f32 = 70.0;
const HUMANOID_SPEED: f32 = 120.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
const ROLL_ACCEL: f32 = 160.0;
const ROLL_SPEED: f32 = 550.0;
const GLIDE_ACCEL: f32 = 15.0;
const GLIDE_SPEED: f32 = 45.0;
// Gravity is 9.81 * 4, so this makes gravity equal to .15
const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95;
// Integrates forces, calculates the new velocity based off of the old velocity // Integrates forces, calculates the new velocity based off of the old velocity
// dt = delta time // dt = delta time
@ -38,34 +43,128 @@ fn integrate_forces(dt: f32, mut lv: Vec3<f32>, damp: f32) -> Vec3<f32> {
lv lv
} }
/// This system applies forces and calculates new positions and velocities.
pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>,
ReadExpect<'a, TerrainMap>, ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>, Read<'a, DeltaTime>,
ReadStorage<'a, MoveDir>,
ReadStorage<'a, Gliding>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, OnGround>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
); );
fn run(&mut self, (terrain, dt, stats, mut positions, mut velocities): Self::SystemData) { fn run(
for (stats, pos, vel) in (&stats, &mut positions, &mut velocities).join() { &mut self,
// Disable while dead TODO: Replace with client states (
entities,
terrain,
dt,
move_dirs,
glidings,
stats,
mut jumpings,
mut rollings,
mut on_grounds,
mut positions,
mut velocities,
mut orientations,
): Self::SystemData,
) {
// Apply movement inputs
for (entity, stats, move_dir, gliding, mut pos, mut vel, mut ori) in (
&entities,
&stats,
move_dirs.maybe(),
glidings.maybe(),
&mut positions,
&mut velocities,
&mut orientations,
)
.join()
{
// Disable while dead TODO: Replace with client states?
if stats.is_dead { if stats.is_dead {
continue; continue;
} }
let on_ground = terrain // Move player according to move_dir
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) if let Some(move_dir) = move_dir {
.map(|vox| !vox.is_empty()) vel.0 += Vec2::broadcast(dt.0)
.unwrap_or(false) * move_dir.0
&& vel.0.z <= 0.0; * match (
on_grounds.get(entity).is_some(),
glidings.get(entity).is_some(),
rollings.get(entity).is_some(),
) {
(true, false, false) if vel.0.magnitude() < HUMANOID_SPEED => {
HUMANOID_ACCEL
}
(false, true, false) if vel.0.magnitude() < GLIDE_SPEED => GLIDE_ACCEL,
(false, false, false) if vel.0.magnitude() < HUMANOID_AIR_SPEED => {
HUMANOID_AIR_ACCEL
}
(true, false, true) if vel.0.magnitude() < ROLL_SPEED => ROLL_ACCEL,
_ => 0.0,
};
}
// Jump
if jumpings.get(entity).is_some() {
vel.0.z = HUMANOID_JUMP_ACCEL;
jumpings.remove(entity);
}
// Glide
if gliding.is_some() && vel.0.magnitude() < GLIDE_SPEED && vel.0.z < 0.0 {
let lift = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2;
vel.0.z += dt.0 * lift * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
}
// Roll
if let Some(time) = rollings.get_mut(entity).map(|r| &mut r.time) {
*time += dt.0;
if *time > 0.55 {
rollings.remove(entity);
}
}
// Set direction based on velocity
if vel.0.magnitude_squared() != 0.0 {
ori.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
}
// Movement // Movement
pos.0 += vel.0 * dt.0; pos.0 += vel.0 * dt.0;
// Update OnGround component
if terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
.unwrap_or(false)
&& vel.0.z <= 0.0
{
on_grounds.insert(entity, OnGround);
} else {
on_grounds.remove(entity);
}
// Integrate forces // Integrate forces
// Friction is assumed to be a constant dependent on location // Friction is assumed to be a constant dependent on location
let friction = 50.0 * if on_ground { FRIC_GROUND } else { FRIC_AIR }; let friction = 50.0
* if on_grounds.get(entity).is_some() {
FRIC_GROUND
} else {
FRIC_AIR
};
vel.0 = integrate_forces(dt.0, vel.0, friction); vel.0 = integrate_forces(dt.0, vel.0, friction);
// Basic collision with terrain // Basic collision with terrain

View File

@ -5,9 +5,8 @@ use crate::{
use log::warn; use log::warn;
use specs::{Entities, Join, Read, System, WriteStorage}; use specs::{Entities, Join, Read, System, WriteStorage};
// Basic ECS AI agent system /// This system kills players
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Entities<'a>, Entities<'a>,

View File

@ -100,18 +100,12 @@ fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32);
match (opt_x, opt_y, opt_z) { match (opt_x, opt_y, opt_z) {
(Some(x), Some(y), Some(z)) => { (Some(x), Some(y), Some(z)) => {
match server match server.state.read_component_cloned::<comp::Pos>(entity) {
.state
.read_component_cloned::<comp::phys::Pos>(entity)
{
Some(current_pos) => { Some(current_pos) => {
server.state.write_component(
entity,
comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z)),
);
server server
.state .state
.write_component(entity, comp::phys::ForceUpdate); .write_component(entity, comp::Pos(current_pos.0 + Vec3::new(x, y, z)));
server.state.write_component(entity, comp::ForceUpdate);
} }
None => server.clients.notify( None => server.clients.notify(
entity, entity,
@ -131,10 +125,8 @@ fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
(Some(x), Some(y), Some(z)) => { (Some(x), Some(y), Some(z)) => {
server server
.state .state
.write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))); .write_component(entity, comp::Pos(Vec3::new(x, y, z)));
server server.state.write_component(entity, comp::ForceUpdate);
.state
.write_component(entity, comp::phys::ForceUpdate);
} }
_ => server _ => server
.clients .clients
@ -173,20 +165,15 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
match opt_alias { match opt_alias {
Some(alias) => { Some(alias) => {
let ecs = server.state.ecs(); let ecs = server.state.ecs();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::player::Player>()) let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join() .join()
.find(|(_, player)| player.alias == alias) .find(|(_, player)| player.alias == alias)
.map(|(entity, _)| entity); .map(|(entity, _)| entity);
match opt_player { match opt_player {
Some(player) => match server Some(player) => match server.state.read_component_cloned::<comp::Pos>(player) {
.state
.read_component_cloned::<comp::phys::Pos>(player)
{
Some(pos) => { Some(pos) => {
server.state.write_component(entity, pos); server.state.write_component(entity, pos);
server server.state.write_component(entity, comp::ForceUpdate);
.state
.write_component(entity, comp::phys::ForceUpdate);
} }
None => server.clients.notify( None => server.clients.notify(
entity, entity,
@ -222,10 +209,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
match (opt_agent, opt_id, opt_amount) { match (opt_agent, opt_id, opt_amount) {
(Some(agent), Some(id), Some(amount)) => { (Some(agent), Some(id), Some(amount)) => {
match server match server.state.read_component_cloned::<comp::Pos>(entity) {
.state
.read_component_cloned::<comp::phys::Pos>(entity)
{
Some(mut pos) => { Some(mut pos) => {
pos.0.x += 1.0; // Temp fix TODO: Solve NaN issue with positions of pets pos.0.x += 1.0; // Temp fix TODO: Solve NaN issue with positions of pets
let body = kind_to_body(id); let body = kind_to_body(id);

View File

@ -135,7 +135,7 @@ impl Server {
#[allow(dead_code)] #[allow(dead_code)]
pub fn create_npc( pub fn create_npc(
&mut self, &mut self,
pos: comp::phys::Pos, pos: comp::Pos,
name: String, name: String,
body: comp::Body, body: comp::Body,
) -> EcsEntityBuilder { ) -> EcsEntityBuilder {
@ -143,13 +143,13 @@ impl Server {
.ecs_mut() .ecs_mut()
.create_entity_synced() .create_entity_synced()
.with(pos) .with(pos)
.with(comp::phys::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::phys::Ori(Vec3::unit_y())) .with(comp::Ori(Vec3::unit_y()))
.with(comp::Control::default()) .with(comp::Controller::default())
.with(comp::AnimationInfo::default()) .with(comp::AnimationInfo::default())
.with(comp::Actor::Character { name, body }) .with(comp::Actor::Character { name, body })
.with(comp::Stats::default()) .with(comp::Stats::default())
.with(comp::phys::ForceUpdate) .with(comp::ForceUpdate)
} }
pub fn create_player_character( pub fn create_player_character(
@ -164,11 +164,12 @@ 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::AnimationInfo::default()); state.write_component(entity, comp::AnimationInfo::default());
state.write_component(entity, comp::phys::Pos(spawn_point)); state.write_component(entity, comp::Controller::default());
state.write_component(entity, comp::phys::Vel(Vec3::zero())); state.write_component(entity, comp::Pos(spawn_point));
state.write_component(entity, comp::phys::Ori(Vec3::unit_y())); state.write_component(entity, comp::Vel(Vec3::zero()));
state.write_component(entity, comp::Ori(Vec3::unit_y()));
// Make sure physics are accepted. // Make sure physics are accepted.
state.write_component(entity, comp::phys::ForceUpdate); state.write_component(entity, comp::ForceUpdate);
// Tell the client its request was successful. // Tell the client its request was successful.
client.allow_state(ClientState::Character); client.allow_state(ClientState::Character);
@ -245,9 +246,8 @@ impl Server {
// Actually kill them // Actually kill them
for entity in todo_kill { for entity in todo_kill {
if let Some(client) = self.clients.get_mut(&entity) { if let Some(client) = self.clients.get_mut(&entity) {
self.state self.state.write_component(entity, comp::Vel(Vec3::zero()));
.write_component(entity, comp::phys::Vel(Vec3::zero())); self.state.write_component(entity, comp::ForceUpdate);
self.state.write_component(entity, comp::phys::ForceUpdate);
client.force_state(ClientState::Dead); client.force_state(ClientState::Dead);
} else { } else {
if let Err(err) = self.state.ecs_mut().delete_entity_synced(entity) { if let Err(err) = self.state.ecs_mut().delete_entity_synced(entity) {
@ -272,12 +272,11 @@ impl Server {
self.state.write_component(entity, comp::Stats::default()); self.state.write_component(entity, comp::Stats::default());
self.state self.state
.ecs_mut() .ecs_mut()
.write_storage::<comp::phys::Pos>() .write_storage::<comp::Pos>()
.get_mut(entity) .get_mut(entity)
.map(|pos| pos.0.z += 100.0); .map(|pos| pos.0.z += 100.0);
self.state self.state.write_component(entity, comp::Vel(Vec3::zero()));
.write_component(entity, comp::phys::Vel(Vec3::zero())); self.state.write_component(entity, comp::ForceUpdate);
self.state.write_component(entity, comp::phys::ForceUpdate);
} }
} }
@ -288,7 +287,7 @@ impl Server {
for (entity, view_distance, pos) in ( for (entity, view_distance, pos) in (
&self.state.ecs().entities(), &self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Player>(), &self.state.ecs().read_storage::<comp::Player>(),
&self.state.ecs().read_storage::<comp::phys::Pos>(), &self.state.ecs().read_storage::<comp::Pos>(),
) )
.join() .join()
.filter_map(|(entity, player, pos)| { .filter_map(|(entity, player, pos)| {
@ -323,7 +322,7 @@ impl Server {
// For each player with a position, calculate the distance. // For each player with a position, calculate the distance.
for (player, pos) in ( for (player, pos) in (
&self.state.ecs().read_storage::<comp::Player>(), &self.state.ecs().read_storage::<comp::Player>(),
&self.state.ecs().read_storage::<comp::phys::Pos>(), &self.state.ecs().read_storage::<comp::Pos>(),
) )
.join() .join()
{ {
@ -492,24 +491,16 @@ impl Server {
} }
ClientState::Pending => {} ClientState::Pending => {}
}, },
ClientMsg::Attack => match client.client_state { ClientMsg::Controller(controller) => match client.client_state {
ClientState::Character => { ClientState::Connected
if state | ClientState::Registered
.ecs() | ClientState::Spectator => {
.read_storage::<comp::Attacking>() client.error_state(RequestStateError::Impossible)
.get(entity)
.is_none()
{
state.write_component(entity, comp::Attacking::start());
}
} }
_ => client.error_state(RequestStateError::Impossible), ClientState::Dead | ClientState::Character => {
}, state.write_component(entity, controller);
ClientMsg::Respawn => match client.client_state {
ClientState::Dead => {
state.write_component(entity, comp::Respawning);
} }
_ => client.error_state(RequestStateError::Impossible), ClientState::Pending => {}
}, },
ClientMsg::Chat(msg) => match client.client_state { ClientMsg::Chat(msg) => match client.client_state {
ClientState::Connected => { ClientState::Connected => {
@ -521,15 +512,6 @@ impl Server {
| ClientState::Character => new_chat_msgs.push((Some(entity), msg)), | ClientState::Character => new_chat_msgs.push((Some(entity), msg)),
ClientState::Pending => {} ClientState::Pending => {}
}, },
ClientMsg::PlayerAnimation(animation_info) => {
match client.client_state {
ClientState::Character => {
state.write_component(entity, animation_info)
}
// Only characters can send animations.
_ => client.error_state(RequestStateError::Impossible),
}
}
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state { ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
ClientState::Character => { ClientState::Character => {
state.write_component(entity, pos); state.write_component(entity, pos);
@ -639,9 +621,9 @@ impl Server {
// Sync physics // Sync physics
for (&uid, &pos, &vel, &ori) in ( for (&uid, &pos, &vel, &ori) in (
&state.ecs().read_storage::<Uid>(), &state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::phys::Pos>(), &state.ecs().read_storage::<comp::Pos>(),
&state.ecs().read_storage::<comp::phys::Vel>(), &state.ecs().read_storage::<comp::Vel>(),
&state.ecs().read_storage::<comp::phys::Ori>(), &state.ecs().read_storage::<comp::Ori>(),
) )
.join() .join()
{ {
@ -680,13 +662,10 @@ impl Server {
for (entity, &uid, &pos, &vel, &ori, force_update) in ( for (entity, &uid, &pos, &vel, &ori, force_update) in (
&self.state.ecs().entities(), &self.state.ecs().entities(),
&self.state.ecs().read_storage::<Uid>(), &self.state.ecs().read_storage::<Uid>(),
&self.state.ecs().read_storage::<comp::phys::Pos>(), &self.state.ecs().read_storage::<comp::Pos>(),
&self.state.ecs().read_storage::<comp::phys::Vel>(), &self.state.ecs().read_storage::<comp::Vel>(),
&self.state.ecs().read_storage::<comp::phys::Ori>(), &self.state.ecs().read_storage::<comp::Ori>(),
self.state self.state.ecs().read_storage::<comp::ForceUpdate>().maybe(),
.ecs()
.read_storage::<comp::phys::ForceUpdate>()
.maybe(),
) )
.join() .join()
{ {
@ -702,7 +681,7 @@ impl Server {
let in_vd = |entity| { let in_vd = |entity| {
// Get client position. // Get client position.
let client_pos = match state.ecs().read_storage::<comp::phys::Pos>().get(entity) { let client_pos = match state.ecs().read_storage::<comp::Pos>().get(entity) {
Some(pos) => pos.0, Some(pos) => pos.0,
None => return false, None => return false,
}; };
@ -733,10 +712,7 @@ impl Server {
&self.state.ecs().entities(), &self.state.ecs().entities(),
&self.state.ecs().read_storage::<Uid>(), &self.state.ecs().read_storage::<Uid>(),
&self.state.ecs().read_storage::<comp::AnimationInfo>(), &self.state.ecs().read_storage::<comp::AnimationInfo>(),
self.state self.state.ecs().read_storage::<comp::ForceUpdate>().maybe(),
.ecs()
.read_storage::<comp::phys::ForceUpdate>()
.maybe(),
) )
.join() .join()
{ {
@ -755,7 +731,7 @@ impl Server {
// Remove all force flags. // Remove all force flags.
self.state self.state
.ecs_mut() .ecs_mut()
.write_storage::<comp::phys::ForceUpdate>() .write_storage::<comp::ForceUpdate>()
.clear(); .clear();
} }

View File

@ -46,11 +46,11 @@ impl Animation for AttackAnimation {
-8.0 + wave_quicken_slow * 10.0, -8.0 + wave_quicken_slow * 10.0,
4.0 + wave_quicken_double * 3.0, 4.0 + wave_quicken_double * 3.0,
9.0, 9.0,
) / 11.0; );
next.l_hand.ori = Quaternion::rotation_z(-0.8) next.l_hand.ori = Quaternion::rotation_z(-0.8)
* Quaternion::rotation_x(0.0 + wave_quicken * -0.8) * Quaternion::rotation_x(0.0 + wave_quicken * -0.8)
* Quaternion::rotation_y(0.0 + wave_quicken * -0.4); * Quaternion::rotation_y(0.0 + wave_quicken * -0.4);
next.l_hand.scale = Vec3::one() / 11.0; next.l_hand.scale = Vec3::one();
next.r_hand.offset = Vec3::new(0.0, -2.0, 6.5) / 11.0; next.r_hand.offset = Vec3::new(0.0, -2.0, 6.5) / 11.0;
next.r_hand.ori = Quaternion::rotation_x(0.0); next.r_hand.ori = Quaternion::rotation_x(0.0);

View File

@ -0,0 +1,104 @@
use super::{super::Animation, CharacterSkeleton};
use std::{f32::consts::PI, ops::Mul};
use vek::*;
pub struct CidleAnimation;
impl Animation for CidleAnimation {
type Skeleton = CharacterSkeleton;
type Dependency = f64;
fn update_skeleton(
skeleton: &Self::Skeleton,
global_time: f64,
anim_time: f64,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let wave_ultra_slow = (anim_time as f32 * 1.0 + PI).sin();
let wave_ultra_slow_cos = (anim_time as f32 * 1.0 + PI).cos();
let head_look = Vec2::new(
((global_time + anim_time) as f32 / 8.0)
.floor()
.mul(7331.0)
.sin()
* 0.5,
((global_time + anim_time) as f32 / 8.0)
.floor()
.mul(1337.0)
.sin()
* 0.25,
);
next.head.offset = Vec3::new(0.0, 2.0, 11.0 + wave_ultra_slow * 0.3);
next.head.ori = Quaternion::rotation_z(head_look.x) * Quaternion::rotation_x(head_look.y);
next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_ultra_slow * 0.3);
next.chest.ori = Quaternion::rotation_x(0.0);
next.chest.scale = Vec3::one();
next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + wave_ultra_slow * 0.3);
next.belt.ori = Quaternion::rotation_x(0.0);
next.belt.scale = Vec3::one();
next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + wave_ultra_slow * 0.3);
next.shorts.ori = Quaternion::rotation_x(0.0);
next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(
-7.5,
-2.0 + wave_ultra_slow_cos * 0.15,
8.0 + wave_ultra_slow * 0.5,
) / 11.0;
next.l_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * 0.06);
next.l_hand.scale = Vec3::one() / 11.0;
next.r_hand.offset = Vec3::new(
7.5,
-2.0 + wave_ultra_slow_cos * 0.15,
8.0 + wave_ultra_slow * 0.5,
) / 11.0;
next.r_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * 0.06);
next.r_hand.scale = Vec3::one() / 11.;
next.l_foot.offset = Vec3::new(-3.4, -0.1, 8.0);
next.l_foot.ori = Quaternion::identity();
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, -0.1, 8.0);
next.r_foot.ori = Quaternion::identity();
next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-7.0, -5.0, 15.0);
next.weapon.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
next.weapon.scale = Vec3::one() * 0.0;
next.l_shoulder.offset = Vec3::new(-10.0, -3.2, 2.5);
next.l_shoulder.ori = Quaternion::rotation_x(0.0);
next.l_shoulder.scale = Vec3::one() * 1.04;
next.r_shoulder.offset = Vec3::new(0.0, -3.2, 2.5);
next.r_shoulder.ori = Quaternion::rotation_x(0.0);
next.r_shoulder.scale = Vec3::one() * 1.04;
next.draw.offset = Vec3::new(0.0, 5.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next.left_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.left_equip.ori = Quaternion::rotation_x(0.0);;
next.left_equip.scale = Vec3::one() * 0.0;
next.right_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.right_equip.ori = Quaternion::rotation_x(0.0);;
next.right_equip.scale = Vec3::one() * 0.0;
next.torso.offset = Vec3::new(0.0, -0.2, 0.1);
next.torso.ori = Quaternion::rotation_x(0.0);
next.torso.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -0,0 +1,97 @@
use super::{super::Animation, CharacterSkeleton};
use std::ops::Mul;
use vek::*;
pub struct CrunAnimation;
impl Animation for CrunAnimation {
type Skeleton = CharacterSkeleton;
type Dependency = (f32, f64);
fn update_skeleton(
skeleton: &Self::Skeleton,
(velocity, global_time): Self::Dependency,
anim_time: f64,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let wave = (anim_time as f32 * 14.0).sin();
let wave_cos = (anim_time as f32 * 14.0).cos();
let head_look = Vec2::new(
((global_time + anim_time) as f32 / 2.0)
.floor()
.mul(7331.0)
.sin()
* 0.2,
((global_time + anim_time) as f32 / 2.0)
.floor()
.mul(1337.0)
.sin()
* 0.1,
);
next.head.offset = Vec3::new(0.0, 3.0, 12.0 + wave_cos * 1.3);
next.head.ori = Quaternion::rotation_z(head_look.x + wave * 0.1)
* Quaternion::rotation_x(head_look.y + 0.35);
next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_cos * 1.1);
next.chest.ori = Quaternion::rotation_z(wave * 0.1);
next.chest.scale = Vec3::one();
next.belt.offset = Vec3::new(0.0, 0.0, 5.0 + wave_cos * 1.1);
next.belt.ori = Quaternion::rotation_z(wave * 0.25);
next.belt.scale = Vec3::one();
next.shorts.offset = Vec3::new(0.0, 0.0, 2.0 + wave_cos * 1.1);
next.shorts.ori = Quaternion::rotation_z(wave * 0.6);
next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(-9.0, 3.0 + wave_cos * 8.0, 12.0 - wave * 1.0) / 11.0;
next.l_hand.ori = Quaternion::rotation_x(wave_cos * 1.1);
next.l_hand.scale = Vec3::one() / 11.0;
next.r_hand.offset = Vec3::new(9.0, 3.0 - wave_cos * 8.0, 12.0 + wave * 1.0) / 11.0;
next.r_hand.ori = Quaternion::rotation_x(wave_cos * -1.1);
next.r_hand.scale = Vec3::one() / 11.0;
next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave_cos * 1.0, 6.0);
next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave_cos * 1.5);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, 0.0 - wave_cos * 1.0, 6.0);
next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.5);
next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-7.0, -5.0, 15.0);
next.weapon.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
next.weapon.scale = Vec3::one() * 0.0;
next.l_shoulder.offset = Vec3::new(-10.0, -3.2, 2.5);
next.l_shoulder.ori = Quaternion::rotation_x(0.0);
next.l_shoulder.scale = Vec3::one() * 1.04;
next.r_shoulder.offset = Vec3::new(0.0, -3.2, 2.5);
next.r_shoulder.ori = Quaternion::rotation_x(0.0);
next.r_shoulder.scale = Vec3::one() * 1.04;
next.draw.offset = Vec3::new(0.0, 5.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next.left_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.left_equip.ori = Quaternion::rotation_x(0.0);;
next.left_equip.scale = Vec3::one() * 0.0;
next.right_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.right_equip.ori = Quaternion::rotation_x(0.0);;
next.right_equip.scale = Vec3::one() * 0.0;
next.torso.offset = Vec3::new(0.0, -0.2, 0.4);
next.torso.ori = Quaternion::rotation_x(-velocity * 0.04 - wave_cos * 0.10);
next.torso.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -6,11 +6,11 @@ pub struct GlidingAnimation;
impl Animation for GlidingAnimation { impl Animation for GlidingAnimation {
type Skeleton = CharacterSkeleton; type Skeleton = CharacterSkeleton;
type Dependency = f64; type Dependency = (f32, f64);
fn update_skeleton( fn update_skeleton(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
global_time: f64, (velocity, global_time): Self::Dependency,
anim_time: f64, anim_time: f64,
) -> Self::Skeleton { ) -> Self::Skeleton {
let mut next = (*skeleton).clone(); let mut next = (*skeleton).clone();
@ -35,48 +35,40 @@ impl Animation for GlidingAnimation {
.sin() .sin()
* 0.25, * 0.25,
); );
next.head.offset = Vec3::new(0.0, 2.0, 12.0); next.head.offset = Vec3::new(0.0, 2.0, 2.0);
next.head.ori = Quaternion::rotation_x(0.35 - wave_very_slow * 0.10 + head_look.y) next.head.ori = Quaternion::rotation_x(0.35 - wave_very_slow * 0.10 + head_look.y)
* Quaternion::rotation_z(head_look.x + wave_very_slow_cos * 0.15); * Quaternion::rotation_z(head_look.x + wave_very_slow_cos * 0.15);
next.head.scale = Vec3::one(); next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(0.0, 0.0, 8.0); next.chest.offset = Vec3::new(0.0, 0.0, -2.0);
next.chest.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.15); next.chest.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.15);
next.chest.scale = Vec3::one(); next.chest.scale = Vec3::one();
next.belt.offset = Vec3::new(0.0, 0.0, 6.0); next.belt.offset = Vec3::new(0.0, 0.0, -4.0);
next.belt.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.20); next.belt.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.20);
next.belt.scale = Vec3::one(); next.belt.scale = Vec3::one();
next.shorts.offset = Vec3::new(0.0, 0.0, 3.0); next.shorts.offset = Vec3::new(0.0, 0.0, -7.0);
next.shorts.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.25); next.shorts.ori = Quaternion::rotation_z(wave_very_slow_cos * 0.25);
next.shorts.scale = Vec3::one(); next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new( next.l_hand.offset = Vec3::new(-10.0, -2.0 + wave_very_slow * 0.10, 10.5);
-10.0, next.l_hand.ori = Quaternion::rotation_x(1.0 + wave_very_slow_cos * -0.10);
6.0 - wave_very_slow * 1.50, next.l_hand.scale = Vec3::one();
15.0 + wave_very_slow * 0.50,
) / 11.0;
next.l_hand.ori = Quaternion::rotation_x(0.2 + wave_very_slow_cos * 0.05);
next.l_hand.scale = Vec3::one() / 11.0;
next.r_hand.offset = Vec3::new( next.r_hand.offset = Vec3::new(10.0, -2.0 + wave_very_slow * 0.10, 10.5);
10.0, next.r_hand.ori = Quaternion::rotation_x(1.0 + wave_very_slow_cos * -0.10);
6.0 - wave_very_slow * 1.50, next.r_hand.scale = Vec3::one();
14.5 + wave_very_slow * 0.50,
) / 11.0;
next.r_hand.ori = Quaternion::rotation_x(0.1 + wave_very_slow * 0.05);
next.r_hand.scale = Vec3::one() / 11.0;
next.l_foot.offset = Vec3::new(-3.4, 1.0, 8.0); next.l_foot.offset = Vec3::new(-3.4, 1.0, -2.0);
next.l_foot.ori = Quaternion::rotation_x( next.l_foot.ori = Quaternion::rotation_x(
wave_stop * -0.7 - wave_slow_cos * -0.21 + wave_very_slow * 0.19, (wave_stop * -0.7 - wave_slow_cos * -0.21 + wave_very_slow * 0.19) * velocity * 0.07,
); );
next.l_foot.scale = Vec3::one(); next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, 1.0, 8.0); next.r_foot.offset = Vec3::new(3.4, 1.0, -2.0);
next.r_foot.ori = Quaternion::rotation_x( next.r_foot.ori = Quaternion::rotation_x(
wave_stop * -0.8 + wave_slow * -0.25 + wave_very_slow_alt * 0.13, (wave_stop * -0.8 + wave_slow * -0.25 + wave_very_slow_alt * 0.13) * velocity * 0.07,
); );
next.r_foot.scale = Vec3::one(); next.r_foot.scale = Vec3::one();
@ -92,21 +84,21 @@ impl Animation for GlidingAnimation {
next.r_shoulder.ori = Quaternion::rotation_x(0.0); next.r_shoulder.ori = Quaternion::rotation_x(0.0);
next.r_shoulder.scale = Vec3::one() * 1.04; next.r_shoulder.scale = Vec3::one() * 1.04;
next.draw.offset = Vec3::new(0.0, -9.0 + wave_very_slow * 0.10, 18.0); next.draw.offset = Vec3::new(0.0, -9.0 + wave_very_slow * 0.10, 8.0);
next.draw.ori = Quaternion::rotation_x(0.95 - wave_very_slow * 0.15) next.draw.ori = Quaternion::rotation_x(1.0)//0.95 - wave_very_slow * 0.08)
* Quaternion::rotation_y(wave_very_slow_cos * 0.04); * Quaternion::rotation_y(wave_very_slow_cos * 0.04);
next.draw.scale = Vec3::one(); next.draw.scale = Vec3::one();
next.left_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0; next.left_equip.offset = Vec3::new(0.0, 0.0, -5.0) / 11.0;
next.left_equip.ori = Quaternion::rotation_x(0.0);; next.left_equip.ori = Quaternion::rotation_x(0.0);;
next.left_equip.scale = Vec3::one() * 0.0; next.left_equip.scale = Vec3::one() * 0.0;
next.right_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0; next.right_equip.offset = Vec3::new(0.0, 0.0, -5.0) / 11.0;
next.right_equip.ori = Quaternion::rotation_x(0.0);; next.right_equip.ori = Quaternion::rotation_x(0.0);;
next.right_equip.scale = Vec3::one() * 0.0; next.right_equip.scale = Vec3::one() * 0.0;
next.torso.offset = Vec3::new(0.0, -0.2, 0.0); next.torso.offset = Vec3::new(0.0, -0.2, 10.0) / 11.0;
next.torso.ori = Quaternion::rotation_x(-0.8 + wave_very_slow * 0.10); next.torso.ori = Quaternion::rotation_x(-0.05 * velocity + wave_very_slow * 0.10);
next.torso.scale = Vec3::one() / 11.0; next.torso.scale = Vec3::one() / 11.0;
next next

View File

@ -51,20 +51,20 @@ impl Animation for IdleAnimation {
next.l_hand.offset = Vec3::new( next.l_hand.offset = Vec3::new(
-7.5, -7.5,
-2.0 + wave_ultra_slow_cos * 0.15, 0.0 + wave_ultra_slow_cos * 0.15,
8.0 + wave_ultra_slow * 0.5, 7.0 + wave_ultra_slow * 0.5,
) / 11.0; );
next.l_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * 0.06); next.l_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * -0.06);
next.l_hand.scale = Vec3::one() / 11.0; next.l_hand.scale = Vec3::one();
next.r_hand.offset = Vec3::new( next.r_hand.offset = Vec3::new(
7.5, 7.5,
-2.0 + wave_ultra_slow_cos * 0.15, 0.0 + wave_ultra_slow_cos * 0.15,
8.0 + wave_ultra_slow * 0.5, 7.0 + wave_ultra_slow * 0.5,
) / 11.0; );
next.r_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * 0.06); next.r_hand.ori = Quaternion::rotation_x(0.0 + wave_ultra_slow * -0.06);
next.r_hand.scale = Vec3::one() / 11.; next.r_hand.scale = Vec3::one();
next.l_foot.offset = Vec3::new(-3.4, -0.1, 8.0); next.l_foot.offset = Vec3::new(-3.4, -0.1, 8.0);
next.l_foot.ori = Quaternion::identity(); next.l_foot.ori = Quaternion::identity();

View File

@ -39,17 +39,17 @@ impl Animation for JumpAnimation {
-8.0, -8.0,
0.0 + wave_stop * 3.8, 0.0 + wave_stop * 3.8,
7.0 + wave_stop * 3.2 - wave * 0.4, 7.0 + wave_stop * 3.2 - wave * 0.4,
) / 11.0; );
next.l_hand.ori = Quaternion::rotation_x(wave_stop_alt * 0.6); next.l_hand.ori = Quaternion::rotation_x(wave_stop_alt * 0.6);
next.l_hand.scale = Vec3::one() / 11.0; next.l_hand.scale = Vec3::one();
next.r_hand.offset = Vec3::new( next.r_hand.offset = Vec3::new(
8.0, 8.0,
0.0 + wave_stop * -3.8, 0.0 + wave_stop * -3.8,
7.0 + wave_stop * 3.2 - wave * 0.4, 7.0 + wave_stop * 3.2 - wave * 0.4,
) / 11.0; );
next.r_hand.ori = Quaternion::rotation_x(-wave_stop_alt * 0.6); next.r_hand.ori = Quaternion::rotation_x(-wave_stop_alt * 0.6);
next.r_hand.scale = Vec3::one() / 11.0; next.r_hand.scale = Vec3::one();
next.l_foot.offset = Vec3::new(-3.4, 1.0, 6.0); next.l_foot.offset = Vec3::new(-3.4, 1.0, 6.0);
next.l_foot.ori = Quaternion::rotation_x(wave_stop * -1.2 - wave_slow * 0.2); next.l_foot.ori = Quaternion::rotation_x(wave_stop * -1.2 - wave_slow * 0.2);

View File

@ -1,14 +1,20 @@
pub mod attack; pub mod attack;
pub mod cidle;
pub mod crun;
pub mod gliding; pub mod gliding;
pub mod idle; pub mod idle;
pub mod jump; pub mod jump;
pub mod roll;
pub mod run; pub mod run;
// Reexports // Reexports
pub use self::attack::AttackAnimation; pub use self::attack::AttackAnimation;
pub use self::cidle::CidleAnimation;
pub use self::crun::CrunAnimation;
pub use self::gliding::GlidingAnimation; pub use self::gliding::GlidingAnimation;
pub use self::idle::IdleAnimation; pub use self::idle::IdleAnimation;
pub use self::jump::JumpAnimation; pub use self::jump::JumpAnimation;
pub use self::roll::RollAnimation;
pub use self::run::RunAnimation; pub use self::run::RunAnimation;
use super::{Bone, Skeleton}; use super::{Bone, Skeleton};
@ -68,8 +74,8 @@ impl Skeleton for CharacterSkeleton {
FigureBoneData::new(torso_mat * chest_mat), FigureBoneData::new(torso_mat * chest_mat),
FigureBoneData::new(torso_mat * self.belt.compute_base_matrix()), FigureBoneData::new(torso_mat * self.belt.compute_base_matrix()),
FigureBoneData::new(torso_mat * self.shorts.compute_base_matrix()), FigureBoneData::new(torso_mat * self.shorts.compute_base_matrix()),
FigureBoneData::new(l_hand_mat), FigureBoneData::new(torso_mat * l_hand_mat),
FigureBoneData::new(self.r_hand.compute_base_matrix()), FigureBoneData::new(torso_mat * self.r_hand.compute_base_matrix()),
FigureBoneData::new(torso_mat * self.l_foot.compute_base_matrix()), FigureBoneData::new(torso_mat * self.l_foot.compute_base_matrix()),
FigureBoneData::new(torso_mat * self.r_foot.compute_base_matrix()), FigureBoneData::new(torso_mat * self.r_foot.compute_base_matrix()),
FigureBoneData::new(torso_mat * chest_mat * weapon_mat), FigureBoneData::new(torso_mat * chest_mat * weapon_mat),

View File

@ -0,0 +1,98 @@
use super::{super::Animation, CharacterSkeleton};
use std::{f32::consts::PI, ops::Mul};
use vek::*;
pub struct RollAnimation;
impl Animation for RollAnimation {
type Skeleton = CharacterSkeleton;
type Dependency = f64;
fn update_skeleton(
skeleton: &Self::Skeleton,
global_time: f64,
anim_time: f64,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let wave = (anim_time as f32 * 5.5).sin();
let wave_quick = (anim_time as f32 * 9.5).sin();
let wave_quick_cos = (anim_time as f32 * 9.5).cos();
let wave_cos = (anim_time as f32 * 5.5).cos();
let wave_slow = (anim_time as f32 * 2.8 + PI).sin();
let wave_dub = (anim_time as f32 * 5.5).sin();
next.head.offset = Vec3::new(0.0, 0.0 + wave_slow * -3.0, 9.0 + wave_dub * -5.0);
next.head.ori = Quaternion::rotation_x(wave_dub * -0.4);
next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_dub * -2.5);
next.chest.ori = Quaternion::rotation_x(wave_dub * -0.5);
next.chest.scale = Vec3::one() * 1.01;
next.belt.offset = Vec3::new(0.0, 0.0, 5.0);
next.belt.ori = Quaternion::rotation_x(0.0);
next.belt.scale = Vec3::one();
next.shorts.offset = Vec3::new(0.0, 0.0, 2.0);
next.shorts.ori = Quaternion::rotation_x(0.0);
next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(
-5.5 + wave * -0.5,
-2.0 + wave_quick_cos * -5.5,
8.0 + wave_quick * 0.5,
);
next.l_hand.ori =
Quaternion::rotation_x(wave_slow * 6.5) * Quaternion::rotation_y(wave * 0.3);
next.l_hand.scale = Vec3::one();
next.r_hand.offset = Vec3::new(
5.5 + wave * 0.5,
-2.0 + wave_quick_cos * 2.5,
8.0 + wave_quick * 3.0,
);
next.r_hand.ori =
Quaternion::rotation_x(wave_slow * 6.5) * Quaternion::rotation_y(wave * 0.3);
next.r_hand.scale = Vec3::one();
next.l_foot.offset = Vec3::new(-3.4, -0.1, 9.0 - 0.0 + wave_dub * -1.2 + wave_slow * 4.0);
next.l_foot.ori = Quaternion::rotation_x(wave * 0.6);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, -0.1, 9.0 - 0.0 + wave_dub * -1.0 + wave_slow * 4.0);
next.r_foot.ori = Quaternion::rotation_x(wave * -0.4);
next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-7.0, -7.0, 15.0);
next.weapon.ori =
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_quick * 1.0);
next.weapon.scale = Vec3::one();
next.l_shoulder.offset = Vec3::new(-10.0, -3.2, 2.5);
next.l_shoulder.ori = Quaternion::rotation_x(0.0);
next.l_shoulder.scale = Vec3::one() * 1.04;
next.r_shoulder.offset = Vec3::new(0.0, -3.2, 2.5);
next.r_shoulder.ori = Quaternion::rotation_x(0.0);
next.r_shoulder.scale = Vec3::one() * 1.04;
next.draw.offset = Vec3::new(0.0, 5.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next.left_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.left_equip.ori = Quaternion::rotation_x(0.0);;
next.left_equip.scale = Vec3::one() * 0.0;
next.right_equip.offset = Vec3::new(0.0, 0.0, 5.0) / 11.0;
next.right_equip.ori = Quaternion::rotation_x(0.0);;
next.right_equip.scale = Vec3::one() * 0.0;
next.torso.offset = Vec3::new(0.0, -2.2, 0.1 + wave_dub * 16.0) / 11.0;
next.torso.ori = Quaternion::rotation_x(wave_slow * 6.0);
next.torso.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -1,4 +1,5 @@
use super::{super::Animation, CharacterSkeleton}; use super::{super::Animation, CharacterSkeleton};
use std::f32::consts::PI;
use std::ops::Mul; use std::ops::Mul;
use vek::*; use vek::*;
@ -15,8 +16,11 @@ impl Animation for RunAnimation {
) -> Self::Skeleton { ) -> Self::Skeleton {
let mut next = (*skeleton).clone(); let mut next = (*skeleton).clone();
let wave = (anim_time as f32 * 14.0).sin(); let wave = (anim_time as f32 * 12.0).sin();
let wave_cos = (anim_time as f32 * 14.0).cos(); let wave_cos = (anim_time as f32 * 12.0).cos();
let wave_diff = (anim_time as f32 * 12.0 + PI / 2.0).sin();
let wave_cos_dub = (anim_time as f32 * 24.0).cos();
let wave_stop = (anim_time as f32 * 2.6).min(PI / 2.0).sin();
let head_look = Vec2::new( let head_look = Vec2::new(
((global_time + anim_time) as f32 / 2.0) ((global_time + anim_time) as f32 / 2.0)
@ -48,24 +52,33 @@ impl Animation for RunAnimation {
next.shorts.ori = Quaternion::rotation_z(wave * 0.6); next.shorts.ori = Quaternion::rotation_z(wave * 0.6);
next.shorts.scale = Vec3::one(); next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(-9.0, 3.0 + wave_cos * 8.0, 12.0 - wave * 1.0) / 11.0; next.l_hand.offset = Vec3::new(
next.l_hand.ori = Quaternion::rotation_x(wave_cos * 1.1); -7.5 + wave_cos_dub * 1.0,
next.l_hand.scale = Vec3::one() / 11.0; 2.0 + wave_cos * 5.0,
7.0 - wave * 1.5,
);
next.l_hand.ori = Quaternion::rotation_x(wave_cos * 0.8);
next.l_hand.scale = Vec3::one() * 1.0;
next.r_hand.offset = Vec3::new(9.0, 3.0 - wave_cos * 8.0, 12.0 + wave * 1.0) / 11.0; next.r_hand.offset = Vec3::new(
next.r_hand.ori = Quaternion::rotation_x(wave_cos * -1.1); 7.5 - wave_cos_dub * 1.0,
next.r_hand.scale = Vec3::one() / 11.0; 2.0 - wave_cos * 5.0,
7.0 + wave * 1.5,
);
next.r_hand.ori = Quaternion::rotation_x(wave_cos * -0.8);
next.r_hand.scale = Vec3::one() * 1.0;
next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave_cos * 1.0, 6.0); next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave_cos * 1.0, 7.0);
next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave_cos * 1.5); next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave_cos * 1.3);
next.l_foot.scale = Vec3::one(); next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, 0.0 - wave_cos * 1.0, 6.0); next.r_foot.offset = Vec3::new(3.4, 0.0 - wave_cos * 1.0, 7.0);
next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.5); next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.3);
next.r_foot.scale = Vec3::one(); next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-7.0, -5.0, 15.0); next.weapon.offset = Vec3::new(-7.0, -5.0, 15.0);
next.weapon.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); next.weapon.ori =
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + wave_cos * 0.25);
next.weapon.scale = Vec3::one(); next.weapon.scale = Vec3::one();
next.l_shoulder.offset = Vec3::new(-10.0, -3.2, 2.5); next.l_shoulder.offset = Vec3::new(-10.0, -3.2, 2.5);
@ -89,7 +102,8 @@ impl Animation for RunAnimation {
next.right_equip.scale = Vec3::one() * 0.0; next.right_equip.scale = Vec3::one() * 0.0;
next.torso.offset = Vec3::new(0.0, -0.2, 0.4); next.torso.offset = Vec3::new(0.0, -0.2, 0.4);
next.torso.ori = Quaternion::rotation_x(-velocity * 0.04 - wave_cos * 0.10); next.torso.ori =
Quaternion::rotation_x(wave_stop * velocity * -0.06 + wave_diff * velocity * -0.005);
next.torso.scale = Vec3::one() / 11.0; next.torso.scale = Vec3::one() / 11.0;
next next

View File

@ -103,7 +103,7 @@ font_ids! {
pub struct DebugInfo { pub struct DebugInfo {
pub tps: f64, pub tps: f64,
pub ping_ms: f64, pub ping_ms: f64,
pub coordinates: Option<comp::phys::Pos>, pub coordinates: Option<comp::Pos>,
} }
pub enum Event { pub enum Event {
@ -312,7 +312,7 @@ impl Hud {
if self.show.ingame { if self.show.ingame {
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let actor = ecs.read_storage::<comp::Actor>(); let actor = ecs.read_storage::<comp::Actor>();
let pos = ecs.read_storage::<comp::phys::Pos>(); let pos = ecs.read_storage::<comp::Pos>();
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
let player = ecs.read_storage::<comp::Player>(); let player = ecs.read_storage::<comp::Player>();
let entities = ecs.entities(); let entities = ecs.entities();
@ -322,7 +322,7 @@ impl Hud {
let player_pos = client let player_pos = client
.state() .state()
.ecs() .ecs()
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);
let mut name_id_walker = self.ids.name_tags.walk(); let mut name_id_walker = self.ids.name_tags.walk();

View File

@ -108,7 +108,7 @@ impl PlayState for CharSelectionState {
if let Err(err) = self if let Err(err) = self
.client .client
.borrow_mut() .borrow_mut()
.tick(comp::Control::default(), clock.get_last_delta()) .tick(comp::Controller::default(), clock.get_last_delta())
{ {
error!("Failed to tick the scene: {:?}", err); error!("Failed to tick the scene: {:?}", err);
return PlayStateResult::Pop; return PlayStateResult::Pop;

View File

@ -477,15 +477,15 @@ impl FigureMgr {
let player_pos = client let player_pos = client
.state() .state()
.ecs() .ecs()
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);
for (entity, pos, vel, ori, actor, animation_info, stats) in ( for (entity, pos, vel, ori, actor, animation_info, stats) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(), &ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(), &ecs.read_storage::<comp::Vel>(),
&ecs.read_storage::<comp::phys::Ori>(), &ecs.read_storage::<comp::Ori>(),
&ecs.read_storage::<comp::Actor>(), &ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::AnimationInfo>(), &ecs.read_storage::<comp::AnimationInfo>(),
ecs.read_storage::<comp::Stats>().maybe(), ecs.read_storage::<comp::Stats>().maybe(),
@ -556,10 +556,25 @@ impl FigureMgr {
time, time,
animation_info.time, animation_info.time,
), ),
comp::Animation::Roll => character::RollAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_info.time,
),
comp::Animation::Crun => character::CrunAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_info.time,
),
comp::Animation::Cidle => character::CidleAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_info.time,
),
comp::Animation::Gliding => { comp::Animation::Gliding => {
character::GlidingAnimation::update_skeleton( character::GlidingAnimation::update_skeleton(
state.skeleton_mut(), state.skeleton_mut(),
time, (vel.0.magnitude(), time),
animation_info.time, animation_info.time,
) )
} }
@ -661,15 +676,15 @@ impl FigureMgr {
let player_pos = client let player_pos = client
.state() .state()
.ecs() .ecs()
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);
for (entity, _, _, _, actor, _, _) in ( for (entity, _, _, _, actor, _, _) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(), &ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(), &ecs.read_storage::<comp::Vel>(),
&ecs.read_storage::<comp::phys::Ori>(), &ecs.read_storage::<comp::Ori>(),
&ecs.read_storage::<comp::Actor>(), &ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::AnimationInfo>(), &ecs.read_storage::<comp::AnimationInfo>(),
ecs.read_storage::<comp::Stats>().maybe(), ecs.read_storage::<comp::Stats>().maybe(),

View File

@ -110,7 +110,7 @@ impl Scene {
let player_pos = client let player_pos = client
.state() .state()
.ecs() .ecs()
.read_storage::<comp::phys::Pos>() .read_storage::<comp::Pos>()
.get(client.entity()) .get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0); .map_or(Vec3::zero(), |pos| pos.0);

View File

@ -8,7 +8,7 @@ use crate::{
Direction, Error, GlobalState, PlayState, PlayStateResult, Direction, Error, GlobalState, PlayState, PlayStateResult,
}; };
use client::{self, Client}; use client::{self, Client};
use common::{clock::Clock, comp, comp::phys::Pos, msg::ClientState}; use common::{clock::Clock, comp, comp::Pos, msg::ClientState};
use log::{error, warn}; use log::{error, warn};
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
use vek::*; use vek::*;
@ -16,8 +16,9 @@ use vek::*;
pub struct SessionState { pub struct SessionState {
scene: Scene, scene: Scene,
client: Rc<RefCell<Client>>, client: Rc<RefCell<Client>>,
key_state: KeyState,
hud: Hud, hud: Hud,
key_state: KeyState,
controller: comp::Controller,
} }
/// Represents an active game session (i.e., the one being played). /// Represents an active game session (i.e., the one being played).
@ -30,6 +31,7 @@ impl SessionState {
scene, scene,
client, client,
key_state: KeyState::new(), key_state: KeyState::new(),
controller: comp::Controller::default(),
hud: Hud::new(window), hud: Hud::new(window),
} }
} }
@ -45,22 +47,8 @@ const BG_COLOR: Rgba<f32> = Rgba {
impl SessionState { impl SessionState {
/// Tick the session (and the client attached to it). /// Tick the session (and the client attached to it).
pub fn tick(&mut self, dt: Duration) -> Result<(), Error> { fn tick(&mut self, dt: Duration) -> Result<(), Error> {
// Calculate the movement input vector of the player from the current key presses for event in self.client.borrow_mut().tick(self.controller.clone(), dt)? {
// and the camera direction.
let ori = self.scene.camera().get_orientation();
let unit_vecs = (
Vec2::new(ori[0].cos(), -ori[0].sin()),
Vec2::new(ori[0].sin(), ori[0].cos()),
);
let dir_vec = self.key_state.dir_vec();
let move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
for event in self
.client
.borrow_mut()
.tick(comp::Control { move_dir }, dt)?
{
match event { match event {
client::Event::Chat(msg) => { client::Event::Chat(msg) => {
self.hud.new_message(msg); self.hud.new_message(msg);
@ -127,19 +115,22 @@ impl PlayState for SessionState {
Event::Close => { Event::Close => {
return PlayStateResult::Shutdown; return PlayStateResult::Shutdown;
} }
Event::InputUpdate(GameInput::Attack, true) => { Event::InputUpdate(GameInput::Attack, state) => {
self.client.borrow_mut().attack(); self.controller.attack = state;
self.client.borrow_mut().respawn(); self.controller.respawn = state; // TODO: Don't do both
} }
Event::InputUpdate(GameInput::Jump, true) => { Event::InputUpdate(GameInput::Roll, state) => {
self.client.borrow_mut().jump(); self.controller.roll = state;
}
Event::InputUpdate(GameInput::Jump, state) => {
self.controller.jump = state;
} }
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) => { Event::InputUpdate(GameInput::Glide, state) => {
self.client.borrow_mut().glide(state) self.controller.glide = state;
} }
// Pass all other events to the scene // Pass all other events to the scene
@ -149,6 +140,16 @@ impl PlayState for SessionState {
} }
} }
// Calculate the movement input vector of the player from the current key presses
// and the camera direction.
let ori = self.scene.camera().get_orientation();
let unit_vecs = (
Vec2::new(ori[0].cos(), -ori[0].sin()),
Vec2::new(ori[0].sin(), ori[0].cos()),
);
let dir_vec = self.key_state.dir_vec();
self.controller.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
// Perform an in-game tick. // Perform an in-game tick.
if let Err(err) = self.tick(clock.get_last_delta()) { if let Err(err) = self.tick(clock.get_last_delta()) {
error!("Failed to tick the scene: {:?}", err); error!("Failed to tick the scene: {:?}", err);

View File

@ -31,6 +31,7 @@ pub struct ControlSettings {
pub screenshot: KeyMouse, pub screenshot: KeyMouse,
pub toggle_ingame_ui: KeyMouse, pub toggle_ingame_ui: KeyMouse,
pub attack: KeyMouse, pub attack: KeyMouse,
pub roll: KeyMouse,
} }
impl Default for ControlSettings { impl Default for ControlSettings {
@ -59,6 +60,7 @@ impl Default for ControlSettings {
screenshot: KeyMouse::Key(VirtualKeyCode::F4), screenshot: KeyMouse::Key(VirtualKeyCode::F4),
toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6),
attack: KeyMouse::Mouse(MouseButton::Left), attack: KeyMouse::Mouse(MouseButton::Left),
roll: KeyMouse::Mouse(MouseButton::Middle),
} }
} }
} }

View File

@ -34,6 +34,7 @@ pub enum GameInput {
Screenshot, Screenshot,
ToggleIngameUi, ToggleIngameUi,
Attack, Attack,
Roll,
Respawn, Respawn,
} }
@ -134,6 +135,7 @@ impl Window {
GameInput::ToggleIngameUi, GameInput::ToggleIngameUi,
); );
key_map.insert(settings.controls.attack, GameInput::Attack); key_map.insert(settings.controls.attack, GameInput::Attack);
key_map.insert(settings.controls.roll, GameInput::Roll);
Ok(Self { Ok(Self {
events_loop, events_loop,