Implement hit time delay

Former-commit-id: 6c1990645fd07e23de5eb97b6fc22f34ce09a6b7
This commit is contained in:
timokoesters 2019-05-22 22:53:24 +02:00
parent ad3b8dffa1
commit 0344f3eba8
21 changed files with 197 additions and 243 deletions

View File

@ -1,4 +1,4 @@
use client::{Client, Event, Input};
use client::{Client, Event};
use common::{clock::Clock, comp};
use log::info;
use std::time::Duration;
@ -23,7 +23,7 @@ fn main() {
client.send_chat("Hello!".to_string());
loop {
let events = match client.tick(Input::default(), clock.get_last_delta()) {
let events = match client.tick(comp::Inputs::default(), clock.get_last_delta()) {
Ok(events) => events,
Err(err) => {
println!("Error: {:?}", err);

View File

@ -1,24 +0,0 @@
use vek::*;
pub enum InputEvent {
Jump,
AttackStarted,
}
pub struct Input {
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
pub events: Vec<InputEvent>,
}
impl Default for Input {
fn default() -> Self {
Input {
move_dir: Vec2::zero(),
jumping: false,
gliding: false,
events: Vec::new(),
}
}
}

View File

@ -1,13 +1,9 @@
#![feature(label_break_value, duration_float)]
pub mod error;
pub mod input;
// Reexports
pub use crate::{
error::Error,
input::{Input, InputEvent},
};
pub use crate::error::Error;
pub use specs::join::Join;
pub use specs::Entity as EcsEntity;
@ -75,8 +71,8 @@ impl Client {
_ => return Err(Error::ServerWentMad),
};
// Initialize ecs components the client has control over
state.write_component(entity, comp::Actions::new());
// Initialize ecs components the client has actions over
state.write_component(entity, comp::Inputs::default());
Ok(Self {
client_state,
@ -180,7 +176,7 @@ impl Client {
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
pub fn tick(&mut self, input: comp::Inputs, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the
@ -193,45 +189,16 @@ impl Client {
// 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
// 7) Finish the tick, passing actions of the main thread back to the frontend
// 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);
}
_ => {}
}
}
// Pass character actions from frontend input to the player's entity.
// TODO: Only do this if the entity already has a Inputs component!
self.state.write_component(self.entity, input.clone());
// Tell the server about the 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!
self.state.write_component(
self.entity,
comp::Control {
move_dir: input.move_dir,
jumping: input.jumping,
gliding: input.gliding,
},
);
// Tell the server about the inputs.
self.postbox
.send_message(ClientMsg::PlayerInputs(input.clone()));
// 2) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec::new();

View File

@ -1,20 +0,0 @@
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

@ -13,24 +13,3 @@ pub enum Agent {
impl Component for Agent {
type Storage = VecStorage<Self>;
}
#[derive(Copy, Clone, Debug)]
pub struct Control {
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
}
impl Default for Control {
fn default() -> Self {
Self {
move_dir: Vec2::zero(),
jumping: false,
gliding: false,
}
}
}
impl Component for Control {
type Storage = VecStorage<Self>;
}

32
common/src/comp/inputs.rs Normal file
View File

@ -0,0 +1,32 @@
use specs::{Component, FlaggedStorage, VecStorage};
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InputEvent {
Jump,
Attack,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Inputs {
// Held down
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
// Event based
pub events: Vec<InputEvent>,
}
impl Component for Inputs {
type Storage = VecStorage<Self>;
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Actions {
pub attack_time: Option<f32>,
}
impl Component for Actions {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -1,21 +1,22 @@
pub mod action;
pub mod actor;
pub mod agent;
pub mod animation;
pub mod inputs;
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::Body;
pub use actor::HumanoidBody;
pub use actor::QuadrupedBody;
pub use agent::{Agent, Control};
pub use agent::Agent;
pub use animation::Animation;
pub use animation::AnimationInfo;
pub use inputs::Actions;
pub use inputs::InputEvent;
pub use inputs::Inputs;
pub use player::Player;
pub use stats::Dying;
pub use stats::Stats;

View File

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

View File

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

View File

@ -110,10 +110,10 @@ impl State {
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Dir>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Inputs>();
ecs.register::<comp::Actions>();
ecs.register::<comp::Dying>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Control>();
ecs.register::<inventory::Inventory>();
// Register synced resources used by the ECS.

View File

@ -1,71 +0,0 @@
// Library
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{
phys::{Dir, ForceUpdate, Pos, Vel},
Action, Actions, Control, Stats,
},
state::{DeltaTime, Time},
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, Time>,
Read<'a, DeltaTime>,
WriteStorage<'a, Actions>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
ReadStorage<'a, Dir>,
WriteStorage<'a, Stats>,
WriteStorage<'a, ForceUpdate>,
);
fn run(
&mut self,
(
entities,
time,
dt,
mut actions,
positions,
mut velocities,
directions,
mut stats,
mut force_updates,
): Self::SystemData,
) {
for (a, actions_a, pos_a, dir_a) in
(&entities, &mut actions, &positions, &directions).join()
{
for event in actions_a.0.drain(..) {
match event {
Action::Attack => {
for (b, pos_b, stat_b, vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
if a == b {
continue;
}
if a != b
&& pos_a.0.distance_squared(pos_b.0) < 50.0
&& dir_a.0.angle_between_degrees(pos_b.0 - pos_a.0) < 70.0
{
stat_b.hp.change_by(-10); // TODO: variable damage
vel_b.0 += (pos_b.0 - pos_a.0).normalized() * 20.0;
vel_b.0.z = 20.0;
force_updates.insert(b, ForceUpdate);
}
}
}
}
}
}
}
}

34
common/src/sys/actions.rs Normal file
View File

@ -0,0 +1,34 @@
// Library
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
// Crate
use crate::{
comp::{
phys::{Dir, Pos, Vel},
Actions, Animation, AnimationInfo,
},
state::DeltaTime,
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
// Basic ECS AI agent system
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (Entities<'a>, Read<'a, DeltaTime>, WriteStorage<'a, Actions>);
fn run(&mut self, (entities, dt, mut actions): Self::SystemData) {
for (entity, mut action) in (&entities, &mut actions).join() {
let should_end = action.attack_time.as_mut().map_or(false, |mut time| {
*time += dt.0;
*time > 0.5 // TODO: constant
});
if should_end {
action.attack_time = None;
}
}
}
}

View File

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

View File

@ -4,7 +4,7 @@ use vek::*;
// Crate
use crate::{
comp::{phys::Pos, Animation, AnimationInfo, Control, Stats},
comp::{phys::Pos, Animation, AnimationInfo, Stats},
state::DeltaTime,
};

View File

@ -5,10 +5,10 @@ use vek::*;
// Crate
use crate::{
comp::{
phys::{Dir, Pos, Vel},
Animation, AnimationInfo, Control,
phys::{Dir, ForceUpdate, Pos, Vel},
Actions, Animation, AnimationInfo, InputEvent, Inputs, Stats,
},
state::DeltaTime,
state::{DeltaTime, Time},
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
@ -18,23 +18,47 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>,
Entities<'a>,
Read<'a, Time>,
Read<'a, DeltaTime>,
ReadExpect<'a, TerrainMap>,
WriteStorage<'a, Inputs>,
WriteStorage<'a, Actions>,
ReadStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Dir>,
WriteStorage<'a, AnimationInfo>,
ReadStorage<'a, Control>,
WriteStorage<'a, Stats>,
WriteStorage<'a, ForceUpdate>,
);
fn run(
&mut self,
(terrain, dt, entities, pos, mut vels, mut dirs, mut animation_infos, controls): Self::SystemData,
(
entities,
time,
dt,
terrain,
mut inputs,
mut actions,
positions,
mut velocities,
mut directions,
mut animation_infos,
mut stats,
mut force_updates,
): Self::SystemData,
) {
for (entity, pos, mut vel, mut dir, control) in
(&entities, &pos, &mut vels, &mut dirs, &controls).join()
for (entity, inputs, pos, mut dir, mut vel) in (
&entities,
&mut inputs,
&positions,
&mut directions,
&mut velocities,
)
.join()
{
// Handle held-down inputs
let on_ground = terrain
.get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
.map(|vox| !vox.is_empty())
@ -44,9 +68,9 @@ impl<'a> System<'a> for Sys {
let (gliding, friction) = if on_ground {
// TODO: Don't hard-code this.
// Apply physics to the player: acceleration and non-linear deceleration.
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 200.0;
vel.0 += Vec2::broadcast(dt.0) * inputs.move_dir * 200.0;
if control.jumping {
if inputs.jumping {
vel.0.z += 16.0;
}
@ -54,9 +78,9 @@ impl<'a> System<'a> for Sys {
} else {
// TODO: Don't hard-code this.
// Apply physics to the player: acceleration and non-linear deceleration.
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 10.0;
vel.0 += Vec2::broadcast(dt.0) * inputs.move_dir * 10.0;
if control.gliding && vel.0.z < 0.0 {
if inputs.gliding && vel.0.z < 0.0 {
// TODO: Don't hard-code this.
let anti_grav = 9.81 * 3.95 + vel.0.z.powf(2.0) * 0.2;
vel.0.z +=
@ -83,7 +107,7 @@ impl<'a> System<'a> for Sys {
}
let animation = if on_ground {
if control.move_dir.magnitude() > 0.01 {
if inputs.move_dir.magnitude() > 0.01 {
Animation::Run
} else {
Animation::Idle
@ -109,5 +133,41 @@ impl<'a> System<'a> for Sys {
},
);
}
for (entity, inputs, mut action, pos, dir) in (
&entities,
&mut inputs,
&mut actions,
&positions,
&mut directions,
)
.join()
{
// Handle event-based inputs
for event in inputs.events.drain(..) {
match event {
InputEvent::Attack => {
// Attack delay
if action.attack_time.is_some() {
continue;
}
for (b, pos_b, mut stat_b, mut vel_b) in
(&entities, &positions, &mut stats, &mut velocities).join()
{
if entity != b
&& pos.0.distance_squared(pos_b.0) < 50.0
&& dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
action.attack_time = Some(0.0);
stat_b.hp.change_by(-10); // TODO: variable damage
vel_b.0 += (pos_b.0 - pos.0).normalized() * 20.0;
vel_b.0.z = 20.0;
force_updates.insert(b, ForceUpdate);
}
}
}
InputEvent::Jump => {}
}
}
}
}
}

View File

@ -1,7 +1,7 @@
pub mod action;
pub mod actions;
pub mod agent;
pub mod animation;
pub mod control;
pub mod inputs;
pub mod phys;
mod stats;
@ -10,21 +10,18 @@ use specs::DispatcherBuilder;
// System names
const AGENT_SYS: &str = "agent_sys";
const CONTROL_SYS: &str = "control_sys";
const INPUTS_SYS: &str = "inputs_sys";
const ACTIONS_SYS: &str = "actions_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 ANIMATION_SYS: &str = "animation_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(anim::Sys, ANIM_SYS, &[]);
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(action::Sys, ACTION_SYS, &[]);
dispatch_builder.add(inputs::Sys, INPUTS_SYS, &[]);
dispatch_builder.add(actions::Sys, ACTIONS_SYS, &[]);
dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[ACTION_SYS]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[INPUTS_SYS]);
}

View File

@ -4,7 +4,7 @@ use vek::*;
// Crate
use crate::{
comp::{Control, Dying, Stats},
comp::{Dying, Stats},
state::DeltaTime,
};

View File

@ -214,7 +214,6 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
"Bungo".to_owned(),
comp::Body::Quadruped(comp::QuadrupedBody::random()),
)
.with(comp::Control::default())
.with(comp::Agent::Pet {
target: entity,
offset: Vec2::zero(),

View File

@ -98,7 +98,7 @@ impl Server {
"Tobermory".to_owned(),
comp::Body::Humanoid(comp::HumanoidBody::random()),
)
.with(comp::Control::default())
.with(comp::Actions::default())
.with(comp::Agent::Wanderer(Vec2::zero()))
.build();
}
@ -133,7 +133,8 @@ 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::Actions::new())
.with(comp::Inputs::default())
.with(comp::Actions::default())
.with(comp::Actor::Character { name, body })
.with(comp::Stats::default())
}
@ -149,18 +150,15 @@ impl Server {
state.write_component(entity, comp::Actor::Character { name, body });
state.write_component(entity, comp::Stats::default());
state.write_component(entity, comp::Inputs::default());
state.write_component(entity, comp::Actions::default());
state.write_component(entity, comp::AnimationInfo::new());
state.write_component(entity, comp::phys::Pos(spawn_point));
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
// Make sure everything is accepted.
// Make sure physics are accepted.
state.write_component(entity, comp::phys::ForceUpdate);
// Set initial animation.
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.allow_state(ClientState::Character);
}
@ -391,13 +389,13 @@ impl Server {
| ClientState::Spectator
| ClientState::Character => new_chat_msgs.push((entity, msg)),
},
ClientMsg::PlayerActions(mut actions) => {
ClientMsg::PlayerInputs(mut inputs) => {
state
.ecs_mut()
.write_storage::<comp::Actions>()
.write_storage::<comp::Inputs>()
.get_mut(entity)
.map(|s| {
s.0.append(&mut actions.0);
s.events.append(&mut inputs.events);
});
}
ClientMsg::PlayerAnimation(animation_info) => {
@ -613,7 +611,7 @@ impl Server {
.remove(entity);
self.state
.ecs_mut()
.write_storage::<comp::Actions>()
.write_storage::<comp::Inputs>()
.remove(entity);
if let Some(client) = self.clients.get_mut(&entity) {
client.force_state(ClientState::Registered);

View File

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

View File

@ -7,8 +7,8 @@ use crate::{
window::{Event, Key, Window},
Direction, Error, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client, Input, InputEvent};
use common::{clock::Clock, msg::ClientState};
use client::{self, Client};
use common::{comp, clock::Clock, msg::ClientState};
use glutin::MouseButton;
use std::{cell::RefCell, mem, rc::Rc, time::Duration};
use vek::*;
@ -19,7 +19,7 @@ pub struct SessionState {
scene: Scene,
client: Rc<RefCell<Client>>,
key_state: KeyState,
input_events: Vec<InputEvent>,
input_events: Vec<comp::InputEvent>,
hud: Hud,
}
@ -65,7 +65,7 @@ impl SessionState {
mem::swap(&mut self.input_events, &mut input_events);
for event in self.client.borrow_mut().tick(
Input {
comp::Inputs {
move_dir,
jumping: self.key_state.jump,
gliding: self.key_state.glide,
@ -144,7 +144,7 @@ impl PlayState for SessionState {
}
// Attack key pressed
Event::Click(MouseButton::Left, state) => {
self.input_events.push(InputEvent::AttackStarted)
self.input_events.push(comp::InputEvent::Attack)
}
// Movement key pressed
Event::KeyDown(Key::MoveForward) => self.key_state.up = true,
@ -152,7 +152,7 @@ impl PlayState for SessionState {
Event::KeyDown(Key::MoveLeft) => self.key_state.left = true,
Event::KeyDown(Key::MoveRight) => self.key_state.right = true,
Event::KeyDown(Key::Jump) => {
self.input_events.push(InputEvent::Jump);
self.input_events.push(comp::InputEvent::Jump);
self.key_state.jump = true;
}
Event::KeyDown(Key::Glide) => self.key_state.glide = true,