Clean up character states

This commit is contained in:
Adam Whitehurst 2020-03-07 10:15:02 -08:00
parent 0d2b26a3b8
commit b1d1299fe6
28 changed files with 758 additions and 861 deletions

View File

@ -1,39 +1,34 @@
use crate::comp;
use specs::{Component, FlaggedStorage, HashMapStorage, VecStorage};
use specs::{Component, DenseVecStorage, FlaggedStorage, HashMapStorage};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub enum AbilityActionKind {
Primary,
Secondary,
Dodge,
Block,
// UpdatePool?
pub enum AbilityState {
BasicAttack,
BasicBlock,
Roll,
}
impl Default for AbilityActionKind {
fn default() -> Self { Self::Primary }
impl Default for AbilityState {
fn default() -> Self { Self::BasicAttack }
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct AbilityAction(pub AbilityActionKind);
impl Component for AbilityAction {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
impl Component for AbilityState {
type Storage = DenseVecStorage<Self>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct AbilityPool {
pub primary: Option<comp::CharacterState>,
pub secondary: Option<comp::CharacterState>,
pub block: Option<comp::CharacterState>,
pub dodge: Option<comp::CharacterState>,
pub primary: Option<AbilityState>,
pub secondary: Option<AbilityState>,
pub block: Option<AbilityState>,
pub dodge: Option<AbilityState>,
}
impl Default for AbilityPool {
fn default() -> Self {
Self {
primary: Some(comp::CharacterState::BasicAttack(None)),
secondary: Some(comp::CharacterState::BasicBlock(None)),
primary: Some(AbilityState::BasicAttack),
secondary: Some(AbilityState::BasicAttack),
block: None,
dodge: Some(comp::CharacterState::Roll(None)),
dodge: Some(AbilityState::Roll),
}
}
}

View File

@ -1,33 +1,12 @@
use crate::{
comp::{
AbilityPool, Body, ControllerInputs, Energy, Ori, PhysicsState, Pos, Stats, ToolData, Vel,
},
comp::{Energy, Ori, Pos, ToolData, Vel},
event::{LocalEvent, ServerEvent},
state::DeltaTime,
states::*,
sync::Uid,
};
use serde::{Deserialize, Serialize};
use specs::{Component, Entity, FlaggedStorage, HashMapStorage, LazyUpdate, VecStorage};
use std::collections::VecDeque;
pub struct EcsStateData<'a> {
pub entity: &'a Entity,
pub uid: &'a Uid,
pub character: &'a CharacterState,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub dt: &'a DeltaTime,
pub inputs: &'a ControllerInputs,
pub stats: &'a Stats,
pub energy: &'a Energy,
pub body: &'a Body,
pub physics: &'a PhysicsState,
pub ability_pool: &'a AbilityPool,
pub updater: &'a LazyUpdate,
}
use specs::{Component, FlaggedStorage, HashMapStorage, VecStorage};
use std::{collections::VecDeque, time::Duration};
/// Data returned from character behavior fn's to Character Behavior System.
pub struct StateUpdate {
pub character: CharacterState,
pub pos: Pos,
@ -40,45 +19,63 @@ pub struct StateUpdate {
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub enum CharacterState {
Idle(Option<idle::State>),
Climb(Option<climb::State>),
Sit(Option<sit::State>),
Wielding(Option<wielding::State>),
Wielded(Option<wielded::State>),
Glide(Option<glide::State>),
BasicAttack(Option<basic_attack::State>),
BasicBlock(Option<basic_block::State>),
//Charge(Option<charge_attack::State>),
Roll(Option<roll::State>),
Idle {},
Climb {},
Sit {},
Equipping {
/// The weapon being equipped
tool: ToolData,
/// Time left before next state
time_left: Duration,
},
Wielding {
/// The weapon being wielded
tool: ToolData,
},
Glide {},
/// A basic attacking state
BasicAttack {
/// How long the state has until exiting
remaining_duration: Duration,
/// Whether the attack can deal more damage
exhausted: bool,
},
/// A basic blocking state
BasicBlock {},
//Charge{},
Roll {
/// How long the state has until exiting
remaining_duration: Duration,
},
}
impl CharacterState {
pub fn is_wielded(&self) -> bool {
match self {
CharacterState::Wielded(_) => true,
CharacterState::BasicAttack(_) => true,
CharacterState::BasicBlock(_) => true,
CharacterState::Wielding { .. } => true,
CharacterState::BasicAttack { .. } => true,
CharacterState::BasicBlock { .. } => true,
_ => false,
}
}
pub fn is_attack(&self) -> bool {
match self {
CharacterState::BasicAttack(_) => true,
CharacterState::BasicAttack { .. } => true,
_ => false,
}
}
pub fn is_block(&self) -> bool {
match self {
CharacterState::BasicBlock(_) => true,
CharacterState::BasicBlock { .. } => true,
_ => false,
}
}
pub fn is_dodge(&self) -> bool {
match self {
CharacterState::Roll(_) => true,
CharacterState::Roll { .. } => true,
_ => false,
}
}
@ -88,53 +85,10 @@ impl CharacterState {
// Check if state is the same without looking at the inner data
std::mem::discriminant(self) == std::mem::discriminant(other)
}
/// Passes data to variant or subvariant handlers
/// States contain `Option<StateHandler Implementor>`s, and will be
/// `None` if state data has not been initialized. So we have to
/// check and intialize new state data if so.
pub fn update(&self, ecs_data: &EcsStateData) -> StateUpdate {
match self {
CharacterState::Idle(opt_state) => opt_state
// If data hasn't been initialized, initialize a new one
.unwrap_or_else(|| idle::State::new(ecs_data))
// Call handler
.handle(ecs_data),
CharacterState::Climb(opt_state) => opt_state
.unwrap_or_else(|| climb::State::new(ecs_data))
.handle(ecs_data),
CharacterState::Sit(opt_state) => opt_state
.unwrap_or_else(|| sit::State::new(ecs_data))
.handle(ecs_data),
CharacterState::Wielding(opt_state) => opt_state
.unwrap_or_else(|| wielding::State::new(ecs_data))
.handle(ecs_data),
CharacterState::Wielded(opt_state) => opt_state
.unwrap_or_else(|| wielded::State::new(ecs_data))
.handle(ecs_data),
CharacterState::BasicAttack(opt_state) => opt_state
.unwrap_or_else(|| basic_attack::State::new(ecs_data))
.handle(ecs_data),
CharacterState::BasicBlock(opt_state) => opt_state
.unwrap_or_else(|| basic_block::State::new(ecs_data))
.handle(ecs_data),
/*CharacterState::Charge(opt_state) => opt_state
.unwrap_or_else(|| charge_attack::State::new(ecs_data))
.handle(ecs_data),*/
CharacterState::Roll(opt_state) => opt_state
.unwrap_or_else(|| roll::State::new(ecs_data))
.handle(ecs_data),
CharacterState::Glide(opt_state) => opt_state
.unwrap_or_else(|| glide::State::new(ecs_data))
.handle(ecs_data),
/* All states should be explicitly handled
* DO NOT use default match: _ => {}, */
}
}
}
impl Default for CharacterState {
fn default() -> Self { Self::Idle(None) }
fn default() -> Self { Self::Idle {} }
}
impl Component for CharacterState {

View File

@ -16,14 +16,14 @@ mod stats;
mod visual;
// Reexports
pub use ability::{AbilityAction, AbilityActionKind, AbilityPool};
pub use ability::{AbilityPool, AbilityState};
pub use admin::Admin;
pub use agent::{Agent, Alignment};
pub use body::{
biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid,
object, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
};
pub use character_state::{Attacking, CharacterState, EcsStateData, StateUpdate};
pub use character_state::{Attacking, CharacterState, StateUpdate};
pub use controller::{
ControlEvent, Controller, ControllerInputs, Input, InputState, InventoryManip, MountState,
Mounting,

View File

@ -21,7 +21,7 @@ sum_type! {
Mass(comp::Mass),
Gravity(comp::Gravity),
Sticky(comp::Sticky),
AbilityAction(comp::AbilityAction),
AbilityState(comp::AbilityState),
AbilityPool(comp::AbilityPool),
Attacking(comp::Attacking),
CharacterState(comp::CharacterState),
@ -45,7 +45,7 @@ sum_type! {
Mass(PhantomData<comp::Mass>),
Gravity(PhantomData<comp::Gravity>),
Sticky(PhantomData<comp::Sticky>),
AbilityAction(PhantomData<comp::AbilityAction>),
AbilityState(PhantomData<comp::AbilityState>),
AbilityPool(PhantomData<comp::AbilityPool>),
Attacking(PhantomData<comp::Attacking>),
CharacterState(PhantomData<comp::CharacterState>),
@ -69,7 +69,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityAction(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Attacking(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
@ -91,7 +91,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityAction(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::AbilityPool(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Attacking(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
@ -115,8 +115,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
EcsCompPhantom::AbilityAction(_) => {
sync::handle_remove::<comp::AbilityAction>(entity, world)
EcsCompPhantom::AbilityState(_) => {
sync::handle_remove::<comp::AbilityState>(entity, world)
},
EcsCompPhantom::AbilityPool(_) => {
sync::handle_remove::<comp::AbilityPool>(entity, world)

View File

@ -107,7 +107,7 @@ impl State {
ecs.register_sync_marker();
// Register server -> all clients synced components.
ecs.register::<comp::AbilityPool>();
ecs.register::<comp::AbilityAction>();
ecs.register::<comp::AbilityState>();
ecs.register::<comp::Projectile>();
ecs.register::<comp::Body>();
ecs.register::<comp::Player>();

View File

@ -1,85 +1,72 @@
use crate::{
comp::{Attacking, CharacterState, EcsStateData, ItemKind::Tool, StateUpdate, ToolData},
states::StateHandler,
comp::{Attacking, CharacterState, ItemKind::Tool, StateUpdate},
states::utils::*,
sys::character_state::JoinData,
};
use std::{collections::VecDeque, time::Duration};
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long the state has until exitting
pub remaining_duration: Duration,
/// Whether the attack can deal more damage
pub exhausted: bool,
}
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
character: *data.character,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
impl StateHandler for State {
fn new(ecs_data: &EcsStateData) -> Self {
let remaining_duration =
if let Some(Tool(data)) = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind) {
data.attack_duration()
} else {
Duration::from_millis(300)
};
Self {
remaining_duration,
exhausted: false,
}
}
if let CharacterState::BasicAttack {
exhausted,
remaining_duration,
} = data.character
{
handle_move(data, &mut update);
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
character: *ecs_data.character,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
let tool_kind = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind);
let can_apply_damage = !self.exhausted
&& if let Some(Tool(data)) = tool_kind {
(self.remaining_duration < data.attack_recover_duration())
let tool_kind = data.stats.equipment.main.as_ref().map(|i| i.kind);
let can_apply_damage = !*exhausted
&& if let Some(Tool(tool)) = tool_kind {
*remaining_duration < tool.attack_recover_duration()
} else {
true
};
let mut exhausted = self.exhausted;
let mut new_exhausted = *exhausted;
if can_apply_damage {
if let Some(Tool(data)) = tool_kind {
ecs_data
.updater
.insert(*ecs_data.entity, Attacking { weapon: Some(data) });
if let Some(Tool(tool)) = tool_kind {
data.updater
.insert(data.entity, Attacking { weapon: Some(tool) });
} else {
ecs_data
.updater
.insert(*ecs_data.entity, Attacking { weapon: None });
data.updater.insert(data.entity, Attacking { weapon: None });
}
exhausted = true;
new_exhausted = true;
} else {
ecs_data.updater.remove::<Attacking>(*ecs_data.entity);
data.updater.remove::<Attacking>(data.entity);
}
let remaining_duration = self
.remaining_duration
.checked_sub(Duration::from_secs_f32(ecs_data.dt.0))
let new_remaining_duration = remaining_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default();
// Tick down
update.character = CharacterState::BasicAttack(Some(State {
remaining_duration,
exhausted,
}));
update.character = CharacterState::BasicAttack {
remaining_duration: new_remaining_duration,
exhausted: new_exhausted,
};
// Check if attack duration has expired
if remaining_duration == Duration::default() {
update.character = CharacterState::Wielded(None);
ecs_data.updater.remove::<Attacking>(*ecs_data.entity);
if new_remaining_duration == Duration::default() {
update.character = if let Some(Tool(tool)) = tool_kind {
CharacterState::Wielding { tool }
} else {
CharacterState::Idle {}
};
data.updater.remove::<Attacking>(data.entity);
}
update
} else {
update
}
}

View File

@ -1,35 +1,30 @@
use super::utils::*;
use crate::{
comp::{CharacterState, EcsStateData, StateUpdate},
states::StateHandler,
comp::{CharacterEntityData, CharacterState, StateUpdate},
};
use std::{collections::VecDeque, time::Duration};
use vek::Vec2;
use crate::sys::character_state::JoinData;
const BLOCK_ACCEL: f32 = 30.0;
const BLOCK_SPEED: f32 = 75.0;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {}
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self { Self {} }
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
pub fn handle(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
character: *ecs_data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
character: *data.character,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move_dir(&ecs_data, &mut update);
handle_move(&data, &mut update);
if !ecs_data.physics.on_ground || !ecs_data.inputs.secondary.is_pressed() {
update.character = CharacterState::Wielded(None);
if !data.physics.on_ground || !data.inputs.secondary.is_pressed() {
update.character = CharacterState::Wielding{};
}
update

View File

@ -1,89 +1,62 @@
use super::utils::*;
use crate::comp::{
ActionState::Attack, AttackKind::Charge, EcsStateData, HealthChange, HealthSource,
ItemKind::Tool, MoveState::Run, StateUpdate, ToolData,
use crate::{
comp::{
ActionState::Attack, AttackKind::Charge, CharacterEntityData, HealthChange, HealthSource,
ItemKind::Tool, MoveState::Run, StateUpdate, ToolData,
},
event::ServerEvent,
sys::character_state::JoinData,
};
use crate::event::ServerEvent;
use crate::states::StateHandler;
use std::time::Duration;
use vek::Vec3;
const CHARGE_SPEED: f32 = 20.0;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long the state has until exitting
pub remaining_duration: Duration,
}
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
character: *data.character,
};
impl StateHandler for State {
fn new(ecs_data: &EcsStateData) -> Self {
let tool_data =
if let Some(Tool(data)) = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind) {
data
} else {
ToolData::default()
};
Self {
remaining_duration: tool_data.attack_duration(),
}
// Move player
update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z)
+ (update.vel.0 * Vec3::new(1.0, 1.0, 0.0)
+ 1.5 * data.inputs.move_dir.try_normalized().unwrap_or_default())
.try_normalized()
.unwrap_or_default()
* CHARGE_SPEED;
// Check if hitting another entity
if let Some(uid_b) = data.physics.touch_entity {
// Send Damage event
data.server_bus.emitter().emit(ServerEvent::Damage {
uid: uid_b,
change: HealthChange {
amount: -20,
cause: HealthSource::Attack { by: *data.uid },
},
});
// Go back to wielding or idling
update.character.action_state = attempt_wield(data.stats);
return update;
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
character: *ecs_data.character,
};
// Prevent move state handling, handled here
update.character.move_state = Run(None);
// Move player
update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z)
+ (update.vel.0 * Vec3::new(1.0, 1.0, 0.0)
+ 1.5
* ecs_data
.inputs
.move_dir
.try_normalized()
.unwrap_or_default())
.try_normalized()
.unwrap_or_default()
* CHARGE_SPEED;
// Check if hitting another entity
if let Some(uid_b) = ecs_data.physics.touch_entity {
// Send Damage event
ecs_data.server_bus.emitter().emit(ServerEvent::Damage {
uid: uid_b,
change: HealthChange {
amount: -20,
cause: HealthSource::Attack { by: *ecs_data.uid },
},
});
// Go back to wielding or idling
update.character.action_state = attempt_wield(ecs_data.stats);
return update;
}
// Check if charge timed out or can't keep moving forward
if self.remaining_duration == Duration::default() || update.vel.0.magnitude_squared() < 10.0
{
update.character.action_state = attempt_wield(ecs_data.stats);
return update;
}
// Tick remaining-duration and keep charging
update.character.action_state = Attack(Charge(Some(State {
remaining_duration: self
.remaining_duration
.checked_sub(Duration::from_secs_f32(ecs_data.dt.0))
.unwrap_or_default(),
})));
update
// Check if charge timed out or can't keep moving forward
if self.remaining_duration == Duration::default() || update.vel.0.magnitude_squared() < 10.0 {
update.character.action_state = attempt_wield(data.stats);
return update;
}
// Tick remaining-duration and keep charging
update.character.action_state = Attack(Charge(Some(State {
remaining_duration: self
.remaining_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
})));
update
}

View File

@ -1,7 +1,7 @@
use crate::{
comp::{CharacterState, EcsStateData, EnergySource, StateUpdate},
states::StateHandler,
sys::phys::GRAVITY,
comp::{CharacterState, EnergySource, StateUpdate},
event::LocalEvent,
sys::{character_state::JoinData, phys::GRAVITY},
};
use std::collections::VecDeque;
use vek::{
@ -12,91 +12,84 @@ use vek::{
const HUMANOID_CLIMB_ACCEL: f32 = 5.0;
const CLIMB_SPEED: f32 = 5.0;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
character: *data.character,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self { Self {} }
if let Err(_) = update.energy.try_change_by(-5, EnergySource::Climb) {
update.character = CharacterState::Idle {};
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
character: *ecs_data.character,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
// If no wall is in front of character ...
if data.physics.on_wall.is_none() || data.physics.on_ground {
if data.inputs.jump.is_pressed() {
// They've climbed atop something, give them a boost
update
.local_events
.push_front(LocalEvent::Jump(data.entity));
}
update.character = CharacterState::Idle {};
return update;
}
// Move player
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if update.vel.0.magnitude_squared() < CLIMB_SPEED.powf(2.0) {
HUMANOID_CLIMB_ACCEL
} else {
0.0
};
if let Err(_) = update.energy.try_change_by(-5, EnergySource::Climb) {
update.character = CharacterState::Idle(None);
}
// If no wall is in front of character ...
if ecs_data.physics.on_wall.is_none() || ecs_data.physics.on_ground {
if ecs_data.inputs.jump.is_pressed() {
// They've climbed atop something, give them a boost
//TODO: JUMP EVENT
}
update.character = CharacterState::Idle(None);
return update;
}
// Move player
update.vel.0 += Vec2::broadcast(ecs_data.dt.0)
* ecs_data.inputs.move_dir
* if update.vel.0.magnitude_squared() < CLIMB_SPEED.powf(2.0) {
HUMANOID_CLIMB_ACCEL
} else {
0.0
};
// Set orientation direction based on wall direction
let ori_dir = if let Some(wall_dir) = ecs_data.physics.on_wall {
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 {
Vec2::from(wall_dir).normalized()
} else {
Vec2::from(update.vel.0)
}
// Set orientation direction based on wall direction
let ori_dir = if let Some(wall_dir) = data.physics.on_wall {
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 {
Vec2::from(wall_dir).normalized()
} else {
Vec2::from(update.vel.0)
};
}
} else {
Vec2::from(update.vel.0)
};
// Smooth orientation
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(
update.ori.0,
ori_dir.into(),
if ecs_data.physics.on_ground { 9.0 } else { 2.0 } * ecs_data.dt.0,
// Smooth orientation
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(
update.ori.0,
ori_dir.into(),
if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0,
);
}
// Apply Vertical Climbing Movement
if let (true, Some(_wall_dir)) = (
(data.inputs.climb.is_pressed() | data.inputs.climb_down.is_pressed())
&& update.vel.0.z <= CLIMB_SPEED,
data.physics.on_wall,
) {
if data.inputs.climb_down.is_pressed() && !data.inputs.climb.is_pressed() {
update.vel.0 -= data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
} else if data.inputs.climb.is_pressed() && !data.inputs.climb_down.is_pressed() {
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
} else {
update.vel.0.z = update.vel.0.z + data.dt.0 * GRAVITY * 1.5;
update.vel.0 = Lerp::lerp(
update.vel.0,
Vec3::zero(),
30.0 * data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0),
);
}
// Apply Vertical Climbing Movement
if let (true, Some(_wall_dir)) = (
(ecs_data.inputs.climb.is_pressed() | ecs_data.inputs.climb_down.is_pressed())
&& update.vel.0.z <= CLIMB_SPEED,
ecs_data.physics.on_wall,
) {
if ecs_data.inputs.climb_down.is_pressed() && !ecs_data.inputs.climb.is_pressed() {
update.vel.0 -=
ecs_data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
} else if ecs_data.inputs.climb.is_pressed() && !ecs_data.inputs.climb_down.is_pressed()
{
update.vel.0.z = (update.vel.0.z + ecs_data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
} else {
update.vel.0.z = update.vel.0.z + ecs_data.dt.0 * GRAVITY * 1.5;
update.vel.0 = Lerp::lerp(
update.vel.0,
Vec3::zero(),
30.0 * ecs_data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0),
);
}
}
update
}
update
}

View File

@ -0,0 +1,38 @@
use super::utils::*;
use crate::{
comp::{CharacterState, StateUpdate},
sys::character_state::JoinData,
};
use std::{collections::VecDeque, time::Duration};
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move(&data, &mut update);
handle_jump(&data, &mut update);
if let CharacterState::Equipping { tool, time_left } = data.character {
if *time_left == Duration::default() {
// Wield delay has expired
update.character = CharacterState::Wielding { tool: *tool };
} else {
// Wield delay hasn't expired yet
// Update wield delay
update.character = CharacterState::Equipping {
time_left: time_left
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
tool: *tool,
};
}
}
update
}

View File

@ -1,4 +1,4 @@
use crate::comp::{ActionState, EcsStateData, MoveState, StateUpdate};
use crate::comp::{ActionState, CharacterEntityData, MoveState, StateUpdate};
use super::utils::*;
use crate::states::StateHandler;
@ -11,11 +11,9 @@ const HUMANOID_AIR_SPEED: f32 = 100.0;
pub struct State;
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self {
Self {}
}
fn new(_ecs_data: &CharacterEntityData) -> Self { Self {} }
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
fn handle(&self, ecs_data: &CharacterEntityData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,

View File

@ -1,6 +1,6 @@
use crate::{
comp::{CharacterState, EcsStateData, StateUpdate},
states::StateHandler,
comp::{CharacterState, StateUpdate},
sys::character_state::JoinData,
};
use std::collections::VecDeque;
use vek::{Vec2, Vec3};
@ -10,65 +10,57 @@ const GLIDE_ANTIGRAV: f32 = crate::sys::phys::GRAVITY * 0.96;
const GLIDE_ACCEL: f32 = 15.0;
const GLIDE_SPEED: f32 = 45.0;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
character: *data.character,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self { Self {} }
// If glide button isn't held or player is on ground, end glide
if !data.inputs.glide.is_pressed() || data.physics.on_ground {
update.character = CharacterState::Idle {};
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
character: *ecs_data.character,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
// If there is a wall in front of character go to climb
if let Some(_) = data.physics.on_wall {
update.character = CharacterState::Climb {};
}
// Move player according to movement direction vector
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if data.vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) {
GLIDE_ACCEL
} else {
0.0
};
// If glide button isn't held or player is on ground, end glide
if !ecs_data.inputs.glide.is_pressed() || ecs_data.physics.on_ground {
update.character = CharacterState::Idle(None);
}
// If there is a wall in front of character go to climb
if let Some(_) = ecs_data.physics.on_wall {
update.character = CharacterState::Climb(None);
}
// Move player according to movement direction vector
update.vel.0 += Vec2::broadcast(ecs_data.dt.0)
* ecs_data.inputs.move_dir
* if ecs_data.vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) {
GLIDE_ACCEL
} else {
0.0
};
// Determine orientation vector from movement direction vector
let ori_dir = Vec2::from(update.vel.0);
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 =
vek::ops::Slerp::slerp(update.ori.0, ori_dir.into(), 2.0 * ecs_data.dt.0);
}
// Apply Glide antigrav lift
if Vec2::<f32>::from(update.vel.0).magnitude_squared() < GLIDE_SPEED.powf(2.0)
&& update.vel.0.z < 0.0
{
let lift = GLIDE_ANTIGRAV + update.vel.0.z.abs().powf(2.0) * 0.15;
update.vel.0.z += ecs_data.dt.0
* lift
* (Vec2::<f32>::from(update.vel.0).magnitude() * 0.075)
.min(1.0)
.max(0.2);
}
// Otherwise keep gliding
update
// Determine orientation vector from movement direction vector
let ori_dir = Vec2::from(update.vel.0);
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(update.ori.0, ori_dir.into(), 2.0 * data.dt.0);
}
// Apply Glide antigrav lift
if Vec2::<f32>::from(update.vel.0).magnitude_squared() < GLIDE_SPEED.powf(2.0)
&& update.vel.0.z < 0.0
{
let lift = GLIDE_ANTIGRAV + update.vel.0.z.abs().powf(2.0) * 0.15;
update.vel.0.z += data.dt.0
* lift
* (Vec2::<f32>::from(update.vel.0).magnitude() * 0.075)
.min(1.0)
.max(0.2);
}
// Otherwise keep gliding
update
}

View File

@ -1,33 +1,24 @@
use super::utils::*;
use crate::comp::{EcsStateData, StateUpdate};
use crate::{comp::StateUpdate, sys::character_state::JoinData};
use std::collections::VecDeque;
use crate::states::StateHandler;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move(data, &mut update);
handle_jump(data, &mut update);
handle_wield(data, &mut update);
handle_sit(data, &mut update);
handle_climb(data, &mut update);
handle_glide(data, &mut update);
handle_dodge(data, &mut update);
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self { Self {} }
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move_dir(ecs_data, &mut update);
handle_jump(ecs_data, &mut update);
handle_wield(ecs_data, &mut update);
handle_sit(ecs_data, &mut update);
handle_climb(ecs_data, &mut update);
handle_glide(ecs_data, &mut update);
handle_dodge(ecs_data, &mut update);
update
}
update
}

View File

@ -1,15 +1,13 @@
use super::{EcsStateData, MoveState, StateHandler, StateUpdate};
use super::{CharacterEntityData, MoveState, StateHandler, StateUpdate};
use crate::event::LocalEvent;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self {
Self {}
}
fn new(_ecs_data: &CharacterEntityData) -> Self { Self {} }
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
fn handle(&self, ecs_data: &CharacterEntityData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,

View File

@ -1,28 +1,11 @@
// Module declarations
pub mod basic_attack;
pub mod basic_block;
// pub mod basic_block;
pub mod climb;
pub mod equipping;
pub mod glide;
pub mod idle;
pub mod roll;
pub mod sit;
pub mod utils;
pub mod wielded;
pub mod wielding;
use crate::comp::{EcsStateData, StateUpdate};
/// ## A type for implementing State Handling Behavior.
///
/// Called by state machines' update functions to allow current states to handle
/// updating their parent machine's current state.
///
/// Structures must implement a `handle()` fn to handle update behavior, and a
/// `new()` for instantiating new instances of a state. `handle()` function
/// recieves `EcsStateData`, a struct of readonly ECS Component data, and
/// returns a `StateUpdate` tuple, with new components that will overwrite an
/// entitie's old components.
pub trait StateHandler: Default {
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate;
fn new(ecs_data: &EcsStateData) -> Self;
}

View File

@ -1,51 +1,28 @@
use crate::{
comp::{CharacterState, EcsStateData, ItemKind::Tool, StateUpdate, ToolData},
states::StateHandler,
comp::{CharacterState, StateUpdate},
sys::character_state::JoinData,
};
use std::{collections::VecDeque, time::Duration};
use vek::Vec3;
const ROLL_SPEED: f32 = 17.0;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long the state has until exitting
remaining_duration: Duration,
}
impl StateHandler for State {
fn new(ecs_data: &EcsStateData) -> Self {
let tool_data =
if let Some(Tool(data)) = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind) {
data
} else {
ToolData::default()
};
Self {
remaining_duration: tool_data.attack_duration(),
}
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
if let CharacterState::Roll { remaining_duration } = data.character {
// Update velocity
update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z)
+ (update.vel.0 * Vec3::new(1.0, 1.0, 0.0)
+ 1.5
* ecs_data
.inputs
.move_dir
.try_normalized()
.unwrap_or_default())
+ 1.5 * data.inputs.move_dir.try_normalized().unwrap_or_default())
.try_normalized()
.unwrap_or_default()
* ROLL_SPEED;
@ -57,22 +34,21 @@ impl StateHandler for State {
> 0.001
{
update.ori.0 =
vek::ops::Slerp::slerp(update.ori.0, update.vel.0.into(), 9.0 * ecs_data.dt.0);
vek::ops::Slerp::slerp(update.ori.0, update.vel.0.into(), 9.0 * data.dt.0);
}
if self.remaining_duration == Duration::default() {
if *remaining_duration == Duration::default() {
// Roll duration has expired
update.character = CharacterState::Idle(None);
update.character = CharacterState::Idle {};
} else {
// Otherwise, tick down remaining_duration
update.character = CharacterState::Roll(Some(State {
remaining_duration: self
.remaining_duration
.checked_sub(Duration::from_secs_f32(ecs_data.dt.0))
update.character = CharacterState::Roll {
remaining_duration: remaining_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
}));
};
}
update
}
update
}

View File

@ -1,38 +1,30 @@
use super::utils::*;
use crate::{
comp::{CharacterState, EcsStateData, StateUpdate},
states::StateHandler,
comp::{CharacterState, StateUpdate},
sys::character_state::JoinData,
};
use std::collections::VecDeque;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self { Self {} }
handle_wield(data, &mut update);
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
//handle_jump(ecs_data, &mut update);
handle_wield(ecs_data, &mut update);
// Try to Fall/Stand up/Move
if !ecs_data.physics.on_ground
|| ecs_data.inputs.sit.is_just_pressed()
|| ecs_data.inputs.move_dir.magnitude_squared() > 0.0
{
update.character = CharacterState::Idle(None);
}
update
// Try to Fall/Stand up/Move
if !data.physics.on_ground
|| data.inputs.sit.is_just_pressed()
|| data.inputs.move_dir.magnitude_squared() > 0.0
{
update.character = CharacterState::Idle {};
}
update
}

View File

@ -1,16 +1,16 @@
use super::utils::*;
use crate::comp::{EcsStateData, MoveState, StateUpdate};
use crate::states::StateHandler;
use crate::{
comp::{CharacterEntityData, MoveState, StateUpdate},
states::StateHandler,
};
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self {
Self {}
}
fn new(_ecs_data: &CharacterEntityData) -> Self { Self {} }
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
fn handle(&self, ecs_data: &CharacterEntityData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,

View File

@ -1,82 +1,56 @@
use crate::comp::{ActionState, EcsStateData, MoveState, StateUpdate};
use crate::states::StateHandler;
use crate::sys::phys::GRAVITY;
use crate::{
comp::StateUpdate,
sys::{character_state::JoinData, phys::GRAVITY},
};
use std::time::Duration;
use vek::{Vec2, Vec3};
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State;
const HUMANOID_WATER_ACCEL: f32 = 70.0;
const HUMANOID_WATER_SPEED: f32 = 120.0;
impl StateHandler for State {
fn new(_ecs_data: &EcsStateData) -> Self {
Self {}
}
pub fn behavior(data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *data.character,
pos: *data.pos,
vel: *data.vel,
ori: *data.ori,
energy: *data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
// Update velocity
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if update.vel.0.magnitude_squared() < HUMANOID_WATER_SPEED.powf(2.0) {
HUMANOID_WATER_ACCEL
} else {
0.0
};
// Update velocity
update.vel.0 += Vec2::broadcast(ecs_data.dt.0)
* ecs_data.inputs.move_dir
* if update.vel.0.magnitude_squared() < HUMANOID_WATER_SPEED.powf(2.0) {
HUMANOID_WATER_ACCEL
} else {
0.0
};
// Set direction based on move direction when on the ground
let ori_dir = if update.character.is_attack() || update.character.is_block() {
Vec2::from(data.inputs.look_dir).normalized()
} else {
Vec2::from(update.vel.0)
};
// Set direction based on move direction when on the ground
let ori_dir =
if let ActionState::Attack(_) | ActionState::Block(_) = update.character.action_state {
Vec2::from(ecs_data.inputs.look_dir).normalized()
} else {
Vec2::from(update.vel.0)
};
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(
update.ori.0,
ori_dir.into(),
if ecs_data.physics.on_ground { 9.0 } else { 2.0 } * ecs_data.dt.0,
);
}
// Force players to press jump in a slow rhythmic fashion to swim up
if ecs_data.inputs.jump.is_pressed()
&& !ecs_data
.inputs
.jump
.is_long_press(Duration::from_millis(600))
{
update.vel.0.z =
(update.vel.0.z + ecs_data.dt.0 * GRAVITY * 1.25).min(HUMANOID_WATER_SPEED);
}
// Not on ground
if !ecs_data.physics.on_ground {
update.character.move_state = MoveState::Swim(None);
update
}
// On ground
else {
// Return to running or standing based on move inputs
update.character.move_state = if ecs_data.inputs.move_dir.magnitude_squared() > 0.0 {
MoveState::Run(None)
} else {
MoveState::Stand(None)
};
update
}
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(
update.ori.0,
ori_dir.into(),
if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0,
);
}
// Force players to pulse jump button to swim up
if data.inputs.jump.is_pressed() && !data.inputs.jump.is_long_press(Duration::from_millis(600))
{
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(HUMANOID_WATER_SPEED);
}
update
}

View File

@ -1,12 +1,24 @@
use crate::{
comp::{Attacking, CharacterState, EcsStateData, EnergySource, ItemKind::Tool, StateUpdate},
comp::{AbilityState, CharacterState, EnergySource, ItemKind::Tool, StateUpdate},
event::LocalEvent,
sys::{character_state::JoinData, phys::GRAVITY},
};
use std::time::Duration;
use vek::vec::{Vec2, Vec3};
pub fn handle_move_dir(ecs_data: &EcsStateData, update: &mut StateUpdate) {
let (accel, speed): (f32, f32) = if ecs_data.physics.on_ground {
const HUMANOID_WATER_ACCEL: f32 = 70.0;
const HUMANOID_WATER_SPEED: f32 = 120.0;
pub fn handle_move(data: &JoinData, update: &mut StateUpdate) {
if data.physics.in_fluid {
handle_swim_move(data, update);
} else {
handle_ground_move(data, update);
}
}
pub fn handle_ground_move(data: &JoinData, update: &mut StateUpdate) {
let (accel, speed): (f32, f32) = if data.physics.on_ground {
let accel = 100.0;
let speed = 8.0;
(accel, speed)
@ -18,8 +30,7 @@ pub fn handle_move_dir(ecs_data: &EcsStateData, update: &mut StateUpdate) {
// Move player according to move_dir
if update.vel.0.magnitude_squared() < speed.powf(2.0) {
update.vel.0 =
update.vel.0 + Vec2::broadcast(ecs_data.dt.0) * ecs_data.inputs.move_dir * accel;
update.vel.0 = update.vel.0 + Vec2::broadcast(data.dt.0) * data.inputs.move_dir * accel;
let mag2 = update.vel.0.magnitude_squared();
if mag2 > speed.powf(2.0) {
update.vel.0 = update.vel.0.normalized() * speed;
@ -31,7 +42,7 @@ pub fn handle_move_dir(ecs_data: &EcsStateData, update: &mut StateUpdate) {
|| update.character.is_attack()
|| update.character.is_block()
{
Vec2::from(ecs_data.inputs.look_dir).normalized()
Vec2::from(data.inputs.look_dir).normalized()
} else {
Vec2::from(update.vel.0)
};
@ -41,94 +52,165 @@ pub fn handle_move_dir(ecs_data: &EcsStateData, update: &mut StateUpdate) {
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.ori.0 = vek::ops::Slerp::slerp(update.ori.0, ori_dir.into(), 9.0 * ecs_data.dt.0);
update.ori.0 = vek::ops::Slerp::slerp(update.ori.0, ori_dir.into(), 9.0 * data.dt.0);
}
}
pub fn handle_wield(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if ecs_data.inputs.primary.is_pressed() {
update.character = CharacterState::Wielding(None);
}
}
pub fn handle_swim_move(data: &JoinData, update: &mut StateUpdate) {
// Update velocity
update.vel.0 += Vec2::broadcast(data.dt.0)
* data.inputs.move_dir
* if update.vel.0.magnitude_squared() < HUMANOID_WATER_SPEED.powf(2.0) {
HUMANOID_WATER_ACCEL
} else {
0.0
};
pub fn handle_sit(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if ecs_data.inputs.sit.is_pressed() && ecs_data.physics.on_ground && ecs_data.body.is_humanoid()
// Set direction based on move direction when on the ground
let ori_dir = if update.character.is_attack() || update.character.is_block() {
Vec2::from(data.inputs.look_dir).normalized()
} else {
Vec2::from(update.vel.0)
};
if ori_dir.magnitude_squared() > 0.0001
&& (update.ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared()
> 0.001
{
update.character = CharacterState::Sit(None);
update.ori.0 = vek::ops::Slerp::slerp(
update.ori.0,
ori_dir.into(),
if data.physics.on_ground { 9.0 } else { 2.0 } * data.dt.0,
);
}
// Force players to pulse jump button to swim up
if data.inputs.jump.is_pressed() && !data.inputs.jump.is_long_press(Duration::from_millis(600))
{
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(HUMANOID_WATER_SPEED);
}
}
pub fn handle_climb(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if (ecs_data.inputs.climb.is_pressed() || ecs_data.inputs.climb_down.is_pressed())
&& ecs_data.physics.on_wall.is_some()
&& !ecs_data.physics.on_ground
pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.primary.is_pressed() {
if let Some(Tool(tool)) = data.stats.equipment.main.as_ref().map(|i| i.kind) {
update.character = CharacterState::Equipping {
tool,
time_left: tool.equip_time(),
};
} else {
update.character = CharacterState::Idle {};
};
}
}
pub fn handle_sit(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.sit.is_pressed() && data.physics.on_ground && data.body.is_humanoid() {
update.character = CharacterState::Sit {};
}
}
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
if (data.inputs.climb.is_pressed() || data.inputs.climb_down.is_pressed())
&& data.physics.on_wall.is_some()
&& !data.physics.on_ground
//&& update.vel.0.z < 0.0
&& ecs_data.body.is_humanoid()
&& data.body.is_humanoid()
{
update.character = CharacterState::Climb(None);
update.character = CharacterState::Climb {};
}
}
pub fn handle_unwield(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if let CharacterState::Wielded(_) = update.character {
if ecs_data.inputs.toggle_wield.is_pressed() {
update.character = CharacterState::Idle(None);
pub fn handle_unwield(data: &JoinData, update: &mut StateUpdate) {
if let CharacterState::Wielding { .. } = update.character {
if data.inputs.toggle_wield.is_pressed() {
update.character = CharacterState::Idle {};
}
}
}
pub fn handle_glide(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if let CharacterState::Idle(_) | CharacterState::Wielded(_) = update.character {
if ecs_data.inputs.glide.is_pressed()
&& !ecs_data.physics.on_ground
&& ecs_data.body.is_humanoid()
{
update.character = CharacterState::Glide(None);
pub fn handle_glide(data: &JoinData, update: &mut StateUpdate) {
if let CharacterState::Idle { .. } | CharacterState::Wielding { .. } = update.character {
if data.inputs.glide.is_pressed() && !data.physics.on_ground && data.body.is_humanoid() {
update.character = CharacterState::Glide {};
}
}
}
pub fn handle_jump(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if ecs_data.inputs.jump.is_pressed() && ecs_data.physics.on_ground {
pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
if data.inputs.jump.is_pressed() && data.physics.on_ground {
update
.local_events
.push_front(LocalEvent::Jump(*ecs_data.entity));
.push_front(LocalEvent::Jump(data.entity));
}
}
pub fn handle_primary(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if let Some(state) = ecs_data.ability_pool.primary {
if let CharacterState::Wielded(_) = update.character {
if ecs_data.inputs.primary.is_pressed() {
update.character = state;
pub fn handle_primary(data: &JoinData, update: &mut StateUpdate) {
if let Some(state) = data.ability_pool.primary {
if let CharacterState::Wielding { .. } = update.character {
if data.inputs.primary.is_pressed() {
// data.updater.insert(data.entity, state);
update.character = character_state_from_ability(data, state);
}
}
}
}
pub fn handle_secondary(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if let Some(state) = ecs_data.ability_pool.secondary {
if let CharacterState::Wielded(_) = update.character {
if ecs_data.inputs.secondary.is_pressed() {
update.character = state;
pub fn handle_secondary(data: &JoinData, update: &mut StateUpdate) {
if let Some(state) = data.ability_pool.secondary {
if let CharacterState::Wielding { .. } = update.character {
if data.inputs.secondary.is_pressed() {
// data.updater.insert(data.entity, state);
update.character = character_state_from_ability(data, state);
}
}
}
}
pub fn handle_dodge(ecs_data: &EcsStateData, update: &mut StateUpdate) {
if let Some(state) = ecs_data.ability_pool.dodge {
if let CharacterState::Idle(_) | CharacterState::Wielded(_) = update.character {
if ecs_data.inputs.roll.is_pressed()
&& ecs_data.physics.on_ground
&& ecs_data.body.is_humanoid()
pub fn handle_dodge(data: &JoinData, update: &mut StateUpdate) {
if let Some(state) = data.ability_pool.dodge {
if let CharacterState::Idle { .. } | CharacterState::Wielding { .. } = update.character {
if data.inputs.roll.is_pressed()
&& data.physics.on_ground
&& data.body.is_humanoid()
&& update
.energy
.try_change_by(-200, EnergySource::Roll)
.is_ok()
{
update.character = state;
// let tool_data =
// if let Some(Tool(data)) = data.stats.equipment.main.as_ref().map(|i|
// i.kind) { data
// } else {
// ToolData::default()
// };
update.character = CharacterState::Roll {
remaining_duration: Duration::from_millis(600), // tool_data.attack_duration(),
};
data.updater.insert(data.entity, state);
}
}
}
}
pub fn character_state_from_ability(
data: &JoinData,
ability_state: AbilityState,
) -> CharacterState {
match ability_state {
AbilityState::BasicAttack { .. } => {
if let Some(Tool(tool)) = data.stats.equipment.main.as_ref().map(|i| i.kind) {
CharacterState::BasicAttack {
exhausted: false,
remaining_duration: tool.attack_duration(),
}
} else {
CharacterState::Idle {}
}
},
AbilityState::BasicBlock { .. } => CharacterState::BasicBlock {},
AbilityState::Roll { .. } => CharacterState::Roll {
remaining_duration: Duration::from_millis(600),
},
}
}

View File

@ -1,51 +0,0 @@
use super::utils::*;
use crate::{
comp::{EcsStateData, ItemKind::Tool, StateUpdate, ToolData},
states::StateHandler,
};
use std::{collections::VecDeque, time::Duration};
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long before a new action can be performed
/// after equipping
pub equip_delay: Duration,
}
impl StateHandler for State {
fn new(ecs_data: &EcsStateData) -> Self {
let tool_data =
if let Some(Tool(data)) = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind) {
data
} else {
ToolData::default()
};
Self {
equip_delay: tool_data.equip_time(),
}
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move_dir(&ecs_data, &mut update);
handle_jump(&ecs_data, &mut update);
handle_sit(&ecs_data, &mut update);
handle_climb(&ecs_data, &mut update);
handle_glide(&ecs_data, &mut update);
handle_unwield(&ecs_data, &mut update);
handle_primary(&ecs_data, &mut update);
handle_secondary(&ecs_data, &mut update);
handle_dodge(&ecs_data, &mut update);
update
}
}

View File

@ -1,56 +1,27 @@
use super::utils::*;
use crate::{
comp::{CharacterState, EcsStateData, ItemKind::Tool, StateUpdate, ToolData},
states::StateHandler,
};
use std::{collections::VecDeque, time::Duration};
use crate::{comp::StateUpdate, sys::character_state::JoinData};
use std::collections::VecDeque;
#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long before a new action can be performed
/// after equipping
pub equip_delay: Duration,
}
impl StateHandler for State {
fn new(ecs_data: &EcsStateData) -> Self {
let equip_delay =
if let Some(Tool(data)) = ecs_data.stats.equipment.main.as_ref().map(|i| i.kind) {
data.equip_time()
} else {
Duration::default()
};
Self { equip_delay }
}
fn handle(&self, ecs_data: &EcsStateData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move_dir(&ecs_data, &mut update);
if self.equip_delay == Duration::default() {
// Wield delay has expired
update.character = CharacterState::Wielded(None);
} else {
// Wield delay hasn't expired yet
// Update wield delay
update.character = CharacterState::Wielding(Some(State {
equip_delay: self
.equip_delay
.checked_sub(Duration::from_secs_f32(ecs_data.dt.0))
.unwrap_or_default(),
}));
}
update
}
pub fn behavior(ecs_data: &JoinData) -> StateUpdate {
let mut update = StateUpdate {
character: *ecs_data.character,
pos: *ecs_data.pos,
vel: *ecs_data.vel,
ori: *ecs_data.ori,
energy: *ecs_data.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
};
handle_move(&ecs_data, &mut update);
handle_jump(&ecs_data, &mut update);
handle_sit(&ecs_data, &mut update);
handle_climb(&ecs_data, &mut update);
handle_glide(&ecs_data, &mut update);
handle_unwield(&ecs_data, &mut update);
handle_primary(&ecs_data, &mut update);
handle_secondary(&ecs_data, &mut update);
handle_dodge(&ecs_data, &mut update);
update
}

View File

@ -1,20 +1,79 @@
use crate::{
comp::{
AbilityPool, Body, CharacterState, Controller, EcsStateData, Energy, Mounting, Ori,
PhysicsState, Pos, Stats, Vel,
AbilityPool, Body, CharacterState, Controller, ControllerInputs, Energy, Mounting, Ori,
PhysicsState, Pos, StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
state::DeltaTime,
states,
sync::{Uid, UidAllocator},
};
use specs::{Entities, Join, LazyUpdate, Read, ReadStorage, System, WriteStorage};
use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, WriteStorage};
/// ## Character State System
use std::collections::VecDeque;
/// Read-Only Data sent from Character Behavior System to bahvior fn's
pub struct JoinData<'a> {
pub entity: Entity,
pub uid: &'a Uid,
pub character: &'a CharacterState,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub dt: &'a DeltaTime,
pub controller: &'a Controller,
pub inputs: &'a ControllerInputs,
pub stats: &'a Stats,
pub energy: &'a Energy,
pub body: &'a Body,
pub physics: &'a PhysicsState,
pub ability_pool: &'a AbilityPool,
pub updater: &'a LazyUpdate,
}
pub type JoinTuple<'a> = (
Entity,
&'a Uid,
&'a mut CharacterState,
&'a mut Pos,
&'a mut Vel,
&'a mut Ori,
&'a mut Energy,
&'a Controller,
&'a Stats,
&'a Body,
&'a PhysicsState,
&'a AbilityPool,
);
impl<'a> JoinData<'a> {
fn new(j: &'a JoinTuple<'a>, updater: &'a LazyUpdate, dt: &'a DeltaTime) -> Self {
Self {
entity: j.0,
uid: j.1,
character: j.2,
pos: j.3,
vel: j.4,
ori: j.5,
energy: j.6,
controller: j.7,
inputs: &j.7.inputs,
stats: j.8,
body: j.9,
physics: j.10,
ability_pool: j.11,
updater,
dt,
}
}
}
/// /// ## Character State System
/// #### Calls updates to `CharacterState`s. Acts on tuples of (
/// `CharacterState`, `Pos`, `Vel`, and `Ori` ).
///
/// _System forms `EcsStateData` tuples and passes those to `ActionState`
/// _System forms `CharacterEntityData` tuples and passes those to `ActionState`
/// `update()` fn, then does the same for `MoveState` `update`_
pub struct Sys;
@ -63,20 +122,7 @@ impl<'a> System<'a> for Sys {
mountings,
): Self::SystemData,
) {
for (
entity,
uid,
character,
pos,
vel,
ori,
energy,
controller,
stats,
body,
physics,
ability_pool,
) in (
let mut join_iter = (
&entities,
&uids,
&mut character_states,
@ -90,50 +136,56 @@ impl<'a> System<'a> for Sys {
&physics_states,
&ability_pools,
)
.join()
{
let inputs = &controller.inputs;
.join();
while let Some(tuple) = join_iter.next() {
let j = JoinData::new(&tuple, &updater, &dt);
let inputs = &j.inputs;
// Being dead overrides all other states
if stats.is_dead {
if j.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_bus.emitter().emit(ServerEvent::Respawn(entity));
server_bus.emitter().emit(ServerEvent::Respawn(j.entity));
}
// Or do nothing
return;
}
// If mounted, character state is controlled by mount
// TODO: Make mounting a state
if let Some(Mounting(_)) = mountings.get(entity) {
*character = CharacterState::Sit(None);
if let Some(Mounting(_)) = mountings.get(j.entity) {
*tuple.2 = CharacterState::Sit {};
return;
}
let mut state_update = character.update(&EcsStateData {
entity: &entity,
uid,
character,
pos,
vel,
ori,
energy,
dt: &dt,
inputs,
stats,
body,
physics,
updater: &updater,
ability_pool,
});
let mut state_update = match j.character {
CharacterState::Idle { .. } => states::idle::behavior(&j),
CharacterState::Climb { .. } => states::climb::behavior(&j),
CharacterState::Glide { .. } => states::glide::behavior(&j),
CharacterState::Roll { .. } => states::roll::behavior(&j),
CharacterState::Wielding { .. } => states::wielding::behavior(&j),
CharacterState::Equipping { .. } => states::equipping::behavior(&j),
CharacterState::BasicAttack { .. } => states::basic_attack::behavior(&j),
CharacterState::Sit { .. } => states::sit::behavior(&j),
*character = state_update.character;
*pos = state_update.pos;
*vel = state_update.vel;
*ori = state_update.ori;
*energy = state_update.energy;
_ => StateUpdate {
character: *j.character,
pos: *j.pos,
vel: *j.vel,
ori: *j.ori,
energy: *j.energy,
local_events: VecDeque::new(),
server_events: VecDeque::new(),
},
};
*tuple.2 = state_update.character;
*tuple.3 = state_update.pos;
*tuple.4 = state_update.vel;
*tuple.5 = state_update.ori;
*tuple.6 = state_update.energy;
local_bus.emitter().append(&mut state_update.local_events);
server_bus.emitter().append(&mut state_update.server_events);
}

View File

@ -75,7 +75,7 @@ impl<'a> System<'a> for Sys {
// Accelerate recharging energy if not wielding.
match character_state {
CharacterState::Idle(_) => {
CharacterState::Idle { .. } => {
if {
let energy = energy.get_unchecked();
energy.current() < energy.maximum()
@ -92,7 +92,7 @@ impl<'a> System<'a> for Sys {
}
},
// Wield does not regen and sets the rate back to zero.
CharacterState::Wielded(_) => {
CharacterState::Wielding { .. } => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}

View File

@ -202,10 +202,10 @@ impl MovementEventMapper {
// Match all other Movemement and Action states
match (previous_state.event, character_state) {
(_, CharacterState::Roll(_)) => SfxEvent::Roll,
(_, CharacterState::Climb(_)) => SfxEvent::Climb,
(SfxEvent::Glide, CharacterState::Idle(_)) => SfxEvent::GliderClose,
(previous_event, CharacterState::Glide(_)) => {
(_, CharacterState::Roll { .. }) => SfxEvent::Roll,
(_, CharacterState::Climb { .. }) => SfxEvent::Climb,
(SfxEvent::Glide, CharacterState::Idle { .. }) => SfxEvent::GliderClose,
(previous_event, CharacterState::Glide { .. }) => {
if previous_event != SfxEvent::GliderOpen && previous_event != SfxEvent::Glide {
SfxEvent::GliderOpen
} else {
@ -226,13 +226,13 @@ impl MovementEventMapper {
}
/// This helps us determine whether we should be emitting the Wield/Unwield
/// events. For now, consider either CharacterState::Wielded or
/// ::Wielding to mean the weapon is drawn. This will need updating if the
/// events. For now, consider either CharacterState::Wielding or
/// ::Equipping to mean the weapon is drawn. This will need updating if the
/// animations change to match the wield_duration associated with the weapon
fn weapon_drawn(character: &CharacterState) -> bool {
character.is_wielded()
|| match character {
CharacterState::Wielding(_) => true,
CharacterState::Equipping { .. } => true,
_ => false,
}
}

View File

@ -91,7 +91,7 @@ fn maps_idle() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -120,7 +120,7 @@ fn maps_run_with_sufficient_velocity() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -149,7 +149,7 @@ fn does_not_map_run_with_insufficient_velocity() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -178,7 +178,7 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: false,
on_wall: None,
@ -207,7 +207,7 @@ fn maps_roll() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Roll(None),
&CharacterState::Roll {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -236,7 +236,7 @@ fn maps_land_on_ground_to_run() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -265,7 +265,7 @@ fn maps_glider_open() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Glide(None),
&CharacterState::Glide {},
&PhysicsState {
on_ground: false,
on_wall: None,
@ -294,7 +294,7 @@ fn maps_glide() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Glide(None),
&CharacterState::Glide {},
&PhysicsState {
on_ground: false,
on_wall: None,
@ -323,7 +323,7 @@ fn maps_glider_close_when_closing_mid_flight() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: false,
on_wall: None,
@ -352,7 +352,7 @@ fn maps_glider_close_when_landing() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(None),
&CharacterState::Idle {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -383,7 +383,7 @@ fn maps_wield() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Wielding(None),
&CharacterState::Equipping {},
&PhysicsState {
on_ground: true,
on_wall: None,
@ -443,7 +443,7 @@ fn does_not_map_wield_when_no_main_weapon() {
);
let result = MovementEventMapper::map_movement_event(
&CharacterState::Wielded(None),
&CharacterState::Wielding {},
&PhysicsState {
on_ground: true,
on_wall: None,

View File

@ -170,10 +170,10 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
if camera_mode != CameraMode::FirstPerson
|| character_state
.map(|cs| match cs {
CharacterState::BasicAttack(_)
| CharacterState::BasicBlock(_)
| CharacterState::Wielding(_)
| CharacterState::Wielded(_) => true,
CharacterState::BasicAttack { .. }
| CharacterState::BasicBlock { .. }
| CharacterState::Equipping { .. }
| CharacterState::Wielding { .. } => true,
_ => false,
})
.unwrap_or_default()

View File

@ -441,14 +441,16 @@ impl FigureMgr {
),
};
let target_bones = match &character {
CharacterState::Roll(_) => anim::character::RollAnimation::update_skeleton(
&target_base,
(active_tool_kind, ori.0, state.last_ori, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
CharacterState::BasicAttack(_) => {
CharacterState::Roll { .. } => {
anim::character::RollAnimation::update_skeleton(
&target_base,
(active_tool_kind, ori.0, state.last_ori, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::BasicAttack { .. } => {
anim::character::AttackAnimation::update_skeleton(
&target_base,
(active_tool_kind, time),
@ -457,7 +459,7 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::BasicBlock(_) => {
CharacterState::BasicBlock { .. } => {
anim::character::BlockIdleAnimation::update_skeleton(
&CharacterSkeleton::new(),
(active_tool_kind, time),
@ -476,7 +478,7 @@ impl FigureMgr {
skeleton_attr,
)
}*/
CharacterState::Wielding(_) => {
CharacterState::Equipping { .. } => {
anim::character::WieldAnimation::update_skeleton(
&target_base,
(active_tool_kind, vel.0.magnitude(), time),
@ -485,7 +487,7 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::Wielded(_) => {
CharacterState::Wielding { .. } => {
anim::character::WieldAnimation::update_skeleton(
&target_base,
(active_tool_kind, vel.0.magnitude(), time),
@ -494,7 +496,7 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::Glide(_) => {
CharacterState::Glide { .. } => {
anim::character::GlidingAnimation::update_skeleton(
&target_base,
(active_tool_kind, vel.0, ori.0, state.last_ori, time),
@ -503,7 +505,7 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::Climb(_) => {
CharacterState::Climb { .. } => {
anim::character::ClimbAnimation::update_skeleton(
&CharacterSkeleton::new(),
(active_tool_kind, vel.0, ori.0, time),
@ -512,13 +514,15 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::Sit(_) => anim::character::SitAnimation::update_skeleton(
&CharacterSkeleton::new(),
(active_tool_kind, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
CharacterState::Sit { .. } => {
anim::character::SitAnimation::update_skeleton(
&CharacterSkeleton::new(),
(active_tool_kind, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
},
_ => target_base,
};