Update timed combo, add CharacerBehavior trait

This commit is contained in:
AdamWhitehurst 2020-03-14 12:50:07 -06:00
parent fe19698d52
commit 6fc94c22ba
8 changed files with 253 additions and 174 deletions

View File

@ -1,4 +1,7 @@
use crate::comp::CharacterState;
use crate::{
comp::{CharacterState, ToolData},
states::*,
};
use specs::{Component, DenseVecStorage, FlaggedStorage, HashMapStorage};
use std::time::Duration;
@ -12,8 +15,9 @@ pub enum CharacterAbility {
Roll,
ChargeAttack,
TimedCombo {
/// Amount of energy required to use ability
cost: i32,
tool: ToolData,
buildup_duration: Duration,
recover_duration: Duration,
},
}
@ -35,11 +39,11 @@ impl From<CharacterAbility> for CharacterState {
CharacterAbility::BasicAttack {
buildup_duration,
recover_duration,
} => CharacterState::BasicAttack {
} => CharacterState::BasicAttack(basic_attack::State {
exhausted: false,
buildup_duration,
recover_duration,
},
}),
CharacterAbility::BasicBlock { .. } => CharacterState::BasicBlock {},
CharacterAbility::Roll { .. } => CharacterState::Roll {
remaining_duration: Duration::from_millis(600),
@ -47,12 +51,18 @@ impl From<CharacterAbility> for CharacterState {
CharacterAbility::ChargeAttack { .. } => CharacterState::ChargeAttack {
remaining_duration: Duration::from_millis(600),
},
CharacterAbility::TimedCombo { .. } => CharacterState::TimedCombo {
stage: 1,
stage_time_active: Duration::default(),
CharacterAbility::TimedCombo {
tool,
buildup_duration,
recover_duration,
} => CharacterState::TimedCombo(timed_combo::State {
tool,
buildup_duration,
recover_duration,
stage: 0,
stage_exhausted: false,
can_transition: false,
},
stage_time_active: Duration::default(),
}),
}
}
}

View File

@ -1,6 +1,7 @@
use crate::{
comp::{Energy, Ori, Pos, ToolData, Vel},
event::{LocalEvent, ServerEvent},
states::*,
};
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage, HashMapStorage, VecStorage};
@ -33,15 +34,6 @@ pub enum CharacterState {
tool: ToolData,
},
Glide,
/// A basic attacking state
BasicAttack {
/// How long till the state deals damage
buildup_duration: Duration,
/// How long till the state remains after dealing damage
recover_duration: Duration,
/// Whether the attack can deal more damage
exhausted: bool,
},
/// A basic blocking state
BasicBlock,
ChargeAttack {
@ -52,18 +44,11 @@ pub enum CharacterState {
/// How long the state has until exiting
remaining_duration: Duration,
},
/// A basic attacking state
BasicAttack(basic_attack::State),
/// A three-stage attack where play must click at appropriate times
/// to continue attack chain.
TimedCombo {
/// `int` denoting what stage (of 3) the attack is in.
stage: i8,
/// How long current stage has been active
stage_time_active: Duration,
/// Whether current stage has exhausted its attack
stage_exhausted: bool,
/// Whether player has clicked at the proper time to go to next stage
can_transition: bool,
},
TimedCombo(timed_combo::State),
/// A three-stage attack where each attack pushes player forward
/// and successive attacks increase in damage, while player holds button.
TripleStrike {
@ -81,16 +66,19 @@ pub enum CharacterState {
impl CharacterState {
pub fn is_wield(&self) -> bool {
match self {
CharacterState::Wielding { .. } => true,
CharacterState::BasicAttack { .. } => true,
CharacterState::BasicBlock { .. } => true,
CharacterState::Wielding { .. }
| CharacterState::BasicAttack(_)
| CharacterState::TimedCombo(_)
| CharacterState::BasicBlock { .. } => true,
_ => false,
}
}
pub fn is_attack(&self) -> bool {
match self {
CharacterState::BasicAttack { .. } | CharacterState::ChargeAttack { .. } => true,
CharacterState::BasicAttack(_)
| CharacterState::TimedCombo(_)
| CharacterState::ChargeAttack { .. } => true,
_ => false,
}
}

View File

@ -37,7 +37,11 @@ impl ToolData {
use ToolKind::*;
match self.kind {
Sword(_) => vec![TimedCombo { cost: -150 }],
Sword(_) => vec![TimedCombo {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),
tool: *self,
}],
Axe => vec![BasicAttack {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(500),

View File

@ -1,42 +1,50 @@
use crate::{
comp::{Attacking, CharacterState, EnergySource, ItemKind::Tool, StateUpdate},
states::utils::*,
sys::character_behavior::JoinData,
sys::character_behavior::*,
};
use std::{collections::VecDeque, time::Duration};
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(),
};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// How long until state should deal damage
pub buildup_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// Whether the attack can deal more damage
pub exhausted: bool,
}
impl CharacterBehavior for State {
fn behavior(&self, 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(),
};
if let CharacterState::BasicAttack {
exhausted,
buildup_duration,
recover_duration,
} = data.character
{
let tool_kind = data.stats.equipment.main.as_ref().map(|i| i.kind);
handle_move(data, &mut update);
if buildup_duration != &Duration::default() {
// Build up window
if self.buildup_duration != Duration::default() {
// Start to swing
update.character = CharacterState::BasicAttack {
buildup_duration: buildup_duration
update.character = CharacterState::BasicAttack(State {
buildup_duration: self
.buildup_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
recover_duration: *recover_duration,
recover_duration: self.recover_duration,
exhausted: false,
};
} else if !*exhausted {
});
}
// Hit attempt window
else if !self.exhausted {
// Swing hits
if let Some(Tool(tool)) = tool_kind {
if let Some(tool) = unwrap_tool_data(data) {
data.updater.insert(data.entity, Attacking {
weapon: Some(tool),
applied: false,
@ -44,31 +52,34 @@ pub fn behavior(data: &JoinData) -> StateUpdate {
});
}
update.character = CharacterState::BasicAttack {
buildup_duration: *buildup_duration,
recover_duration: *recover_duration,
update.character = CharacterState::BasicAttack(State {
buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration,
exhausted: true,
};
} else if recover_duration != &Duration::default() {
// Recover from swing
update.character = CharacterState::BasicAttack {
buildup_duration: *buildup_duration,
recover_duration: recover_duration
});
}
// Swing recovery window
else if self.recover_duration != Duration::default() {
update.character = CharacterState::BasicAttack(State {
buildup_duration: self.buildup_duration,
recover_duration: self
.recover_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
exhausted: true,
}
} else {
// Done
if let Some(Tool(tool)) = tool_kind {
});
}
// Done
else {
if let Some(tool) = unwrap_tool_data(data) {
update.character = CharacterState::Wielding { tool };
// Make sure attack component is removed
data.updater.remove::<Attacking>(data.entity);
} else {
update.character = CharacterState::Idle;
}
}
// More handling
// Subtract energy on successful hit
if let Some(attack) = data.attacking {
if attack.applied && attack.hit_count > 0 {
data.updater.remove::<Attacking>(data.entity);
@ -76,9 +87,6 @@ pub fn behavior(data: &JoinData) -> StateUpdate {
}
}
update
} else {
update.character = CharacterState::Idle {};
update
}
}

View File

@ -1,98 +1,137 @@
use crate::{
comp::{Attacking, CharacterState, ItemKind::Tool, StateUpdate},
comp::{Attacking, CharacterState, EnergySource, StateUpdate, ToolData},
states::utils::*,
sys::character_behavior::JoinData,
sys::character_behavior::{CharacterBehavior, JoinData},
};
use std::{collections::VecDeque, time::Duration};
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(),
};
if let CharacterState::TimedCombo {
stage,
stage_time_active,
stage_exhausted,
can_transition,
} = data.character
{
// Sorry adam, I don't want to fix this rn, check out basic_attack to
// see how to do it
//
/*
let mut new_can_transition = *can_transition;
let mut new_stage_exhausted = *stage_exhausted;
let new_stage_time_active = stage_time_active
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or(Duration::default());
match stage {
1 => {
if new_stage_time_active > tool.attack_buildup_duration() {
if !*stage_exhausted {
// Try to deal damage
data.updater.insert(data.entity, Attacking {
weapon: Some(*tool),
applied: false,
hit_count: 0,
});
new_stage_exhausted = true;
} else {
// Make sure to remove Attacking component
data.updater.remove::<Attacking>(data.entity);
}
// Check if player has timed click right
if data.inputs.primary.is_just_pressed() {
println!("Can transition");
new_can_transition = true;
}
}
if new_stage_time_active > tool.attack_duration() {
if new_can_transition {
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 2,
stage_time_active: Duration::default(),
stage_exhausted: false,
can_transition: false,
}
} else {
println!("Failed");
attempt_wield(data, &mut update);
}
} else {
update.character = CharacterState::TimedCombo {
tool: *tool,
stage: 1,
stage_time_active: new_stage_time_active,
stage_exhausted: new_stage_exhausted,
can_transition: new_can_transition,
}
}
},
2 => {
println!("2");
attempt_wield(data, &mut update);
},
3 => {
println!("3");
attempt_wield(data, &mut update);
},
_ => {
// Should never get here.
},
}
*/
}
update
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct State {
/// Denotes what stage (of 3) the attack is in
pub stage: i8,
/// Whether current stage has exhausted its attack
pub stage_exhausted: bool,
/// How long state waits before it should deal damage
pub buildup_duration: Duration,
/// How long the state waits until exiting
pub recover_duration: Duration,
/// Tracks how long current stage has been active
pub stage_time_active: Duration,
/// `ToolData` to be sent to `Attacking` component
pub tool: ToolData,
}
impl CharacterBehavior for State {
fn behavior(&self, 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(),
};
let new_stage_time_active = self
.stage_time_active
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or(Duration::default());
println!("Stage {:?}", self.stage);
if self.stage < 3 {
// Build up window
if new_stage_time_active < self.buildup_duration {
// If the player is pressing primary btn
if data.inputs.primary.is_just_pressed() {
// They failed, go back to `Wielding`
update.character = CharacterState::Wielding { tool: self.tool };
}
// Keep updating
else {
update.character = CharacterState::TimedCombo(State {
tool: self.tool,
stage: self.stage,
buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration,
stage_exhausted: false,
stage_time_active: new_stage_time_active,
});
}
}
// Hit attempt window
else if !self.stage_exhausted {
// Swing hits
data.updater.insert(data.entity, Attacking {
weapon: Some(self.tool),
applied: false,
hit_count: 0,
});
update.character = CharacterState::TimedCombo(State {
tool: self.tool,
stage: self.stage,
buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration,
stage_exhausted: true,
stage_time_active: new_stage_time_active,
});
}
// Swing recovery window
else if new_stage_time_active
< self
.buildup_duration
.checked_add(self.recover_duration)
.unwrap_or(Duration::default())
{
// Try to transition to next stage
if data.inputs.primary.is_just_pressed() {
update.character = CharacterState::TimedCombo(State {
tool: self.tool,
stage: self.stage + 1,
buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration,
stage_exhausted: true,
stage_time_active: Duration::default(),
});
}
// Player didn't click this frame
else {
// Update state
update.character = CharacterState::TimedCombo(State {
tool: self.tool,
stage: self.stage,
buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration,
stage_exhausted: true,
stage_time_active: new_stage_time_active,
});
}
}
// Stage expired but missed transition to next stage
else {
// Back to `Wielding`
update.character = CharacterState::Wielding { tool: self.tool };
// Make sure attack component is removed
data.updater.remove::<Attacking>(data.entity);
}
}
// Made three successful hits!
else {
// Back to `Wielding`
update.character = CharacterState::Wielding { tool: self.tool };
// Make sure attack component is removed
data.updater.remove::<Attacking>(data.entity);
}
// Subtract energy on successful hit
if let Some(attack) = data.attacking {
if attack.applied && attack.hit_count > 0 {
data.updater.remove::<Attacking>(data.entity);
update.energy.change_by(100, EnergySource::HitEnemy);
}
}
update
}
}

View File

@ -1,6 +1,7 @@
use crate::{
comp::{CharacterAbility, CharacterState, EnergySource, ItemKind::Tool, StateUpdate, ToolData},
event::LocalEvent,
states::*,
sys::{character_behavior::JoinData, phys::GRAVITY},
};
use std::time::Duration;
@ -236,3 +237,11 @@ pub fn attempt_dodge_ability(data: &JoinData, update: &mut StateUpdate) {
update.character = ability.into();
}
}
pub fn unwrap_tool_data(data: &JoinData) -> Option<ToolData> {
if let Some(Tool(tool)) = data.stats.equipment.main.as_ref().map(|i| i.kind) {
Some(tool)
} else {
None
}
}

View File

@ -1,7 +1,7 @@
use crate::{
comp::{
AbilityPool, Attacking, Body, CharacterState, Controller, ControllerInputs, Energy,
Mounting, Ori, PhysicsState, Pos, Stats, Vel,
Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
state::DeltaTime,
@ -13,6 +13,11 @@ use specs::{Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, Write
// use std::collections::VecDeque;
pub trait CharacterBehavior {
fn behavior(&self, data: &JoinData) -> StateUpdate;
// fn init(data: &JoinData) -> CharacterState;
}
/// Read-Only Data sent from Character Behavior System to bahvior fn's
pub struct JoinData<'a> {
pub entity: Entity,
@ -173,12 +178,12 @@ impl<'a> System<'a> for Sys {
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::BasicBlock { .. } => states::basic_block::behavior(&j),
CharacterState::ChargeAttack { .. } => states::charge_attack::behavior(&j),
CharacterState::Sit { .. } => states::sit::behavior(&j),
CharacterState::TripleStrike { .. } => states::triple_strike::behavior(&j),
CharacterState::TimedCombo { .. } => states::timed_combo::behavior(&j),
CharacterState::BasicAttack (state) => state.behavior(&j),
CharacterState::TimedCombo(state) => state.behavior(&j),
// Do not use default match.
// _ => StateUpdate {

View File

@ -454,7 +454,7 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::BasicAttack { .. } => {
CharacterState::BasicAttack(_) => {
anim::character::AttackAnimation::update_skeleton(
&target_base,
(active_tool_kind, time),
@ -463,6 +463,22 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::TimedCombo(s) => match s.stage {
0 | 2 => anim::character::AttackAnimation::update_skeleton(
&target_base,
(active_tool_kind, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
_ => anim::character::ChargeAnimation::update_skeleton(
&target_base,
(active_tool_kind, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
),
},
CharacterState::BasicBlock { .. } => {
anim::character::BlockIdleAnimation::update_skeleton(
&CharacterSkeleton::new(),