mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial implementation of combo melee attack.
This commit is contained in:
parent
b3117feea6
commit
cf573a42bd
@ -94,19 +94,19 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(First), Sword): (
|
Attack(ComboMelee(First), Sword): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.swing_sword",
|
"voxygen.audio.sfx.abilities.swing_sword",
|
||||||
],
|
],
|
||||||
threshold: 0.7,
|
threshold: 0.7,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(Second), Sword): (
|
Attack(ComboMelee(Second), Sword): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.separated_second_swing",
|
"voxygen.audio.sfx.abilities.separated_second_swing",
|
||||||
],
|
],
|
||||||
threshold: 0.7,
|
threshold: 0.7,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(Third), Sword): (
|
Attack(ComboMelee(Third), Sword): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.separated_third_swing",
|
"voxygen.audio.sfx.abilities.separated_third_swing",
|
||||||
],
|
],
|
||||||
@ -174,19 +174,19 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(First), Axe): (
|
Attack(ComboMelee(First), Axe): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.swing",
|
"voxygen.audio.sfx.abilities.swing",
|
||||||
],
|
],
|
||||||
threshold: 0.7,
|
threshold: 0.7,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(Second), Axe): (
|
Attack(ComboMelee(Second), Axe): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.swing",
|
"voxygen.audio.sfx.abilities.swing",
|
||||||
],
|
],
|
||||||
threshold: 0.7,
|
threshold: 0.7,
|
||||||
),
|
),
|
||||||
Attack(TripleStrike(Third), Axe): (
|
Attack(ComboMelee(Third), Axe): (
|
||||||
files: [
|
files: [
|
||||||
"voxygen.audio.sfx.abilities.swing",
|
"voxygen.audio.sfx.abilities.swing",
|
||||||
],
|
],
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
ability::Stage,
|
|
||||||
item::{armor::Protection, Item, ItemKind},
|
item::{armor::Protection, Item, ItemKind},
|
||||||
Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate,
|
Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate,
|
||||||
},
|
},
|
||||||
states::{triple_strike::*, *},
|
states::*,
|
||||||
sys::character_behavior::JoinData,
|
sys::character_behavior::JoinData,
|
||||||
};
|
};
|
||||||
use arraygen::Arraygen;
|
use arraygen::Arraygen;
|
||||||
@ -21,7 +20,7 @@ pub enum CharacterAbilityType {
|
|||||||
ChargedRanged,
|
ChargedRanged,
|
||||||
DashMelee,
|
DashMelee,
|
||||||
BasicBlock,
|
BasicBlock,
|
||||||
TripleStrike(Stage),
|
ComboMelee,
|
||||||
LeapMelee,
|
LeapMelee,
|
||||||
SpinMelee,
|
SpinMelee,
|
||||||
GroundShockwave,
|
GroundShockwave,
|
||||||
@ -36,7 +35,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
|||||||
CharacterState::DashMelee(_) => Self::DashMelee,
|
CharacterState::DashMelee(_) => Self::DashMelee,
|
||||||
CharacterState::BasicBlock => Self::BasicBlock,
|
CharacterState::BasicBlock => Self::BasicBlock,
|
||||||
CharacterState::LeapMelee(_) => Self::LeapMelee,
|
CharacterState::LeapMelee(_) => Self::LeapMelee,
|
||||||
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
|
CharacterState::ComboMelee(_) => Self::ComboMelee,
|
||||||
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
||||||
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
||||||
CharacterState::GroundShockwave(_) => Self::ChargedRanged,
|
CharacterState::GroundShockwave(_) => Self::ChargedRanged,
|
||||||
@ -79,9 +78,12 @@ pub enum CharacterAbility {
|
|||||||
},
|
},
|
||||||
BasicBlock,
|
BasicBlock,
|
||||||
Roll,
|
Roll,
|
||||||
TripleStrike {
|
ComboMelee {
|
||||||
base_damage: u32,
|
stage_data: Vec<combo_melee::Stage>,
|
||||||
needs_timing: bool,
|
initial_energy_gain: u32,
|
||||||
|
max_energy_gain: u32,
|
||||||
|
energy_increase: u32,
|
||||||
|
combo_duration: Duration,
|
||||||
},
|
},
|
||||||
LeapMelee {
|
LeapMelee {
|
||||||
energy_cost: u32,
|
energy_cost: u32,
|
||||||
@ -130,11 +132,6 @@ impl CharacterAbility {
|
|||||||
/// applicable.
|
/// applicable.
|
||||||
pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
|
pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
|
||||||
match self {
|
match self {
|
||||||
CharacterAbility::TripleStrike { .. } => {
|
|
||||||
data.physics.on_ground
|
|
||||||
&& data.body.is_humanoid()
|
|
||||||
&& data.inputs.look_dir.xy().magnitude_squared() > 0.01
|
|
||||||
},
|
|
||||||
CharacterAbility::Roll => {
|
CharacterAbility::Roll => {
|
||||||
data.physics.on_ground
|
data.physics.on_ground
|
||||||
&& data.body.is_humanoid()
|
&& data.body.is_humanoid()
|
||||||
@ -328,20 +325,23 @@ impl From<&CharacterAbility> for CharacterState {
|
|||||||
remaining_duration: Duration::from_millis(500),
|
remaining_duration: Duration::from_millis(500),
|
||||||
was_wielded: false, // false by default. utils might set it to true
|
was_wielded: false, // false by default. utils might set it to true
|
||||||
}),
|
}),
|
||||||
CharacterAbility::TripleStrike {
|
CharacterAbility::ComboMelee {
|
||||||
base_damage,
|
stage_data,
|
||||||
needs_timing,
|
initial_energy_gain,
|
||||||
} => CharacterState::TripleStrike(triple_strike::Data {
|
max_energy_gain,
|
||||||
base_damage: *base_damage,
|
energy_increase,
|
||||||
stage: triple_strike::Stage::First,
|
combo_duration,
|
||||||
stage_exhausted: false,
|
} => CharacterState::ComboMelee(combo_melee::Data {
|
||||||
stage_time_active: Duration::default(),
|
stage: 1,
|
||||||
initialized: false,
|
num_stages: stage_data.len() as u32,
|
||||||
transition_style: if *needs_timing {
|
combo: 0,
|
||||||
TransitionStyle::Timed(TimingState::NotPressed)
|
stage_data: stage_data.clone(),
|
||||||
} else {
|
exhausted: false,
|
||||||
TransitionStyle::Hold(HoldingState::Holding)
|
initial_energy_gain: *initial_energy_gain,
|
||||||
},
|
max_energy_gain: *max_energy_gain,
|
||||||
|
energy_increase: *energy_increase,
|
||||||
|
combo_duration: *combo_duration,
|
||||||
|
timer: Duration::default(),
|
||||||
}),
|
}),
|
||||||
CharacterAbility::LeapMelee {
|
CharacterAbility::LeapMelee {
|
||||||
energy_cost: _,
|
energy_cost: _,
|
||||||
|
@ -62,7 +62,7 @@ pub enum CharacterState {
|
|||||||
DashMelee(dash_melee::Data),
|
DashMelee(dash_melee::Data),
|
||||||
/// A three-stage attack where each attack pushes player forward
|
/// A three-stage attack where each attack pushes player forward
|
||||||
/// and successive attacks increase in damage, while player holds button.
|
/// and successive attacks increase in damage, while player holds button.
|
||||||
TripleStrike(triple_strike::Data),
|
ComboMelee(combo_melee::Data),
|
||||||
/// A leap followed by a small aoe ground attack
|
/// A leap followed by a small aoe ground attack
|
||||||
LeapMelee(leap_melee::Data),
|
LeapMelee(leap_melee::Data),
|
||||||
/// Spin around, dealing damage to enemies surrounding you
|
/// Spin around, dealing damage to enemies surrounding you
|
||||||
@ -80,7 +80,7 @@ impl CharacterState {
|
|||||||
| CharacterState::BasicMelee(_)
|
| CharacterState::BasicMelee(_)
|
||||||
| CharacterState::BasicRanged(_)
|
| CharacterState::BasicRanged(_)
|
||||||
| CharacterState::DashMelee(_)
|
| CharacterState::DashMelee(_)
|
||||||
| CharacterState::TripleStrike(_)
|
| CharacterState::ComboMelee(_)
|
||||||
| CharacterState::BasicBlock
|
| CharacterState::BasicBlock
|
||||||
| CharacterState::LeapMelee(_)
|
| CharacterState::LeapMelee(_)
|
||||||
| CharacterState::SpinMelee(_)
|
| CharacterState::SpinMelee(_)
|
||||||
@ -94,7 +94,7 @@ impl CharacterState {
|
|||||||
CharacterState::BasicMelee(_)
|
CharacterState::BasicMelee(_)
|
||||||
| CharacterState::BasicRanged(_)
|
| CharacterState::BasicRanged(_)
|
||||||
| CharacterState::DashMelee(_)
|
| CharacterState::DashMelee(_)
|
||||||
| CharacterState::TripleStrike(_)
|
| CharacterState::ComboMelee(_)
|
||||||
| CharacterState::LeapMelee(_)
|
| CharacterState::LeapMelee(_)
|
||||||
| CharacterState::SpinMelee(_)
|
| CharacterState::SpinMelee(_)
|
||||||
| CharacterState::ChargedRanged(_)
|
| CharacterState::ChargedRanged(_)
|
||||||
@ -107,7 +107,7 @@ impl CharacterState {
|
|||||||
CharacterState::BasicMelee(_)
|
CharacterState::BasicMelee(_)
|
||||||
| CharacterState::BasicRanged(_)
|
| CharacterState::BasicRanged(_)
|
||||||
| CharacterState::DashMelee(_)
|
| CharacterState::DashMelee(_)
|
||||||
| CharacterState::TripleStrike(_)
|
| CharacterState::ComboMelee(_)
|
||||||
| CharacterState::BasicBlock
|
| CharacterState::BasicBlock
|
||||||
| CharacterState::LeapMelee(_)
|
| CharacterState::LeapMelee(_)
|
||||||
| CharacterState::ChargedRanged(_)
|
| CharacterState::ChargedRanged(_)
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
// Note: If you changes here "break" old character saves you can change the
|
// Note: If you changes here "break" old character saves you can change the
|
||||||
// version in voxygen\src\meta.rs in order to reset save files to being empty
|
// version in voxygen\src\meta.rs in order to reset save files to being empty
|
||||||
|
|
||||||
use crate::comp::{
|
use crate::{
|
||||||
body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile,
|
comp::{
|
||||||
|
body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile,
|
||||||
|
},
|
||||||
|
states::combo_melee,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -116,9 +119,46 @@ impl Tool {
|
|||||||
|
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
Sword(_) => vec![
|
Sword(_) => vec![
|
||||||
TripleStrike {
|
ComboMelee {
|
||||||
base_damage: (60.0 * self.base_power()) as u32,
|
stage_data: vec![
|
||||||
needs_timing: false,
|
combo_melee::Stage {
|
||||||
|
stage: 1,
|
||||||
|
base_damage: 30,
|
||||||
|
max_damage: 50,
|
||||||
|
damage_increase: 10,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
combo_melee::Stage {
|
||||||
|
stage: 2,
|
||||||
|
base_damage: 50,
|
||||||
|
max_damage: 80,
|
||||||
|
damage_increase: 15,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
combo_melee::Stage {
|
||||||
|
stage: 3,
|
||||||
|
base_damage: 70,
|
||||||
|
max_damage: 110,
|
||||||
|
damage_increase: 20,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initial_energy_gain: 0,
|
||||||
|
max_energy_gain: 100,
|
||||||
|
energy_increase: 20,
|
||||||
|
combo_duration: Duration::from_millis(250),
|
||||||
},
|
},
|
||||||
DashMelee {
|
DashMelee {
|
||||||
energy_cost: 700,
|
energy_cost: 700,
|
||||||
@ -128,9 +168,46 @@ impl Tool {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
Axe(_) => vec![
|
Axe(_) => vec![
|
||||||
TripleStrike {
|
ComboMelee {
|
||||||
base_damage: (80.0 * self.base_power()) as u32,
|
stage_data: vec![
|
||||||
needs_timing: true,
|
combo_melee::Stage {
|
||||||
|
stage: 1,
|
||||||
|
base_damage: 30,
|
||||||
|
max_damage: 50,
|
||||||
|
damage_increase: 10,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
combo_melee::Stage {
|
||||||
|
stage: 2,
|
||||||
|
base_damage: 50,
|
||||||
|
max_damage: 80,
|
||||||
|
damage_increase: 15,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
combo_melee::Stage {
|
||||||
|
stage: 3,
|
||||||
|
base_damage: 70,
|
||||||
|
max_damage: 110,
|
||||||
|
damage_increase: 20,
|
||||||
|
knockback: 5.0,
|
||||||
|
range: 3.5,
|
||||||
|
angle: 45.0,
|
||||||
|
base_buildup_duration: Duration::from_millis(150),
|
||||||
|
base_recover_duration: Duration::from_millis(100),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initial_energy_gain: 0,
|
||||||
|
max_energy_gain: 100,
|
||||||
|
energy_increase: 20,
|
||||||
|
combo_duration: Duration::from_millis(250),
|
||||||
},
|
},
|
||||||
SpinMelee {
|
SpinMelee {
|
||||||
energy_cost: 100,
|
energy_cost: 100,
|
||||||
|
170
common/src/states/combo_melee.rs
Normal file
170
common/src/states/combo_melee.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
use crate::{
|
||||||
|
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||||
|
states::utils::*,
|
||||||
|
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Stage {
|
||||||
|
/// Specifies which stage the combo attack is in
|
||||||
|
pub stage: u32,
|
||||||
|
/// Initial damage of stage
|
||||||
|
pub base_damage: u32,
|
||||||
|
/// Max damage of stage
|
||||||
|
pub max_damage: u32,
|
||||||
|
/// Damage scaling per combo
|
||||||
|
pub damage_increase: u32,
|
||||||
|
/// Knockback of stage
|
||||||
|
pub knockback: f32,
|
||||||
|
/// Range of attack
|
||||||
|
pub range: f32,
|
||||||
|
/// Angle of attack
|
||||||
|
pub angle: f32,
|
||||||
|
/// Initial buildup duration of stage (how long until state can deal damage)
|
||||||
|
pub base_buildup_duration: Duration,
|
||||||
|
/// Initial recover duration of stage (how long until character exits state)
|
||||||
|
pub base_recover_duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of attacks that can incrementally become faster and more damaging.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
/// Indicates what stage the combo is in
|
||||||
|
pub stage: u32,
|
||||||
|
/// Indicates number of stages in combo
|
||||||
|
pub num_stages: u32,
|
||||||
|
/// Number of consecutive strikes
|
||||||
|
pub combo: u32,
|
||||||
|
/// Data for first stage
|
||||||
|
pub stage_data: Vec<Stage>,
|
||||||
|
/// Whether state can deal more damage
|
||||||
|
pub exhausted: bool,
|
||||||
|
/// Initial energy gain per strike
|
||||||
|
pub initial_energy_gain: u32,
|
||||||
|
/// Max energy gain per strike
|
||||||
|
pub max_energy_gain: u32,
|
||||||
|
/// Energy gain increase per combo
|
||||||
|
pub energy_increase: u32,
|
||||||
|
/// Duration for the next stage to be activated
|
||||||
|
pub combo_duration: Duration,
|
||||||
|
/// Timer for each stage
|
||||||
|
pub timer: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterBehavior for Data {
|
||||||
|
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
|
||||||
|
handle_orientation(data, &mut update, 5.0);
|
||||||
|
handle_move(data, &mut update, 0.8);
|
||||||
|
|
||||||
|
let stage_index = (self.stage - 1) as usize;
|
||||||
|
|
||||||
|
if !self.exhausted && self.timer < self.stage_data[stage_index].base_buildup_duration {
|
||||||
|
// Build up
|
||||||
|
update.character = CharacterState::ComboMelee(Data {
|
||||||
|
stage: self.stage,
|
||||||
|
num_stages: self.num_stages,
|
||||||
|
combo: self.combo,
|
||||||
|
stage_data: self.stage_data.clone(),
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
initial_energy_gain: self.initial_energy_gain,
|
||||||
|
max_energy_gain: self.max_energy_gain,
|
||||||
|
energy_increase: self.energy_increase,
|
||||||
|
combo_duration: self.combo_duration,
|
||||||
|
timer: self
|
||||||
|
.timer
|
||||||
|
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
} else if !self.exhausted {
|
||||||
|
// Hit attempt
|
||||||
|
data.updater.insert(data.entity, Attacking {
|
||||||
|
base_healthchange: -((self.stage_data[stage_index].max_damage.min(self.stage_data[stage_index].base_damage + self.combo / self.num_stages * self.stage_data[stage_index].damage_increase)) as i32),
|
||||||
|
range: self.stage_data[stage_index].range,
|
||||||
|
max_angle: self.stage_data[stage_index].angle.to_radians(),
|
||||||
|
applied: false,
|
||||||
|
hit_count: 0,
|
||||||
|
knockback: self.stage_data[stage_index].knockback,
|
||||||
|
});
|
||||||
|
|
||||||
|
update.character = CharacterState::ComboMelee(Data {
|
||||||
|
stage: self.stage,
|
||||||
|
num_stages: self.num_stages,
|
||||||
|
combo: self.combo,
|
||||||
|
stage_data: self.stage_data.clone(),
|
||||||
|
exhausted: true,
|
||||||
|
initial_energy_gain: self.initial_energy_gain,
|
||||||
|
max_energy_gain: self.max_energy_gain,
|
||||||
|
energy_increase: self.energy_increase,
|
||||||
|
combo_duration: self.combo_duration,
|
||||||
|
timer: Duration::default(),
|
||||||
|
});
|
||||||
|
} else if self.timer < self.stage_data[stage_index].base_recover_duration {
|
||||||
|
update.character = CharacterState::ComboMelee(Data {
|
||||||
|
stage: self.stage,
|
||||||
|
num_stages: self.num_stages,
|
||||||
|
combo: self.combo,
|
||||||
|
stage_data: self.stage_data.clone(),
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
initial_energy_gain: self.initial_energy_gain,
|
||||||
|
max_energy_gain: self.max_energy_gain,
|
||||||
|
energy_increase: self.energy_increase,
|
||||||
|
combo_duration: self.combo_duration,
|
||||||
|
timer: self
|
||||||
|
.timer
|
||||||
|
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
} else if self.timer < self.combo_duration + self.stage_data[stage_index].base_recover_duration {
|
||||||
|
if data.inputs.primary.is_pressed() {
|
||||||
|
update.character = CharacterState::ComboMelee(Data {
|
||||||
|
stage: (self.stage % self.num_stages) + 1,
|
||||||
|
num_stages: self.num_stages,
|
||||||
|
combo: self.combo + 1,
|
||||||
|
stage_data: self.stage_data.clone(),
|
||||||
|
exhausted: false,
|
||||||
|
initial_energy_gain: self.initial_energy_gain,
|
||||||
|
max_energy_gain: self.max_energy_gain,
|
||||||
|
energy_increase: self.energy_increase,
|
||||||
|
combo_duration: self.combo_duration,
|
||||||
|
timer: Duration::default(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
update.character = CharacterState::ComboMelee(Data {
|
||||||
|
stage: self.stage,
|
||||||
|
num_stages: self.num_stages,
|
||||||
|
combo: self.combo,
|
||||||
|
stage_data: self.stage_data.clone(),
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
initial_energy_gain: self.initial_energy_gain,
|
||||||
|
max_energy_gain: self.max_energy_gain,
|
||||||
|
energy_increase: self.energy_increase,
|
||||||
|
combo_duration: self.combo_duration,
|
||||||
|
timer: self
|
||||||
|
.timer
|
||||||
|
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Done
|
||||||
|
update.character = CharacterState::Wielding;
|
||||||
|
// Make sure attack component is removed
|
||||||
|
data.updater.remove::<Attacking>(data.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grant energy on successful hit
|
||||||
|
if let Some(attack) = data.attacking {
|
||||||
|
if attack.applied && attack.hit_count > 0 {
|
||||||
|
let energy = self.max_energy_gain.min(self.initial_energy_gain + self.combo * self.energy_increase) as i32;
|
||||||
|
data.updater.remove::<Attacking>(data.entity);
|
||||||
|
update.energy.change_by(energy, EnergySource::HitEnemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,6 @@ pub mod roll;
|
|||||||
pub mod sit;
|
pub mod sit;
|
||||||
pub mod sneak;
|
pub mod sneak;
|
||||||
pub mod spin_melee;
|
pub mod spin_melee;
|
||||||
pub mod triple_strike;
|
pub mod combo_melee;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod wielding;
|
pub mod wielding;
|
||||||
|
@ -1,220 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
|
||||||
states::utils::*,
|
|
||||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::time::Duration;
|
|
||||||
use vek::vec::Vec3;
|
|
||||||
use HoldingState::*;
|
|
||||||
use TimingState::*;
|
|
||||||
use TransitionStyle::*;
|
|
||||||
|
|
||||||
// In millis
|
|
||||||
const STAGE_DURATION: u64 = 700;
|
|
||||||
const TIMING_DELAY: u64 = 350;
|
|
||||||
const INITIAL_ACCEL: f32 = 90.0;
|
|
||||||
const BASE_SPEED: f32 = 25.0;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
|
||||||
pub enum Stage {
|
|
||||||
First,
|
|
||||||
Second,
|
|
||||||
Third,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
|
||||||
pub enum TimingState {
|
|
||||||
NotPressed,
|
|
||||||
PressedEarly,
|
|
||||||
Success,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
|
||||||
pub enum HoldingState {
|
|
||||||
Holding,
|
|
||||||
Released,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
|
||||||
pub enum TransitionStyle {
|
|
||||||
/// Player must time a button press properly to transition
|
|
||||||
Timed(TimingState),
|
|
||||||
/// Player must hold button for whole move
|
|
||||||
Hold(HoldingState),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ### A sequence of 3 incrementally increasing attacks.
|
|
||||||
///
|
|
||||||
/// While holding down the `primary` button, perform a series of 3 attacks,
|
|
||||||
/// each one pushes the player forward as the character steps into the swings.
|
|
||||||
/// The player can let go of the left mouse button at any time
|
|
||||||
/// and stop their attacks by interrupting the attack animation.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
|
||||||
pub struct Data {
|
|
||||||
/// The tool this state will read to handle damage, etc.
|
|
||||||
pub base_damage: u32,
|
|
||||||
/// What stage (of 3) the attack is in
|
|
||||||
pub stage: Stage,
|
|
||||||
/// How long current stage has been active
|
|
||||||
pub stage_time_active: Duration,
|
|
||||||
/// Whether current stage has exhausted its attack
|
|
||||||
pub stage_exhausted: bool,
|
|
||||||
/// Whether state has performed initialization logic
|
|
||||||
pub initialized: bool,
|
|
||||||
/// What this instance's current transition stat is
|
|
||||||
pub transition_style: TransitionStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CharacterBehavior for Data {
|
|
||||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
|
||||||
let mut update = StateUpdate::from(data);
|
|
||||||
|
|
||||||
handle_move(data, &mut update, 0.3);
|
|
||||||
|
|
||||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
|
||||||
let stage_time_active = self
|
|
||||||
.stage_time_active
|
|
||||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
|
||||||
.unwrap_or(Duration::default());
|
|
||||||
|
|
||||||
if !self.initialized {
|
|
||||||
update.vel.0 = Vec3::zero();
|
|
||||||
if let Some(dir) = data.inputs.look_dir.try_normalized() {
|
|
||||||
update.ori.0 = dir.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let initialized = true;
|
|
||||||
|
|
||||||
// Update transition
|
|
||||||
let transition_style = match self.transition_style {
|
|
||||||
Timed(state) => match state {
|
|
||||||
NotPressed => {
|
|
||||||
if data.inputs.primary.is_just_pressed() {
|
|
||||||
if stage_time_active > Duration::from_millis(TIMING_DELAY) {
|
|
||||||
Timed(Success)
|
|
||||||
} else {
|
|
||||||
Timed(PressedEarly)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.transition_style
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => self.transition_style,
|
|
||||||
},
|
|
||||||
Hold(_) => {
|
|
||||||
if !data.inputs.primary.is_pressed() {
|
|
||||||
Hold(Released)
|
|
||||||
} else {
|
|
||||||
self.transition_style
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handling movement
|
|
||||||
if stage_time_active < Duration::from_millis(STAGE_DURATION / 3) {
|
|
||||||
let adjusted_accel = match (self.stage, data.physics.touch_entities.is_empty()) {
|
|
||||||
(Stage::First, true) => INITIAL_ACCEL,
|
|
||||||
(Stage::Second, true) => INITIAL_ACCEL * 0.75,
|
|
||||||
(Stage::Third, true) => INITIAL_ACCEL * 0.75,
|
|
||||||
(_, _) => 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Move player forward while in first third of each stage
|
|
||||||
if update.vel.0.magnitude_squared() < BASE_SPEED.powf(2.0) {
|
|
||||||
update.vel.0 += data.dt.0 * (adjusted_accel * Vec3::from(data.ori.0.xy()));
|
|
||||||
let mag2 = update.vel.0.magnitude_squared();
|
|
||||||
if mag2 > BASE_SPEED.powf(2.0) {
|
|
||||||
update.vel.0 = update.vel.0.normalized() * BASE_SPEED;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
handle_orientation(data, &mut update, 50.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handling attacking
|
|
||||||
update.character = if stage_time_active > Duration::from_millis(STAGE_DURATION / 2)
|
|
||||||
&& !self.stage_exhausted
|
|
||||||
{
|
|
||||||
let dmg = match self.stage {
|
|
||||||
Stage::First => self.base_damage / 2,
|
|
||||||
Stage::Second => self.base_damage,
|
|
||||||
Stage::Third => (self.base_damage as f32 * 1.5) as u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
update.vel.0 = Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * 5.0;
|
|
||||||
|
|
||||||
// Try to deal damage in second half of stage
|
|
||||||
data.updater.insert(data.entity, Attacking {
|
|
||||||
base_healthchange: -(dmg as i32),
|
|
||||||
range: 3.5,
|
|
||||||
max_angle: 45_f32.to_radians(),
|
|
||||||
applied: false,
|
|
||||||
hit_count: 0,
|
|
||||||
knockback: 10.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
CharacterState::TripleStrike(Data {
|
|
||||||
base_damage: self.base_damage,
|
|
||||||
stage: self.stage,
|
|
||||||
stage_time_active,
|
|
||||||
stage_exhausted: true,
|
|
||||||
initialized,
|
|
||||||
transition_style,
|
|
||||||
})
|
|
||||||
} else if stage_time_active > Duration::from_millis(STAGE_DURATION) {
|
|
||||||
let next_stage =
|
|
||||||
// Determine whether stage can transition based on TransitionStyle
|
|
||||||
if let Hold(Holding) | Timed(Success) = transition_style {
|
|
||||||
// Determine what stage to transition to
|
|
||||||
match self.stage {
|
|
||||||
Stage::First => Some(Stage::Second),
|
|
||||||
Stage::Second => Some(Stage::Third),
|
|
||||||
Stage::Third => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Player messed up inputs, don't transition
|
|
||||||
else { None };
|
|
||||||
|
|
||||||
update.vel.0 = Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * 5.0;
|
|
||||||
|
|
||||||
if let Some(stage) = next_stage {
|
|
||||||
CharacterState::TripleStrike(Data {
|
|
||||||
base_damage: self.base_damage,
|
|
||||||
stage,
|
|
||||||
stage_time_active: Duration::default(),
|
|
||||||
stage_exhausted: false,
|
|
||||||
initialized,
|
|
||||||
transition_style: match transition_style {
|
|
||||||
Hold(_) => Hold(Holding),
|
|
||||||
Timed(_) => Timed(NotPressed),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Make sure attack component is removed
|
|
||||||
data.updater.remove::<Attacking>(data.entity);
|
|
||||||
// Done
|
|
||||||
CharacterState::Wielding
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CharacterState::TripleStrike(Data {
|
|
||||||
base_damage: self.base_damage,
|
|
||||||
stage: self.stage,
|
|
||||||
stage_time_active,
|
|
||||||
stage_exhausted: self.stage_exhausted,
|
|
||||||
initialized,
|
|
||||||
transition_style,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Grant 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(50, EnergySource::HitEnemy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update
|
|
||||||
}
|
|
||||||
}
|
|
@ -250,7 +250,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Roll(data) => data.handle_event(&j, action),
|
CharacterState::Roll(data) => data.handle_event(&j, action),
|
||||||
CharacterState::Wielding => states::wielding::Data.handle_event(&j, action),
|
CharacterState::Wielding => states::wielding::Data.handle_event(&j, action),
|
||||||
CharacterState::Equipping(data) => data.handle_event(&j, action),
|
CharacterState::Equipping(data) => data.handle_event(&j, action),
|
||||||
CharacterState::TripleStrike(data) => data.handle_event(&j, action),
|
CharacterState::ComboMelee(data) => data.handle_event(&j, action),
|
||||||
CharacterState::BasicMelee(data) => data.handle_event(&j, action),
|
CharacterState::BasicMelee(data) => data.handle_event(&j, action),
|
||||||
CharacterState::BasicRanged(data) => data.handle_event(&j, action),
|
CharacterState::BasicRanged(data) => data.handle_event(&j, action),
|
||||||
CharacterState::Boost(data) => data.handle_event(&j, action),
|
CharacterState::Boost(data) => data.handle_event(&j, action),
|
||||||
@ -279,7 +279,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Roll(data) => data.behavior(&j),
|
CharacterState::Roll(data) => data.behavior(&j),
|
||||||
CharacterState::Wielding => states::wielding::Data.behavior(&j),
|
CharacterState::Wielding => states::wielding::Data.behavior(&j),
|
||||||
CharacterState::Equipping(data) => data.behavior(&j),
|
CharacterState::Equipping(data) => data.behavior(&j),
|
||||||
CharacterState::TripleStrike(data) => data.behavior(&j),
|
CharacterState::ComboMelee(data) => data.behavior(&j),
|
||||||
CharacterState::BasicMelee(data) => data.behavior(&j),
|
CharacterState::BasicMelee(data) => data.behavior(&j),
|
||||||
CharacterState::BasicRanged(data) => data.behavior(&j),
|
CharacterState::BasicRanged(data) => data.behavior(&j),
|
||||||
CharacterState::Boost(data) => data.behavior(&j),
|
CharacterState::Boost(data) => data.behavior(&j),
|
||||||
|
@ -110,7 +110,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
| CharacterState::DashMelee { .. }
|
| CharacterState::DashMelee { .. }
|
||||||
| CharacterState::LeapMelee { .. }
|
| CharacterState::LeapMelee { .. }
|
||||||
| CharacterState::SpinMelee { .. }
|
| CharacterState::SpinMelee { .. }
|
||||||
| CharacterState::TripleStrike { .. }
|
| CharacterState::ComboMelee { .. }
|
||||||
| CharacterState::BasicRanged { .. }
|
| CharacterState::BasicRanged { .. }
|
||||||
| CharacterState::ChargedRanged { .. }
|
| CharacterState::ChargedRanged { .. }
|
||||||
| CharacterState::GroundShockwave { .. } => {
|
| CharacterState::GroundShockwave { .. } => {
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
//! ],
|
//! ],
|
||||||
//! threshold: 1.2,
|
//! threshold: 1.2,
|
||||||
//! ),
|
//! ),
|
||||||
//! // A multi-stage attack ability which depends on the weapon
|
//! // A multi-stage attack ability which depends on the weapon (To do: update example)
|
||||||
//! Attack(TripleStrike(First), Sword): (
|
//! Attack(TripleStrike(First), Sword): (
|
||||||
//! files: [
|
//! files: [
|
||||||
//! "voxygen.audio.sfx.weapon.sword_03",
|
//! "voxygen.audio.sfx.weapon.sword_03",
|
||||||
|
@ -30,7 +30,6 @@ use common::{
|
|||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
state::{DeltaTime, State},
|
state::{DeltaTime, State},
|
||||||
states::triple_strike,
|
|
||||||
terrain::TerrainChunk,
|
terrain::TerrainChunk,
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
@ -912,8 +911,8 @@ impl FigureMgr {
|
|||||||
skeleton_attr,
|
skeleton_attr,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
CharacterState::TripleStrike(s) => match s.stage {
|
CharacterState::ComboMelee(s) => match s.stage {
|
||||||
triple_strike::Stage::First => {
|
1 => {
|
||||||
anim::character::AlphaAnimation::update_skeleton(
|
anim::character::AlphaAnimation::update_skeleton(
|
||||||
&target_base,
|
&target_base,
|
||||||
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
|
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
|
||||||
@ -922,7 +921,7 @@ impl FigureMgr {
|
|||||||
skeleton_attr,
|
skeleton_attr,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
triple_strike::Stage::Second => {
|
2 => {
|
||||||
anim::character::SpinAnimation::update_skeleton(
|
anim::character::SpinAnimation::update_skeleton(
|
||||||
&target_base,
|
&target_base,
|
||||||
(active_tool_kind, second_tool_kind, time),
|
(active_tool_kind, second_tool_kind, time),
|
||||||
@ -931,7 +930,7 @@ impl FigureMgr {
|
|||||||
skeleton_attr,
|
skeleton_attr,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
triple_strike::Stage::Third => {
|
_ => {
|
||||||
anim::character::BetaAnimation::update_skeleton(
|
anim::character::BetaAnimation::update_skeleton(
|
||||||
&target_base,
|
&target_base,
|
||||||
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
|
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
|
||||||
|
Loading…
Reference in New Issue
Block a user