mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Move from state components to single CharaterState struct
This makes split animations easy and improves overall code quality
This commit is contained in:
parent
2211f79d28
commit
5d5ccd7b99
@ -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) => {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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>>;
|
||||
}
|
68
common/src/comp/character_state.rs
Normal file
68
common/src/comp/character_state.rs
Normal 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>>;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)]
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user