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
|
- 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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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![
|
||||||
|
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_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;
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user