Bow charged shot attack

This commit is contained in:
Samuel Keiffer 2020-07-26 03:06:53 +00:00 committed by Imbris
parent 9156dbadb8
commit 827b91d691
9 changed files with 287 additions and 21 deletions

View File

@ -73,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Pathfinding is much smoother and pets are cleverer - Pathfinding is much smoother and pets are cleverer
- Animals run/turn at different speeds - Animals run/turn at different speeds
- Updated windowing library (winit 0.19 -> 0.22) - Updated windowing library (winit 0.19 -> 0.22)
- Bow M2 is now a charged attack that scales the longer it's held
### Removed ### Removed

View File

@ -18,6 +18,7 @@ pub enum CharacterAbilityType {
BasicMelee, BasicMelee,
BasicRanged, BasicRanged,
Boost, Boost,
ChargedRanged,
DashMelee, DashMelee,
BasicBlock, BasicBlock,
TripleStrike(Stage), TripleStrike(Stage),
@ -36,6 +37,7 @@ impl From<&CharacterState> for CharacterAbilityType {
CharacterState::LeapMelee(_) => Self::LeapMelee, CharacterState::LeapMelee(_) => Self::LeapMelee,
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage), CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
CharacterState::SpinMelee(_) => Self::SpinMelee, CharacterState::SpinMelee(_) => Self::SpinMelee,
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
_ => Self::BasicMelee, _ => Self::BasicMelee,
} }
} }
@ -90,6 +92,19 @@ pub enum CharacterAbility {
recover_duration: Duration, recover_duration: Duration,
base_damage: u32, base_damage: u32,
}, },
ChargedRanged {
energy_cost: u32,
energy_drain: u32,
initial_damage: u32,
max_damage: u32,
initial_knockback: f32,
max_knockback: f32,
prepare_duration: Duration,
charge_duration: Duration,
recover_duration: Duration,
projectile_body: Body,
projectile_light: Option<LightEmitter>,
},
} }
impl CharacterAbility { impl CharacterAbility {
@ -131,6 +146,10 @@ impl CharacterAbility {
.energy .energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability) .try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(), .is_ok(),
CharacterAbility::ChargedRanged { energy_cost, .. } => update
.energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(),
_ => true, _ => true,
} }
} }
@ -311,6 +330,32 @@ impl From<&CharacterAbility> for CharacterState {
* this value can be removed if ability moved to * this value can be removed if ability moved to
* skillbar */ * skillbar */
}), }),
CharacterAbility::ChargedRanged {
energy_cost: _,
energy_drain,
initial_damage,
max_damage,
initial_knockback,
max_knockback,
prepare_duration,
charge_duration,
recover_duration,
projectile_body,
projectile_light,
} => CharacterState::ChargedRanged(charged_ranged::Data {
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,
}),
} }
} }
} }

View File

@ -66,6 +66,8 @@ pub enum CharacterState {
LeapMelee(leap_melee::Data), LeapMelee(leap_melee::Data),
/// Spin around, dealing damage to enemies surrounding you /// Spin around, dealing damage to enemies surrounding you
SpinMelee(spin_melee::Data), SpinMelee(spin_melee::Data),
/// A charged ranged attack (e.g. bow)
ChargedRanged(charged_ranged::Data),
} }
impl CharacterState { impl CharacterState {
@ -78,7 +80,8 @@ impl CharacterState {
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::BasicBlock | CharacterState::BasicBlock
| CharacterState::LeapMelee(_) | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) => true, | CharacterState::SpinMelee(_)
| CharacterState::ChargedRanged(_) => true,
_ => false, _ => false,
} }
} }
@ -90,7 +93,8 @@ impl CharacterState {
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::LeapMelee(_) | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) => true, | CharacterState::SpinMelee(_)
| CharacterState::ChargedRanged(_) => true,
_ => false, _ => false,
} }
} }
@ -102,7 +106,8 @@ impl CharacterState {
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::BasicBlock | CharacterState::BasicBlock
| CharacterState::LeapMelee(_) => true, | CharacterState::LeapMelee(_)
| CharacterState::ChargedRanged(_) => true,
_ => false, _ => false,
} }
} }

View File

@ -273,25 +273,18 @@ impl Tool {
projectile_light: None, projectile_light: None,
projectile_gravity: Some(Gravity(0.2)), projectile_gravity: Some(Gravity(0.2)),
}, },
BasicRanged { ChargedRanged {
energy_cost: 350, energy_cost: 0,
holdable: true, energy_drain: 300,
prepare_duration: Duration::from_millis(250), initial_damage: 3,
recover_duration: Duration::from_millis(700), max_damage: 15,
projectile: Projectile { initial_knockback: 10.0,
hit_solid: vec![projectile::Effect::Stick], max_knockback: 20.0,
hit_entity: vec![ prepare_duration: Duration::from_millis(100),
projectile::Effect::Damage(-9), charge_duration: Duration::from_millis(1500),
projectile::Effect::Knockback(15.0), recover_duration: Duration::from_millis(500),
projectile::Effect::RewardEnergy(50),
projectile::Effect::Vanish,
],
time_left: Duration::from_secs(15),
owner: None,
},
projectile_body: Body::Object(object::Body::Arrow), projectile_body: Body::Object(object::Body::Arrow),
projectile_light: None, projectile_light: None,
projectile_gravity: Some(Gravity(0.05)),
}, },
], ],
Dagger(_) => vec![ Dagger(_) => vec![

View File

@ -0,0 +1,192 @@
use crate::{
comp::{
projectile, Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile,
StateUpdate,
},
event::ServerEvent,
states::utils::*,
sys::character_behavior::*,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
const MAX_GRAVITY: f32 = 0.2;
const MIN_GRAVITY: f32 = 0.05;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Whether the attack fired already
pub exhausted: bool,
/// How much energy is drained per second when charging
pub energy_drain: u32,
/// How much damage is dealt with no charge
pub initial_damage: u32,
/// How much damage is dealt with max charge
pub max_damage: u32,
/// How much knockback there is with no chage
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>,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(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,
});
} 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,
});
// 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,
});
// 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,
};
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: Some(Gravity(
MAX_GRAVITY - charge_amount * (MAX_GRAVITY - MIN_GRAVITY),
)),
});
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,
});
} 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,
});
} else {
// Done
update.character = CharacterState::Wielding;
}
update
}
}

View File

@ -2,6 +2,7 @@ pub mod basic_block;
pub mod basic_melee; pub mod basic_melee;
pub mod basic_ranged; pub mod basic_ranged;
pub mod boost; pub mod boost;
pub mod charged_ranged;
pub mod climb; pub mod climb;
pub mod dance; pub mod dance;
pub mod dash_melee; pub mod dash_melee;

View File

@ -245,6 +245,7 @@ impl<'a> System<'a> for Sys {
CharacterState::DashMelee(data) => data.handle_event(&j, action), CharacterState::DashMelee(data) => data.handle_event(&j, action),
CharacterState::LeapMelee(data) => data.handle_event(&j, action), CharacterState::LeapMelee(data) => data.handle_event(&j, action),
CharacterState::SpinMelee(data) => data.handle_event(&j, action), CharacterState::SpinMelee(data) => data.handle_event(&j, action),
CharacterState::ChargedRanged(data) => data.handle_event(&j, action),
}; };
local_emitter.append(&mut state_update.local_events); local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events); server_emitter.append(&mut state_update.server_events);
@ -271,6 +272,7 @@ impl<'a> System<'a> for Sys {
CharacterState::DashMelee(data) => data.behavior(&j), CharacterState::DashMelee(data) => data.behavior(&j),
CharacterState::LeapMelee(data) => data.behavior(&j), CharacterState::LeapMelee(data) => data.behavior(&j),
CharacterState::SpinMelee(data) => data.behavior(&j), CharacterState::SpinMelee(data) => data.behavior(&j),
CharacterState::ChargedRanged(data) => data.behavior(&j),
}; };
local_emitter.append(&mut state_update.local_events); local_emitter.append(&mut state_update.local_events);

View File

@ -105,7 +105,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::LeapMelee { .. } | CharacterState::LeapMelee { .. }
| CharacterState::SpinMelee { .. } | CharacterState::SpinMelee { .. }
| CharacterState::TripleStrike { .. } | CharacterState::TripleStrike { .. }
| CharacterState::BasicRanged { .. } => { | CharacterState::BasicRanged { .. }
| CharacterState::ChargedRanged { .. } => {
if energy.get_unchecked().regen_rate != 0.0 { if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0 energy.get_mut_unchecked().regen_rate = 0.0
} }

View File

@ -681,6 +681,32 @@ impl FigureMgr {
) )
} }
}, },
CharacterState::ChargedRanged(data) => {
if data.exhausted {
anim::character::ShootAnimation::update_skeleton(
&target_base,
(active_tool_kind, second_tool_kind, vel.0.magnitude(), time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
} else {
anim::character::ChargeAnimation::update_skeleton(
&target_base,
(
active_tool_kind,
second_tool_kind,
vel.0.magnitude(),
ori,
state.last_ori,
time,
),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
}
},
CharacterState::Boost(_) => { CharacterState::Boost(_) => {
anim::character::AlphaAnimation::update_skeleton( anim::character::AlphaAnimation::update_skeleton(
&target_base, &target_base,