mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial implementation for beam attack.
This commit is contained in:
@ -24,6 +24,7 @@ pub enum CharacterAbilityType {
|
|||||||
LeapMelee,
|
LeapMelee,
|
||||||
SpinMelee,
|
SpinMelee,
|
||||||
GroundShockwave,
|
GroundShockwave,
|
||||||
|
BasicBeam,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&CharacterState> for CharacterAbilityType {
|
impl From<&CharacterState> for CharacterAbilityType {
|
||||||
@ -39,6 +40,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
|||||||
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
||||||
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
||||||
CharacterState::GroundShockwave(_) => Self::ChargedRanged,
|
CharacterState::GroundShockwave(_) => Self::ChargedRanged,
|
||||||
|
CharacterState::BasicBeam(_) => Self::BasicBeam,
|
||||||
_ => Self::BasicMelee,
|
_ => Self::BasicMelee,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,6 +148,17 @@ pub enum CharacterAbility {
|
|||||||
shockwave_duration: Duration,
|
shockwave_duration: Duration,
|
||||||
requires_ground: bool,
|
requires_ground: bool,
|
||||||
},
|
},
|
||||||
|
BasicBeam {
|
||||||
|
energy_cost: u32,
|
||||||
|
buildup_duration: Duration,
|
||||||
|
recover_duration: Duration,
|
||||||
|
base_hps: u32,
|
||||||
|
base_dps: u32,
|
||||||
|
range: f32,
|
||||||
|
max_angle: f32,
|
||||||
|
lifesteal_eff: f32,
|
||||||
|
energy_regen: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterAbility {
|
impl CharacterAbility {
|
||||||
@ -190,6 +203,10 @@ impl CharacterAbility {
|
|||||||
.energy
|
.energy
|
||||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||||
.is_ok(),
|
.is_ok(),
|
||||||
|
CharacterAbility::BasicBeam { energy_cost, .. } => update
|
||||||
|
.energy
|
||||||
|
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||||
|
.is_ok(),
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,6 +514,29 @@ impl From<&CharacterAbility> for CharacterState {
|
|||||||
shockwave_duration: *shockwave_duration,
|
shockwave_duration: *shockwave_duration,
|
||||||
requires_ground: *requires_ground,
|
requires_ground: *requires_ground,
|
||||||
}),
|
}),
|
||||||
|
CharacterAbility::BasicBeam {
|
||||||
|
energy_cost: _,
|
||||||
|
buildup_duration,
|
||||||
|
recover_duration,
|
||||||
|
base_hps,
|
||||||
|
base_dps,
|
||||||
|
range,
|
||||||
|
max_angle,
|
||||||
|
lifesteal_eff,
|
||||||
|
energy_regen,
|
||||||
|
} => CharacterState::BasicBeam(basic_beam::Data {
|
||||||
|
exhausted: false,
|
||||||
|
buildup_duration: *buildup_duration,
|
||||||
|
cooldown_duration: Duration::from_millis(250),
|
||||||
|
cooldown_duration_default: Duration::from_millis(250),
|
||||||
|
recover_duration: *recover_duration,
|
||||||
|
base_hps: *base_hps,
|
||||||
|
base_dps: *base_dps,
|
||||||
|
range: *range,
|
||||||
|
max_angle: *max_angle,
|
||||||
|
lifesteal_eff: *lifesteal_eff,
|
||||||
|
energy_regen: *energy_regen,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,9 @@ pub enum CharacterState {
|
|||||||
ChargedRanged(charged_ranged::Data),
|
ChargedRanged(charged_ranged::Data),
|
||||||
/// A ground shockwave attack
|
/// A ground shockwave attack
|
||||||
GroundShockwave(ground_shockwave::Data),
|
GroundShockwave(ground_shockwave::Data),
|
||||||
|
/// A continuous attack that affects all creatures in a cone originating
|
||||||
|
/// from the source
|
||||||
|
BasicBeam(basic_beam::Data),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterState {
|
impl CharacterState {
|
||||||
@ -86,6 +89,7 @@ impl CharacterState {
|
|||||||
| CharacterState::SpinMelee(_)
|
| CharacterState::SpinMelee(_)
|
||||||
| CharacterState::ChargedRanged(_)
|
| CharacterState::ChargedRanged(_)
|
||||||
| CharacterState::GroundShockwave(_)
|
| CharacterState::GroundShockwave(_)
|
||||||
|
| CharacterState::BasicBeam(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +103,7 @@ impl CharacterState {
|
|||||||
| CharacterState::SpinMelee(_)
|
| CharacterState::SpinMelee(_)
|
||||||
| CharacterState::ChargedRanged(_)
|
| CharacterState::ChargedRanged(_)
|
||||||
| CharacterState::GroundShockwave(_)
|
| CharacterState::GroundShockwave(_)
|
||||||
|
| CharacterState::BasicBeam(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +117,7 @@ impl CharacterState {
|
|||||||
| CharacterState::LeapMelee(_)
|
| CharacterState::LeapMelee(_)
|
||||||
| CharacterState::ChargedRanged(_)
|
| CharacterState::ChargedRanged(_)
|
||||||
| CharacterState::GroundShockwave(_)
|
| CharacterState::GroundShockwave(_)
|
||||||
|
| CharacterState::BasicBeam(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +142,15 @@ impl Component for CharacterState {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Attacking {
|
pub struct Attacking {
|
||||||
pub base_healthchange: i32,
|
pub base_damage: u32,
|
||||||
|
pub base_heal: u32,
|
||||||
pub range: f32,
|
pub range: f32,
|
||||||
pub max_angle: f32,
|
pub max_angle: f32,
|
||||||
pub applied: bool,
|
pub applied: bool,
|
||||||
pub hit_count: u32,
|
pub hit_count: u32,
|
||||||
pub knockback: f32,
|
pub knockback: f32,
|
||||||
|
pub is_melee: bool,
|
||||||
|
pub lifesteal_eff: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Attacking {
|
impl Component for Attacking {
|
||||||
|
@ -16,6 +16,7 @@ pub enum DamageSource {
|
|||||||
Explosion,
|
Explosion,
|
||||||
Falling,
|
Falling,
|
||||||
Shockwave,
|
Shockwave,
|
||||||
|
Energy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Damage {
|
impl Damage {
|
||||||
@ -87,6 +88,16 @@ impl Damage {
|
|||||||
self.healthchange = -10.0;
|
self.healthchange = -10.0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
DamageSource::Energy => {
|
||||||
|
// Armor
|
||||||
|
let damage_reduction = loadout.get_damage_reduction();
|
||||||
|
self.healthchange *= 1.0 - damage_reduction;
|
||||||
|
|
||||||
|
// Min damage
|
||||||
|
if (damage_reduction - 1.0).abs() > f32::EPSILON && self.healthchange > -10.0 {
|
||||||
|
self.healthchange = -10.0;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,16 +300,18 @@ impl Tool {
|
|||||||
max_angle: 20.0,
|
max_angle: 20.0,
|
||||||
}],
|
}],
|
||||||
Staff(kind) => {
|
Staff(kind) => {
|
||||||
if kind == "Sceptre" {
|
if kind == "Sceptre" || kind == "SceptreVelorite" {
|
||||||
vec![
|
vec![
|
||||||
BasicMelee {
|
BasicBeam {
|
||||||
energy_cost: 0,
|
energy_cost: 0,
|
||||||
buildup_duration: Duration::from_millis(0),
|
buildup_duration: Duration::from_millis(250),
|
||||||
recover_duration: Duration::from_millis(300),
|
recover_duration: Duration::from_millis(250),
|
||||||
base_healthchange: (-10.0 * self.base_power()) as i32,
|
base_hps: (100.0 * self.base_power()) as u32,
|
||||||
knockback: 0.0,
|
base_dps: (80.0 * self.base_power()) as u32,
|
||||||
range: 5.0,
|
range: 25.0,
|
||||||
max_angle: 20.0,
|
max_angle: 1.0,
|
||||||
|
lifesteal_eff: 0.2,
|
||||||
|
energy_regen: 50,
|
||||||
},
|
},
|
||||||
BasicMelee {
|
BasicMelee {
|
||||||
energy_cost: 350,
|
energy_cost: 350,
|
||||||
@ -321,27 +323,6 @@ impl Tool {
|
|||||||
max_angle: 90.0,
|
max_angle: 90.0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
} else if kind == "SceptreVelorite" {
|
|
||||||
vec![
|
|
||||||
BasicMelee {
|
|
||||||
energy_cost: 0,
|
|
||||||
buildup_duration: Duration::from_millis(0),
|
|
||||||
recover_duration: Duration::from_millis(300),
|
|
||||||
base_healthchange: (-10.0 * self.base_power()) as i32,
|
|
||||||
knockback: 0.0,
|
|
||||||
range: 5.0,
|
|
||||||
max_angle: 20.0,
|
|
||||||
},
|
|
||||||
BasicMelee {
|
|
||||||
energy_cost: 350,
|
|
||||||
buildup_duration: Duration::from_millis(0),
|
|
||||||
recover_duration: Duration::from_millis(1000),
|
|
||||||
base_healthchange: (350.0 * self.base_power()) as i32,
|
|
||||||
knockback: 0.0,
|
|
||||||
range: 100.0,
|
|
||||||
max_angle: 90.0,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
BasicMelee {
|
BasicMelee {
|
||||||
|
161
common/src/states/basic_beam.rs
Normal file
161
common/src/states/basic_beam.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
use crate::{
|
||||||
|
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||||
|
states::utils::*,
|
||||||
|
sys::character_behavior::*,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Data {
|
||||||
|
/// Whether the attack can currently deal damage
|
||||||
|
pub exhausted: bool,
|
||||||
|
/// How long until state should deal damage or heal
|
||||||
|
pub buildup_duration: Duration,
|
||||||
|
/// How long until weapon can deal another tick of damage
|
||||||
|
pub cooldown_duration: Duration,
|
||||||
|
/// Value that cooldown_duration defaults to
|
||||||
|
pub cooldown_duration_default: Duration,
|
||||||
|
/// How long the state has until exiting
|
||||||
|
pub recover_duration: Duration,
|
||||||
|
/// Base healing per second
|
||||||
|
pub base_hps: u32,
|
||||||
|
/// Base damage per second
|
||||||
|
pub base_dps: u32,
|
||||||
|
/// Max range
|
||||||
|
pub range: f32,
|
||||||
|
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||||
|
pub max_angle: f32,
|
||||||
|
/// Lifesteal efficiency (0 gives 0% conversion of damage to health, 1 gives
|
||||||
|
/// 100% conversion of damage to health)
|
||||||
|
pub lifesteal_eff: f32,
|
||||||
|
/// Energy regened per second
|
||||||
|
pub energy_regen: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CharacterBehavior for Data {
|
||||||
|
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
|
||||||
|
handle_move(data, &mut update, 0.4);
|
||||||
|
handle_jump(data, &mut update);
|
||||||
|
|
||||||
|
let ticks_per_sec = 1.0 / self.cooldown_duration_default.as_secs_f32();
|
||||||
|
|
||||||
|
if self.buildup_duration != Duration::default() {
|
||||||
|
// Build up
|
||||||
|
update.character = CharacterState::BasicBeam(Data {
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
buildup_duration: self
|
||||||
|
.buildup_duration
|
||||||
|
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
cooldown_duration: self.cooldown_duration,
|
||||||
|
cooldown_duration_default: self.cooldown_duration_default,
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
base_hps: self.base_hps,
|
||||||
|
base_dps: self.base_dps,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle,
|
||||||
|
lifesteal_eff: self.lifesteal_eff,
|
||||||
|
energy_regen: self.energy_regen,
|
||||||
|
});
|
||||||
|
} else if data.inputs.primary.is_pressed() && !self.exhausted {
|
||||||
|
let damage = (self.base_dps as f32 / ticks_per_sec) as u32;
|
||||||
|
let heal = (self.base_hps as f32 / ticks_per_sec) as u32;
|
||||||
|
// Hit attempt
|
||||||
|
data.updater.insert(data.entity, Attacking {
|
||||||
|
base_damage: damage,
|
||||||
|
base_heal: heal,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle.to_radians(),
|
||||||
|
applied: false,
|
||||||
|
hit_count: 0,
|
||||||
|
knockback: 0.0,
|
||||||
|
is_melee: false,
|
||||||
|
lifesteal_eff: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
update.character = CharacterState::BasicBeam(Data {
|
||||||
|
exhausted: true,
|
||||||
|
buildup_duration: self.buildup_duration,
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
cooldown_duration: self.cooldown_duration_default,
|
||||||
|
cooldown_duration_default: self.cooldown_duration_default,
|
||||||
|
base_hps: self.base_hps,
|
||||||
|
base_dps: self.base_dps,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle,
|
||||||
|
lifesteal_eff: self.lifesteal_eff,
|
||||||
|
energy_regen: self.energy_regen,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grant energy and lifesteal on successful hit
|
||||||
|
if let Some(attack) = data.attacking {
|
||||||
|
if attack.applied && attack.hit_count > 0 {
|
||||||
|
let energy = (self.energy_regen as f32 / ticks_per_sec) as i32;
|
||||||
|
update.energy.change_by(energy, EnergySource::HitEnemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if data.inputs.primary.is_pressed() && self.cooldown_duration != Duration::default() {
|
||||||
|
// Cooldown until next tick of damage
|
||||||
|
update.character = CharacterState::BasicBeam(Data {
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
buildup_duration: self.buildup_duration,
|
||||||
|
cooldown_duration: self
|
||||||
|
.cooldown_duration
|
||||||
|
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
cooldown_duration_default: self.cooldown_duration_default,
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
base_hps: self.base_hps,
|
||||||
|
base_dps: self.base_dps,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle,
|
||||||
|
lifesteal_eff: self.lifesteal_eff,
|
||||||
|
energy_regen: self.energy_regen,
|
||||||
|
});
|
||||||
|
} else if data.inputs.primary.is_pressed() {
|
||||||
|
update.character = CharacterState::BasicBeam(Data {
|
||||||
|
exhausted: false,
|
||||||
|
buildup_duration: self.buildup_duration,
|
||||||
|
recover_duration: self.recover_duration,
|
||||||
|
cooldown_duration: self.cooldown_duration_default,
|
||||||
|
cooldown_duration_default: self.cooldown_duration_default,
|
||||||
|
base_hps: self.base_hps,
|
||||||
|
base_dps: self.base_dps,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle,
|
||||||
|
lifesteal_eff: self.lifesteal_eff,
|
||||||
|
energy_regen: self.energy_regen,
|
||||||
|
});
|
||||||
|
} else if self.recover_duration != Duration::default() {
|
||||||
|
// Recovery
|
||||||
|
update.character = CharacterState::BasicBeam(Data {
|
||||||
|
exhausted: self.exhausted,
|
||||||
|
buildup_duration: self.buildup_duration,
|
||||||
|
cooldown_duration: self.cooldown_duration,
|
||||||
|
cooldown_duration_default: self.cooldown_duration_default,
|
||||||
|
recover_duration: self
|
||||||
|
.recover_duration
|
||||||
|
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
base_hps: self.base_hps,
|
||||||
|
base_dps: self.base_dps,
|
||||||
|
range: self.range,
|
||||||
|
max_angle: self.max_angle,
|
||||||
|
lifesteal_eff: self.lifesteal_eff,
|
||||||
|
energy_regen: self.energy_regen,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Done
|
||||||
|
update.character = CharacterState::Wielding;
|
||||||
|
// Make sure attack component is removed
|
||||||
|
data.updater.remove::<Attacking>(data.entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
update
|
||||||
|
}
|
||||||
|
}
|
@ -46,14 +46,25 @@ impl CharacterBehavior for Data {
|
|||||||
exhausted: false,
|
exhausted: false,
|
||||||
});
|
});
|
||||||
} else if !self.exhausted {
|
} else if !self.exhausted {
|
||||||
|
let (damage, heal): (u32, u32);
|
||||||
|
if self.base_healthchange > 0 {
|
||||||
|
heal = self.base_healthchange as u32;
|
||||||
|
damage = 0;
|
||||||
|
} else {
|
||||||
|
damage = (-self.base_healthchange) as u32;
|
||||||
|
heal = 0;
|
||||||
|
}
|
||||||
// Hit attempt
|
// Hit attempt
|
||||||
data.updater.insert(data.entity, Attacking {
|
data.updater.insert(data.entity, Attacking {
|
||||||
base_healthchange: self.base_healthchange,
|
base_damage: damage,
|
||||||
|
base_heal: heal,
|
||||||
range: self.range,
|
range: self.range,
|
||||||
max_angle: self.max_angle.to_radians(),
|
max_angle: self.max_angle.to_radians(),
|
||||||
applied: false,
|
applied: false,
|
||||||
hit_count: 0,
|
hit_count: 0,
|
||||||
knockback: self.knockback,
|
knockback: self.knockback,
|
||||||
|
is_melee: true,
|
||||||
|
lifesteal_eff: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
update.character = CharacterState::BasicMelee(Data {
|
update.character = CharacterState::BasicMelee(Data {
|
||||||
|
@ -73,12 +73,15 @@ impl CharacterBehavior for Data {
|
|||||||
} else if !self.exhausted {
|
} else if !self.exhausted {
|
||||||
// Hit attempt
|
// Hit attempt
|
||||||
data.updater.insert(data.entity, Attacking {
|
data.updater.insert(data.entity, Attacking {
|
||||||
base_healthchange: -(self.base_damage as i32),
|
base_damage: self.base_damage,
|
||||||
|
base_heal: 0,
|
||||||
range: 4.5,
|
range: 4.5,
|
||||||
max_angle: 360_f32.to_radians(),
|
max_angle: 360_f32.to_radians(),
|
||||||
applied: false,
|
applied: false,
|
||||||
hit_count: 0,
|
hit_count: 0,
|
||||||
knockback: 25.0,
|
knockback: 25.0,
|
||||||
|
is_melee: true,
|
||||||
|
lifesteal_eff: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
update.character = CharacterState::LeapMelee(Data {
|
update.character = CharacterState::LeapMelee(Data {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod basic_beam;
|
||||||
pub mod basic_block;
|
pub mod basic_block;
|
||||||
pub mod basic_melee;
|
pub mod basic_melee;
|
||||||
pub mod basic_ranged;
|
pub mod basic_ranged;
|
||||||
|
223
common/src/states/triple_strike.rs
Normal file
223
common/src/states/triple_strike.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
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_damage: dmg,
|
||||||
|
base_heal: 0,
|
||||||
|
range: 3.5,
|
||||||
|
max_angle: 45_f32.to_radians(),
|
||||||
|
applied: false,
|
||||||
|
hit_count: 0,
|
||||||
|
knockback: 10.0,
|
||||||
|
is_melee: true,
|
||||||
|
lifesteal_eff: 0.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
|
||||||
|
}
|
||||||
|
}
|
@ -259,6 +259,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::SpinMelee(data) => data.handle_event(&j, action),
|
CharacterState::SpinMelee(data) => data.handle_event(&j, action),
|
||||||
CharacterState::ChargedRanged(data) => data.handle_event(&j, action),
|
CharacterState::ChargedRanged(data) => data.handle_event(&j, action),
|
||||||
CharacterState::GroundShockwave(data) => data.handle_event(&j, action),
|
CharacterState::GroundShockwave(data) => data.handle_event(&j, action),
|
||||||
|
CharacterState::BasicBeam(data) => data.handle_event(&j, action),
|
||||||
};
|
};
|
||||||
local_emitter.append(&mut state_update.local_events);
|
local_emitter.append(&mut state_update.local_events);
|
||||||
server_emitter.append(&mut state_update.server_events);
|
server_emitter.append(&mut state_update.server_events);
|
||||||
@ -288,6 +289,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::SpinMelee(data) => data.behavior(&j),
|
CharacterState::SpinMelee(data) => data.behavior(&j),
|
||||||
CharacterState::ChargedRanged(data) => data.behavior(&j),
|
CharacterState::ChargedRanged(data) => data.behavior(&j),
|
||||||
CharacterState::GroundShockwave(data) => data.behavior(&j),
|
CharacterState::GroundShockwave(data) => data.behavior(&j),
|
||||||
|
CharacterState::BasicBeam(data) => data.behavior(&j),
|
||||||
};
|
};
|
||||||
|
|
||||||
local_emitter.append(&mut state_update.local_events);
|
local_emitter.append(&mut state_update.local_events);
|
||||||
|
@ -99,7 +99,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
|
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
|
||||||
let rad_b = body_b.radius() * scale_b;
|
let rad_b = body_b.radius() * scale_b;
|
||||||
|
|
||||||
// Check if it is a hit
|
// Check if it is a damaging hit
|
||||||
if entity != b
|
if entity != b
|
||||||
&& !stats_b.is_dead
|
&& !stats_b.is_dead
|
||||||
// Spherical wedge shaped attack field
|
// Spherical wedge shaped attack field
|
||||||
@ -113,18 +113,24 @@ impl<'a> System<'a> for Sys {
|
|||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
// Don't heal if outside group
|
// Don't heal if outside group
|
||||||
// Don't damage in the same group
|
// Don't damage in the same group
|
||||||
if same_group != (attack.base_healthchange > 0) {
|
let (mut is_heal, mut is_damage) = (false, false);
|
||||||
continue;
|
if !same_group && (attack.base_damage > 0) {
|
||||||
|
is_damage = true;
|
||||||
|
}
|
||||||
|
if same_group && (attack.base_heal > 0) {
|
||||||
|
is_heal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weapon gives base damage
|
// Weapon gives base damage
|
||||||
let source = if attack.base_healthchange > 0 {
|
let source = if is_heal {
|
||||||
DamageSource::Healing
|
DamageSource::Healing
|
||||||
} else {
|
} else if attack.is_melee {
|
||||||
DamageSource::Melee
|
DamageSource::Melee
|
||||||
|
} else {
|
||||||
|
DamageSource::Energy
|
||||||
};
|
};
|
||||||
let mut damage = Damage {
|
let mut damage = Damage {
|
||||||
healthchange: attack.base_healthchange as f32,
|
healthchange: -(attack.base_damage as f32),
|
||||||
source,
|
source,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,22 +141,24 @@ impl<'a> System<'a> for Sys {
|
|||||||
damage.modify_damage(block, loadout);
|
damage.modify_damage(block, loadout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if damage.healthchange < 0.0 {
|
if damage.healthchange != 0.0 {
|
||||||
|
let cause = if is_heal { HealthSource::Healing { by: Some(*uid) } } else { HealthSource::Attack { by: *uid } };
|
||||||
server_emitter.emit(ServerEvent::Damage {
|
server_emitter.emit(ServerEvent::Damage {
|
||||||
uid: *uid_b,
|
uid: *uid_b,
|
||||||
change: HealthChange {
|
change: HealthChange {
|
||||||
amount: damage.healthchange as i32,
|
amount: damage.healthchange as i32,
|
||||||
|
cause,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if attack.lifesteal_eff != 0.0 && is_damage {
|
||||||
|
server_emitter.emit(ServerEvent::Damage {
|
||||||
|
uid: *uid,
|
||||||
|
change: HealthChange {
|
||||||
|
amount: (-damage.healthchange * attack.lifesteal_eff) as i32,
|
||||||
cause: HealthSource::Attack { by: *uid },
|
cause: HealthSource::Attack { by: *uid },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if damage.healthchange > 0.0 {
|
}
|
||||||
server_emitter.emit(ServerEvent::Damage {
|
|
||||||
uid: *uid_b,
|
|
||||||
change: HealthChange {
|
|
||||||
amount: damage.healthchange as i32,
|
|
||||||
cause: HealthSource::Healing { by: Some(*uid) },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||||
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
||||||
|
@ -113,7 +113,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
| CharacterState::ComboMelee { .. }
|
| CharacterState::ComboMelee { .. }
|
||||||
| CharacterState::BasicRanged { .. }
|
| CharacterState::BasicRanged { .. }
|
||||||
| CharacterState::ChargedRanged { .. }
|
| CharacterState::ChargedRanged { .. }
|
||||||
| CharacterState::GroundShockwave { .. } => {
|
| CharacterState::GroundShockwave { .. }
|
||||||
|
| CharacterState::BasicBeam { .. } => {
|
||||||
if energy.get_unchecked().regen_rate != 0.0 {
|
if energy.get_unchecked().regen_rate != 0.0 {
|
||||||
energy.get_mut_unchecked().regen_rate = 0.0
|
energy.get_mut_unchecked().regen_rate = 0.0
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user