veloren/common/src/states/charged_ranged.rs

209 lines
9.0 KiB
Rust

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;
#[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 charge
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>,
pub projectile_gravity: Option<Gravity>,
pub initial_projectile_speed: f32,
pub max_projectile_speed: f32,
}
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,
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,
});
// 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,
});
// 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),
});
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;
}
update
}
}