mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Bow charged shot attack
This commit is contained in:
parent
9156dbadb8
commit
827b91d691
@ -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
|
||||
- Animals run/turn at different speeds
|
||||
- Updated windowing library (winit 0.19 -> 0.22)
|
||||
- Bow M2 is now a charged attack that scales the longer it's held
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -18,6 +18,7 @@ pub enum CharacterAbilityType {
|
||||
BasicMelee,
|
||||
BasicRanged,
|
||||
Boost,
|
||||
ChargedRanged,
|
||||
DashMelee,
|
||||
BasicBlock,
|
||||
TripleStrike(Stage),
|
||||
@ -36,6 +37,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
||||
CharacterState::LeapMelee(_) => Self::LeapMelee,
|
||||
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
|
||||
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
||||
CharacterState::ChargedRanged(_) => Self::ChargedRanged,
|
||||
_ => Self::BasicMelee,
|
||||
}
|
||||
}
|
||||
@ -90,6 +92,19 @@ pub enum CharacterAbility {
|
||||
recover_duration: Duration,
|
||||
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 {
|
||||
@ -131,6 +146,10 @@ impl CharacterAbility {
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::ChargedRanged { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@ -311,6 +330,32 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
* this value can be removed if ability moved to
|
||||
* 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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,8 @@ pub enum CharacterState {
|
||||
LeapMelee(leap_melee::Data),
|
||||
/// Spin around, dealing damage to enemies surrounding you
|
||||
SpinMelee(spin_melee::Data),
|
||||
/// A charged ranged attack (e.g. bow)
|
||||
ChargedRanged(charged_ranged::Data),
|
||||
}
|
||||
|
||||
impl CharacterState {
|
||||
@ -78,7 +80,8 @@ impl CharacterState {
|
||||
| CharacterState::TripleStrike(_)
|
||||
| CharacterState::BasicBlock
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_) => true,
|
||||
| CharacterState::SpinMelee(_)
|
||||
| CharacterState::ChargedRanged(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -90,7 +93,8 @@ impl CharacterState {
|
||||
| CharacterState::DashMelee(_)
|
||||
| CharacterState::TripleStrike(_)
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_) => true,
|
||||
| CharacterState::SpinMelee(_)
|
||||
| CharacterState::ChargedRanged(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -102,7 +106,8 @@ impl CharacterState {
|
||||
| CharacterState::DashMelee(_)
|
||||
| CharacterState::TripleStrike(_)
|
||||
| CharacterState::BasicBlock
|
||||
| CharacterState::LeapMelee(_) => true,
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::ChargedRanged(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -273,25 +273,18 @@ impl Tool {
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.2)),
|
||||
},
|
||||
BasicRanged {
|
||||
energy_cost: 350,
|
||||
holdable: true,
|
||||
prepare_duration: Duration::from_millis(250),
|
||||
recover_duration: Duration::from_millis(700),
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damage(-9),
|
||||
projectile::Effect::Knockback(15.0),
|
||||
projectile::Effect::RewardEnergy(50),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
time_left: Duration::from_secs(15),
|
||||
owner: None,
|
||||
},
|
||||
ChargedRanged {
|
||||
energy_cost: 0,
|
||||
energy_drain: 300,
|
||||
initial_damage: 3,
|
||||
max_damage: 15,
|
||||
initial_knockback: 10.0,
|
||||
max_knockback: 20.0,
|
||||
prepare_duration: Duration::from_millis(100),
|
||||
charge_duration: Duration::from_millis(1500),
|
||||
recover_duration: Duration::from_millis(500),
|
||||
projectile_body: Body::Object(object::Body::Arrow),
|
||||
projectile_light: None,
|
||||
projectile_gravity: Some(Gravity(0.05)),
|
||||
},
|
||||
],
|
||||
Dagger(_) => vec![
|
||||
|
192
common/src/states/charged_ranged.rs
Normal file
192
common/src/states/charged_ranged.rs
Normal 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
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ pub mod basic_block;
|
||||
pub mod basic_melee;
|
||||
pub mod basic_ranged;
|
||||
pub mod boost;
|
||||
pub mod charged_ranged;
|
||||
pub mod climb;
|
||||
pub mod dance;
|
||||
pub mod dash_melee;
|
||||
|
@ -245,6 +245,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::DashMelee(data) => data.handle_event(&j, action),
|
||||
CharacterState::LeapMelee(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);
|
||||
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::LeapMelee(data) => data.behavior(&j),
|
||||
CharacterState::SpinMelee(data) => data.behavior(&j),
|
||||
CharacterState::ChargedRanged(data) => data.behavior(&j),
|
||||
};
|
||||
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
|
@ -105,7 +105,8 @@ impl<'a> System<'a> for Sys {
|
||||
| CharacterState::LeapMelee { .. }
|
||||
| CharacterState::SpinMelee { .. }
|
||||
| CharacterState::TripleStrike { .. }
|
||||
| CharacterState::BasicRanged { .. } => {
|
||||
| CharacterState::BasicRanged { .. }
|
||||
| CharacterState::ChargedRanged { .. } => {
|
||||
if energy.get_unchecked().regen_rate != 0.0 {
|
||||
energy.get_mut_unchecked().regen_rate = 0.0
|
||||
}
|
||||
|
@ -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(_) => {
|
||||
anim::character::AlphaAnimation::update_skeleton(
|
||||
&target_base,
|
||||
|
Loading…
Reference in New Issue
Block a user