mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
begin impl state handle traits
This commit is contained in:
parent
226826e026
commit
e074c7ce1d
@ -1,12 +1,142 @@
|
||||
use crate::comp::{Body, CharacterState, Controller, ControllerInputs, PhysicsState};
|
||||
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||
use specs::{Entities, Join, LazyUpdate, Read, ReadStorage, System};
|
||||
use sphynx::{Uid, UidAllocator};
|
||||
//use specs_idvs::IDVStorage;
|
||||
use self::{ActionState::*, MovementState::*};
|
||||
use std::time::Duration;
|
||||
pub trait State {
|
||||
fn handle(
|
||||
&self,
|
||||
character: &CharacterState,
|
||||
inputs: &ControllerInputs,
|
||||
body: &Body,
|
||||
physics: &PhysicsState,
|
||||
) -> CharacterState;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub struct RunData;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub struct StandData;
|
||||
|
||||
impl State for StandData {
|
||||
fn handle(
|
||||
&self,
|
||||
character: &CharacterState,
|
||||
inputs: &ControllerInputs,
|
||||
body: &Body,
|
||||
physics: &PhysicsState,
|
||||
) -> CharacterState {
|
||||
let mut new_move: MovementState = if inputs.move_dir.magnitude_squared() > 0.0 {
|
||||
MovementState::Run(RunData)
|
||||
} else {
|
||||
MovementState::Stand(StandData)
|
||||
};
|
||||
|
||||
// Try to sit
|
||||
if inputs.sit.is_pressed() && physics.on_ground && body.is_humanoid() {
|
||||
return CharacterState {
|
||||
movement: Sit,
|
||||
action: Idle,
|
||||
};
|
||||
}
|
||||
|
||||
// Try to climb
|
||||
if let (true, Some(_wall_dir)) = (
|
||||
inputs.climb.is_pressed() | inputs.climb_down.is_pressed() && body.is_humanoid(),
|
||||
physics.on_wall,
|
||||
) {
|
||||
return CharacterState {
|
||||
movement: Climb,
|
||||
action: Idle,
|
||||
};
|
||||
}
|
||||
|
||||
// Try to swim
|
||||
if !physics.on_ground && physics.in_fluid {
|
||||
return CharacterState {
|
||||
action: character.action,
|
||||
movement: Swim,
|
||||
};
|
||||
}
|
||||
|
||||
// While on ground ...
|
||||
if physics.on_ground {
|
||||
// Try to jump
|
||||
if inputs.jump.is_pressed() && !inputs.jump.is_held_down() {
|
||||
return CharacterState {
|
||||
action: character.action,
|
||||
movement: Jump,
|
||||
};
|
||||
}
|
||||
|
||||
// Try to charge
|
||||
if inputs.charge.is_pressed() && !inputs.charge.is_held_down() {
|
||||
return CharacterState {
|
||||
action: Charge {
|
||||
time_left: Duration::from_millis(250),
|
||||
},
|
||||
movement: Run(RunData),
|
||||
};
|
||||
}
|
||||
|
||||
// Try to roll
|
||||
if inputs.roll.is_pressed() && body.is_humanoid() {
|
||||
return CharacterState {
|
||||
action: Roll {
|
||||
time_left: Duration::from_millis(600),
|
||||
was_wielding: character.action.is_wield(),
|
||||
},
|
||||
movement: Run(RunData),
|
||||
};
|
||||
}
|
||||
}
|
||||
// While not on ground ...
|
||||
else {
|
||||
// Try to glide
|
||||
if physics.on_wall == None
|
||||
&& inputs.glide.is_pressed()
|
||||
&& !inputs.glide.is_held_down()
|
||||
&& body.is_humanoid()
|
||||
{
|
||||
character.movement = Glide;
|
||||
continue;
|
||||
}
|
||||
character.movement = Fall;
|
||||
}
|
||||
|
||||
// Tool Actions
|
||||
if inputs.toggle_wield.is_just_pressed() {
|
||||
match action_state {
|
||||
Wield { .. } | Attack { .. } => {
|
||||
// Prevent instantaneous reequipping by checking
|
||||
// for done wielding
|
||||
if character.action.is_action_finished() {
|
||||
character.action = Idle;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Idle => {
|
||||
character.action = try_wield(stats);
|
||||
continue;
|
||||
}
|
||||
Charge { .. } | Roll { .. } | Block { .. } => {}
|
||||
}
|
||||
}
|
||||
if inputs.primary.is_pressed() {
|
||||
// TODO: PrimaryStart
|
||||
} else if inputs.secondary.is_pressed() {
|
||||
// TODO: SecondaryStart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub enum MovementState {
|
||||
Stand,
|
||||
Stand(Stand),
|
||||
Sit,
|
||||
Run,
|
||||
Run(Run),
|
||||
Jump,
|
||||
Fall,
|
||||
Glide,
|
||||
@ -35,7 +165,7 @@ pub enum ActionState {
|
||||
Charge {
|
||||
time_left: Duration,
|
||||
},
|
||||
//Carry,
|
||||
// Handle(CharacterAction),
|
||||
}
|
||||
|
||||
impl ActionState {
|
||||
|
@ -154,21 +154,6 @@ impl ControllerInputs {
|
||||
self.toggle_wield.tick(dt);
|
||||
self.charge.tick(dt);
|
||||
}
|
||||
/// Updates `inputs.move_dir`.
|
||||
pub fn update_move_dir(&mut self) {
|
||||
self.move_dir = if self.move_dir.magnitude_squared() > 1.0 {
|
||||
// Cap move_dir to 1
|
||||
self.move_dir.normalized()
|
||||
} else {
|
||||
self.move_dir
|
||||
};
|
||||
}
|
||||
/// Updates `inputs.look_dir`
|
||||
pub fn update_look_dir(&mut self) {
|
||||
self.look_dir
|
||||
.try_normalized()
|
||||
.unwrap_or(self.move_dir.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
|
401
common/src/sys/character_state.rs
Normal file
401
common/src/sys/character_state.rs
Normal file
@ -0,0 +1,401 @@
|
||||
use super::movement::ROLL_DURATION;
|
||||
use crate::{
|
||||
comp::{
|
||||
self, item, projectile, ActionState, ActionState::*, Body, CharacterState, ControlEvent,
|
||||
Controller, ControllerInputs, HealthChange, HealthSource, ItemKind, Mounting,
|
||||
MovementState, MovementState::*, PhysicsState, Projectile, Stats, Vel,
|
||||
},
|
||||
event::{Emitter, EventBus, LocalEvent, ServerEvent},
|
||||
state::DeltaTime,
|
||||
};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Entity, Join, Read, ReadStorage, System, WriteStorage,
|
||||
};
|
||||
use sphynx::{Uid, UidAllocator};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
/// # Character State System
|
||||
/// #### Updates then detemrines next Character States based on ControllerInputs
|
||||
pub struct Sys;
|
||||
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
Read<'a, EventBus<LocalEvent>>,
|
||||
Read<'a, DeltaTime>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
ReadStorage<'a, Controller>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Vel>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Mounting>,
|
||||
);
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
uid_allocator,
|
||||
server_bus,
|
||||
local_bus,
|
||||
dt,
|
||||
mut character_states,
|
||||
controllers,
|
||||
stats,
|
||||
bodies,
|
||||
velocities,
|
||||
physics_states,
|
||||
uids,
|
||||
mountings,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut local_emitter = local_bus.emitter();
|
||||
for (entity, uid, mut character, controller, stats, body, vel, physics, mount) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&mut character_states,
|
||||
&controllers,
|
||||
&stats,
|
||||
&bodies,
|
||||
&velocities,
|
||||
&physics_states,
|
||||
mountings.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let inputs = &controller.inputs;
|
||||
|
||||
// Returns a Wield action, or Idle if nothing to wield
|
||||
let try_wield = |stats: &Stats| -> ActionState {
|
||||
// Get weapon to wield
|
||||
if let Some(ItemKind::Tool { kind, .. }) =
|
||||
stats.equipment.main.as_ref().map(|i| &i.kind)
|
||||
{
|
||||
let wield_duration = kind.wield_duration();
|
||||
Wield {
|
||||
time_left: wield_duration,
|
||||
}
|
||||
} else {
|
||||
Idle
|
||||
}
|
||||
};
|
||||
|
||||
let get_state_from_move_dir = |move_dir: &Vec2<f32>| -> MovementState {
|
||||
if move_dir.magnitude_squared() > 0.0 {
|
||||
Run(_)
|
||||
} else {
|
||||
Stand(_)
|
||||
}
|
||||
};
|
||||
|
||||
// Being dead overrides all other states
|
||||
if stats.is_dead {
|
||||
// Only options: click respawn
|
||||
// prevent instant-respawns (i.e. player was holding attack)
|
||||
// by disallowing while input is held down
|
||||
if inputs.respawn.is_pressed() && !inputs.respawn.is_held_down() {
|
||||
server_emitter.emit(ServerEvent::Respawn(entity));
|
||||
}
|
||||
// Or do nothing
|
||||
continue;
|
||||
}
|
||||
// If mounted, character state is controlled by mount
|
||||
if mount.is_some() {
|
||||
character.movement = Sit;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update Action States
|
||||
match character.action {
|
||||
Attack {
|
||||
ref mut time_left, ..
|
||||
} => {
|
||||
*time_left = time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Roll {
|
||||
ref mut time_left, ..
|
||||
} => {
|
||||
*time_left = time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Charge { ref mut time_left } => {
|
||||
*time_left = time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Wield { ref mut time_left } => {
|
||||
*time_left = time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Block {
|
||||
ref mut time_active,
|
||||
} => {
|
||||
*time_active = time_active
|
||||
.checked_add(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default();
|
||||
}
|
||||
Idle => {}
|
||||
}
|
||||
|
||||
// Determine new states
|
||||
match (character.action, character.movement) {
|
||||
// Jumping, one frame state that calls jump server event
|
||||
(_, Jump) => {
|
||||
character.movement = Fall;
|
||||
local_emitter.emit(LocalEvent::Jump(entity));
|
||||
}
|
||||
// Charging + Any Movement, prioritizes finishing charge
|
||||
// over movement states
|
||||
(Charge { time_left }, _) => {
|
||||
if let Some(uid_b) = physics.touch_entity {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: uid_b,
|
||||
change: HealthChange {
|
||||
amount: -20,
|
||||
cause: HealthSource::Attack { by: *uid },
|
||||
},
|
||||
});
|
||||
|
||||
character.action = try_wield(stats);
|
||||
} else if time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 {
|
||||
character.action = try_wield(stats);
|
||||
}
|
||||
}
|
||||
// Rolling + Any Movement, prioritizes finishing charge
|
||||
// over movement states
|
||||
(
|
||||
Roll {
|
||||
time_left,
|
||||
was_wielding,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
if time_left == Duration::default() {
|
||||
if was_wielding {
|
||||
character.action = try_wield(stats);
|
||||
} else {
|
||||
character.action = Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Any Action + Falling
|
||||
(action_state, Fall) => {
|
||||
// character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
if inputs.glide.is_pressed() && !inputs.glide.is_held_down() {
|
||||
character.movement = Glide;
|
||||
continue;
|
||||
}
|
||||
// Reset to Falling while not standing on ground,
|
||||
// otherwise keep the state given above
|
||||
if !physics.on_ground {
|
||||
if physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
} else {
|
||||
character.movement = Stand(comp::character_state::Stand);
|
||||
continue;
|
||||
}
|
||||
|
||||
match action_state {
|
||||
// Unwield if buttons pressed
|
||||
Wield { .. } | Attack { .. } => {
|
||||
if inputs.toggle_wield.is_just_pressed() {
|
||||
character.action = Idle;
|
||||
}
|
||||
}
|
||||
// Try to wield if any of buttons pressed
|
||||
Idle => {
|
||||
if inputs.primary.is_pressed() || inputs.secondary.is_pressed() {
|
||||
character.action = try_wield(stats);
|
||||
}
|
||||
}
|
||||
// Cancel blocks
|
||||
Block { .. } => {
|
||||
character.action = try_wield(stats);
|
||||
}
|
||||
// Don't change action
|
||||
Charge { .. } | Roll { .. } => {}
|
||||
}
|
||||
}
|
||||
// Any Action + Swimming
|
||||
(_, Swim) => {
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
if !physics.on_ground && physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
}
|
||||
if inputs.primary.is_pressed() {
|
||||
// TODO: PrimaryStart
|
||||
} else if inputs.secondary.is_pressed() {
|
||||
// TODO: SecondaryStart
|
||||
}
|
||||
}
|
||||
// // Blocking, restricted look_dir compared to other states
|
||||
// (Block { .. }, Stand) | (Block { .. }, Run) => {
|
||||
// character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
// if !inputs.secondary.is_pressed() {
|
||||
// character.action = try_wield(stats);
|
||||
// } else {
|
||||
// // TODO: SecondaryStart
|
||||
// }
|
||||
|
||||
// if !physics.on_ground && physics.in_fluid {
|
||||
// character.movement = Swim;
|
||||
// }
|
||||
// }
|
||||
// // Standing and Running states, typical states :shrug:
|
||||
// (action_state, Run) | (action_state, Stand) => {
|
||||
// character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
// // Try to sit
|
||||
// if inputs.sit.is_pressed() && physics.on_ground && body.is_humanoid() {
|
||||
// character.movement = Sit;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Try to climb
|
||||
// if let (true, Some(_wall_dir)) = (
|
||||
// inputs.climb.is_pressed() | inputs.climb_down.is_pressed()
|
||||
// && body.is_humanoid(),
|
||||
// physics.on_wall,
|
||||
// ) {
|
||||
// character.movement = Climb;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Try to swim
|
||||
// if !physics.on_ground && physics.in_fluid {
|
||||
// character.movement = Swim;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // While on ground ...
|
||||
// if physics.on_ground {
|
||||
// // Try to jump
|
||||
// if inputs.jump.is_pressed() && !inputs.jump.is_held_down() {
|
||||
// character.movement = Jump;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Try to charge
|
||||
// if inputs.charge.is_pressed() && !inputs.charge.is_held_down() {
|
||||
// character.action = Charge {
|
||||
// time_left: Duration::from_millis(250),
|
||||
// };
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // Try to roll
|
||||
// if character.movement == Run
|
||||
// && inputs.roll.is_pressed()
|
||||
// && body.is_humanoid()
|
||||
// {
|
||||
// character.action = Roll {
|
||||
// time_left: ROLL_DURATION,
|
||||
// was_wielding: character.action.is_wield(),
|
||||
// };
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// // While not on ground ...
|
||||
// else {
|
||||
// // Try to glide
|
||||
// if physics.on_wall == None
|
||||
// && inputs.glide.is_pressed()
|
||||
// && !inputs.glide.is_held_down()
|
||||
// && body.is_humanoid()
|
||||
// {
|
||||
// character.movement = Glide;
|
||||
// continue;
|
||||
// }
|
||||
// character.movement = Fall;
|
||||
// }
|
||||
|
||||
// // Tool Actions
|
||||
// if inputs.toggle_wield.is_just_pressed() {
|
||||
// match action_state {
|
||||
// Wield { .. } | Attack { .. } => {
|
||||
// // Prevent instantaneous reequipping by checking
|
||||
// // for done wielding
|
||||
// if character.action.is_action_finished() {
|
||||
// character.action = Idle;
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// Idle => {
|
||||
// character.action = try_wield(stats);
|
||||
// continue;
|
||||
// }
|
||||
// Charge { .. } | Roll { .. } | Block { .. } => {}
|
||||
// }
|
||||
// }
|
||||
// if inputs.primary.is_pressed() {
|
||||
// // TODO: PrimaryStart
|
||||
// } else if inputs.secondary.is_pressed() {
|
||||
// // TODO: SecondaryStart
|
||||
// }
|
||||
// }
|
||||
// Sitting
|
||||
(_, Sit) => {
|
||||
character.action = Idle;
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
// character.movement will be Stand after updating when
|
||||
// no movement has occurred
|
||||
if character.movement == Stand(_) {
|
||||
character.movement = Sit;
|
||||
}
|
||||
if inputs.jump.is_pressed() && !inputs.jump.is_held_down() {
|
||||
character.movement = Jump;
|
||||
continue;
|
||||
}
|
||||
if !physics.on_ground {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
// Any Action + Gliding, shouldnt care about action,
|
||||
// because should be Idle
|
||||
(_, Glide) => {
|
||||
character.action = Idle;
|
||||
|
||||
if !inputs.glide.is_pressed() {
|
||||
character.movement = Fall;
|
||||
} else if let Some(_wall_dir) = physics.on_wall {
|
||||
character.movement = Fall;
|
||||
}
|
||||
|
||||
if physics.on_ground {
|
||||
character.movement = Stand(Stand)
|
||||
}
|
||||
}
|
||||
// Any Action + Climbing, shouldnt care about action,
|
||||
// because should be Idle
|
||||
(_, Climb) => {
|
||||
character.action = Idle;
|
||||
if let None = physics.on_wall {
|
||||
if inputs.jump.is_pressed() {
|
||||
character.movement = Jump;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
if physics.on_ground {
|
||||
character.movement = Stand(Stand);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -17,226 +17,9 @@ use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
/// # Controller System
|
||||
/// #### Responsible for validating controller inputs and setting new Character States
|
||||
/// ----
|
||||
///
|
||||
/// **Writes:**
|
||||
/// `CharacterState`, `ControllerInputs`
|
||||
///
|
||||
/// **Reads:**
|
||||
/// `Stats`, `Vel`, `PhysicsState`, `Uid`, `Mounting`
|
||||
///
|
||||
/// _TODO: Join ActionStates and MovementStates into one and have a handle() trait / fn?_
|
||||
/// _TODO: Move weapon action to trait fn?_
|
||||
/// #### Responsible for validating and updating controller inputs
|
||||
pub struct Sys;
|
||||
|
||||
impl Sys {
|
||||
/// Assumes `input.primary` has been pressed
|
||||
/// handles primary actions. ie. equipping, mainhand weapon attacks.
|
||||
///
|
||||
/// Returns the `ActionState` that occurred
|
||||
fn handle_primary(
|
||||
inputs: &mut ControllerInputs,
|
||||
character: &mut CharacterState,
|
||||
stats: &Stats,
|
||||
entity: Entity,
|
||||
uid: &Uid,
|
||||
server_emitter: &mut Emitter<'_, ServerEvent>,
|
||||
local_emitter: &mut Emitter<'_, LocalEvent>,
|
||||
) -> ActionState {
|
||||
match stats.equipment.main.as_ref().map(|i| &i.kind) {
|
||||
// Character is wielding something
|
||||
Some(ItemKind::Tool { kind, power, .. }) => {
|
||||
let attack_duration = kind.attack_duration();
|
||||
let wield_duration = kind.wield_duration();
|
||||
|
||||
// Since primary input was pressed, set
|
||||
// action to new Wield, in case of
|
||||
// instant primary actions
|
||||
if character.action == Idle {
|
||||
character.action = Wield {
|
||||
time_left: wield_duration,
|
||||
};
|
||||
}
|
||||
|
||||
match kind {
|
||||
item::Tool::Bow if character.action.is_action_finished() => {
|
||||
// Immediately end the wield
|
||||
server_emitter.emit(ServerEvent::Shoot {
|
||||
entity,
|
||||
dir: inputs.look_dir,
|
||||
body: comp::Body::Object(comp::object::Body::Arrow),
|
||||
light: None,
|
||||
gravity: Some(comp::Gravity(0.3)),
|
||||
projectile: Projectile {
|
||||
owner: *uid,
|
||||
hit_ground: vec![projectile::Effect::Stick],
|
||||
hit_wall: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage(HealthChange {
|
||||
amount: -(*power as i32),
|
||||
cause: HealthSource::Attack { by: *uid },
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(15),
|
||||
},
|
||||
});
|
||||
Attack {
|
||||
time_left: attack_duration,
|
||||
applied: false, // We don't want to do a melee attack
|
||||
}
|
||||
//character.action
|
||||
}
|
||||
item::Tool::Debug(item::Debug::Boost) => {
|
||||
local_emitter.emit(LocalEvent::Boost {
|
||||
entity,
|
||||
vel: inputs.look_dir * 7.0,
|
||||
});
|
||||
character.action
|
||||
}
|
||||
|
||||
item::Tool::Debug(item::Debug::Possess)
|
||||
if character.action.is_action_finished() =>
|
||||
{
|
||||
server_emitter.emit(ServerEvent::Shoot {
|
||||
entity,
|
||||
gravity: Some(comp::Gravity(0.1)),
|
||||
dir: inputs.look_dir,
|
||||
body: comp::Body::Object(comp::object::Body::ArrowSnake),
|
||||
light: Some(comp::LightEmitter {
|
||||
col: (0.0, 1.0, 0.3).into(),
|
||||
..Default::default()
|
||||
}),
|
||||
projectile: Projectile {
|
||||
owner: *uid,
|
||||
hit_ground: vec![projectile::Effect::Stick],
|
||||
hit_wall: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Stick,
|
||||
projectile::Effect::Possess,
|
||||
],
|
||||
time_left: Duration::from_secs(10),
|
||||
},
|
||||
});
|
||||
|
||||
character.action
|
||||
}
|
||||
// All other weapons
|
||||
_ if character.action.is_action_finished() => Attack {
|
||||
time_left: attack_duration,
|
||||
applied: false,
|
||||
},
|
||||
_ => {
|
||||
// Return the new Wield action
|
||||
character.action
|
||||
}
|
||||
}
|
||||
}
|
||||
// Without a weapon
|
||||
None => {
|
||||
// Attack
|
||||
if !character.action.is_attack() {
|
||||
Attack {
|
||||
time_left: Duration::from_millis(100),
|
||||
applied: false,
|
||||
}
|
||||
} else {
|
||||
character.action
|
||||
}
|
||||
}
|
||||
_ => character.action,
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes `input.seconday` has been pressed
|
||||
/// handles seconday actions. ie. blocking, althand weapons
|
||||
///
|
||||
/// Returns the `ActionState` that occurred
|
||||
fn handle_secondary(
|
||||
inputs: &mut ControllerInputs,
|
||||
character: &mut CharacterState,
|
||||
stats: &Stats,
|
||||
entity: Entity,
|
||||
uid: &Uid,
|
||||
server_emitter: &mut Emitter<'_, ServerEvent>,
|
||||
local_emitter: &mut Emitter<'_, LocalEvent>,
|
||||
) -> ActionState {
|
||||
match stats.equipment.main.as_ref().map(|i| &i.kind) {
|
||||
// Character is wielding something
|
||||
Some(ItemKind::Tool { kind, power, .. }) => {
|
||||
let attack_duration = kind.attack_duration();
|
||||
let wield_duration = kind.wield_duration();
|
||||
|
||||
// Since primary input was pressed, set
|
||||
// action to new Wield, in case of
|
||||
// instant primary actions
|
||||
if character.action == Idle {
|
||||
character.action = Wield {
|
||||
time_left: wield_duration,
|
||||
};
|
||||
}
|
||||
|
||||
match kind {
|
||||
// Magical Bolt
|
||||
item::Tool::Staff
|
||||
if character.movement == Stand && character.action.is_action_finished() =>
|
||||
{
|
||||
server_emitter.emit(ServerEvent::Shoot {
|
||||
entity,
|
||||
dir: inputs.look_dir,
|
||||
body: comp::Body::Object(comp::object::Body::BoltFire),
|
||||
gravity: Some(comp::Gravity(0.0)),
|
||||
light: Some(comp::LightEmitter {
|
||||
col: (0.72, 0.11, 0.11).into(),
|
||||
strength: 10.0,
|
||||
offset: Vec3::new(0.0, -5.0, 2.0),
|
||||
}),
|
||||
projectile: Projectile {
|
||||
owner: *uid,
|
||||
hit_ground: vec![projectile::Effect::Vanish],
|
||||
hit_wall: vec![projectile::Effect::Vanish],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage(HealthChange {
|
||||
amount: -(*power as i32),
|
||||
cause: HealthSource::Attack { by: *uid },
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(5),
|
||||
},
|
||||
});
|
||||
// TODO: Don't play melee animation
|
||||
Attack {
|
||||
time_left: attack_duration,
|
||||
applied: true, // We don't want to do a melee attack
|
||||
}
|
||||
}
|
||||
|
||||
// Go upward
|
||||
item::Tool::Debug(item::Debug::Boost) => {
|
||||
local_emitter.emit(LocalEvent::Boost {
|
||||
entity,
|
||||
vel: Vec3::new(0.0, 0.0, 7.0),
|
||||
});
|
||||
|
||||
character.action
|
||||
}
|
||||
|
||||
// All other weapons block
|
||||
_ if character.action.is_action_finished() => Block {
|
||||
time_active: Duration::from_secs(0),
|
||||
},
|
||||
|
||||
_ => character.action,
|
||||
}
|
||||
}
|
||||
|
||||
_ => character.action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
@ -245,442 +28,30 @@ impl<'a> System<'a> for Sys {
|
||||
Read<'a, EventBus<LocalEvent>>,
|
||||
Read<'a, DeltaTime>,
|
||||
WriteStorage<'a, Controller>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Vel>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Mounting>,
|
||||
);
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
uid_allocator,
|
||||
server_bus,
|
||||
local_bus,
|
||||
dt,
|
||||
mut controllers,
|
||||
mut character_states,
|
||||
stats,
|
||||
bodies,
|
||||
velocities,
|
||||
physics_states,
|
||||
uids,
|
||||
mountings,
|
||||
): Self::SystemData,
|
||||
(entities, uid_allocator, server_bus, local_bus, dt, mut controllers, uids): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut local_emitter = local_bus.emitter();
|
||||
for (entity, uid, controller, mut character, stats, body, vel, physics, mount) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&mut controllers,
|
||||
&mut character_states,
|
||||
&stats,
|
||||
&bodies,
|
||||
&velocities,
|
||||
&physics_states,
|
||||
mountings.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
for (entity, uid, controller) in (&entities, &uids, &mut controllers).join() {
|
||||
let inputs = &mut controller.inputs;
|
||||
|
||||
// ---------------------------------------
|
||||
// Common actions for multiple states as closure fn's for convenience
|
||||
// Returns a Wield action, or Idle if nothing to wield
|
||||
let try_wield = |stats: &Stats| -> ActionState {
|
||||
// Get weapon to wield
|
||||
if let Some(ItemKind::Tool { kind, .. }) =
|
||||
stats.equipment.main.as_ref().map(|i| &i.kind)
|
||||
{
|
||||
let wield_duration = kind.wield_duration();
|
||||
Wield {
|
||||
time_left: wield_duration,
|
||||
}
|
||||
} else {
|
||||
Idle
|
||||
}
|
||||
// Update `inputs.move_dir`.
|
||||
inputs.move_dir = if inputs.move_dir.magnitude_squared() > 1.0 {
|
||||
// Cap move_dir to 1
|
||||
inputs.move_dir.normalized()
|
||||
} else {
|
||||
inputs.move_dir
|
||||
};
|
||||
|
||||
let get_state_from_move_dir = |move_dir: &Vec2<f32>| -> MovementState {
|
||||
if move_dir.magnitude_squared() > 0.0 {
|
||||
Run
|
||||
} else {
|
||||
Stand
|
||||
}
|
||||
};
|
||||
|
||||
// End common actions
|
||||
// ---------------------------------------
|
||||
|
||||
// Being dead overrides all other states
|
||||
if stats.is_dead {
|
||||
// Only options: click respawn
|
||||
// prevent instant-respawns (i.e. player was holding attack)
|
||||
// by disallowing while input is held down
|
||||
if inputs.respawn.is_pressed() && !inputs.respawn.is_held_down() {
|
||||
server_emitter.emit(ServerEvent::Respawn(entity));
|
||||
}
|
||||
// Or do nothing
|
||||
continue;
|
||||
}
|
||||
// If mounted, character state is controlled by mount
|
||||
if mount.is_some() {
|
||||
character.movement = Sit;
|
||||
continue;
|
||||
}
|
||||
|
||||
inputs.update_look_dir();
|
||||
inputs.update_move_dir();
|
||||
|
||||
match (character.action, character.movement) {
|
||||
// Jumping, one frame state that calls jump server event
|
||||
(_, Jump) => {
|
||||
character.movement = Fall;
|
||||
local_emitter.emit(LocalEvent::Jump(entity));
|
||||
}
|
||||
// Charging + Any Movement, prioritizes finishing charge
|
||||
// over movement states
|
||||
(Charge { time_left }, _) => {
|
||||
inputs.update_move_dir();
|
||||
if time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 {
|
||||
character.action = try_wield(stats);
|
||||
} else {
|
||||
character.action = Charge {
|
||||
time_left: time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
}
|
||||
if let Some(uid_b) = physics.touch_entity {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: uid_b,
|
||||
change: HealthChange {
|
||||
amount: -20,
|
||||
cause: HealthSource::Attack { by: *uid },
|
||||
},
|
||||
});
|
||||
|
||||
character.action = try_wield(stats);
|
||||
}
|
||||
}
|
||||
// Rolling + Any Movement, prioritizes finishing charge
|
||||
// over movement states
|
||||
(
|
||||
Roll {
|
||||
time_left,
|
||||
was_wielding,
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
if time_left == Duration::default() {
|
||||
if was_wielding {
|
||||
character.action = try_wield(stats);
|
||||
} else {
|
||||
character.action = Idle;
|
||||
}
|
||||
} else {
|
||||
character.action = Roll {
|
||||
time_left: time_left
|
||||
.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
.unwrap_or_default(),
|
||||
was_wielding,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Any Action + Falling
|
||||
(action_state, Fall) => {
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
if inputs.glide.is_pressed() {
|
||||
character.movement = Glide;
|
||||
continue;
|
||||
}
|
||||
// Try to climb
|
||||
if let (true, Some(_wall_dir)) = (
|
||||
inputs.climb.is_pressed() | inputs.climb_down.is_pressed()
|
||||
&& body.is_humanoid(),
|
||||
physics.on_wall,
|
||||
) {
|
||||
character.movement = Climb;
|
||||
continue;
|
||||
}
|
||||
// Reset to Falling while not standing on ground,
|
||||
// otherwise keep the state given above
|
||||
if !physics.on_ground {
|
||||
if physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
} else {
|
||||
character.movement = Stand;
|
||||
continue;
|
||||
}
|
||||
|
||||
match action_state {
|
||||
// Unwield if buttons pressed
|
||||
Wield { .. } | Attack { .. } => {
|
||||
if inputs.toggle_wield.is_just_pressed() {
|
||||
character.action = Idle;
|
||||
}
|
||||
}
|
||||
// Try to wield if any of buttons pressed
|
||||
Idle => {
|
||||
if inputs.primary.is_pressed() || inputs.secondary.is_pressed() {
|
||||
character.action = try_wield(stats);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Cancel blocks
|
||||
Block { .. } => {
|
||||
character.action = try_wield(stats);
|
||||
continue;
|
||||
}
|
||||
// Don't change action
|
||||
Charge { .. } | Roll { .. } => {}
|
||||
}
|
||||
if inputs.primary.is_pressed() {
|
||||
character.action = Self::handle_primary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
} else if inputs.secondary.is_pressed() {
|
||||
character.action = Self::handle_secondary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Any Action + Swimming
|
||||
(_action_state, Swim) => {
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
if !physics.on_ground && physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
}
|
||||
if inputs.primary.is_pressed() {
|
||||
character.action = Self::handle_primary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
} else if inputs.secondary.is_pressed() {
|
||||
character.action = Self::handle_secondary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Blocking, restricted look_dir compared to other states
|
||||
(Block { .. }, Stand) | (Block { .. }, Run) => {
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
if !inputs.secondary.is_pressed() {
|
||||
character.action = try_wield(stats);
|
||||
} else {
|
||||
character.action = Self::handle_secondary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
}
|
||||
|
||||
if !physics.on_ground {
|
||||
if physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Standing and Running states, typical states :shrug:
|
||||
(action_state, Run) | (action_state, Stand) => {
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
// Try to sit
|
||||
if inputs.sit.is_pressed() && physics.on_ground && body.is_humanoid() {
|
||||
character.movement = Sit;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to climb
|
||||
if let (true, Some(_wall_dir)) = (
|
||||
inputs.climb.is_pressed() | inputs.climb_down.is_pressed()
|
||||
&& body.is_humanoid(),
|
||||
physics.on_wall,
|
||||
) {
|
||||
character.movement = Climb;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to swim
|
||||
if !physics.on_ground {
|
||||
if physics.in_fluid {
|
||||
character.movement = Swim;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
|
||||
// While on ground ...
|
||||
if physics.on_ground {
|
||||
// Try to jump
|
||||
if inputs.jump.is_pressed() {
|
||||
character.movement = Jump;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to charge
|
||||
if inputs.charge.is_pressed() && !inputs.charge.is_held_down() {
|
||||
character.action = Charge {
|
||||
time_left: Duration::from_millis(250),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to roll
|
||||
if character.movement == Run
|
||||
&& inputs.roll.is_pressed()
|
||||
&& body.is_humanoid()
|
||||
{
|
||||
character.action = Roll {
|
||||
time_left: ROLL_DURATION,
|
||||
was_wielding: character.action.is_wield(),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// While not on ground ...
|
||||
else {
|
||||
// Try to glide
|
||||
if physics.on_wall == None
|
||||
&& inputs.glide.is_pressed()
|
||||
&& body.is_humanoid()
|
||||
{
|
||||
character.movement = Glide;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Tool Actions
|
||||
if inputs.toggle_wield.is_just_pressed() {
|
||||
match action_state {
|
||||
Wield { .. } | Attack { .. } => {
|
||||
// Prevent instantaneous reequipping by checking
|
||||
// for done wielding
|
||||
if character.action.is_action_finished() {
|
||||
character.action = Idle;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Idle => {
|
||||
character.action = try_wield(stats);
|
||||
continue;
|
||||
}
|
||||
Charge { .. } | Roll { .. } | Block { .. } => {}
|
||||
}
|
||||
}
|
||||
if inputs.primary.is_pressed() {
|
||||
character.action = Self::handle_primary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
} else if inputs.secondary.is_pressed() {
|
||||
character.action = Self::handle_secondary(
|
||||
inputs,
|
||||
character,
|
||||
stats,
|
||||
entity,
|
||||
uid,
|
||||
&mut server_emitter,
|
||||
&mut local_emitter,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Sitting
|
||||
(_, Sit) => {
|
||||
character.action = Idle;
|
||||
character.movement = get_state_from_move_dir(&inputs.move_dir);
|
||||
|
||||
// character.movement will be Stand after updating when
|
||||
// no movement has occurred
|
||||
if character.movement == Stand {
|
||||
character.movement = Sit;
|
||||
}
|
||||
if inputs.jump.is_pressed() {
|
||||
character.movement = Jump;
|
||||
continue;
|
||||
}
|
||||
if !physics.on_ground {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
// Any Action + Gliding, shouldnt care about action,
|
||||
// because should be Idle
|
||||
(_, Glide) => {
|
||||
character.action = Idle;
|
||||
|
||||
if !inputs.glide.is_pressed() {
|
||||
character.movement = Fall;
|
||||
} else if let Some(_wall_dir) = physics.on_wall {
|
||||
character.movement = Climb;
|
||||
}
|
||||
|
||||
if physics.on_ground {
|
||||
character.movement = Stand
|
||||
}
|
||||
}
|
||||
// Any Action + Climbing, shouldnt care about action,
|
||||
// because should be Idle
|
||||
(_, Climb) => {
|
||||
character.action = Idle;
|
||||
if let None = physics.on_wall {
|
||||
if inputs.jump.is_pressed() {
|
||||
character.movement = Jump;
|
||||
} else {
|
||||
character.movement = Fall;
|
||||
}
|
||||
}
|
||||
if physics.on_ground {
|
||||
character.movement = Stand;
|
||||
}
|
||||
} // In case of adding new states
|
||||
// (_, _) => {
|
||||
// println!("UNKNOWN STATE");
|
||||
// character.action = Idle;
|
||||
// character.movement = Fall;
|
||||
// }
|
||||
};
|
||||
// Update `inputs.look_dir`
|
||||
inputs
|
||||
.look_dir
|
||||
.try_normalized()
|
||||
.unwrap_or(inputs.move_dir.into());
|
||||
|
||||
// Process other controller events
|
||||
for event in controller.events.drain(..) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod agent;
|
||||
pub mod character_state;
|
||||
mod cleanup;
|
||||
pub mod combat;
|
||||
pub mod controller;
|
||||
@ -12,6 +13,7 @@ use specs::DispatcherBuilder;
|
||||
|
||||
// System names
|
||||
const AGENT_SYS: &str = "agent_sys";
|
||||
const CHARACTER_STATE_SYS: &str = "character_state_sys";
|
||||
const CONTROLLER_SYS: &str = "controller_sys";
|
||||
const PHYS_SYS: &str = "phys_sys";
|
||||
const MOVEMENT_SYS: &str = "movement_sys";
|
||||
@ -23,7 +25,8 @@ const CLEANUP_SYS: &str = "cleanup_sys";
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
|
||||
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
|
||||
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[]);
|
||||
dispatch_builder.add(character_state::Sys, CHARACTER_STATE_SYS, &[CONTROLLER_SYS]);
|
||||
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[CHARACTER_STATE_SYS]);
|
||||
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
|
||||
dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);
|
||||
dispatch_builder.add(
|
||||
|
@ -105,6 +105,10 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
if character.movement == Run || character.movement == Stand {
|
||||
continue;
|
||||
}
|
||||
|
||||
if stats.is_dead {
|
||||
continue;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user