Move from state components to single CharaterState struct

This makes split animations easy and improves overall code quality
This commit is contained in:
timokoesters 2019-08-23 12:11:37 +02:00
parent 2211f79d28
commit 5d5ccd7b99
No known key found for this signature in database
GPG Key ID: CD80BE9AAEE78097
19 changed files with 447 additions and 474 deletions

View File

@ -444,12 +444,12 @@ impl Client {
self.state.write_component(entity, ori);
}
}
ServerMsg::EntityActionState {
ServerMsg::EntityCharacterState {
entity,
action_state,
character_state,
} => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) {
self.state.write_component(entity, action_state);
self.state.write_component(entity, character_state);
}
}
ServerMsg::InventoryUpdate(inventory) => {

View File

@ -188,8 +188,8 @@ impl Asset for String {
}
}
/// Lazy static to find and cache where the asset directory is.
lazy_static! {
/// Lazy static to find and cache where the asset directory is.
static ref ASSETS_PATH: PathBuf = {
let mut paths = Vec::new();

View File

@ -1,29 +0,0 @@
use specs::{Component, FlaggedStorage, HashMapStorage};
use specs_idvs::IDVStorage;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct ActionState {
pub moving: bool,
pub on_ground: bool,
pub attacking: bool,
pub rolling: bool,
pub gliding: bool,
pub wielding: bool,
}
impl Default for ActionState {
fn default() -> Self {
Self {
moving: false,
on_ground: false,
attacking: false,
rolling: false,
gliding: false,
wielding: false,
}
}
}
impl Component for ActionState {
type Storage = FlaggedStorage<Self, HashMapStorage<Self>>;
}

View File

@ -0,0 +1,68 @@
use specs::{Component, FlaggedStorage, HashMapStorage};
use specs_idvs::IDVStorage;
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum MovementState {
Stand,
Run,
Jump,
Glide,
Roll { time_left: Duration },
//Swim,
}
impl MovementState {
pub fn is_roll(&self) -> bool {
if let Self::Roll { .. } = self {
true
} else {
false
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum ActionState {
Idle,
Wield { time_left: Duration },
Attack { time_left: Duration, applied: bool },
//Carry,
}
impl ActionState {
pub fn is_wield(&self) -> bool {
if let Self::Wield { .. } = self {
true
} else {
false
}
}
pub fn is_attack(&self) -> bool {
if let Self::Attack { .. } = self {
true
} else {
false
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct CharacterState {
pub movement: MovementState,
pub action: ActionState,
}
impl Default for CharacterState {
fn default() -> Self {
Self {
movement: MovementState::Jump,
action: ActionState::Idle,
}
}
}
impl Component for CharacterState {
type Storage = FlaggedStorage<Self, HashMapStorage<Self>>;
}

View File

@ -1,8 +1,8 @@
mod action_state;
mod admin;
mod agent;
mod animation;
mod body;
mod character_state;
mod controller;
mod inputs;
mod inventory;
@ -13,18 +13,16 @@ mod stats;
mod visual;
// Reexports
pub use action_state::ActionState;
pub use admin::Admin;
pub use agent::Agent;
pub use animation::{Animation, AnimationInfo};
pub use body::{humanoid, object, quadruped, quadruped_medium, Body};
pub use character_state::{ActionState, CharacterState, MovementState};
pub use controller::Controller;
pub use inputs::{
Attacking, CanBuild, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling, Wielding,
};
pub use inputs::CanBuild;
pub use inventory::{item, Inventory, InventoryUpdate, Item};
pub use last::Last;
pub use phys::{ForceUpdate, Ori, Pos, Scale, Vel};
pub use phys::{ForceUpdate, Ori, PhysicsState, Pos, Scale, Vel};
pub use player::Player;
pub use stats::{Dying, Exp, HealthSource, Level, Stats};
pub use stats::{Exp, HealthSource, Level, Stats};
pub use visual::LightEmitter;

View File

@ -34,6 +34,16 @@ impl Component for Scale {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
// PhysicsState
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhysicsState {
pub on_ground: bool,
}
impl Component for PhysicsState {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
// ForceUpdate
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct ForceUpdate;

View File

@ -1,11 +1,24 @@
use crate::comp;
use parking_lot::Mutex;
use specs::Entity as EcsEntity;
use std::{collections::VecDeque, ops::DerefMut};
use vek::*;
pub enum Event {
LandOnGround { entity: EcsEntity, vel: Vec3<f32> },
Explosion { pos: Vec3<f32>, radius: f32 },
LandOnGround {
entity: EcsEntity,
vel: Vec3<f32>,
},
Explosion {
pos: Vec3<f32>,
radius: f32,
},
Die {
entity: EcsEntity,
cause: comp::HealthSource,
},
Jump(EcsEntity),
Respawn(EcsEntity),
}
#[derive(Default)]

View File

@ -51,9 +51,9 @@ pub enum ServerMsg {
entity: u64,
ori: comp::Ori,
},
EntityActionState {
EntityCharacterState {
entity: u64,
action_state: comp::ActionState,
character_state: comp::CharacterState,
},
InventoryUpdate(comp::Inventory),
TerrainChunkUpdate {

View File

@ -122,7 +122,8 @@ impl State {
ecs.register::<comp::Controller>();
// Register components send directly from server -> all but one client
ecs.register::<comp::ActionState>();
ecs.register::<comp::CharacterState>();
ecs.register::<comp::PhysicsState>();
// Register components synced from client -> server -> all other clients
ecs.register::<comp::Pos>();
@ -132,27 +133,17 @@ impl State {
// Register client-local components
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Jumping>();
// Register server-local components
ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Last<comp::ActionState>>();
ecs.register::<comp::Last<comp::CharacterState>>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Respawning>();
ecs.register::<comp::Dying>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Inventory>();
ecs.register::<comp::Admin>();
// Controller effects
ecs.register::<comp::MoveDir>();
ecs.register::<comp::OnGround>();
ecs.register::<comp::Attacking>();
ecs.register::<comp::Wielding>();
ecs.register::<comp::Rolling>();
ecs.register::<comp::Gliding>();
// Register synced resources used by the ECS.
ecs.add_resource_synced(TimeOfDay(0.0));

View File

@ -1,69 +0,0 @@
use crate::{
comp::{ActionState, Attacking, Controller, Gliding, OnGround, Rolling, Vel, Wielding},
sys::movement::MOVEMENT_THRESHOLD_VEL,
};
use specs::{Entities, Join, ReadStorage, System, WriteStorage};
/// This system will set the ActionState component as specified by other components
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Controller>,
ReadStorage<'a, Vel>,
ReadStorage<'a, OnGround>,
ReadStorage<'a, Gliding>,
ReadStorage<'a, Attacking>,
ReadStorage<'a, Wielding>,
ReadStorage<'a, Rolling>,
WriteStorage<'a, ActionState>,
);
fn run(
&mut self,
(
entities,
controllers, // To make sure it only runs on the single client and the server
velocities,
on_grounds,
glidings,
attackings,
wieldings,
rollings,
mut action_states,
): Self::SystemData,
) {
for (
_entity,
vel,
_controller,
on_ground,
gliding,
attacking,
wielding,
rolling,
action_state,
) in (
&entities,
&velocities,
&controllers,
on_grounds.maybe(),
glidings.maybe(),
attackings.maybe(),
wieldings.maybe(),
rollings.maybe(),
&mut action_states,
)
.join()
{
*action_state = ActionState {
on_ground: on_ground.is_some(),
moving: vel.0.magnitude_squared() > MOVEMENT_THRESHOLD_VEL.powf(2.0),
attacking: attacking.is_some(),
wielding: wielding.is_some(),
rolling: rolling.is_some(),
gliding: gliding.is_some(),
};
}
}
}

View File

@ -1,4 +1,4 @@
use crate::comp::{ActionState, Agent, Controller, Pos, Stats};
use crate::comp::{Agent, CharacterState, Controller, MovementState::Glide, Pos, Stats};
use rand::{seq::SliceRandom, thread_rng};
use specs::{Entities, Join, ReadStorage, System, WriteStorage};
use vek::*;
@ -10,14 +10,14 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>,
ReadStorage<'a, ActionState>,
ReadStorage<'a, CharacterState>,
WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>,
);
fn run(
&mut self,
(entities, positions, stats, action_states, mut agents, mut controllers): Self::SystemData,
(entities, positions, stats, character_states, mut agents, mut controllers): Self::SystemData,
) {
for (entity, pos, agent, controller) in
(&entities, &positions, &mut agents, &mut controllers).join()
@ -67,12 +67,12 @@ impl<'a> System<'a> for Sys {
const SIGHT_DIST: f32 = 30.0;
let mut choose_new = false;
if let Some((Some(target_pos), Some(target_stats), Some(a))) =
if let Some((Some(target_pos), Some(target_stats), Some(target_character))) =
target.map(|target| {
(
positions.get(target),
stats.get(target),
action_states.get(target),
character_states.get(target),
)
})
{
@ -97,7 +97,8 @@ impl<'a> System<'a> for Sys {
controller.roll = true;
}
if a.gliding && target_pos.0.z > pos.0.z + 5.0 {
if target_character.movement == Glide && target_pos.0.z > pos.0.z + 5.0
{
controller.glide = true;
controller.jump = true;
}

View File

@ -1,8 +1,11 @@
use crate::{
comp::{ActionState, Animation, AnimationInfo},
comp::{
ActionState::*, Animation, AnimationInfo, CharacterState, MovementState::*, PhysicsState,
},
state::DeltaTime,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::fmt::Debug;
/// This system will apply the animation that fits best to the users actions
pub struct Sys;
@ -10,37 +13,32 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
ReadStorage<'a, ActionState>,
ReadStorage<'a, CharacterState>,
ReadStorage<'a, PhysicsState>,
WriteStorage<'a, AnimationInfo>,
);
fn run(&mut self, (entities, dt, action_states, mut animation_infos): Self::SystemData) {
for (entity, a) in (&entities, &action_states).join() {
fn impossible_animation(message: &str) -> Animation {
warn!("{}", message);
Animation::Idle
fn run(
&mut self,
(entities, dt, character_states, physics_states, mut animation_infos): Self::SystemData,
) {
for (entity, character, physics) in (&entities, &character_states, &physics_states).join() {
fn impossible_animation(physics: PhysicsState, character: CharacterState) -> Animation {
warn!("Impossible animation: {:?} {:?}", physics, character);
Animation::Roll
}
let animation = match (
a.on_ground,
a.moving,
a.attacking,
a.gliding,
a.rolling,
a.wielding,
) {
(_, _, 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, false) => Animation::Idle,
(true, true, false, false, false, false) => Animation::Run,
(false, _, false, false, false, false) => Animation::Jump,
(true, false, false, false, false, true) => Animation::Cidle,
(true, true, false, false, false, true) => Animation::Crun,
(false, _, false, false, false, true) => Animation::Cjump,
(_, _, false, true, false, _) => Animation::Gliding,
(_, _, true, false, false, _) => Animation::Attack,
let animation = match (physics.on_ground, &character.movement, &character.action) {
(_, Roll { .. }, Idle) => Animation::Roll,
(true, Stand, Idle) => Animation::Idle,
(true, Run, Idle) => Animation::Run,
(false, Jump, Idle) => Animation::Jump,
(true, Stand, Wield { .. }) => Animation::Cidle,
(true, Run, Wield { .. }) => Animation::Crun,
(false, Jump, Wield { .. }) => Animation::Cjump,
(_, Glide, Idle) => Animation::Gliding,
(_, _, Attack { .. }) => Animation::Attack,
_ => impossible_animation(physics.clone(), character.clone()),
};
let new_time = animation_infos

View File

@ -1,8 +1,9 @@
use crate::{
comp::{Attacking, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel, Wielding},
comp::{ActionState::*, CharacterState, ForceUpdate, HealthSource, Ori, Pos, Stats, Vel},
state::{DeltaTime, Uid},
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::time::Duration;
/// This system is responsible for handling accepted inputs like moving or attacking
pub struct Sys;
@ -14,8 +15,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Wielding>,
WriteStorage<'a, CharacterState>,
WriteStorage<'a, Stats>,
WriteStorage<'a, ForceUpdate>,
);
@ -29,18 +29,26 @@ impl<'a> System<'a> for Sys {
positions,
orientations,
mut velocities,
mut attackings,
mut wieldings,
mut character_states,
mut stats,
mut force_updates,
): Self::SystemData,
) {
// Attacks
(&entities, &uids, &positions, &orientations, &mut attackings)
for (entity, uid, pos, ori, mut character) in (
&entities,
&uids,
&positions,
&orientations,
&mut character_states,
)
.join()
.filter_map(|(entity, uid, pos, ori, mut attacking)| {
if !attacking.applied {
// Go through all other entities
{
let mut todo_end = false;
// Go through all other entities
if let Attack { time_left, applied } = &mut character.action {
if !*applied {
for (b, pos_b, mut vel_b, stat_b) in
(&entities, &positions, &mut velocities, &mut stats).join()
{
@ -59,29 +67,28 @@ impl<'a> System<'a> for Sys {
let _ = force_updates.insert(b, ForceUpdate);
}
}
attacking.applied = true;
*applied = true;
}
if attacking.time > 0.5 {
Some(entity)
if *time_left == Duration::default() {
todo_end = true;
} else {
attacking.time += dt.0;
None
*time_left = time_left
.checked_sub(Duration::from_secs_f32(dt.0))
.unwrap_or_default();
}
})
.collect::<Vec<_>>()
.into_iter()
.for_each(|e| {
attackings.remove(e);
});
{
// Wields
for wielding in (&mut wieldings).join() {
if !wielding.applied && wielding.time > 0.3 {
wielding.applied = true;
} else {
wielding.time += dt.0;
}
if todo_end {
character.action = Wield {
time_left: Duration::default(),
};
}
if let Wield { time_left } = &mut character.action {
if *time_left != Duration::default() {
*time_left = time_left
.checked_sub(Duration::from_secs_f32(dt.0))
.unwrap_or_default();
}
}
}

View File

@ -1,125 +1,135 @@
use crate::comp::{
ActionState, Attacking, Body, Controller, Gliding, Jumping, MoveDir, Respawning, Rolling,
Stats, Vel, Wielding,
use crate::{
comp::{
ActionState::*, Body, CharacterState, Controller, MovementState::*, PhysicsState, Stats,
Vel,
},
event::{Event, EventBus},
};
use specs::{Entities, Join, ReadStorage, System, WriteStorage};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::time::Duration;
/// This system is responsible for validating controller inputs
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, EventBus>,
WriteStorage<'a, Controller>,
ReadStorage<'a, Stats>,
ReadStorage<'a, Body>,
ReadStorage<'a, Vel>,
WriteStorage<'a, ActionState>,
WriteStorage<'a, MoveDir>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Attacking>,
WriteStorage<'a, Wielding>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, Respawning>,
WriteStorage<'a, Gliding>,
ReadStorage<'a, PhysicsState>,
WriteStorage<'a, CharacterState>,
);
fn run(
&mut self,
(
entities,
event_bus,
mut controllers,
stats,
bodies,
velocities,
mut action_states,
mut move_dirs,
mut jumpings,
mut attackings,
mut wieldings,
mut rollings,
mut respawns,
mut glidings,
physics_states,
mut character_states,
): Self::SystemData,
) {
for (entity, controller, stats, body, vel, mut a) in (
let mut event_emitter = event_bus.emitter();
for (entity, controller, stats, body, vel, physics, mut character) in (
&entities,
&mut controllers,
&stats,
&bodies,
&velocities,
// Although this is changed, it is only kept for this system
// as it will be replaced in the action state system
&mut action_states,
&physics_states,
&mut character_states,
)
.join()
{
if stats.is_dead {
// Respawn
if controller.respawn {
let _ = respawns.insert(entity, Respawning);
event_emitter.emit(Event::Respawn(entity));
}
continue;
}
// Move dir
if !a.rolling {
let _ = move_dirs.insert(
entity,
MoveDir(if controller.move_dir.magnitude_squared() > 1.0 {
controller.move_dir.normalized()
} else {
controller.move_dir
}),
);
// Move
controller.move_dir = if controller.move_dir.magnitude_squared() > 1.0 {
controller.move_dir.normalized()
} else {
controller.move_dir
};
if character.movement == Stand && controller.move_dir.magnitude_squared() > 0.0 {
character.movement = Run;
} else if character.movement == Run && controller.move_dir.magnitude_squared() == 0.0 {
character.movement = Stand;
}
// Glide
if controller.glide && !a.on_ground && !a.attacking && !a.rolling && body.is_humanoid()
if controller.glide
&& !physics.on_ground
&& (character.action == Idle || character.action.is_wield())
&& character.movement == Jump
// TODO: Ask zesterer if we can remove this
&& body.is_humanoid()
{
let _ = glidings.insert(entity, Gliding);
a.gliding = true;
} else {
let _ = glidings.remove(entity);
a.gliding = false;
character.movement = Glide;
} else if !controller.glide && character.movement == Glide {
character.movement = Jump;
}
// Wield
if controller.attack && !a.wielding && !a.gliding && !a.rolling {
let _ = wieldings.insert(entity, Wielding::start());
a.wielding = true;
if controller.attack
&& character.action == Idle
&& (character.movement == Stand || character.movement == Run)
{
character.action = Wield {
time_left: Duration::from_millis(300),
};
}
// Attack
if controller.attack
&& !a.attacking
&& wieldings.get(entity).map(|w| w.applied).unwrap_or(false)
&& !a.gliding
&& !a.rolling
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
{
let _ = attackings.insert(entity, Attacking::start());
a.attacking = true;
// TODO: Check if wield ability exists
if let Wield { time_left } = character.action {
if time_left == Duration::default() {
character.action = Attack {
time_left: Duration::from_millis(300),
applied: false,
};
}
}
}
// Roll
if controller.roll
&& !a.rolling
&& a.on_ground
&& a.moving
&& !a.attacking
&& !a.gliding
&& (character.action == Idle || character.action.is_wield())
&& character.movement == Run
&& physics.on_ground
{
let _ = rollings.insert(entity, Rolling::start());
a.rolling = true;
character.movement = Roll {
time_left: Duration::from_millis(600),
};
}
// Jump
if controller.jump && a.on_ground && vel.0.z <= 0.0 {
let _ = jumpings.insert(entity, Jumping);
a.on_ground = false;
if controller.jump && physics.on_ground && vel.0.z <= 0.0 {
dbg!();
event_emitter.emit(Event::Jump(entity));
}
// TODO before merge: reset controller in a final ecs system
// Reset the controller ready for the next tick
*controller = Controller::default();
//*controller = Controller::default();
}
}
}

View File

@ -1,4 +1,3 @@
mod action_state;
pub mod agent;
pub mod animation;
pub mod combat;
@ -13,7 +12,6 @@ use specs::DispatcherBuilder;
// System names
const AGENT_SYS: &str = "agent_sys";
const CONTROLLER_SYS: &str = "controller_sys";
const ACTION_STATE_SYS: &str = "action_state_sys";
const PHYS_SYS: &str = "phys_sys";
const MOVEMENT_SYS: &str = "movement_sys";
const COMBAT_SYS: &str = "combat_sys";
@ -25,12 +23,7 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[PHYS_SYS]);
dispatch_builder.add(
action_state::Sys,
ACTION_STATE_SYS,
&[CONTROLLER_SYS, PHYS_SYS],
);
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[ACTION_STATE_SYS]);
dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[ACTION_STATE_SYS]);
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(animation::Sys, ANIMATION_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
}

View File

@ -1,10 +1,14 @@
use crate::{
comp::{ActionState, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Stats, Vel, Wielding},
comp::{
ActionState::*, CharacterState, Controller, MovementState::*, Ori, PhysicsState, Pos,
Stats, Vel,
},
state::DeltaTime,
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use std::time::Duration;
use vek::*;
const HUMANOID_ACCEL: f32 = 70.0;
@ -13,7 +17,6 @@ const WIELD_ACCEL: f32 = 70.0;
const WIELD_SPEED: f32 = 120.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_JUMP_ACCEL: f32 = 18.0;
const ROLL_ACCEL: f32 = 160.0;
const ROLL_SPEED: f32 = 550.0;
const GLIDE_ACCEL: f32 = 15.0;
@ -30,13 +33,10 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>,
ReadStorage<'a, MoveDir>,
ReadStorage<'a, Stats>,
ReadStorage<'a, ActionState>,
WriteStorage<'a, Jumping>,
WriteStorage<'a, Wielding>,
WriteStorage<'a, Rolling>,
WriteStorage<'a, OnGround>,
ReadStorage<'a, Controller>,
ReadStorage<'a, PhysicsState>,
WriteStorage<'a, CharacterState>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
@ -48,105 +48,87 @@ impl<'a> System<'a> for Sys {
entities,
terrain,
dt,
move_dirs,
stats,
action_states,
mut jumpings,
mut wieldings,
mut rollings,
mut on_grounds,
controllers,
physics_states,
mut character_states,
mut positions,
mut velocities,
mut orientations,
): Self::SystemData,
) {
// Apply movement inputs
for (entity, stats, a, move_dir, mut pos, mut vel, mut ori) in (
for (entity, stats, controller, physics, mut character, mut pos, mut vel, mut ori) in (
&entities,
&stats,
&action_states,
move_dirs.maybe(),
&controllers,
&physics_states,
&mut character_states,
&mut positions,
&mut velocities,
&mut orientations,
)
.join()
{
// Disable while dead TODO: Replace with client states?
if stats.is_dead {
continue;
}
// Move player according to move_dir
if let Some(move_dir) = move_dir {
vel.0 += Vec2::broadcast(dt.0)
* move_dir.0
* match (a.on_ground, a.gliding, a.rolling, a.wielding) {
(true, false, false, false)
if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) =>
{
HUMANOID_ACCEL
}
(false, true, false, false)
if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) =>
{
GLIDE_ACCEL
}
(false, false, false, false)
if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) =>
{
HUMANOID_AIR_ACCEL
}
(true, false, true, _)
if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) =>
{
ROLL_ACCEL
}
(true, false, false, true)
if vel.0.magnitude_squared() < WIELD_SPEED.powf(2.0) =>
{
WIELD_ACCEL
}
_ => 0.0,
};
// Set direction based on move direction when on the ground
let ori_dir = if a.gliding || a.rolling {
Vec2::from(vel.0)
} else {
move_dir.0
vel.0 += Vec2::broadcast(dt.0)
* controller.move_dir
* match (physics.on_ground, &character.movement) {
(true, Run) if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => {
HUMANOID_ACCEL
}
(false, Glide) if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) => {
GLIDE_ACCEL
}
(false, Jump) if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) => {
HUMANOID_AIR_ACCEL
}
(true, Roll { .. }) if vel.0.magnitude_squared() < ROLL_SPEED.powf(2.0) => {
ROLL_ACCEL
}
_ => 0.0,
};
if ori_dir.magnitude_squared() > 0.0001
&& (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
ori.0 = vek::ops::Slerp::slerp(
ori.0,
ori_dir.into(),
if a.on_ground { 12.0 } else { 2.0 } * dt.0,
);
}
}
// Jump
if jumpings.get(entity).is_some() {
vel.0.z = HUMANOID_JUMP_ACCEL;
jumpings.remove(entity);
// Set direction based on move direction when on the ground
let ori_dir = if character.movement == Glide || character.movement.is_roll() {
Vec2::from(vel.0)
} else {
controller.move_dir
};
if ori_dir.magnitude_squared() > 0.0001
&& (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
ori.0 = vek::ops::Slerp::slerp(
ori.0,
ori_dir.into(),
if physics.on_ground { 12.0 } else { 2.0 } * dt.0,
);
}
// Glide
if a.gliding && vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) && vel.0.z < 0.0 {
let _ = wieldings.remove(entity);
if character.movement == Glide
&& vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0)
&& vel.0.z < 0.0
{
character.action = Idle;
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) {
let _ = wieldings.remove(entity);
*time += dt.0;
if *time > 0.6 || !a.moving {
rollings.remove(entity);
if let Roll { time_left } = &mut character.movement {
character.action = Idle;
if *time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 {
character.movement = Run;
} else {
*time_left = time_left
.checked_sub(Duration::from_secs_f32(dt.0))
.unwrap_or_default();
}
}
}

View File

@ -1,16 +1,14 @@
use crate::{
comp::HealthSource,
comp::{
ActionState, Body, Jumping, MoveDir, OnGround, Ori, Pos, Rolling, Scale, Stats, Vel,
Wielding,
use {
crate::{
comp::{Body, CharacterState, MovementState::*, Ori, PhysicsState, Pos, Scale, Stats, Vel},
event::{Event, EventBus},
state::DeltaTime,
terrain::TerrainMap,
vol::{ReadVol, Vox},
},
event::{Event, EventBus},
state::DeltaTime,
terrain::TerrainMap,
vol::{ReadVol, Vox},
specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage},
vek::*,
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
const GRAVITY: f32 = 9.81 * 4.0;
const FRIC_GROUND: f32 = 0.15;
@ -37,10 +35,10 @@ impl<'a> System<'a> for Sys {
ReadExpect<'a, TerrainMap>,
Read<'a, DeltaTime>,
Read<'a, EventBus>,
ReadStorage<'a, ActionState>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Body>,
WriteStorage<'a, OnGround>,
WriteStorage<'a, CharacterState>,
WriteStorage<'a, PhysicsState>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
@ -53,10 +51,10 @@ impl<'a> System<'a> for Sys {
terrain,
dt,
event_bus,
action_states,
scales,
bodies,
mut on_grounds,
mut character_states,
mut physics_states,
mut positions,
mut velocities,
mut orientations,
@ -65,9 +63,8 @@ impl<'a> System<'a> for Sys {
let mut event_emitter = event_bus.emitter();
// Apply movement inputs
for (entity, a, scale, b, mut pos, mut vel, mut ori) in (
for (entity, scale, b, mut pos, mut vel, mut ori) in (
&entities,
&action_states,
scales.maybe(),
&bodies,
&mut positions,
@ -76,12 +73,14 @@ impl<'a> System<'a> for Sys {
)
.join()
{
let mut character_state = character_states.get(entity).cloned().unwrap_or_default();
let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default();
let scale = scale.map(|s| s.0).unwrap_or(1.0);
// Integrate forces
// Friction is assumed to be a constant dependent on location
let friction = 50.0
* if on_grounds.get(entity).is_some() {
* if physics_state.on_ground {
FRIC_GROUND
} else {
FRIC_AIR
@ -108,7 +107,7 @@ impl<'a> System<'a> for Sys {
if terrain
.get(block_pos)
.map(|vox| vox.is_solid())
.map(|vox| !vox.is_empty())
.unwrap_or(false)
{
let player_aabb = Aabb {
@ -128,8 +127,8 @@ impl<'a> System<'a> for Sys {
false
};
let was_on_ground = a.on_ground;
on_grounds.remove(entity); // Assume we're in the air - unless we can prove otherwise
let was_on_ground = physics_state.on_ground;
physics_state.on_ground = false;
let mut on_ground = false;
let mut attempts = 0; // Don't loop infinitely here
@ -183,7 +182,7 @@ impl<'a> System<'a> for Sys {
.filter(|(block_pos, _)| {
terrain
.get(*block_pos)
.map(|vox| vox.is_solid())
.map(|vox| !vox.is_empty())
.unwrap_or(false)
})
// Find the maximum of the minimum collision axes (this bit is weird, trust me that it works)
@ -216,6 +215,7 @@ impl<'a> System<'a> for Sys {
if !was_on_ground {
event_emitter.emit(Event::LandOnGround { entity, vel: vel.0 });
character_state.movement = Stand;
}
}
@ -257,13 +257,16 @@ impl<'a> System<'a> for Sys {
if attempts == MAX_ATTEMPTS {
pos.0 = old_pos;
vel.0 = Vec3::zero();
break;
}
}
if on_ground {
let _ = on_grounds.insert(entity, OnGround);
physics_state.on_ground = true;
if !was_on_ground {
character_state.movement = Stand;
}
// If the space below us is free, then "snap" to the ground
} else if collision_with(pos.0 - Vec3::unit_z() * 1.05, near_iter.clone())
&& vel.0.z < 0.0
@ -271,8 +274,13 @@ impl<'a> System<'a> for Sys {
&& was_on_ground
{
pos.0.z = (pos.0.z - 0.05).floor();
let _ = on_grounds.insert(entity, OnGround);
physics_state.on_ground = true;
} else if was_on_ground {
character_state.movement = Jump;
}
let _ = character_states.insert(entity, character_state);
let _ = physics_states.insert(entity, physics_state);
}
// Apply pushback

View File

@ -1,5 +1,6 @@
use crate::{
comp::{Dying, HealthSource, Stats},
comp::{HealthSource, Stats},
event::{Event, EventBus},
state::DeltaTime,
};
use log::warn;
@ -11,27 +12,29 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
Read<'a, EventBus>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Dying>,
);
fn run(&mut self, (entities, dt, mut stats, mut dyings): Self::SystemData) {
fn run(&mut self, (entities, dt, event_bus, mut stats): Self::SystemData) {
let mut event_emitter = event_bus.emitter();
for (entity, mut stat) in (&entities, &mut stats).join() {
if stat.should_die() && !stat.is_dead {
let _ = dyings.insert(
event_emitter.emit(Event::Die {
entity,
Dying {
cause: match stat.health.last_change {
Some(change) => change.2,
None => {
warn!("Nothing caused an entity to die!");
HealthSource::Unknown
}
},
cause: match stat.health.last_change {
Some(change) => change.2,
None => {
warn!("Nothing caused an entity to die!");
HealthSource::Unknown
}
},
);
});
stat.is_dead = true;
}
if let Some(change) = &mut stat.health.last_change {
change.1 += f64::from(dt.0);
}

View File

@ -37,6 +37,7 @@ use vek::*;
use world::{ChunkSupplement, World};
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
const HUMANOID_JUMP_ACCEL: f32 = 18.0;
pub enum Event {
ClientConnected {
@ -153,8 +154,7 @@ impl Server {
.with(comp::Controller::default())
.with(body)
.with(comp::Stats::new(name))
.with(comp::ActionState::default())
.with(comp::ForceUpdate)
.with(comp::CharacterState::default())
}
/// Build a static object entity
@ -175,8 +175,7 @@ impl Server {
..comp::LightEmitter::default()
})
//.with(comp::LightEmitter::default())
.with(comp::ActionState::default())
.with(comp::ForceUpdate)
.with(comp::CharacterState::default())
}
pub fn create_player_character(
@ -195,7 +194,7 @@ impl Server {
state.write_component(entity, comp::Pos(spawn_point));
state.write_component(entity, comp::Vel(Vec3::zero()));
state.write_component(entity, comp::Ori(Vec3::unit_y()));
state.write_component(entity, comp::ActionState::default());
state.write_component(entity, comp::CharacterState::default());
state.write_component(entity, comp::Inventory::default());
state.write_component(entity, comp::InventoryUpdate);
// Make sure physics are accepted.
@ -221,6 +220,9 @@ impl Server {
let terrain = self.state.ecs().read_resource::<TerrainMap>();
let mut block_change = self.state.ecs().write_resource::<BlockChange>();
let mut stats = self.state.ecs().write_storage::<comp::Stats>();
let mut positions = self.state.ecs().write_storage::<comp::Pos>();
let mut velocities = self.state.ecs().write_storage::<comp::Vel>();
let mut force_updates = self.state.ecs().write_storage::<comp::ForceUpdate>();
for event in self.state.ecs().read_resource::<EventBus>().recv_all() {
match event {
@ -250,6 +252,74 @@ impl Server {
.cast();
}
}
GameEvent::Jump(entity) => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0.z = HUMANOID_JUMP_ACCEL;
let _ = force_updates.insert(entity, comp::ForceUpdate);
}
}
GameEvent::Die { entity, cause } => {
// Chat message
if let Some(player) =
self.state.ecs().read_storage::<comp::Player>().get(entity)
{
let msg = if let comp::HealthSource::Attack { by } = cause {
self.state()
.ecs()
.entity_from_uid(by.into())
.and_then(|attacker| {
self.state
.ecs()
.read_storage::<comp::Player>()
.get(attacker)
.map(|attacker_alias| {
format!(
"{} was killed by {}",
&player.alias, &attacker_alias.alias
)
})
})
} else {
None
}
.unwrap_or(format!("{} died", &player.alias));
self.clients.notify_registered(ServerMsg::kill(msg));
}
// Give EXP to the client
if let Some(entity_stats) = stats.get(entity).cloned() {
if let comp::HealthSource::Attack { by } = cause {
self.state.ecs().entity_from_uid(by.into()).map(|attacker| {
if let Some(attacker_stats) = stats.get_mut(attacker) {
// TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats.exp.change_by(
entity_stats.health.maximum() as f64 / 10.0
+ entity_stats.level.level() as f64 * 10.0,
);
}
});
}
}
if let Some(client) = self.clients.get_mut(&entity) {
let _ = velocities.insert(entity, comp::Vel(Vec3::zero()));
let _ = force_updates.insert(entity, comp::ForceUpdate);
client.force_state(ClientState::Dead);
} else {
//let _ = self.state.ecs_mut().delete_entity_synced(entity);
continue;
}
}
GameEvent::Respawn(entity) => {
// Only clients can respawn
if let Some(client) = self.clients.get_mut(&entity) {
client.allow_state(ClientState::Character);
stats.get_mut(entity).map(|stats| stats.revive());
positions.get_mut(entity).map(|pos| pos.0.z += 20.0);
force_updates.insert(entity, comp::ForceUpdate);
}
}
}
}
}
@ -357,7 +427,7 @@ impl Server {
.with(comp::Controller::default())
.with(body)
.with(stats)
.with(comp::ActionState::default())
.with(comp::CharacterState::default())
.with(comp::Agent::enemy())
.with(comp::Scale(scale))
.build();
@ -475,13 +545,6 @@ impl Server {
// 7) Finish the tick, pass control back to the frontend.
// Cleanup
let ecs = self.state.ecs_mut();
for entity in ecs.entities().join() {
ecs.write_storage::<comp::Dying>().remove(entity);
ecs.write_storage::<comp::Respawning>().remove(entity);
}
Ok(frontend_events)
}
@ -885,12 +948,12 @@ impl Server {
state.write_component(entity, player);
// Sync physics of all entities
for (&uid, &pos, vel, ori, action_state) in (
for (&uid, &pos, vel, ori, character_state) in (
&state.ecs().read_storage::<Uid>(),
&state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position
state.ecs().read_storage::<comp::Vel>().maybe(),
state.ecs().read_storage::<comp::Ori>().maybe(),
state.ecs().read_storage::<comp::ActionState>().maybe(),
state.ecs().read_storage::<comp::CharacterState>().maybe(),
)
.join()
{
@ -910,10 +973,10 @@ impl Server {
ori,
});
}
if let Some(action_state) = action_state.copied() {
client.notify(ServerMsg::EntityActionState {
if let Some(character_state) = character_state.copied() {
client.notify(ServerMsg::EntityCharacterState {
entity: uid.into(),
action_state,
character_state,
});
}
}
@ -928,84 +991,7 @@ impl Server {
self.clients
.notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
// TODO: Move this into some new method like `handle_sys_outputs` right after ticking the world
// Handle deaths.
let ecs = self.state.ecs_mut();
let clients = &mut self.clients;
let todo_kill = (&ecs.entities(), &ecs.read_storage::<comp::Dying>())
.join()
.map(|(entity, dying)| {
// Chat message
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) {
let msg = if let comp::HealthSource::Attack { by } = dying.cause {
ecs.entity_from_uid(by.into()).and_then(|attacker| {
ecs.read_storage::<comp::Player>()
.get(attacker)
.map(|attacker_alias| {
format!(
"{} was killed by {}",
&player.alias, &attacker_alias.alias
)
})
})
} else {
None
}
.unwrap_or(format!("{} died", &player.alias));
clients.notify_registered(ServerMsg::kill(msg));
}
// Give EXP to the client
let mut stats = ecs.write_storage::<comp::Stats>();
if let Some(entity_stats) = stats.get(entity).cloned() {
if let comp::HealthSource::Attack { by } = dying.cause {
ecs.entity_from_uid(by.into()).map(|attacker| {
if let Some(attacker_stats) = stats.get_mut(attacker) {
// TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats.exp.change_by(
entity_stats.health.maximum() as f64 / 10.0
+ entity_stats.level.level() as f64 * 10.0,
);
}
});
}
}
entity
})
.collect::<Vec<_>>();
// Actually kill them
for entity in todo_kill {
if let Some(client) = self.clients.get_mut(&entity) {
let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero()));
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
client.force_state(ClientState::Dead);
} else {
let _ = ecs.delete_entity_synced(entity);
continue;
}
}
// Handle respawns
let todo_respawn = (&ecs.entities(), &ecs.read_storage::<comp::Respawning>())
.join()
.map(|(entity, _)| entity)
.collect::<Vec<EcsEntity>>();
for entity in todo_respawn {
if let Some(client) = self.clients.get_mut(&entity) {
client.allow_state(ClientState::Character);
ecs.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
ecs.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| pos.0.z += 20.0);
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
}
}
// Sync physics
for (entity, &uid, &pos, force_update) in (
@ -1043,7 +1029,7 @@ impl Server {
let mut last_pos = ecs.write_storage::<comp::Last<comp::Pos>>();
let mut last_vel = ecs.write_storage::<comp::Last<comp::Vel>>();
let mut last_ori = ecs.write_storage::<comp::Last<comp::Ori>>();
let mut last_action_state = ecs.write_storage::<comp::Last<comp::ActionState>>();
let mut last_character_state = ecs.write_storage::<comp::Last<comp::CharacterState>>();
if let Some(client_pos) = ecs.read_storage::<comp::Pos>().get(entity) {
if last_pos
@ -1099,16 +1085,19 @@ impl Server {
}
}
if let Some(client_action_state) = ecs.read_storage::<comp::ActionState>().get(entity) {
if last_action_state
if let Some(client_character_state) =
ecs.read_storage::<comp::CharacterState>().get(entity)
{
if last_character_state
.get(entity)
.map(|&l| l != *client_action_state)
.map(|&l| l != *client_character_state)
.unwrap_or(true)
{
let _ = last_action_state.insert(entity, comp::Last(*client_action_state));
let msg = ServerMsg::EntityActionState {
let _ =
last_character_state.insert(entity, comp::Last(*client_character_state));
let msg = ServerMsg::EntityCharacterState {
entity: uid.into(),
action_state: *client_action_state,
character_state: *client_character_state,
};
match force_update {
Some(_) => clients.notify_ingame_if(msg, in_vd),