mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/small-fixes' into 'master'
Sam/small fixes part 1 Closes #525, #410, #412, and #397 See merge request veloren/veloren!1455
This commit is contained in:
commit
6566802b7f
170
common/src/combat.rs
Normal file
170
common/src/combat.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use crate::{
|
||||
comp::{HealthChange, HealthSource, Loadout},
|
||||
sync::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
|
||||
/// Each section of this struct determines what damage is applied to a
|
||||
/// particular target, using some identifier
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Damages {
|
||||
/// Targets enemies, and all other creatures not in your group
|
||||
pub enemy: Option<Damage>,
|
||||
/// Targets people in the same group as you, and any pets you have
|
||||
pub group: Option<Damage>,
|
||||
}
|
||||
|
||||
impl Damages {
|
||||
pub fn new(enemy: Option<Damage>, group: Option<Damage>) -> Self { Damages { enemy, group } }
|
||||
|
||||
pub fn get_damage(self, same_group: bool) -> Option<Damage> {
|
||||
if same_group { self.group } else { self.enemy }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Damage {
|
||||
Melee(f32),
|
||||
Healing(f32),
|
||||
Projectile(f32),
|
||||
Explosion(f32),
|
||||
Falling(f32),
|
||||
Shockwave(f32),
|
||||
Energy(f32),
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub fn modify_damage(
|
||||
self,
|
||||
block: bool,
|
||||
loadout: Option<&Loadout>,
|
||||
uid: Option<Uid>,
|
||||
) -> HealthChange {
|
||||
match self {
|
||||
Damage::Melee(damage) => {
|
||||
let mut damage = damage;
|
||||
// Critical hit
|
||||
let mut critdamage = 0.0;
|
||||
if rand::random() {
|
||||
critdamage = damage * 0.3;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
// Critical damage applies after armor for melee
|
||||
if (damage_reduction - 1.0).abs() > f32::EPSILON {
|
||||
damage += critdamage;
|
||||
}
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Attack { by: uid.unwrap() },
|
||||
}
|
||||
},
|
||||
Damage::Projectile(damage) => {
|
||||
let mut damage = damage;
|
||||
// Critical hit
|
||||
if rand::random() {
|
||||
damage *= 1.2;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Projectile { owner: uid },
|
||||
}
|
||||
},
|
||||
Damage::Explosion(damage) => {
|
||||
let mut damage = damage;
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Explosion { owner: uid },
|
||||
}
|
||||
},
|
||||
Damage::Shockwave(damage) => {
|
||||
let mut damage = damage;
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Attack { by: uid.unwrap() },
|
||||
}
|
||||
},
|
||||
Damage::Energy(damage) => {
|
||||
let mut damage = damage;
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Energy { owner: uid },
|
||||
}
|
||||
},
|
||||
Damage::Healing(heal) => HealthChange {
|
||||
amount: heal as i32,
|
||||
cause: HealthSource::Healing { by: uid },
|
||||
},
|
||||
Damage::Falling(damage) => {
|
||||
let mut damage = damage;
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
if (damage_reduction - 1.0).abs() < f32::EPSILON {
|
||||
damage = 0.0;
|
||||
}
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::World,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Knockback {
|
||||
Away(f32),
|
||||
Towards(f32),
|
||||
Up(f32),
|
||||
TowardsUp(f32),
|
||||
}
|
||||
|
||||
impl Knockback {
|
||||
pub fn calculate_impulse(self, dir: Dir) -> Vec3<f32> {
|
||||
match self {
|
||||
Knockback::Away(strength) => strength * *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5),
|
||||
Knockback::Towards(strength) => {
|
||||
strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5)
|
||||
},
|
||||
Knockback::Up(strength) => strength * Vec3::unit_z(),
|
||||
Knockback::TowardsUp(strength) => {
|
||||
strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.85)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use crate::{
|
||||
*,
|
||||
},
|
||||
sys::character_behavior::JoinData,
|
||||
Knockback,
|
||||
};
|
||||
use arraygen::Arraygen;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -59,16 +60,16 @@ pub enum CharacterAbility {
|
||||
BasicMelee {
|
||||
energy_cost: u32,
|
||||
buildup_duration: Duration,
|
||||
swing_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
base_healthchange: i32,
|
||||
base_damage: u32,
|
||||
knockback: f32,
|
||||
range: f32,
|
||||
max_angle: f32,
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: u32,
|
||||
holdable: bool,
|
||||
prepare_duration: Duration,
|
||||
buildup_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
projectile: Projectile,
|
||||
projectile_body: Body,
|
||||
@ -91,7 +92,7 @@ pub enum CharacterAbility {
|
||||
reps_remaining: u32,
|
||||
},
|
||||
Boost {
|
||||
duration: Duration,
|
||||
movement_duration: Duration,
|
||||
only_up: bool,
|
||||
},
|
||||
DashMelee {
|
||||
@ -169,7 +170,7 @@ pub enum CharacterAbility {
|
||||
max_damage: u32,
|
||||
initial_knockback: f32,
|
||||
max_knockback: f32,
|
||||
prepare_duration: Duration,
|
||||
buildup_duration: Duration,
|
||||
charge_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
projectile_body: Body,
|
||||
@ -184,7 +185,7 @@ pub enum CharacterAbility {
|
||||
swing_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
damage: u32,
|
||||
knockback: f32,
|
||||
knockback: Knockback,
|
||||
shockwave_angle: f32,
|
||||
shockwave_vertical_angle: f32,
|
||||
shockwave_speed: f32,
|
||||
@ -234,10 +235,13 @@ impl CharacterAbility {
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::LeapMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::LeapMelee { energy_cost, .. } => {
|
||||
update.vel.0.z >= 0.0
|
||||
&& update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::SpinMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
@ -250,10 +254,15 @@ impl CharacterAbility {
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::RepeaterRanged { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::RepeaterRanged {
|
||||
energy_cost, leap, ..
|
||||
} => {
|
||||
(leap.is_none() || update.vel.0.z >= 0.0)
|
||||
&& update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::Shockwave { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
@ -356,24 +365,29 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
match ability {
|
||||
CharacterAbility::BasicMelee {
|
||||
buildup_duration,
|
||||
swing_duration,
|
||||
recover_duration,
|
||||
base_healthchange,
|
||||
base_damage,
|
||||
knockback,
|
||||
range,
|
||||
max_angle,
|
||||
energy_cost: _,
|
||||
} => CharacterState::BasicMelee(basic_melee::Data {
|
||||
static_data: basic_melee::StaticData {
|
||||
buildup_duration: *buildup_duration,
|
||||
swing_duration: *swing_duration,
|
||||
recover_duration: *recover_duration,
|
||||
base_damage: *base_damage,
|
||||
knockback: *knockback,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
exhausted: false,
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
base_healthchange: *base_healthchange,
|
||||
knockback: *knockback,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
}),
|
||||
CharacterAbility::BasicRanged {
|
||||
holdable,
|
||||
prepare_duration,
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
projectile,
|
||||
projectile_body,
|
||||
@ -382,21 +396,29 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
projectile_speed,
|
||||
energy_cost: _,
|
||||
} => CharacterState::BasicRanged(basic_ranged::Data {
|
||||
static_data: basic_ranged::StaticData {
|
||||
buildup_duration: *buildup_duration,
|
||||
recover_duration: *recover_duration,
|
||||
projectile: projectile.clone(),
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
projectile_speed: *projectile_speed,
|
||||
ability_key: key,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
exhausted: false,
|
||||
prepare_timer: Duration::default(),
|
||||
holdable: *holdable,
|
||||
prepare_duration: *prepare_duration,
|
||||
recover_duration: *recover_duration,
|
||||
projectile: projectile.clone(),
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
projectile_speed: *projectile_speed,
|
||||
ability_key: key,
|
||||
}),
|
||||
CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data {
|
||||
duration: *duration,
|
||||
only_up: *only_up,
|
||||
CharacterAbility::Boost {
|
||||
movement_duration,
|
||||
only_up,
|
||||
} => CharacterState::Boost(boost::Data {
|
||||
static_data: boost::StaticData {
|
||||
movement_duration: *movement_duration,
|
||||
only_up: *only_up,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
}),
|
||||
CharacterAbility::DashMelee {
|
||||
energy_cost: _,
|
||||
@ -431,14 +453,21 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
recover_duration: *recover_duration,
|
||||
is_interruptible: *is_interruptible,
|
||||
},
|
||||
end_charge: false,
|
||||
auto_charge: false,
|
||||
timer: Duration::default(),
|
||||
refresh_timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
exhausted: false,
|
||||
}),
|
||||
CharacterAbility::BasicBlock => CharacterState::BasicBlock,
|
||||
CharacterAbility::Roll => CharacterState::Roll(roll::Data {
|
||||
remaining_duration: Duration::from_millis(500),
|
||||
static_data: roll::StaticData {
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
movement_duration: Duration::from_millis(300),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
was_wielded: false, // false by default. utils might set it to true
|
||||
}),
|
||||
CharacterAbility::ComboMelee {
|
||||
@ -566,7 +595,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
max_damage,
|
||||
initial_knockback,
|
||||
max_knockback,
|
||||
prepare_duration,
|
||||
buildup_duration,
|
||||
charge_duration,
|
||||
recover_duration,
|
||||
projectile_body,
|
||||
@ -575,21 +604,24 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
initial_projectile_speed,
|
||||
max_projectile_speed,
|
||||
} => CharacterState::ChargedRanged(charged_ranged::Data {
|
||||
static_data: charged_ranged::StaticData {
|
||||
buildup_duration: *buildup_duration,
|
||||
charge_duration: *charge_duration,
|
||||
recover_duration: *recover_duration,
|
||||
energy_drain: *energy_drain,
|
||||
initial_damage: *initial_damage,
|
||||
max_damage: *max_damage,
|
||||
initial_knockback: *initial_knockback,
|
||||
max_knockback: *max_knockback,
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
initial_projectile_speed: *initial_projectile_speed,
|
||||
max_projectile_speed: *max_projectile_speed,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
exhausted: false,
|
||||
energy_drain: *energy_drain,
|
||||
initial_damage: *initial_damage,
|
||||
max_damage: *max_damage,
|
||||
initial_knockback: *initial_knockback,
|
||||
max_knockback: *max_knockback,
|
||||
prepare_duration: *prepare_duration,
|
||||
charge_duration: *charge_duration,
|
||||
charge_timer: Duration::default(),
|
||||
recover_duration: *recover_duration,
|
||||
projectile_body: *projectile_body,
|
||||
projectile_light: *projectile_light,
|
||||
projectile_gravity: *projectile_gravity,
|
||||
initial_projectile_speed: *initial_projectile_speed,
|
||||
max_projectile_speed: *max_projectile_speed,
|
||||
}),
|
||||
CharacterAbility::RepeaterRanged {
|
||||
energy_cost: _,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::sync::Uid;
|
||||
use crate::{sync::Uid, Damages};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -8,8 +8,7 @@ use std::time::Duration;
|
||||
pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub speed: f32,
|
||||
pub damage: u32,
|
||||
pub heal: u32,
|
||||
pub damages: Damages,
|
||||
pub lifesteal_eff: f32,
|
||||
pub energy_regen: u32,
|
||||
pub energy_cost: u32,
|
||||
|
@ -3,6 +3,7 @@ use crate::{
|
||||
event::{LocalEvent, ServerEvent},
|
||||
states::*,
|
||||
sys::character_behavior::JoinData,
|
||||
Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage, VecStorage};
|
||||
@ -152,13 +153,12 @@ impl Component for CharacterState {
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Attacking {
|
||||
pub base_damage: u32,
|
||||
pub base_heal: u32,
|
||||
pub damages: Damages,
|
||||
pub range: f32,
|
||||
pub max_angle: f32,
|
||||
pub applied: bool,
|
||||
pub hit_count: u32,
|
||||
pub knockback: f32,
|
||||
pub knockback: Knockback,
|
||||
}
|
||||
|
||||
impl Component for Attacking {
|
||||
|
@ -1,79 +0,0 @@
|
||||
use crate::comp::Loadout;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
|
||||
pub struct Damage {
|
||||
pub healthchange: f32,
|
||||
pub source: DamageSource,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum DamageSource {
|
||||
Melee,
|
||||
Healing,
|
||||
Projectile,
|
||||
Explosion,
|
||||
Falling,
|
||||
Shockwave,
|
||||
Energy,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub fn modify_damage(&mut self, block: bool, loadout: &Loadout) {
|
||||
match self.source {
|
||||
DamageSource::Melee => {
|
||||
// Critical hit
|
||||
let mut critdamage = 0.0;
|
||||
if rand::random() {
|
||||
critdamage = self.healthchange * 0.3;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
self.healthchange *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
|
||||
// Critical damage applies after armor for melee
|
||||
if (damage_reduction - 1.0).abs() > f32::EPSILON {
|
||||
self.healthchange += critdamage;
|
||||
}
|
||||
},
|
||||
DamageSource::Projectile => {
|
||||
// Critical hit
|
||||
if rand::random() {
|
||||
self.healthchange *= 1.2;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
self.healthchange *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
},
|
||||
DamageSource::Explosion => {
|
||||
// Block
|
||||
if block {
|
||||
self.healthchange *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
},
|
||||
DamageSource::Shockwave => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
},
|
||||
DamageSource::Energy => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.get_damage_reduction();
|
||||
self.healthchange *= 1.0 - damage_reduction;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
use crate::{
|
||||
comp::{body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile},
|
||||
states::combo_melee,
|
||||
Explosion,
|
||||
Damage, Damages, Explosion, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -174,15 +174,15 @@ impl Tool {
|
||||
DashMelee {
|
||||
energy_cost: 200,
|
||||
base_damage: (120.0 * self.base_power()) as u32,
|
||||
max_damage: (260.0 * self.base_power()) as u32,
|
||||
base_knockback: 10.0,
|
||||
max_knockback: 20.0,
|
||||
max_damage: (240.0 * self.base_power()) as u32,
|
||||
base_knockback: 8.0,
|
||||
max_knockback: 15.0,
|
||||
range: 5.0,
|
||||
angle: 45.0,
|
||||
energy_drain: 500,
|
||||
forward_speed: 4.0,
|
||||
buildup_duration: Duration::from_millis(250),
|
||||
charge_duration: Duration::from_millis(400),
|
||||
charge_duration: Duration::from_millis(600),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(500),
|
||||
infinite_charge: true,
|
||||
@ -206,9 +206,10 @@ impl Tool {
|
||||
Axe(_) => vec![
|
||||
BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-120.0 * self.base_power()) as i32,
|
||||
base_damage: (120.0 * self.base_power()) as u32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
@ -244,9 +245,10 @@ impl Tool {
|
||||
Hammer(_) => vec![
|
||||
BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: (-120.0 * self.base_power()) as i32,
|
||||
base_damage: (120.0 * self.base_power()) as u32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
@ -280,9 +282,10 @@ impl Tool {
|
||||
],
|
||||
Farming(_) => vec![BasicMelee {
|
||||
energy_cost: 1,
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(150),
|
||||
base_healthchange: (-50.0 * self.base_power()) as i32,
|
||||
base_damage: (50.0 * self.base_power()) as u32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
@ -290,14 +293,16 @@ impl Tool {
|
||||
Bow(_) => vec![
|
||||
BasicRanged {
|
||||
energy_cost: 0,
|
||||
holdable: true,
|
||||
prepare_duration: Duration::from_millis(100),
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage((-40.0 * self.base_power()) as i32),
|
||||
projectile::Effect::Knockback(10.0),
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(40.0 * self.base_power())),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Knockback(Knockback::Away(10.0)),
|
||||
projectile::Effect::RewardEnergy(50),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
@ -317,7 +322,7 @@ impl Tool {
|
||||
max_damage: (200.0 * self.base_power()) as u32,
|
||||
initial_knockback: 10.0,
|
||||
max_knockback: 20.0,
|
||||
prepare_duration: Duration::from_millis(100),
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
charge_duration: Duration::from_millis(1500),
|
||||
recover_duration: Duration::from_millis(500),
|
||||
projectile_body: Body::Object(object::Body::MultiArrow),
|
||||
@ -332,13 +337,15 @@ impl Tool {
|
||||
buildup_duration: Duration::from_millis(200),
|
||||
shoot_duration: Duration::from_millis(200),
|
||||
recover_duration: Duration::from_millis(800),
|
||||
leap: Some(10.0),
|
||||
leap: Some(5.0),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage((-40.0 * self.base_power()) as i32),
|
||||
projectile::Effect::Knockback(10.0),
|
||||
projectile::Effect::RewardEnergy(50),
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(40.0 * self.base_power())),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Knockback(Knockback::Away(10.0)),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(15),
|
||||
@ -355,8 +362,9 @@ impl Tool {
|
||||
Dagger(_) => vec![BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: (-50.0 * self.base_power()) as i32,
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: (50.0 * self.base_power()) as u32,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 20.0,
|
||||
@ -378,8 +386,7 @@ impl Tool {
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: 800,
|
||||
holdable: true,
|
||||
prepare_duration: Duration::from_millis(800),
|
||||
buildup_duration: Duration::from_millis(800),
|
||||
recover_duration: Duration::from_millis(50),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![
|
||||
@ -422,8 +429,7 @@ impl Tool {
|
||||
Staff(_) => vec![
|
||||
BasicRanged {
|
||||
energy_cost: 0,
|
||||
holdable: false,
|
||||
prepare_duration: Duration::from_millis(500),
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
recover_duration: Duration::from_millis(350),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![
|
||||
@ -482,7 +488,7 @@ impl Tool {
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
damage: (200.0 * self.base_power()) as u32,
|
||||
knockback: 25.0,
|
||||
knockback: Knockback::Away(25.0),
|
||||
shockwave_angle: 360.0,
|
||||
shockwave_vertical_angle: 90.0,
|
||||
shockwave_speed: 20.0,
|
||||
@ -495,8 +501,9 @@ impl Tool {
|
||||
BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: (-40.0 * self.base_power()) as i32,
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: (40.0 * self.base_power()) as u32,
|
||||
knockback: 0.0,
|
||||
range: 3.0,
|
||||
max_angle: 120.0,
|
||||
@ -508,10 +515,11 @@ impl Tool {
|
||||
vec![
|
||||
BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
buildup_duration: Duration::from_millis(400),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(250),
|
||||
knockback: 25.0,
|
||||
base_healthchange: -200,
|
||||
base_damage: 200,
|
||||
range: 5.0,
|
||||
max_angle: 120.0,
|
||||
},
|
||||
@ -521,7 +529,7 @@ impl Tool {
|
||||
swing_duration: Duration::from_millis(200),
|
||||
recover_duration: Duration::from_millis(800),
|
||||
damage: 500,
|
||||
knockback: -40.0,
|
||||
knockback: Knockback::TowardsUp(40.0),
|
||||
shockwave_angle: 90.0,
|
||||
shockwave_vertical_angle: 15.0,
|
||||
shockwave_speed: 20.0,
|
||||
@ -533,10 +541,11 @@ impl Tool {
|
||||
} else if kind == "BeastClaws" {
|
||||
vec![BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
buildup_duration: Duration::from_millis(250),
|
||||
swing_duration: Duration::from_millis(250),
|
||||
recover_duration: Duration::from_millis(250),
|
||||
knockback: 25.0,
|
||||
base_healthchange: -200,
|
||||
base_damage: 200,
|
||||
range: 5.0,
|
||||
max_angle: 120.0,
|
||||
}]
|
||||
@ -544,58 +553,50 @@ impl Tool {
|
||||
vec![BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_healthchange: -10,
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(200),
|
||||
base_damage: 10,
|
||||
knockback: 0.0,
|
||||
range: 1.0,
|
||||
max_angle: 30.0,
|
||||
}]
|
||||
}
|
||||
},
|
||||
Debug(kind) => {
|
||||
if kind == "Boost" {
|
||||
vec![
|
||||
CharacterAbility::Boost {
|
||||
duration: Duration::from_millis(50),
|
||||
only_up: false,
|
||||
},
|
||||
CharacterAbility::Boost {
|
||||
duration: Duration::from_millis(50),
|
||||
only_up: true,
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: 0,
|
||||
holdable: false,
|
||||
prepare_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(10),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Stick,
|
||||
projectile::Effect::Possess,
|
||||
],
|
||||
time_left: Duration::from_secs(10),
|
||||
owner: None,
|
||||
ignore_group: false,
|
||||
},
|
||||
projectile_body: Body::Object(object::Body::ArrowSnake),
|
||||
projectile_light: Some(LightEmitter {
|
||||
col: (0.0, 1.0, 0.33).into(),
|
||||
..Default::default()
|
||||
}),
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
Debug(_) => vec![
|
||||
CharacterAbility::Boost {
|
||||
movement_duration: Duration::from_millis(50),
|
||||
only_up: false,
|
||||
},
|
||||
CharacterAbility::Boost {
|
||||
movement_duration: Duration::from_millis(50),
|
||||
only_up: true,
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(10),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![projectile::Effect::Stick, projectile::Effect::Possess],
|
||||
time_left: Duration::from_secs(10),
|
||||
owner: None,
|
||||
ignore_group: false,
|
||||
},
|
||||
projectile_body: Body::Object(object::Body::ArrowSnake),
|
||||
projectile_light: Some(LightEmitter {
|
||||
col: (0.0, 1.0, 0.33).into(),
|
||||
..Default::default()
|
||||
}),
|
||||
projectile_gravity: None,
|
||||
projectile_speed: 100.0,
|
||||
},
|
||||
],
|
||||
Empty => vec![BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(1000),
|
||||
base_healthchange: -20,
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(900),
|
||||
base_damage: 20,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 15.0,
|
||||
|
@ -7,7 +7,6 @@ pub mod buff;
|
||||
mod character_state;
|
||||
pub mod chat;
|
||||
mod controller;
|
||||
mod damage;
|
||||
mod energy;
|
||||
pub mod group;
|
||||
mod inputs;
|
||||
@ -44,7 +43,6 @@ pub use controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||
InventoryManip, MountState, Mounting,
|
||||
};
|
||||
pub use damage::{Damage, DamageSource};
|
||||
pub use energy::{Energy, EnergySource};
|
||||
pub use group::Group;
|
||||
pub use inputs::CanBuild;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{sync::Uid, Explosion};
|
||||
use crate::{sync::Uid, Damages, Explosion, Knockback};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -6,8 +6,8 @@ use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Effect {
|
||||
Damage(i32),
|
||||
Knockback(f32),
|
||||
Damages(Damages),
|
||||
Knockback(Knockback),
|
||||
RewardEnergy(u32),
|
||||
Explode(Explosion),
|
||||
Vanish,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::sync::Uid;
|
||||
use crate::{sync::Uid, Damages, Knockback};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -9,8 +9,8 @@ pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub vertical_angle: f32,
|
||||
pub speed: f32,
|
||||
pub damage: u32,
|
||||
pub knockback: f32,
|
||||
pub damages: Damages,
|
||||
pub knockback: Knockback,
|
||||
pub requires_ground: bool,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
|
@ -22,6 +22,7 @@ pub mod astar;
|
||||
pub mod character;
|
||||
pub mod clock;
|
||||
pub mod cmd;
|
||||
pub mod combat;
|
||||
pub mod comp;
|
||||
pub mod effect;
|
||||
pub mod event;
|
||||
@ -51,5 +52,6 @@ pub mod util;
|
||||
pub mod vol;
|
||||
pub mod volumes;
|
||||
|
||||
pub use combat::{Damage, Damages, Knockback};
|
||||
pub use explosion::Explosion;
|
||||
pub use loadout_builder::LoadoutBuilder;
|
||||
|
@ -163,8 +163,9 @@ impl LoadoutBuilder {
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(0),
|
||||
recover_duration: Duration::from_millis(400),
|
||||
base_healthchange: -40,
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(300),
|
||||
base_damage: 40,
|
||||
knockback: 0.0,
|
||||
range: 3.5,
|
||||
max_angle: 15.0,
|
||||
@ -343,9 +344,10 @@ impl LoadoutBuilder {
|
||||
item: Item::new_from_asset_expect("common.items.weapons.empty.empty"),
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 10,
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
buildup_duration: Duration::from_millis(500),
|
||||
swing_duration: Duration::from_millis(100),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
base_healthchange: -(body.base_dmg() as i32),
|
||||
base_damage: body.base_dmg(),
|
||||
knockback: 0.0,
|
||||
range: body.base_range(),
|
||||
max_angle: 20.0,
|
||||
|
@ -9,5 +9,5 @@ pub struct SysMetrics {
|
||||
pub stats_ns: AtomicI64,
|
||||
pub phys_ns: AtomicI64,
|
||||
pub projectile_ns: AtomicI64,
|
||||
pub combat_ns: AtomicI64,
|
||||
pub melee_ns: AtomicI64,
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use crate::{
|
||||
states::utils::*,
|
||||
sync::Uid,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -73,14 +74,12 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
particle_ori: Some(*data.inputs.look_dir),
|
||||
offset: self.offset,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Creates beam
|
||||
@ -96,11 +95,11 @@ impl CharacterBehavior for Data {
|
||||
};
|
||||
// Build up
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Cast,
|
||||
particle_ori: Some(*data.inputs.look_dir),
|
||||
offset: eye_height * 0.55,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -108,10 +107,12 @@ impl CharacterBehavior for Data {
|
||||
if ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
&& (self.static_data.energy_drain == 0 || update.energy.current() > 0)
|
||||
{
|
||||
let damage =
|
||||
(self.static_data.base_dps as f32 / self.static_data.tick_rate) as u32;
|
||||
let heal =
|
||||
(self.static_data.base_hps as f32 / self.static_data.tick_rate) as u32;
|
||||
let damage = Damage::Energy(
|
||||
self.static_data.base_dps as f32 / self.static_data.tick_rate,
|
||||
);
|
||||
let heal = Damage::Healing(
|
||||
self.static_data.base_hps as f32 / self.static_data.tick_rate,
|
||||
);
|
||||
let energy_regen =
|
||||
(self.static_data.energy_regen as f32 / self.static_data.tick_rate) as u32;
|
||||
let energy_cost =
|
||||
@ -121,8 +122,7 @@ impl CharacterBehavior for Data {
|
||||
let properties = beam::Properties {
|
||||
angle: self.static_data.max_angle.to_radians(),
|
||||
speed,
|
||||
damage,
|
||||
heal,
|
||||
damages: Damages::new(Some(damage), Some(heal)),
|
||||
lifesteal_eff: self.static_data.lifesteal_eff,
|
||||
energy_regen,
|
||||
energy_cost,
|
||||
@ -137,14 +137,12 @@ impl CharacterBehavior for Data {
|
||||
ori: Ori(data.inputs.look_dir),
|
||||
});
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
particle_ori: Some(*data.inputs.look_dir),
|
||||
offset: self.offset,
|
||||
..*self
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and ability key is held down
|
||||
@ -154,25 +152,22 @@ impl CharacterBehavior for Data {
|
||||
);
|
||||
} else {
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
particle_ori: Some(*data.inputs.look_dir),
|
||||
offset: self.offset,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
particle_ori: Some(*data.inputs.look_dir),
|
||||
offset: self.offset,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -2,24 +2,39 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
pub struct StaticData {
|
||||
/// How long until state should deal damage
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state is swinging for
|
||||
pub swing_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Base damage (negative) or healing (positive)
|
||||
pub base_healthchange: i32,
|
||||
/// Base damage
|
||||
pub base_damage: u32,
|
||||
/// Knockback
|
||||
pub knockback: f32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||
pub max_angle: f32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Whether the attack can deal more damage
|
||||
pub exhausted: bool,
|
||||
}
|
||||
@ -31,65 +46,87 @@ impl CharacterBehavior for Data {
|
||||
handle_move(data, &mut update, 0.7);
|
||||
handle_jump(data, &mut update);
|
||||
|
||||
if self.buildup_duration != Duration::default() {
|
||||
// Build up
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
buildup_duration: self
|
||||
.buildup_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
recover_duration: self.recover_duration,
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: false,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
let (damage, heal) = if self.base_healthchange > 0 {
|
||||
(0, self.base_healthchange as u32)
|
||||
} else {
|
||||
((-self.base_healthchange) as u32, 0)
|
||||
};
|
||||
// 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: self.knockback,
|
||||
});
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Swing,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if !self.exhausted {
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: Duration::default(),
|
||||
exhausted: true,
|
||||
..*self
|
||||
});
|
||||
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
buildup_duration: self.buildup_duration,
|
||||
recover_duration: self
|
||||
.recover_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
base_healthchange: self.base_healthchange,
|
||||
knockback: self.knockback,
|
||||
range: self.range,
|
||||
max_angle: self.max_angle,
|
||||
exhausted: true,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
range: self.static_data.range,
|
||||
max_angle: 180_f32.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: Knockback::Away(self.static_data.knockback),
|
||||
});
|
||||
} else if self.timer < self.static_data.swing_duration {
|
||||
// Swings
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Recovery
|
||||
update.character = CharacterState::BasicMelee(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
},
|
||||
}
|
||||
|
||||
// Grant energy on successful hit
|
||||
|
@ -7,27 +7,36 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Can you hold the ability beyond the prepare duration
|
||||
pub holdable: bool,
|
||||
/// How long we have to prepare the weapon
|
||||
pub prepare_duration: Duration,
|
||||
/// How long we prepared the weapon already
|
||||
pub prepare_timer: Duration,
|
||||
pub struct StaticData {
|
||||
/// How much buildup is required before the attack
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Projectile variables
|
||||
pub projectile: Projectile,
|
||||
pub projectile_body: Body,
|
||||
pub projectile_light: Option<LightEmitter>,
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
pub projectile_speed: f32,
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
@ -35,77 +44,67 @@ impl CharacterBehavior for Data {
|
||||
handle_move(data, &mut update, 0.3);
|
||||
handle_jump(data, &mut update);
|
||||
|
||||
if !self.exhausted
|
||||
&& if self.holdable {
|
||||
ability_key_is_pressed(data, self.ability_key)
|
||||
|| self.prepare_timer < self.prepare_duration
|
||||
} else {
|
||||
self.prepare_timer < self.prepare_duration
|
||||
}
|
||||
{
|
||||
// Prepare (draw the bow)
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
prepare_timer: self.prepare_timer + Duration::from_secs_f32(data.dt.0),
|
||||
holdable: self.holdable,
|
||||
prepare_duration: self.prepare_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile: self.projectile.clone(),
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: false,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Fire
|
||||
let mut projectile = self.projectile.clone();
|
||||
projectile.owner = Some(*data.uid);
|
||||
update.server_events.push_front(ServerEvent::Shoot {
|
||||
entity: data.entity,
|
||||
dir: data.inputs.look_dir,
|
||||
body: self.projectile_body,
|
||||
projectile,
|
||||
light: self.projectile_light,
|
||||
gravity: self.projectile_gravity,
|
||||
speed: self.projectile_speed,
|
||||
});
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if !self.exhausted {
|
||||
// Fire
|
||||
let mut projectile = self.static_data.projectile.clone();
|
||||
projectile.owner = Some(*data.uid);
|
||||
update.server_events.push_front(ServerEvent::Shoot {
|
||||
entity: data.entity,
|
||||
dir: data.inputs.look_dir,
|
||||
body: self.static_data.projectile_body,
|
||||
projectile,
|
||||
light: self.static_data.projectile_light,
|
||||
gravity: self.static_data.projectile_gravity,
|
||||
speed: self.static_data.projectile_speed,
|
||||
});
|
||||
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
prepare_timer: self.prepare_timer,
|
||||
holdable: self.holdable,
|
||||
prepare_duration: self.prepare_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile: self.projectile.clone(),
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
prepare_timer: self.prepare_timer,
|
||||
holdable: self.holdable,
|
||||
prepare_duration: self.prepare_duration,
|
||||
recover_duration: self
|
||||
.recover_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
projectile: self.projectile.clone(),
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
projectile_speed: self.projectile_speed,
|
||||
exhausted: true,
|
||||
ability_key: self.ability_key,
|
||||
});
|
||||
return update;
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
exhausted: true,
|
||||
..*self
|
||||
});
|
||||
} else if self.timer < self.static_data.recover_duration {
|
||||
// Recovers
|
||||
update.character = CharacterState::BasicRanged(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -1,16 +1,25 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{CharacterState, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
pub movement_duration: Duration,
|
||||
pub only_up: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// How long the state has until exiting
|
||||
pub duration: Duration,
|
||||
pub only_up: bool,
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -19,34 +28,25 @@ impl CharacterBehavior for Data {
|
||||
|
||||
handle_move(data, &mut update, 1.0);
|
||||
|
||||
// Still going
|
||||
if self.duration != Duration::default() {
|
||||
if self.only_up {
|
||||
if self.timer < self.static_data.movement_duration {
|
||||
// Movement
|
||||
if self.static_data.only_up {
|
||||
update.vel.0.z += 500.0 * data.dt.0;
|
||||
} else {
|
||||
update.vel.0 += *data.inputs.look_dir * 500.0 * data.dt.0;
|
||||
}
|
||||
update.character = CharacterState::Boost(Data {
|
||||
duration: self
|
||||
.duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
only_up: self.only_up,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
// Done
|
||||
else {
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
|
||||
// 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(100, EnergySource::HitEnemy);
|
||||
}
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
states::utils::{StageSection, *},
|
||||
sys::character_behavior::*,
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -67,14 +68,12 @@ impl CharacterBehavior for Data {
|
||||
|
||||
// Charge the attack
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: self.stage_section,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: charge,
|
||||
..*self
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
@ -87,14 +86,11 @@ impl CharacterBehavior for Data {
|
||||
{
|
||||
// Maintains charge
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: self.stage_section,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
@ -105,65 +101,55 @@ impl CharacterBehavior for Data {
|
||||
} else {
|
||||
// Transitions to swing
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: StageSection::Swing,
|
||||
timer: Duration::default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if !self.exhausted {
|
||||
let damage = self.static_data.initial_damage
|
||||
+ ((self.static_data.max_damage - self.static_data.initial_damage) as f32
|
||||
* self.charge_amount) as u32;
|
||||
let damage = self.static_data.initial_damage as f32
|
||||
+ (self.static_data.max_damage - self.static_data.initial_damage) as f32
|
||||
* self.charge_amount;
|
||||
let knockback = self.static_data.initial_knockback
|
||||
+ (self.static_data.max_knockback - self.static_data.initial_knockback)
|
||||
* self.charge_amount;
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: damage as u32,
|
||||
base_heal: 0,
|
||||
damages: Damages::new(Some(Damage::Melee(damage)), None),
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.max_angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback,
|
||||
knockback: Knockback::Away(knockback),
|
||||
});
|
||||
|
||||
// Starts swinging
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: self.stage_section,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
exhausted: true,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
} else if self.timer < self.static_data.swing_duration {
|
||||
// Swings
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: self.stage_section,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: StageSection::Recover,
|
||||
timer: Duration::default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -171,14 +157,11 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Recovers
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
static_data: self.static_data,
|
||||
stage_section: self.stage_section,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
exhausted: self.exhausted,
|
||||
charge_amount: self.charge_amount,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -6,14 +6,20 @@ use crate::{
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
/// How long the weapon needs to be prepared for
|
||||
pub buildup_duration: Duration,
|
||||
/// How long it takes to charge the weapon to max damage and knockback
|
||||
pub charge_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// How much energy is drained per second when charging
|
||||
pub energy_drain: u32,
|
||||
/// How much damage is dealt with no charge
|
||||
@ -24,14 +30,6 @@ pub struct Data {
|
||||
pub initial_knockback: f32,
|
||||
/// How much knockback there is at max charge
|
||||
pub max_knockback: f32,
|
||||
/// How long the weapon needs to be prepared for
|
||||
pub prepare_duration: Duration,
|
||||
/// How long it takes to charge the weapon to max damage and knockback
|
||||
pub charge_duration: Duration,
|
||||
/// How long the state has been charging
|
||||
pub charge_timer: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Projectile information
|
||||
pub projectile_body: Body,
|
||||
pub projectile_light: Option<LightEmitter>,
|
||||
@ -40,6 +38,19 @@ pub struct Data {
|
||||
pub max_projectile_speed: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Whether the attack fired already
|
||||
pub exhausted: bool,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
@ -47,160 +58,127 @@ impl CharacterBehavior for Data {
|
||||
handle_move(data, &mut update, 0.3);
|
||||
handle_jump(data, &mut update);
|
||||
|
||||
if self.prepare_duration != Duration::default() {
|
||||
// Prepare (draw the bow)
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
exhausted: self.exhausted,
|
||||
energy_drain: self.energy_drain,
|
||||
initial_damage: self.initial_damage,
|
||||
max_damage: self.max_damage,
|
||||
initial_knockback: self.initial_knockback,
|
||||
max_knockback: self.max_knockback,
|
||||
prepare_duration: self
|
||||
.prepare_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
charge_duration: self.charge_duration,
|
||||
charge_timer: self.charge_timer,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else if data.inputs.secondary.is_pressed()
|
||||
&& self.charge_timer < self.charge_duration
|
||||
&& update.energy.current() > 0
|
||||
{
|
||||
// Charge the bow
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
exhausted: self.exhausted,
|
||||
energy_drain: self.energy_drain,
|
||||
initial_damage: self.initial_damage,
|
||||
max_damage: self.max_damage,
|
||||
initial_knockback: self.initial_knockback,
|
||||
max_knockback: self.max_knockback,
|
||||
prepare_duration: self.prepare_duration,
|
||||
charge_timer: self
|
||||
.charge_timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
charge_duration: self.charge_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Charge,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Charge => {
|
||||
if !data.inputs.secondary.is_pressed() && !self.exhausted {
|
||||
let charge_frac = (self.timer.as_secs_f32()
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
let damage = self.static_data.initial_damage as f32
|
||||
+ (charge_frac
|
||||
* (self.static_data.max_damage - self.static_data.initial_damage)
|
||||
as f32);
|
||||
let knockback = self.static_data.initial_knockback as f32
|
||||
+ (charge_frac
|
||||
* (self.static_data.max_knockback - self.static_data.initial_knockback)
|
||||
as f32);
|
||||
// Fire
|
||||
let mut projectile = Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(damage)),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Knockback(Knockback::Away(knockback)),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(15),
|
||||
owner: None,
|
||||
ignore_group: true,
|
||||
};
|
||||
projectile.owner = Some(*data.uid);
|
||||
update.server_events.push_front(ServerEvent::Shoot {
|
||||
entity: data.entity,
|
||||
dir: data.inputs.look_dir,
|
||||
body: self.static_data.projectile_body,
|
||||
projectile,
|
||||
light: self.static_data.projectile_light,
|
||||
gravity: self.static_data.projectile_gravity,
|
||||
speed: self.static_data.initial_projectile_speed
|
||||
+ charge_frac
|
||||
* (self.static_data.max_projectile_speed
|
||||
- self.static_data.initial_projectile_speed),
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
} else if data.inputs.secondary.is_pressed() {
|
||||
// Charge the bow
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
exhausted: self.exhausted,
|
||||
energy_drain: self.energy_drain,
|
||||
initial_damage: self.initial_damage,
|
||||
max_damage: self.max_damage,
|
||||
initial_knockback: self.initial_knockback,
|
||||
max_knockback: self.max_knockback,
|
||||
prepare_duration: self.prepare_duration,
|
||||
charge_timer: self.charge_timer,
|
||||
charge_duration: self.charge_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
exhausted: true,
|
||||
..*self
|
||||
});
|
||||
} else if self.timer < self.static_data.charge_duration
|
||||
&& data.inputs.secondary.is_pressed()
|
||||
{
|
||||
// Charges
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
} else if !self.exhausted {
|
||||
let charge_amount =
|
||||
(self.charge_timer.as_secs_f32() / self.charge_duration.as_secs_f32()).min(1.0);
|
||||
// Fire
|
||||
let mut projectile = Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage(
|
||||
-(self.initial_damage as i32
|
||||
+ (charge_amount * (self.max_damage - self.initial_damage) as f32)
|
||||
as i32),
|
||||
),
|
||||
projectile::Effect::Knockback(
|
||||
self.initial_knockback
|
||||
+ charge_amount * (self.max_knockback - self.initial_knockback),
|
||||
),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(15),
|
||||
owner: None,
|
||||
ignore_group: true,
|
||||
};
|
||||
projectile.owner = Some(*data.uid);
|
||||
update.server_events.push_front(ServerEvent::Shoot {
|
||||
entity: data.entity,
|
||||
dir: data.inputs.look_dir,
|
||||
body: self.projectile_body,
|
||||
projectile,
|
||||
light: self.projectile_light,
|
||||
gravity: self.projectile_gravity,
|
||||
speed: self.initial_projectile_speed
|
||||
+ charge_amount * (self.max_projectile_speed - self.initial_projectile_speed),
|
||||
});
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
} else if data.inputs.secondary.is_pressed() {
|
||||
// Holds charge
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
exhausted: true,
|
||||
energy_drain: self.energy_drain,
|
||||
initial_damage: self.initial_damage,
|
||||
max_damage: self.max_damage,
|
||||
initial_knockback: self.initial_knockback,
|
||||
max_knockback: self.max_knockback,
|
||||
prepare_duration: self.prepare_duration,
|
||||
charge_timer: self.charge_timer,
|
||||
charge_duration: self.charge_duration,
|
||||
recover_duration: self.recover_duration,
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
exhausted: self.exhausted,
|
||||
energy_drain: self.energy_drain,
|
||||
initial_damage: self.initial_damage,
|
||||
max_damage: self.max_damage,
|
||||
initial_knockback: self.initial_knockback,
|
||||
max_knockback: self.max_knockback,
|
||||
prepare_duration: self.prepare_duration,
|
||||
charge_timer: self.charge_timer,
|
||||
charge_duration: self.charge_duration,
|
||||
recover_duration: self
|
||||
.recover_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
projectile_body: self.projectile_body,
|
||||
projectile_light: self.projectile_light,
|
||||
projectile_gravity: self.projectile_gravity,
|
||||
initial_projectile_speed: self.initial_projectile_speed,
|
||||
max_projectile_speed: self.max_projectile_speed,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Recovers
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -99,8 +100,6 @@ impl CharacterBehavior for Data {
|
||||
// Build up
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(
|
||||
@ -110,51 +109,50 @@ impl CharacterBehavior for Data {
|
||||
* data.dt.0,
|
||||
))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
next_stage: self.next_stage,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Swing,
|
||||
next_stage: self.next_stage,
|
||||
..*self
|
||||
});
|
||||
|
||||
// Hit attempt
|
||||
let damage = self.static_data.stage_data[stage_index].max_damage.min(
|
||||
self.static_data.stage_data[stage_index].base_damage
|
||||
+ self.combo / self.static_data.num_stages
|
||||
* self.static_data.stage_data[stage_index].damage_increase,
|
||||
);
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: self.static_data.stage_data[stage_index].max_damage.min(
|
||||
self.static_data.stage_data[stage_index].base_damage
|
||||
+ self.combo / self.static_data.num_stages
|
||||
* self.static_data.stage_data[stage_index].damage_increase,
|
||||
),
|
||||
base_heal: 0,
|
||||
damages: Damages::new(Some(Damage::Melee(damage as f32)), None),
|
||||
range: self.static_data.stage_data[stage_index].range,
|
||||
max_angle: self.static_data.stage_data[stage_index].angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: self.static_data.stage_data[stage_index].knockback,
|
||||
knockback: Knockback::Away(
|
||||
self.static_data.stage_data[stage_index].knockback,
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if self.timer < self.static_data.stage_data[stage_index].base_swing_duration {
|
||||
// Forward movement
|
||||
forward_move(
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
strength: self.static_data.stage_data[stage_index].forward_movement,
|
||||
},
|
||||
0.3,
|
||||
self.static_data.stage_data[stage_index].forward_movement,
|
||||
);
|
||||
|
||||
// Swings
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(
|
||||
@ -164,18 +162,15 @@ impl CharacterBehavior for Data {
|
||||
* data.dt.0,
|
||||
))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
next_stage: self.next_stage,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
next_stage: self.next_stage,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -186,8 +181,6 @@ impl CharacterBehavior for Data {
|
||||
// Checks if state will transition to next stage after recover
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(
|
||||
@ -200,14 +193,12 @@ impl CharacterBehavior for Data {
|
||||
* data.dt.0,
|
||||
))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
next_stage: true,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: self.stage,
|
||||
combo: self.combo,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(
|
||||
@ -220,8 +211,7 @@ impl CharacterBehavior for Data {
|
||||
* data.dt.0,
|
||||
))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
next_stage: self.next_stage,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
} else if self.next_stage {
|
||||
@ -229,10 +219,10 @@ impl CharacterBehavior for Data {
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
stage: (self.stage % self.static_data.num_stages) + 1,
|
||||
combo: self.combo,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
next_stage: false,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -46,9 +47,11 @@ pub struct Data {
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Whether the charge should end
|
||||
pub end_charge: bool,
|
||||
pub auto_charge: bool,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// Timer used to limit how often another attack will be applied
|
||||
pub refresh_timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Whether the state should attempt attacking again
|
||||
@ -78,117 +81,93 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to charge section of stage
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
auto_charge: !data.inputs.secondary.is_pressed(),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Charge => {
|
||||
if (self.timer < self.static_data.charge_duration
|
||||
|| (self.static_data.infinite_charge && data.inputs.secondary.is_pressed()))
|
||||
if (self.static_data.infinite_charge
|
||||
|| self.timer < self.static_data.charge_duration)
|
||||
&& (data.inputs.secondary.is_pressed()
|
||||
|| (self.auto_charge && self.timer < self.static_data.charge_duration))
|
||||
&& update.energy.current() > 0
|
||||
&& !self.end_charge
|
||||
{
|
||||
// Forward movement
|
||||
forward_move(data, &mut update, 0.1, self.static_data.forward_speed);
|
||||
|
||||
// Hit attempt (also checks if player is moving)
|
||||
if !self.exhausted && update.vel.0.distance_squared(Vec3::zero()) > 1.0 {
|
||||
let charge_frac = (self.timer.as_secs_f32()
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
let damage = (self.static_data.max_damage as f32
|
||||
- self.static_data.base_damage as f32)
|
||||
* charge_frac
|
||||
+ self.static_data.base_damage as f32;
|
||||
let knockback = (self.static_data.max_knockback
|
||||
- self.static_data.base_knockback)
|
||||
* charge_frac
|
||||
+ self.static_data.base_knockback;
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: damage as u32,
|
||||
base_heal: 0,
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback,
|
||||
});
|
||||
}
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
strength: self.static_data.forward_speed,
|
||||
},
|
||||
0.1,
|
||||
);
|
||||
|
||||
// This logic basically just decides if a charge should end, and prevents the
|
||||
// character state spamming attacks while checking if it has hit something
|
||||
if !self.exhausted {
|
||||
// Hit attempt (also checks if player is moving)
|
||||
if update.vel.0.distance_squared(Vec3::zero()) > 1.0 {
|
||||
let charge_frac = (self.timer.as_secs_f32()
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
let damage = (self.static_data.max_damage as f32
|
||||
- self.static_data.base_damage as f32)
|
||||
* charge_frac
|
||||
+ self.static_data.base_damage as f32;
|
||||
let knockback = (self.static_data.max_knockback
|
||||
- self.static_data.base_knockback)
|
||||
* charge_frac
|
||||
+ self.static_data.base_knockback;
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(Some(Damage::Melee(damage)), None),
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: Knockback::Away(knockback),
|
||||
});
|
||||
}
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: true,
|
||||
..*self
|
||||
})
|
||||
} else if self.refresh_timer < Duration::from_millis(50) {
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
refresh_timer: self
|
||||
.refresh_timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
})
|
||||
} else if let Some(attack) = data.attacking {
|
||||
if attack.applied && attack.hit_count > 0 {
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: !self.static_data.infinite_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: false,
|
||||
})
|
||||
} else if attack.applied {
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: false,
|
||||
})
|
||||
} else {
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: !self.static_data.infinite_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: self.exhausted,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: !self.static_data.infinite_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: StageSection::Charge,
|
||||
exhausted: self.exhausted,
|
||||
refresh_timer: Duration::default(),
|
||||
exhausted: false,
|
||||
..*self
|
||||
})
|
||||
}
|
||||
|
||||
@ -200,11 +179,9 @@ impl CharacterBehavior for Data {
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Swing,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -212,23 +189,18 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.swing_duration {
|
||||
// Swings
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -236,14 +208,11 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Recover
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
static_data: self.static_data,
|
||||
end_charge: self.end_charge,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -6,10 +6,20 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
/// Time required to draw weapon
|
||||
pub buildup_duration: Duration,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Time left before next state
|
||||
pub time_left: Duration,
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -19,18 +29,18 @@ impl CharacterBehavior for Data {
|
||||
handle_move(&data, &mut update, 1.0);
|
||||
handle_jump(&data, &mut update);
|
||||
|
||||
if self.time_left == Duration::default() {
|
||||
// Wield delay has expired
|
||||
update.character = CharacterState::Wielding;
|
||||
} else {
|
||||
// Wield delay hasn't expired yet
|
||||
// Update wield delay
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Draw weapon
|
||||
update.character = CharacterState::Equipping(Data {
|
||||
time_left: self
|
||||
.time_left
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -2,10 +2,10 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, StateUpdate},
|
||||
states::utils::{StageSection, *},
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use vek::Vec3;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -75,28 +75,20 @@ impl CharacterBehavior for Data {
|
||||
},
|
||||
StageSection::Movement => {
|
||||
if self.timer < self.static_data.movement_duration {
|
||||
// Apply jumping force while in Movement portion of state
|
||||
update.vel.0 = Vec3::new(
|
||||
data.inputs.look_dir.x,
|
||||
data.inputs.look_dir.y,
|
||||
self.static_data.vertical_leap_strength,
|
||||
) * 2.0
|
||||
// Multiply decreasing amount linearly over time of
|
||||
// movement duration
|
||||
* (1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32())
|
||||
// Apply inputted movement directions at 0.25 strength
|
||||
+ (update.vel.0 * Vec3::new(2.0, 2.0, 0.0)
|
||||
+ 0.25 * data.inputs.move_dir.try_normalized().unwrap_or_default())
|
||||
.try_normalized()
|
||||
.unwrap_or_default()
|
||||
// Multiply by forward leap strength
|
||||
* self.static_data.forward_leap_strength
|
||||
// Control forward movement based on look direction.
|
||||
// This allows players to stop moving forward when they
|
||||
// look downward at target
|
||||
* (1.0 - data.inputs.look_dir.z.abs());
|
||||
// Apply jumping force
|
||||
let progress = 1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32();
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Leap {
|
||||
vertical: self.static_data.vertical_leap_strength,
|
||||
forward: self.static_data.forward_leap_strength,
|
||||
progress,
|
||||
},
|
||||
0.15,
|
||||
);
|
||||
|
||||
// Increment duration
|
||||
// If we were to set a timeout for state, this would be
|
||||
@ -141,13 +133,15 @@ impl CharacterBehavior for Data {
|
||||
if !self.exhausted {
|
||||
// Hit attempt, when animation plays
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: self.static_data.base_damage,
|
||||
base_heal: 0,
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.max_angle.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: self.static_data.knockback,
|
||||
knockback: Knockback::Away(self.static_data.knockback),
|
||||
});
|
||||
|
||||
update.character = CharacterState::LeapMelee(Data {
|
||||
|
@ -54,13 +54,18 @@ impl CharacterBehavior for Data {
|
||||
StageSection::Movement => {
|
||||
// Jumping
|
||||
if let Some(leap_strength) = self.static_data.leap {
|
||||
update.vel.0 = Vec3::new(
|
||||
data.vel.0.x,
|
||||
data.vel.0.y,
|
||||
leap_strength
|
||||
* (1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32()),
|
||||
let progress = 1.0
|
||||
- self.timer.as_secs_f32()
|
||||
/ self.static_data.movement_duration.as_secs_f32();
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Leap {
|
||||
vertical: leap_strength,
|
||||
forward: 10.0,
|
||||
progress,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
if self.timer < self.static_data.movement_duration {
|
||||
@ -71,8 +76,7 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transition to buildup
|
||||
@ -80,14 +84,19 @@ impl CharacterBehavior for Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Buildup => {
|
||||
// Aim gliding
|
||||
if self.static_data.leap.is_some() {
|
||||
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0);
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Hover { move_input: 0.1 },
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Buildup to attack
|
||||
@ -97,8 +106,7 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transition to shoot
|
||||
@ -106,7 +114,7 @@ impl CharacterBehavior for Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Shoot,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -152,8 +160,8 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
reps_remaining: self.reps_remaining - 1,
|
||||
..*self
|
||||
});
|
||||
} else if self.timer < self.static_data.shoot_duration {
|
||||
// Finish shooting
|
||||
@ -163,8 +171,7 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transition to recover
|
||||
@ -172,7 +179,7 @@ impl CharacterBehavior for Data {
|
||||
static_data: self.static_data.clone(),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -188,8 +195,7 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
reps_remaining: self.reps_remaining,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
comp::{CharacterState, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
util::Dir,
|
||||
};
|
||||
@ -8,10 +9,27 @@ use std::time::Duration;
|
||||
use vek::Vec3;
|
||||
|
||||
const ROLL_SPEED: f32 = 25.0;
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
/// How long until state should roll
|
||||
pub buildup_duration: Duration,
|
||||
/// How long state is rolling for
|
||||
pub movement_duration: Duration,
|
||||
/// How long it takes to recover from roll
|
||||
pub recover_duration: Duration,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// How long the state has until exiting
|
||||
pub remaining_duration: Duration,
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Had weapon
|
||||
pub was_wielded: bool,
|
||||
}
|
||||
@ -31,23 +49,72 @@ impl CharacterBehavior for Data {
|
||||
// Smooth orientation
|
||||
update.ori.0 = Dir::slerp_to_vec3(update.ori.0, update.vel.0.xy().into(), 9.0 * data.dt.0);
|
||||
|
||||
if self.remaining_duration == Duration::default() {
|
||||
// Roll duration has expired
|
||||
update.vel.0 *= 0.3;
|
||||
if self.was_wielded {
|
||||
update.character = CharacterState::Wielding;
|
||||
} else {
|
||||
update.character = CharacterState::Idle;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, tick down remaining_duration
|
||||
update.character = CharacterState::Roll(Data {
|
||||
remaining_duration: self
|
||||
.remaining_duration
|
||||
.checked_sub(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
was_wielded: self.was_wielded,
|
||||
});
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::Roll(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to movement section of stage
|
||||
update.character = CharacterState::Roll(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Movement,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Movement => {
|
||||
if self.timer < self.static_data.movement_duration {
|
||||
// Movement
|
||||
update.character = CharacterState::Roll(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::Roll(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::Roll(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
if self.was_wielded {
|
||||
update.character = CharacterState::Wielding;
|
||||
} else {
|
||||
update.character = CharacterState::Idle;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
if self.was_wielded {
|
||||
update.character = CharacterState::Wielding;
|
||||
} else {
|
||||
update.character = CharacterState::Idle;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -3,6 +3,7 @@ use crate::{
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -19,7 +20,7 @@ pub struct StaticData {
|
||||
/// Base damage
|
||||
pub damage: u32,
|
||||
/// Knockback
|
||||
pub knockback: f32,
|
||||
pub knockback: Knockback,
|
||||
/// Angle of the shockwave
|
||||
pub shockwave_angle: f32,
|
||||
/// Vertical angle of the shockwave
|
||||
@ -56,12 +57,11 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Attack
|
||||
@ -70,7 +70,10 @@ impl CharacterBehavior for Data {
|
||||
vertical_angle: self.static_data.shockwave_vertical_angle,
|
||||
speed: self.static_data.shockwave_speed,
|
||||
duration: self.static_data.shockwave_duration,
|
||||
damage: self.static_data.damage,
|
||||
damages: Damages::new(
|
||||
Some(Damage::Shockwave(self.static_data.damage as f32)),
|
||||
None,
|
||||
),
|
||||
knockback: self.static_data.knockback,
|
||||
requires_ground: self.static_data.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
@ -83,9 +86,9 @@ impl CharacterBehavior for Data {
|
||||
|
||||
// Transitions to swing
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Swing,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -93,19 +96,18 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.swing_duration {
|
||||
// Swings
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -113,12 +115,11 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.swing_duration {
|
||||
// Recovers
|
||||
update.character = CharacterState::Shockwave(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
stage_section: self.stage_section,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
sys::{
|
||||
character_behavior::{CharacterBehavior, JoinData},
|
||||
phys::GRAVITY,
|
||||
},
|
||||
Damage, Damages, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -56,7 +60,8 @@ impl CharacterBehavior for Data {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
if self.static_data.is_helicopter {
|
||||
update.vel.0 = Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * 5.0;
|
||||
update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z + GRAVITY * data.dt.0)
|
||||
+ data.inputs.move_dir * 5.0;
|
||||
}
|
||||
|
||||
// Allows for other states to interrupt this state
|
||||
@ -75,61 +80,60 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: StageSection::Swing,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if !self.exhausted {
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: self.stage_section,
|
||||
exhausted: true,
|
||||
..*self
|
||||
});
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
base_damage: self.static_data.base_damage,
|
||||
base_heal: 0,
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
range: self.static_data.range,
|
||||
max_angle: 180_f32.to_radians(),
|
||||
applied: false,
|
||||
hit_count: 0,
|
||||
knockback: self.static_data.knockback,
|
||||
knockback: Knockback::Away(self.static_data.knockback),
|
||||
});
|
||||
} else if self.timer < self.static_data.swing_duration {
|
||||
if !self.static_data.is_helicopter {
|
||||
forward_move(data, &mut update, 0.1, self.static_data.forward_speed);
|
||||
handle_forced_movement(
|
||||
data,
|
||||
&mut update,
|
||||
ForcedMovement::Forward {
|
||||
strength: self.static_data.forward_speed,
|
||||
},
|
||||
0.1,
|
||||
);
|
||||
handle_orientation(data, &mut update, 1.0);
|
||||
}
|
||||
|
||||
// Swings
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else if update.energy.current() >= self.static_data.energy_cost
|
||||
&& (self.spins_remaining != 0
|
||||
@ -141,11 +145,10 @@ impl CharacterBehavior for Data {
|
||||
self.spins_remaining - 1
|
||||
};
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
spins_remaining: new_spins_remaining,
|
||||
stage_section: self.stage_section,
|
||||
exhausted: false,
|
||||
..*self
|
||||
});
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
@ -155,11 +158,9 @@ impl CharacterBehavior for Data {
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: Duration::default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: StageSection::Recover,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -167,14 +168,11 @@ impl CharacterBehavior for Data {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
// Recover
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
static_data: self.static_data,
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
spins_remaining: self.spins_remaining,
|
||||
stage_section: self.stage_section,
|
||||
exhausted: self.exhausted,
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
|
||||
@ -90,18 +91,54 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
handle_orientation(data, update, data.body.base_ori_rate());
|
||||
}
|
||||
|
||||
/// Similar to basic_move function, but with forced forward movement
|
||||
pub fn forward_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, forward: f32) {
|
||||
let accel = if data.physics.on_ground {
|
||||
data.body.base_accel()
|
||||
} else {
|
||||
BASE_HUMANOID_AIR_ACCEL
|
||||
};
|
||||
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||
* accel
|
||||
* (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * forward);
|
||||
/// Handles forced movement
|
||||
pub fn handle_forced_movement(
|
||||
data: &JoinData,
|
||||
update: &mut StateUpdate,
|
||||
movement: ForcedMovement,
|
||||
efficiency: f32,
|
||||
) {
|
||||
match movement {
|
||||
ForcedMovement::Forward { strength } => {
|
||||
let accel = if data.physics.on_ground {
|
||||
data.body.base_accel()
|
||||
} else {
|
||||
BASE_HUMANOID_AIR_ACCEL
|
||||
};
|
||||
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||
* accel
|
||||
* (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * strength);
|
||||
},
|
||||
ForcedMovement::Leap {
|
||||
vertical,
|
||||
forward,
|
||||
progress,
|
||||
} => {
|
||||
// Apply jumping force
|
||||
update.vel.0 = Vec3::new(
|
||||
data.inputs.look_dir.x,
|
||||
data.inputs.look_dir.y,
|
||||
vertical,
|
||||
)
|
||||
// Multiply decreasing amount linearly over time (with average of 1)
|
||||
* 2.0 * progress
|
||||
// Apply inputted movement directions with some efficiency
|
||||
+ (data.inputs.move_dir.try_normalized().unwrap_or_default() + update.vel.0.xy())
|
||||
.try_normalized()
|
||||
.unwrap_or_default()
|
||||
// Multiply by forward leap strength
|
||||
* forward
|
||||
// Control forward movement based on look direction.
|
||||
// This allows players to stop moving forward when they
|
||||
// look downward at target
|
||||
* (1.0 - data.inputs.look_dir.z.abs());
|
||||
},
|
||||
ForcedMovement::Hover { move_input } => {
|
||||
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
|
||||
+ move_input * data.inputs.move_dir.try_normalized().unwrap_or_default();
|
||||
},
|
||||
}
|
||||
handle_orientation(data, update, data.body.base_ori_rate() * efficiency);
|
||||
}
|
||||
|
||||
@ -161,7 +198,10 @@ pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) {
|
||||
pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) {
|
||||
if let Some(ItemKind::Tool(tool)) = data.loadout.active_item.as_ref().map(|i| i.item.kind()) {
|
||||
update.character = CharacterState::Equipping(equipping::Data {
|
||||
time_left: tool.equip_time(),
|
||||
static_data: equipping::StaticData {
|
||||
buildup_duration: tool.equip_time(),
|
||||
},
|
||||
timer: Duration::default(),
|
||||
});
|
||||
} else {
|
||||
update.character = CharacterState::Idle;
|
||||
@ -385,3 +425,18 @@ pub enum AbilityKey {
|
||||
Skill1,
|
||||
Dodge,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ForcedMovement {
|
||||
Forward {
|
||||
strength: f32,
|
||||
},
|
||||
Leap {
|
||||
vertical: f32,
|
||||
forward: f32,
|
||||
progress: f32,
|
||||
},
|
||||
Hover {
|
||||
move_input: f32,
|
||||
},
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Beam, BeamSegment, Body, CharacterState, Damage, DamageSource, Energy, EnergySource,
|
||||
HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats,
|
||||
group, Beam, BeamSegment, Body, CharacterState, Energy, EnergySource, HealthChange,
|
||||
HealthSource, Last, Loadout, Ori, Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
sync::{Uid, UidAllocator},
|
||||
Damage,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
@ -166,54 +167,29 @@ impl<'a> System<'a> for Sys {
|
||||
if Some(*uid_b) == beam_segment.owner {
|
||||
continue;
|
||||
}
|
||||
// Don't heal if outside group
|
||||
// Don't damage in the same group
|
||||
let is_damage = !same_group && (beam_segment.damage > 0);
|
||||
let is_heal = same_group && (beam_segment.heal > 0);
|
||||
if !is_heal && !is_damage {
|
||||
|
||||
let damage = if let Some(damage) = beam_segment.damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Weapon gives base damage
|
||||
let source = if is_heal {
|
||||
DamageSource::Healing
|
||||
} else {
|
||||
DamageSource::Energy
|
||||
};
|
||||
let healthchange = if is_heal {
|
||||
beam_segment.heal as f32
|
||||
} else {
|
||||
-(beam_segment.damage as f32)
|
||||
};
|
||||
|
||||
let mut damage = Damage {
|
||||
healthchange,
|
||||
source,
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
// TODO: investigate whether this calculation is proper for beams
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadouts.get(b) {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner);
|
||||
|
||||
if is_damage {
|
||||
if matches!(damage, Damage::Healing(_)) {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Energy {
|
||||
owner: beam_segment.owner,
|
||||
},
|
||||
},
|
||||
change,
|
||||
});
|
||||
if beam_segment.lifesteal_eff > 0.0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: beam_segment.owner.unwrap_or(*uid),
|
||||
change: HealthChange {
|
||||
amount: (-damage.healthchange * beam_segment.lifesteal_eff)
|
||||
amount: (-change.amount as f32 * beam_segment.lifesteal_eff)
|
||||
as i32,
|
||||
cause: HealthSource::Healing {
|
||||
by: beam_segment.owner,
|
||||
@ -227,26 +203,18 @@ impl<'a> System<'a> for Sys {
|
||||
EnergySource::HitEnemy,
|
||||
);
|
||||
}
|
||||
}
|
||||
if is_heal {
|
||||
if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) {
|
||||
if energy_mut
|
||||
.try_change_by(
|
||||
-(beam_segment.energy_cost as i32), // Stamina use
|
||||
EnergySource::Ability,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Healing {
|
||||
by: beam_segment.owner,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) {
|
||||
if energy_mut
|
||||
.try_change_by(
|
||||
-(beam_segment.energy_cost as i32), // Stamina use
|
||||
EnergySource::Ability,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Adds entities that were hit to the hit_entities list on the beam, sees if it
|
||||
|
@ -1,8 +1,5 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange,
|
||||
HealthSource, Loadout, Ori, Pos, Scale, Stats,
|
||||
},
|
||||
comp::{buff, group, Attacking, Body, CharacterState, Loadout, Ori, Pos, Scale, Stats},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
@ -14,7 +11,6 @@ use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
@ -59,7 +55,7 @@ impl<'a> System<'a> for Sys {
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
span!(_guard, "run", "combat::Sys::run");
|
||||
span!(_guard, "run", "melee::Sys::run");
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut _local_emitter = local_bus.emitter();
|
||||
// Attacks
|
||||
@ -113,56 +109,34 @@ impl<'a> System<'a> for Sys {
|
||||
.get(entity)
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(false);
|
||||
// Don't heal if outside group
|
||||
// Don't damage in the same group
|
||||
let is_damage = !same_group && (attack.base_damage > 0);
|
||||
let is_heal = same_group && (attack.base_heal > 0);
|
||||
if !is_heal && !is_damage {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Weapon gives base damage
|
||||
let (source, healthchange) = if is_heal {
|
||||
(DamageSource::Healing, attack.base_heal as f32)
|
||||
let damage = if let Some(damage) = attack.damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
(DamageSource::Melee, -(attack.base_damage as f32))
|
||||
};
|
||||
let mut damage = Damage {
|
||||
healthchange,
|
||||
source,
|
||||
continue;
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadouts.get(b) {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
let change = damage.modify_damage(block, loadouts.get(b), Some(*uid));
|
||||
|
||||
if damage.healthchange != 0.0 {
|
||||
let cause = if is_heal {
|
||||
HealthSource::Healing { by: Some(*uid) }
|
||||
} else {
|
||||
HealthSource::Attack { by: *uid }
|
||||
};
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause,
|
||||
},
|
||||
change,
|
||||
});
|
||||
|
||||
// Apply bleeding buff on melee hits with 10% chance
|
||||
// TODO: Don't have buff uniformly applied on all melee attacks
|
||||
if thread_rng().gen::<f32>() < 0.1 {
|
||||
if change.amount < 0 && thread_rng().gen::<f32>() < 0.1 {
|
||||
use buff::*;
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity: b,
|
||||
buff_change: BuffChange::Add(Buff::new(
|
||||
BuffKind::Bleeding,
|
||||
BuffData {
|
||||
strength: attack.base_damage as f32 / 10.0,
|
||||
strength: -change.amount as f32 / 10.0,
|
||||
duration: Some(Duration::from_secs(10)),
|
||||
},
|
||||
vec![BuffCategory::Physical],
|
||||
@ -172,18 +146,18 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
attack.hit_count += 1;
|
||||
}
|
||||
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
|
||||
if change.amount != 0 {
|
||||
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
||||
server_emitter.emit(ServerEvent::Knockback {
|
||||
entity: b,
|
||||
impulse: attack.knockback
|
||||
* *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||
});
|
||||
let impulse = attack.knockback.calculate_impulse(kb_dir);
|
||||
if !impulse.is_approx_zero() {
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sys_metrics.combat_ns.store(
|
||||
sys_metrics.melee_ns.store(
|
||||
start_time.elapsed().as_nanos() as i64,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
@ -2,8 +2,8 @@ pub mod agent;
|
||||
mod beam;
|
||||
mod buff;
|
||||
pub mod character_behavior;
|
||||
pub mod combat;
|
||||
pub mod controller;
|
||||
pub mod melee;
|
||||
mod mount;
|
||||
pub mod phys;
|
||||
mod projectile;
|
||||
@ -15,7 +15,7 @@ use specs::DispatcherBuilder;
|
||||
|
||||
// System names
|
||||
pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys";
|
||||
pub const COMBAT_SYS: &str = "combat_sys";
|
||||
pub const MELEE_SYS: &str = "melee_sys";
|
||||
pub const AGENT_SYS: &str = "agent_sys";
|
||||
pub const BEAM_SYS: &str = "beam_sys";
|
||||
pub const CONTROLLER_SYS: &str = "controller_sys";
|
||||
@ -39,5 +39,5 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(beam::Sys, BEAM_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]);
|
||||
dispatch_builder.add(melee::Sys, MELEE_SYS, &[PROJECTILE_SYS]);
|
||||
}
|
||||
|
@ -1,20 +1,18 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
projectile, Damage, DamageSource, Energy, EnergySource, Group, HealthChange, HealthSource,
|
||||
Loadout, Ori, PhysicsState, Pos, Projectile, Vel,
|
||||
projectile, Energy, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, Pos,
|
||||
Projectile, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
state::DeltaTime,
|
||||
sync::UidAllocator,
|
||||
util::Dir,
|
||||
};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
/// This system is responsible for handling projectile effect triggers
|
||||
pub struct Sys;
|
||||
@ -73,20 +71,21 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
// Hit entity
|
||||
for other in physics.touch_entities.iter().copied() {
|
||||
let same_group = projectile
|
||||
.owner
|
||||
// Note: somewhat inefficient since we do the lookup for every touching
|
||||
// entity, but if we pull this out of the loop we would want to do it only
|
||||
// if there is at least one touching entity
|
||||
.and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into()))
|
||||
.and_then(|e| groups.get(e))
|
||||
.map_or(false, |owner_group|
|
||||
Some(owner_group) == uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
.and_then(|e| groups.get(e))
|
||||
);
|
||||
if projectile.ignore_group
|
||||
// Skip if in the same group
|
||||
&& projectile
|
||||
.owner
|
||||
// Note: somewhat inefficient since we do the lookup for every touching
|
||||
// entity, but if we pull this out of the loop we would want to do it only
|
||||
// if there is at least one touching entity
|
||||
.and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into()))
|
||||
.and_then(|e| groups.get(e))
|
||||
.map_or(false, |owner_group|
|
||||
Some(owner_group) == uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
.and_then(|e| groups.get(e))
|
||||
)
|
||||
&& same_group
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -97,51 +96,34 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
for effect in projectile.hit_entity.drain(..) {
|
||||
match effect {
|
||||
projectile::Effect::Damage(healthchange) => {
|
||||
let owner_uid = projectile.owner.unwrap();
|
||||
let mut damage = Damage {
|
||||
healthchange: healthchange as f32,
|
||||
source: DamageSource::Projectile,
|
||||
};
|
||||
|
||||
let other_entity = uid_allocator.retrieve_entity_internal(other.into());
|
||||
if let Some(loadout) = other_entity.and_then(|e| loadouts.get(e)) {
|
||||
damage.modify_damage(false, loadout);
|
||||
projectile::Effect::Damages(damages) => {
|
||||
if Some(other) == projectile.owner {
|
||||
continue;
|
||||
}
|
||||
let damage = if let Some(damage) = damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let other_entity_loadout = uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
.and_then(|e| loadouts.get(e));
|
||||
let change =
|
||||
damage.modify_damage(false, other_entity_loadout, projectile.owner);
|
||||
|
||||
if other != owner_uid {
|
||||
if damage.healthchange < 0.0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: other,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Projectile {
|
||||
owner: Some(owner_uid),
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if damage.healthchange > 0.0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: other,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Healing {
|
||||
by: Some(owner_uid),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage { uid: other, change });
|
||||
}
|
||||
},
|
||||
projectile::Effect::Knockback(knockback) => {
|
||||
if let Some(entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
{
|
||||
local_emitter.emit(LocalEvent::ApplyImpulse {
|
||||
entity,
|
||||
impulse: knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5),
|
||||
});
|
||||
let impulse = knockback.calculate_impulse(ori.0);
|
||||
if !impulse.is_approx_zero() {
|
||||
local_emitter
|
||||
.emit(LocalEvent::ApplyImpulse { entity, impulse });
|
||||
}
|
||||
}
|
||||
},
|
||||
projectile::Effect::RewardEnergy(energy) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, Last,
|
||||
Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
|
||||
group, Body, CharacterState, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale,
|
||||
Shockwave, ShockwaveHitEntities, Stats,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
@ -189,51 +189,32 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
}
|
||||
&& (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground)
|
||||
&& !same_group;
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground);
|
||||
|
||||
if hit {
|
||||
let mut damage = Damage {
|
||||
healthchange: -(shockwave.damage as f32),
|
||||
source: DamageSource::Shockwave,
|
||||
let damage = if let Some(damage) = shockwave.damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadouts.get(b) {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
let owner_uid = shockwave.owner.unwrap_or(*uid);
|
||||
let change = damage.modify_damage(block, loadouts.get(b), Some(owner_uid));
|
||||
|
||||
if damage.healthchange != 0.0 {
|
||||
let cause = if damage.healthchange < 0.0 {
|
||||
HealthSource::Attack {
|
||||
by: shockwave.owner.unwrap_or(*uid),
|
||||
}
|
||||
} else {
|
||||
HealthSource::Healing {
|
||||
by: Some(shockwave.owner.unwrap_or(*uid)),
|
||||
}
|
||||
};
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause,
|
||||
},
|
||||
change,
|
||||
});
|
||||
shockwave_hit_list.hit_entities.push(*uid_b);
|
||||
}
|
||||
if shockwave.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
||||
let impulse = if shockwave.knockback < 0.0 {
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85)
|
||||
} else {
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5)
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
let impulse = shockwave.knockback.calculate_impulse(kb_dir);
|
||||
if !impulse.is_approx_zero() {
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,13 @@ impl std::ops::Deref for Dir {
|
||||
impl From<Vec3<f32>> for Dir {
|
||||
fn from(dir: Vec3<f32>) -> Self { Dir::new(dir) }
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Dir {
|
||||
type Output = Dir;
|
||||
|
||||
fn neg(self) -> Dir { Dir::new(-self.0) }
|
||||
}
|
||||
|
||||
/// Begone ye NaN's
|
||||
/// Slerp two `Vec3`s skipping the slerp if their directions are very close
|
||||
/// This avoids a case where `vek`s slerp produces NaN's
|
||||
|
@ -8,18 +8,17 @@ use common::{
|
||||
comp::{
|
||||
self, buff,
|
||||
chat::{KillSource, KillType},
|
||||
object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Item,
|
||||
Player, Pos, Stats,
|
||||
object, Alignment, Body, Group, HealthChange, HealthSource, Item, Player, Pos, Stats,
|
||||
},
|
||||
lottery::Lottery,
|
||||
msg::{PlayerListUpdate, ServerGeneral},
|
||||
outcome::Outcome,
|
||||
state::BlockChange,
|
||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||
sys::combat::BLOCK_ANGLE,
|
||||
sys::melee::BLOCK_ANGLE,
|
||||
terrain::{Block, TerrainGrid},
|
||||
vol::ReadVol,
|
||||
Explosion,
|
||||
Damage, Explosion,
|
||||
};
|
||||
use comp::item::Reagent;
|
||||
use rand::prelude::*;
|
||||
@ -456,17 +455,10 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
if vel.z <= -30.0 {
|
||||
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().get_mut(entity) {
|
||||
let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0;
|
||||
let mut damage = Damage {
|
||||
healthchange: -falldmg,
|
||||
source: DamageSource::Falling,
|
||||
};
|
||||
if let Some(loadout) = state.ecs().read_storage::<comp::Loadout>().get(entity) {
|
||||
damage.modify_damage(false, loadout);
|
||||
}
|
||||
stats.health.change_by(comp::HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: comp::HealthSource::World,
|
||||
});
|
||||
let damage = Damage::Falling(falldmg);
|
||||
let loadouts = state.ecs().read_storage::<comp::Loadout>();
|
||||
let change = damage.modify_damage(false, loadouts.get(entity), None);
|
||||
stats.health.change_by(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -576,43 +568,26 @@ pub fn handle_explosion(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Weapon gives base damage
|
||||
let source = if is_heal {
|
||||
DamageSource::Healing
|
||||
} else {
|
||||
DamageSource::Explosion
|
||||
};
|
||||
let strength = 1.0 - distance_squared / explosion.radius.powi(2);
|
||||
let healthchange = if is_heal {
|
||||
explosion.min_heal as f32
|
||||
+ (explosion.max_heal - explosion.min_heal) as f32 * strength
|
||||
let damage = if is_heal {
|
||||
Damage::Healing(
|
||||
explosion.min_heal as f32
|
||||
+ (explosion.max_heal - explosion.min_heal) as f32 * strength,
|
||||
)
|
||||
} else {
|
||||
-(explosion.min_damage as f32
|
||||
+ (explosion.max_damage - explosion.min_damage) as f32 * strength)
|
||||
};
|
||||
|
||||
let mut damage = Damage {
|
||||
healthchange,
|
||||
source,
|
||||
Damage::Explosion(
|
||||
explosion.min_damage as f32
|
||||
+ (explosion.max_damage - explosion.min_damage) as f32 * strength,
|
||||
)
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadout_b {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
let change = damage.modify_damage(block, loadout_b, owner);
|
||||
|
||||
if damage.healthchange != 0.0 {
|
||||
let cause = if is_heal {
|
||||
HealthSource::Healing { by: owner }
|
||||
} else {
|
||||
HealthSource::Explosion { owner }
|
||||
};
|
||||
stats_b.health.change_by(HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause,
|
||||
});
|
||||
if change.amount != 0 {
|
||||
stats_b.health.change_by(change);
|
||||
if let Some(owner) = owner_entity {
|
||||
if let Some(energy) = ecs.write_storage::<comp::Energy>().get_mut(owner) {
|
||||
energy
|
||||
|
@ -717,7 +717,7 @@ impl Server {
|
||||
let stats_ns = res.stats_ns.load(Ordering::Relaxed);
|
||||
let phys_ns = res.phys_ns.load(Ordering::Relaxed);
|
||||
let projectile_ns = res.projectile_ns.load(Ordering::Relaxed);
|
||||
let combat_ns = res.combat_ns.load(Ordering::Relaxed);
|
||||
let melee_ns = res.melee_ns.load(Ordering::Relaxed);
|
||||
|
||||
c.with_label_values(&[common::sys::AGENT_SYS])
|
||||
.inc_by(agent_ns);
|
||||
@ -733,8 +733,8 @@ impl Server {
|
||||
.inc_by(phys_ns);
|
||||
c.with_label_values(&[common::sys::PROJECTILE_SYS])
|
||||
.inc_by(projectile_ns);
|
||||
c.with_label_values(&[common::sys::COMBAT_SYS])
|
||||
.inc_by(combat_ns);
|
||||
c.with_label_values(&[common::sys::MELEE_SYS])
|
||||
.inc_by(melee_ns);
|
||||
|
||||
const NANOSEC_PER_SEC: f64 = Duration::from_secs(1).as_nanos() as f64;
|
||||
let h = &self.state_tick_metrics.state_tick_time_hist;
|
||||
@ -752,8 +752,8 @@ impl Server {
|
||||
.observe(phys_ns as f64 / NANOSEC_PER_SEC);
|
||||
h.with_label_values(&[common::sys::PROJECTILE_SYS])
|
||||
.observe(projectile_ns as f64 / NANOSEC_PER_SEC);
|
||||
h.with_label_values(&[common::sys::COMBAT_SYS])
|
||||
.observe(combat_ns as f64 / NANOSEC_PER_SEC);
|
||||
h.with_label_values(&[common::sys::MELEE_SYS])
|
||||
.observe(melee_ns as f64 / NANOSEC_PER_SEC);
|
||||
}
|
||||
|
||||
// Report other info
|
||||
|
@ -23,7 +23,10 @@ fn maps_wield_while_equipping() {
|
||||
|
||||
let result = CombatEventMapper::map_event(
|
||||
&CharacterState::Equipping(states::equipping::Data {
|
||||
time_left: Duration::from_millis(10),
|
||||
static_data: states::equipping::StaticData {
|
||||
buildup_duration: Duration::from_millis(10),
|
||||
},
|
||||
timer: Duration::default(),
|
||||
}),
|
||||
&PreviousEntityState {
|
||||
event: SfxEvent::Idle,
|
||||
@ -77,12 +80,17 @@ fn maps_basic_melee() {
|
||||
|
||||
let result = CombatEventMapper::map_event(
|
||||
&CharacterState::BasicMelee(states::basic_melee::Data {
|
||||
buildup_duration: Duration::default(),
|
||||
recover_duration: Duration::default(),
|
||||
knockback: 0.0,
|
||||
base_healthchange: 10,
|
||||
range: 1.0,
|
||||
max_angle: 1.0,
|
||||
static_data: states::basic_melee::StaticData {
|
||||
buildup_duration: Duration::default(),
|
||||
swing_duration: Duration::default(),
|
||||
recover_duration: Duration::default(),
|
||||
base_damage: 10,
|
||||
knockback: 0.0,
|
||||
range: 1.0,
|
||||
max_angle: 1.0,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: states::utils::StageSection::Buildup,
|
||||
exhausted: false,
|
||||
}),
|
||||
&PreviousEntityState {
|
||||
|
@ -153,7 +153,13 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
|
||||
fn maps_roll() {
|
||||
let result = MovementEventMapper::map_movement_event(
|
||||
&CharacterState::Roll(states::roll::Data {
|
||||
remaining_duration: Duration::from_millis(300),
|
||||
static_data: states::roll::StaticData {
|
||||
buildup_duration: Duration::default(),
|
||||
movement_duration: Duration::default(),
|
||||
recover_duration: Duration::default(),
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: states::utils::StageSection::Buildup,
|
||||
was_wielded: true,
|
||||
}),
|
||||
&PhysicsState {
|
||||
|
@ -79,7 +79,7 @@ impl State {
|
||||
if let ItemKind::Tool(kind) = kind {
|
||||
match &kind.kind {
|
||||
ToolKind::Staff(_) => true,
|
||||
ToolKind::Debug(kind) => kind == "Boost",
|
||||
ToolKind::Debug(_) => true,
|
||||
ToolKind::Sword(_) => true,
|
||||
ToolKind::Hammer(_) => true,
|
||||
ToolKind::Axe(_) => true,
|
||||
|
@ -524,6 +524,14 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.map(|i| i.item.kind())
|
||||
.and_then(|kind| match kind {
|
||||
ItemKind::Tool(Tool { kind, .. }) => match kind {
|
||||
ToolKind::Hammer(_) => Some((
|
||||
"Smash of Doom",
|
||||
"\nAn AOE attack with knockback. \nLeaps to position of \
|
||||
cursor.",
|
||||
)),
|
||||
ToolKind::Axe(_) => {
|
||||
Some(("Spin Leap", "\nA slashing running spin leap."))
|
||||
},
|
||||
ToolKind::Staff(_) => Some((
|
||||
"Firebomb",
|
||||
"\nWhirls a big fireball into the air. \nExplodes the ground \
|
||||
@ -533,14 +541,14 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
"Whirlwind",
|
||||
"\nMove forward while spinning with \n your sword.",
|
||||
)),
|
||||
ToolKind::Debug(kind) => match kind.as_ref() {
|
||||
"Boost" => Some((
|
||||
"Possessing Arrow",
|
||||
"\nShoots a poisonous arrow.\nLets you control your \
|
||||
target.",
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
ToolKind::Bow(_) => Some((
|
||||
"Burst",
|
||||
"\nLaunches a burst of arrows at the top \nof a running leap.",
|
||||
)),
|
||||
ToolKind::Debug(_) => Some((
|
||||
"Possessing Arrow",
|
||||
"\nShoots a poisonous arrow.\nLets you control your target.",
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
@ -621,10 +629,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
ToolKind::Bow(_) => self.imgs.bow_m1,
|
||||
ToolKind::Sceptre(_) => self.imgs.heal_0,
|
||||
ToolKind::Staff(_) => self.imgs.fireball,
|
||||
ToolKind::Debug(kind) => match kind.as_ref() {
|
||||
"Boost" => self.imgs.flyingrod_m1,
|
||||
_ => self.imgs.nothing,
|
||||
},
|
||||
ToolKind::Debug(_) => self.imgs.flyingrod_m1,
|
||||
_ => self.imgs.nothing,
|
||||
},
|
||||
_ => self.imgs.nothing,
|
||||
@ -671,10 +676,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
Some(ToolKind::Bow(_)) => self.imgs.bow_m2,
|
||||
Some(ToolKind::Sceptre(_)) => self.imgs.heal_bomb,
|
||||
Some(ToolKind::Staff(_)) => self.imgs.flamethrower,
|
||||
Some(ToolKind::Debug(kind)) => match kind.as_ref() {
|
||||
"Boost" => self.imgs.flyingrod_m2,
|
||||
_ => self.imgs.nothing,
|
||||
},
|
||||
Some(ToolKind::Debug(_)) => self.imgs.flyingrod_m2,
|
||||
_ => self.imgs.nothing,
|
||||
})
|
||||
.w_h(36.0, 36.0)
|
||||
|
@ -116,10 +116,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
ToolKind::Hammer(_) => Some(HotbarImage::HammerLeap),
|
||||
ToolKind::Axe(_) => Some(HotbarImage::AxeLeapSlash),
|
||||
ToolKind::Bow(_) => Some(HotbarImage::BowJumpBurst),
|
||||
ToolKind::Debug(kind) => match kind.as_ref() {
|
||||
"Boost" => Some(HotbarImage::SnakeArrow),
|
||||
_ => None,
|
||||
},
|
||||
ToolKind::Debug(_) => Some(HotbarImage::SnakeArrow),
|
||||
ToolKind::Sword(_) => Some(HotbarImage::SwordWhirlwind),
|
||||
_ => None,
|
||||
},
|
||||
|
@ -594,8 +594,8 @@ impl ParticleMgr {
|
||||
));
|
||||
}
|
||||
} else {
|
||||
for d in 0..10 * distance as i32 {
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 10.0;
|
||||
for d in 0..3 * distance as i32 {
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0;
|
||||
|
||||
let position = pos.0
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
Loading…
Reference in New Issue
Block a user