Implement killing

Change animation history to animation
Add attack input event
Implement killing with ecs systems for damage and death
Sync attack properly
Sync deaths


Former-commit-id: 72b5be7d65d9d3fcbef50d4836a6f06ec218d69e
This commit is contained in:
timokoesters 2019-05-17 22:47:58 +02:00
parent 3ddf4bddab
commit b3e4ca0a5d
22 changed files with 351 additions and 177 deletions

View File

@ -2,6 +2,7 @@ use vek::*;
pub enum InputEvent {
Jump,
AttackStarted,
}
pub struct Input {

View File

@ -38,7 +38,7 @@ pub struct Client {
thread_pool: ThreadPool,
last_ping: f64,
pub postbox: PostBox<ClientMsg, ServerMsg>,
postbox: PostBox<ClientMsg, ServerMsg>,
last_server_ping: Instant,
last_ping_delta: f64,
@ -59,7 +59,7 @@ impl Client {
let mut postbox = PostBox::to(addr)?;
// Wait for initial sync
let (state, entity) = match postbox.next_message() {
let (mut state, entity) = match postbox.next_message() {
Some(ServerMsg::InitialSync {
ecs_state,
entity_uid,
@ -74,6 +74,9 @@ impl Client {
_ => return Err(Error::ServerWentMad),
};
// Initialize ecs components the client has control over
state.write_component(entity, comp::Actions::new());
Ok(Self {
client_state,
thread_pool: threadpool::Builder::new()
@ -99,6 +102,11 @@ impl Client {
self.postbox.send_message(ClientMsg::Register { player });
}
pub fn request_character(&mut self, name: String, body: comp::Body) {
self.postbox
.send_message(ClientMsg::Character { name, body });
}
pub fn set_view_distance(&mut self, view_distance: u32) {
self.view_distance = Some(view_distance.max(5).min(25));
self.postbox
@ -157,17 +165,40 @@ impl Client {
// approximate order of things. Please update it as this code changes.
//
// 1) Collect input from the frontend, apply input effects to the state of the game
// 2) Go through any events (timer-driven or otherwise) that need handling and apply them
// 2) Handle messages from the server
// 3) Go through any events (timer-driven or otherwise) that need handling and apply them
// to the state of the game
// 3) Perform a single LocalState tick (i.e: update the world and entities in the world)
// 4) Go through the terrain update queue and apply all changes to the terrain
// 5) Finish the tick, passing control of the main thread back to the frontend
// 4) Perform a single LocalState tick (i.e: update the world and entities in the world)
// 5) Go through the terrain update queue and apply all changes to the terrain
// 6) Sync information to the server
// 7) Finish the tick, passing control of the main thread back to the frontend
// Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new();
// 1) Handle input from frontend.
for event in input.events {
match event {
InputEvent::AttackStarted => {
self.state
.ecs_mut()
.write_storage::<comp::Actions>()
.get_mut(self.entity)
.unwrap() // We initialized it in the constructor
.0
.push(comp::Action::Attack);
}
_ => {}
}
}
// Handle new messages from the server.
frontend_events.append(&mut self.handle_new_messages()?);
// Tell the server about the actions.
if let Some(actions) = self
.state
.ecs()
.read_storage::<comp::Actions>()
.get(self.entity)
{
self.postbox
.send_message(ClientMsg::PlayerActions(actions.clone()));
}
// Pass character control from frontend input to the player's entity.
// TODO: Only do this if the entity already has a Control component!
@ -180,35 +211,18 @@ impl Client {
},
);
// Tick the client's LocalState (step 3).
// 2) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new();
// Handle new messages from the server.
frontend_events.append(&mut self.handle_new_messages()?);
// 3)
// 4) Tick the client's LocalState
self.state.tick(dt);
// Update the server about the player's physics attributes.
match (
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
) {
(Some(pos), Some(vel), Some(dir)) => {
self.postbox
.send_message(ClientMsg::PlayerPhysics { pos, vel, dir });
}
_ => {}
}
// Update the server about the player's currently playing animation and the previous one.
if let Some(animation_history) = self
.state
.read_storage::<comp::AnimationHistory>()
.get(self.entity)
.cloned()
{
if Some(animation_history.current) != animation_history.last {
self.postbox
.send_message(ClientMsg::PlayerAnimation(animation_history));
}
}
// 5) Terrain
let pos = self
.state
.read_storage::<comp::phys::Pos>()
@ -259,13 +273,39 @@ impl Client {
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(10));
}
// send a ping to the server once every second
// Send a ping to the server once every second
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
self.postbox.send_message(ClientMsg::Ping);
self.last_server_ping = Instant::now();
}
// Finish the tick, pass control back to the frontend (step 6).
// 6) Update the server about the player's physics attributes.
match (
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
) {
(Some(pos), Some(vel), Some(dir)) => {
self.postbox
.send_message(ClientMsg::PlayerPhysics { pos, vel, dir });
}
_ => {}
}
// Update the server about the player's current animation.
if let Some(mut 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()));
}
}
// 7) Finish the tick, pass control back to the frontend.
self.tick += 1;
Ok(frontend_events)
}
@ -319,10 +359,10 @@ impl Client {
},
ServerMsg::EntityAnimation {
entity,
animation_history,
animation_info,
} => match self.state.ecs().entity_from_uid(entity) {
Some(entity) => {
self.state.write_component(entity, animation_history);
self.state.write_component(entity, animation_info);
}
None => {}
},

20
common/src/comp/action.rs Normal file
View File

@ -0,0 +1,20 @@
use specs::{Component, FlaggedStorage, VecStorage};
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Action {
Attack,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Actions(pub Vec<Action>);
impl Actions {
pub fn new() -> Self {
Self(Vec::new())
}
}
impl Component for Actions {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -229,33 +229,3 @@ pub enum Actor {
impl Component for Actor {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct AnimationHistory {
pub last: Option<Animation>,
pub current: Animation,
pub time: f64,
}
impl AnimationHistory {
pub fn new(animation: Animation) -> Self {
Self {
last: None,
current: animation,
time: 0.0,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Animation {
Idle,
Run,
Jump,
Gliding,
Attack,
}
impl Component for AnimationHistory {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -0,0 +1,32 @@
use specs::{Component, FlaggedStorage, VecStorage};
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Animation {
Idle,
Run,
Jump,
Gliding,
Attack,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct AnimationInfo {
pub animation: Animation,
pub time: f64,
pub changed: bool,
}
impl AnimationInfo {
pub fn new() -> Self {
Self {
animation: Animation::Idle,
time: 0.0,
changed: true,
}
}
}
impl Component for AnimationInfo {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -1,16 +1,21 @@
pub mod action;
pub mod actor;
pub mod agent;
pub mod animation;
pub mod phys;
pub mod player;
pub mod stats;
// Reexports
pub use action::Action;
pub use action::Actions;
pub use actor::Actor;
pub use actor::Animation;
pub use actor::AnimationHistory;
pub use actor::Body;
pub use actor::HumanoidBody;
pub use actor::QuadrupedBody;
pub use agent::{Agent, Control};
pub use animation::Animation;
pub use animation::AnimationInfo;
pub use player::Player;
pub use stats::Dying;
pub use stats::Stats;

View File

@ -28,7 +28,7 @@ impl Component for Dir {
type Storage = VecStorage<Self>;
}
// Update
// ForceUpdate
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct ForceUpdate;

View File

@ -1,6 +1,6 @@
use specs::{Component, FlaggedStorage, VecStorage};
use specs::{Component, FlaggedStorage, NullStorage, VecStorage};
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Health {
pub current: u32,
pub maximum: u32,
@ -14,7 +14,7 @@ impl Health {
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Stats {
pub hp: Health,
pub xp: u32,
@ -36,3 +36,10 @@ impl Default for Stats {
impl Component for Stats {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Dying;
impl Component for Dying {
type Storage = NullStorage<Self>;
}

View File

@ -16,7 +16,8 @@ pub enum ClientMsg {
Ping,
Pong,
Chat(String),
PlayerAnimation(comp::AnimationHistory),
PlayerActions(comp::Actions),
PlayerAnimation(comp::AnimationInfo),
PlayerPhysics {
pos: comp::phys::Pos,
vel: comp::phys::Vel,

View File

@ -31,7 +31,7 @@ pub enum ServerMsg {
},
EntityAnimation {
entity: u64,
animation_history: comp::AnimationHistory,
animation_info: comp::AnimationInfo,
},
TerrainChunkUpdate {
key: Vec2<i32>,

View File

@ -108,7 +108,9 @@ impl State {
ecs.register::<comp::phys::Pos>();
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Dir>();
ecs.register::<comp::AnimationHistory>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Actions>();
ecs.register::<comp::Dying>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Control>();
ecs.register::<inventory::Inventory>();

42
common/src/sys/action.rs Normal file
View File

@ -0,0 +1,42 @@
// Library
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{phys::Pos, Action, Actions, Control, Stats},
state::DeltaTime,
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
WriteStorage<'a, Actions>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Stats>,
);
fn run(&mut self, (entities, dt, mut actions, positions, mut stats): Self::SystemData) {
for (a, mut actions_a, pos_a) in (&entities, &mut actions, &positions).join() {
for event in actions_a.0.drain(..) {
match event {
Action::Attack => {
for (b, pos_b, stat_b) in (&entities, &positions, &mut stats).join() {
if a == b {
continue;
}
if pos_a.0.distance_squared(pos_b.0) < 50.0 {
&mut stat_b.hp.change_by(-60, 0.0); // TODO: variable damage and current time
&stat_b.hp;
}
}
}
}
}
}
}
}

View File

@ -1,22 +0,0 @@
// Library
use specs::{Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{phys::Pos, AnimationHistory, Control},
state::DeltaTime,
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (Read<'a, DeltaTime>, WriteStorage<'a, AnimationHistory>);
fn run(&mut self, (dt, mut anim_history): Self::SystemData) {
for (mut anim_history) in (&mut anim_history).join() {
anim_history.time += dt.0 as f64;
}
}
}

View File

@ -0,0 +1,25 @@
// Library
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{phys::Pos, ActionState, ActionEvent, Control, Stats},
state::DeltaTime,
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, DeltaTime>,
WriteStorage<'a, ActionState>,
);
fn run(&mut self, (dt, mut action_states): Self::SystemData) {
for (dt, mut animation) in (dt, &mut animation).join() {
animation.time += dt.0 as f64;
}
}
}

View File

@ -6,7 +6,7 @@ use vek::*;
use crate::{
comp::{
phys::{Dir, Pos, Vel},
Animation, AnimationHistory, Control,
Animation, AnimationInfo, Control,
},
state::DeltaTime,
terrain::TerrainMap,
@ -24,13 +24,13 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Dir>,
WriteStorage<'a, AnimationHistory>,
WriteStorage<'a, AnimationInfo>,
ReadStorage<'a, Control>,
);
fn run(
&mut self,
(terrain, dt, entities, pos, mut vels, mut dirs, mut anims, controls): Self::SystemData,
(terrain, dt, entities, pos, mut vels, mut dirs, mut animation_infos, controls): Self::SystemData,
) {
for (entity, pos, mut vel, mut dir, control) in
(&entities, &pos, &mut vels, &mut dirs, &controls).join()
@ -94,22 +94,18 @@ impl<'a> System<'a> for Sys {
Animation::Jump
};
let last_history = anims.get_mut(entity).cloned();
let last = animation_infos
.get_mut(entity)
.cloned()
.unwrap_or(AnimationInfo::new());
let changed = last.animation != animation;
let time = if let Some((true, time)) =
last_history.map(|last| (last.current == animation, last.time))
{
time
} else {
0.0
};
anims.insert(
animation_infos.insert(
entity,
AnimationHistory {
last: last_history.map(|last| last.current),
current: animation,
time,
AnimationInfo {
animation,
time: if changed { 0.0 } else { last.time },
changed,
},
);
}

View File

@ -1,7 +1,8 @@
pub mod action;
pub mod agent;
pub mod anim;
pub mod control;
pub mod phys;
mod stats;
// External
use specs::DispatcherBuilder;
@ -11,10 +12,16 @@ const AGENT_SYS: &str = "agent_sys";
const CONTROL_SYS: &str = "control_sys";
const PHYS_SYS: &str = "phys_sys";
const ANIM_SYS: &str = "anim_sys";
const MOVEMENT_SYS: &str = "movement_sys";
const ACTION_SYS: &str = "action_sys";
const STATS_SYS: &str = "stats_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[]);
dispatch_builder.add(control::Sys, CONTROL_SYS, &["phys_sys"]);
dispatch_builder.add(control::Sys, CONTROL_SYS, &[PHYS_SYS]);
dispatch_builder.add(anim::Sys, ANIM_SYS, &[]);
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(action::Sys, ACTION_SYS, &[]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[ACTION_SYS]);
}

28
common/src/sys/stats.rs Normal file
View File

@ -0,0 +1,28 @@
// Library
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{Control, Dying, Stats},
state::DeltaTime,
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Stats>,
WriteStorage<'a, Dying>,
);
fn run(&mut self, (entities, stats, mut dyings): Self::SystemData) {
for (entity, stat) in (&entities, &stats).join() {
if stat.hp.current == 0 {
dyings.insert(entity, Dying);
}
}
}
}

View File

@ -134,7 +134,7 @@ impl Server {
.with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)))
.with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y()))
.with(comp::AnimationHistory::new(comp::Animation::Idle))
.with(comp::Actions::new())
.with(comp::Actor::Character { name, body })
.with(comp::Stats::default())
}
@ -156,7 +156,10 @@ impl Server {
state.write_component(entity, comp::phys::ForceUpdate);
// Set initial animation.
state.write_component(entity, comp::AnimationHistory::new(comp::Animation::Idle));
state.write_component(entity, comp::AnimationInfo::new());
// Set initial actions (none).
state.write_component(entity, comp::Actions::new());
// Tell the client its request was successful.
client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character)));
@ -180,7 +183,7 @@ impl Server {
// 6) Send relevant state updates to all clients
// 7) Finish the tick, passing control of the main thread back to the frontend
// Build up a list of events for this frame, to be passed to the frontend.
// 1) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new();
// If networking has problems, handle them.
@ -188,19 +191,19 @@ impl Server {
return Err(err.into());
}
// Handle new client connections (step 2).
frontend_events.append(&mut self.handle_new_connections()?);
// 2)
// Handle new messages from clients
// 3) Handle inputs from clients
frontend_events.append(&mut self.handle_new_connections()?);
frontend_events.append(&mut self.handle_new_messages()?);
// Tick the client's LocalState (step 3).
// 4) Tick the client's LocalState.
self.state.tick(dt);
// Tick the world
self.world.tick(dt);
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
// 5) Fetch any generated `TerrainChunk`s and insert them into the terrain.
// Also, send the chunk data to anybody that is close by.
if let Ok((key, chunk)) = self.chunk_rx.try_recv() {
// Send the chunk to all nearby players.
@ -261,10 +264,10 @@ impl Server {
self.state.remove_chunk(key);
}
// Synchronise clients with the new state of the world.
// 6) Synchronise clients with the new state of the world.
self.sync_clients();
// Finish the tick, pass control back to the frontend (step 6).
// 7) Finish the tick, pass control back to the frontend.
Ok(frontend_events)
}
@ -389,10 +392,19 @@ impl Server {
| ClientState::Spectator
| ClientState::Character => new_chat_msgs.push((entity, msg)),
},
ClientMsg::PlayerAnimation(animation_history) => {
ClientMsg::PlayerActions(mut actions) => {
state
.ecs_mut()
.write_storage::<comp::Actions>()
.get_mut(entity)
.map(|s| {
s.0.append(&mut actions.0);
});
}
ClientMsg::PlayerAnimation(animation_info) => {
match client.client_state {
ClientState::Character => {
state.write_component(entity, animation_history)
state.write_component(entity, animation_info)
}
// Only characters can send animations.
_ => client.error_state(RequestStateError::Impossible),
@ -492,17 +504,17 @@ impl Server {
state.write_component(entity, player);
// Sync logical information other players have authority over, not the server.
for (other_entity, &uid, &animation_history) in (
for (other_entity, &uid, &animation_info) in (
&state.ecs().entities(),
&state.ecs().read_storage::<common::state::Uid>(),
&state.ecs().read_storage::<comp::AnimationHistory>(),
&state.ecs().read_storage::<comp::AnimationInfo>(),
)
.join()
{
// AnimationHistory
// Animation
client.postbox.send_message(ServerMsg::EntityAnimation {
entity: uid.into(),
animation_history: animation_history,
animation_info: animation_info,
});
}
@ -543,36 +555,36 @@ impl Server {
}
}
// Sync animation states.
for (entity, &uid, &animation_history) in (
// Sync animation.
for (entity, &uid, &animation_info) in (
&self.state.ecs().entities(),
&self.state.ecs().read_storage::<Uid>(),
&self.state.ecs().read_storage::<comp::AnimationHistory>(),
&self.state.ecs().read_storage::<comp::AnimationInfo>(),
)
.join()
{
// Check if we need to sync.
if Some(animation_history.current) == animation_history.last {
continue;
if animation_info.changed {
self.clients.notify_ingame_except(
entity,
ServerMsg::EntityAnimation {
entity: uid.into(),
animation_info: animation_info.clone(),
},
);
}
self.clients.notify_ingame_except(
entity,
ServerMsg::EntityAnimation {
entity: uid.into(),
animation_history,
},
);
}
// Update animation last/current state.
for (entity, mut animation_history) in (
// Sync deaths.
let todo_remove = (
&self.state.ecs().entities(),
&mut self.state.ecs().write_storage::<comp::AnimationHistory>(),
&self.state.ecs().read_storage::<comp::Dying>(),
)
.join()
{
animation_history.last = Some(animation_history.current);
.map(|(entity, _)| entity)
.collect::<Vec<EcsEntity>>();
for entity in todo_remove {
self.state.ecs_mut().delete_entity_synced(entity);
}
// Remove all force flags.

View File

@ -74,13 +74,10 @@ impl PlayState for CharSelectionState {
return PlayStateResult::Pop;
}
ui::Event::Play => {
self.client
.borrow_mut()
.postbox
.send_message(ClientMsg::Character {
name: self.char_selection_ui.character_name.clone(),
body: comp::Body::Humanoid(self.char_selection_ui.character_body),
});
self.client.borrow_mut().request_character(
self.char_selection_ui.character_name.clone(),
comp::Body::Humanoid(self.char_selection_ui.character_body),
);
return PlayStateResult::Switch(Box::new(SessionState::new(
&mut global_state.window,
self.client.clone(),

View File

@ -347,13 +347,13 @@ impl FigureMgr {
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let time = client.state().get_time();
let ecs = client.state().ecs();
for (entity, pos, vel, dir, actor, animation_history, stats) in (
for (entity, pos, vel, dir, actor, animation_info, stats) in (
&ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(),
&ecs.read_storage::<comp::phys::Dir>(),
&ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::AnimationHistory>(),
&ecs.read_storage::<comp::AnimationInfo>(),
ecs.read_storage::<comp::Stats>().maybe(),
)
.join()
@ -365,21 +365,21 @@ impl FigureMgr {
FigureState::new(renderer, CharacterSkeleton::new())
});
let target_skeleton = match animation_history.current {
let target_skeleton = match animation_info.animation {
comp::Animation::Idle => character::IdleAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
animation_info.time,
),
comp::Animation::Run => character::RunAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
animation_info.time,
),
comp::Animation::Jump => character::JumpAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
animation_info.time,
),
comp::Animation::Attack => character::AttackAnimation::update_skeleton(
state.skeleton_mut(),
@ -390,7 +390,7 @@ impl FigureMgr {
character::GlidingAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
animation_info.time,
)
}
};
@ -404,21 +404,21 @@ impl FigureMgr {
FigureState::new(renderer, QuadrupedSkeleton::new())
});
let target_skeleton = match animation_history.current {
let target_skeleton = match animation_info.animation {
comp::Animation::Run => quadruped::RunAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
animation_info.time,
),
comp::Animation::Idle => quadruped::IdleAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
animation_info.time,
),
comp::Animation::Jump => quadruped::JumpAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
animation_info.time,
),
// TODO!

View File

@ -9,6 +9,7 @@ use crate::{
};
use client::{self, Client, Input, InputEvent};
use common::clock::Clock;
use glutin::MouseButton;
use std::{cell::RefCell, mem, rc::Rc, time::Duration};
use vek::*;
@ -138,7 +139,11 @@ impl PlayState for SessionState {
Event::Close => {
return PlayStateResult::Shutdown;
}
// Movement Key Pressed
// Attack key pressed
Event::Click(MouseButton::Left, state) => {
self.input_events.push(InputEvent::AttackStarted)
}
// Movement key pressed
Event::KeyDown(Key::MoveForward) => self.key_state.up = true,
Event::KeyDown(Key::MoveBack) => self.key_state.down = true,
Event::KeyDown(Key::MoveLeft) => self.key_state.left = true,
@ -148,7 +153,7 @@ impl PlayState for SessionState {
self.key_state.jump = true;
}
Event::KeyDown(Key::Glide) => self.key_state.glide = true,
// Movement Key Released
// Movement key released
Event::KeyUp(Key::MoveForward) => self.key_state.up = false,
Event::KeyUp(Key::MoveBack) => self.key_state.down = false,
Event::KeyUp(Key::MoveLeft) => self.key_state.left = false,

View File

@ -3,6 +3,7 @@ use crate::{
settings::Settings,
ui, Error,
};
use glutin::{ElementState, MouseButton};
use std::collections::HashMap;
use vek::*;
@ -124,7 +125,11 @@ 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 && state == ElementState::Pressed =>
{
events.push(Event::Click(button, state))
}
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode
{
Some(keycode) => match key_map.get(&keycode) {
@ -292,6 +297,7 @@ pub enum Event {
/// A key has been typed that corresponds to a specific character.
Char(char),
/// The cursor has been panned across the screen while grabbed.
Click(MouseButton, ElementState),
CursorPan(Vec2<f32>),
/// The camera has been requested to zoom.
Zoom(f32),