veloren/common/sys/src/stats.rs

336 lines
13 KiB
Rust
Raw Normal View History

use common::{
comp::{
skills::{GeneralSkill, Skill},
2020-12-10 00:32:24 +00:00
Body, CharacterState, Energy, EnergyChange, EnergySource, Health, Poise, PoiseChange,
2020-12-16 23:30:33 +00:00
PoiseSource, PoiseState, Pos, Stats,
},
event::{EventBus, ServerEvent},
metrics::SysMetrics,
outcome::Outcome,
resources::DeltaTime,
span,
uid::Uid,
};
2020-12-13 17:21:51 +00:00
use hashbrown::HashSet;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage};
2020-12-16 23:30:33 +00:00
use std::time::Duration;
use vek::Vec3;
const ENERGY_REGEN_ACCEL: f32 = 10.0;
2020-12-16 23:30:33 +00:00
const POISE_REGEN_ACCEL: f32 = 2.0;
/// This system kills players, levels them up, and regenerates energy.
pub struct Sys;
impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)]
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, SysMetrics>,
2020-12-16 23:30:33 +00:00
WriteStorage<'a, CharacterState>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Health>,
2020-12-10 00:32:24 +00:00
WriteStorage<'a, Poise>,
WriteStorage<'a, Energy>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
Write<'a, Vec<Outcome>>,
ReadStorage<'a, Body>,
);
fn run(
&mut self,
(
entities,
dt,
server_event_bus,
sys_metrics,
2020-12-16 23:30:33 +00:00
mut character_states,
mut stats,
mut healths,
2020-12-10 00:32:24 +00:00
mut poises,
mut energies,
uids,
positions,
mut outcomes,
bodies,
): Self::SystemData,
) {
let start_time = std::time::Instant::now();
span!(_guard, "run", "stats::Sys::run");
let mut server_event_emitter = server_event_bus.emitter();
// Increment last change timer
healths.set_event_emission(false); // avoid unnecessary syncing
2020-12-16 23:30:33 +00:00
poises.set_event_emission(false); // avoid unnecessary syncing
for health in (&mut healths).join() {
health.last_change.0 += f64::from(dt.0);
}
2020-12-16 23:30:33 +00:00
for poise in (&mut poises).join() {
poise.last_change.0 += f64::from(dt.0);
}
healths.set_event_emission(true);
2020-12-16 23:30:33 +00:00
poises.set_event_emission(true);
2020-07-05 12:39:28 +00:00
// Update stats
2020-12-16 23:30:33 +00:00
for (entity, uid, mut stats, mut health, mut poise, character_state, pos) in (
&entities,
&uids,
&mut stats.restrict_mut(),
&mut healths.restrict_mut(),
2020-12-16 23:30:33 +00:00
&mut poises.restrict_mut(),
&mut character_states,
&positions,
)
.join()
{
let set_dead = {
let health = health.get_unchecked();
health.should_die() && !health.is_dead
};
if set_dead {
let mut health = health.get_mut_unchecked();
server_event_emitter.emit(ServerEvent::Destroy {
entity,
cause: health.last_change.1.cause,
});
health.is_dead = true;
}
let stat = stats.get_unchecked();
let skills_to_level = stat
.skill_set
.skill_groups
.iter()
.filter_map(|s_g| {
(s_g.exp >= stat.skill_set.skill_point_cost(s_g.skill_group_kind))
.then(|| s_g.skill_group_kind)
})
.collect::<HashSet<_>>();
if !skills_to_level.is_empty() {
let mut stat = stats.get_mut_unchecked();
for skill_group in skills_to_level {
stat.skill_set.earn_skill_point(skill_group);
outcomes.push(Outcome::SkillPointGain {
uid: *uid,
skill_tree: skill_group,
total_points: stat.skill_set.earned_sp(skill_group),
pos: pos.0,
});
}
}
2020-07-05 12:39:28 +00:00
}
// Apply effects from leveling skills
for (mut stats, mut health, mut energy, body) in (
&mut stats.restrict_mut(),
&mut healths.restrict_mut(),
&mut energies.restrict_mut(),
&bodies,
)
.join()
{
let stat = stats.get_unchecked();
if stat.skill_set.modify_health {
let mut health = health.get_mut_unchecked();
let health_level = stat
.skill_set
.skill_level(Skill::General(GeneralSkill::HealthIncrease))
.unwrap_or(None)
.unwrap_or(0);
health.update_max_hp(Some(*body), health_level);
let mut stat = stats.get_mut_unchecked();
stat.skill_set.modify_health = false;
}
let stat = stats.get_unchecked();
if stat.skill_set.modify_energy {
let mut energy = energy.get_mut_unchecked();
let energy_level = stat
.skill_set
.skill_level(Skill::General(GeneralSkill::EnergyIncrease))
.unwrap_or(None)
.unwrap_or(0);
energy.update_max_energy(Some(*body), energy_level);
let mut stat = stats.get_mut_unchecked();
stat.skill_set.modify_energy = false;
}
2020-12-16 23:30:33 +00:00
let was_wielded = character_state.is_wield();
let poise = poise.get_mut_unchecked();
match poise.poise_state() {
PoiseState::Normal => {},
PoiseState::Interrupted => {
poise.reset();
*character_state = CharacterState::Stunned(common::states::stunned::Data {
static_data: common::states::stunned::StaticData {
buildup_duration: Duration::from_millis(150),
recover_duration: Duration::from_millis(150),
movement_speed: 0.3,
2020-12-16 23:30:33 +00:00
},
timer: Duration::default(),
stage_section: common::states::utils::StageSection::Buildup,
was_wielded,
});
},
PoiseState::Stunned => {
poise.reset();
*character_state = CharacterState::Stunned(common::states::stunned::Data {
static_data: common::states::stunned::StaticData {
buildup_duration: Duration::from_millis(500),
recover_duration: Duration::from_millis(500),
movement_speed: 0.1,
2020-12-16 23:30:33 +00:00
},
timer: Duration::default(),
stage_section: common::states::utils::StageSection::Buildup,
was_wielded,
});
server_event_emitter.emit(ServerEvent::Knockback {
entity,
impulse: 5.0 * poise.knockback(),
});
},
PoiseState::Dazed => {
poise.reset();
*character_state = CharacterState::Staggered(common::states::staggered::Data {
static_data: common::states::staggered::StaticData {
buildup_duration: Duration::from_millis(1000),
recover_duration: Duration::from_millis(1000),
},
timer: Duration::default(),
stage_section: common::states::utils::StageSection::Buildup,
was_wielded,
});
server_event_emitter.emit(ServerEvent::Knockback {
entity,
impulse: 10.0 * poise.knockback(),
});
2020-12-16 23:30:33 +00:00
},
PoiseState::KnockedDown => {
poise.reset();
*character_state = CharacterState::Staggered(common::states::staggered::Data {
static_data: common::states::staggered::StaticData {
buildup_duration: Duration::from_millis(3000),
recover_duration: Duration::from_millis(500),
2020-12-16 23:30:33 +00:00
},
timer: Duration::default(),
stage_section: common::states::utils::StageSection::Buildup,
was_wielded,
});
server_event_emitter.emit(ServerEvent::Knockback {
entity,
impulse: 10.0 * poise.knockback(),
});
2020-12-16 23:30:33 +00:00
},
}
}
2020-12-10 00:32:24 +00:00
// Update energies and poises
for (character_state, mut energy, mut poise) in (
&character_states,
&mut energies.restrict_mut(),
&mut poises.restrict_mut(),
)
.join()
2020-07-05 12:39:28 +00:00
{
match character_state {
2020-06-13 11:35:31 +00:00
// Accelerate recharging energy.
CharacterState::Idle { .. }
| CharacterState::Sit { .. }
| CharacterState::Dance { .. }
2020-08-02 05:09:11 +00:00
| CharacterState::Sneak { .. }
| CharacterState::GlideWield { .. }
| CharacterState::Wielding { .. }
| CharacterState::Equipping { .. }
| CharacterState::Boost { .. } => {
let res = {
2020-01-19 19:19:44 +00:00
let energy = energy.get_unchecked();
energy.current() < energy.maximum()
};
if res {
2020-01-19 19:19:44 +00:00
let mut energy = energy.get_mut_unchecked();
let energy = &mut *energy;
// Have to account for Calc I differential equations due to acceleration
2020-10-30 21:49:58 +00:00
energy.change_by(EnergyChange {
amount: (energy.regen_rate * dt.0
+ ENERGY_REGEN_ACCEL * dt.0.powi(2) / 2.0)
2020-01-19 21:39:01 +00:00
as i32,
2020-10-30 21:49:58 +00:00
source: EnergySource::Regen,
});
energy.regen_rate =
(energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0);
2020-01-19 19:19:44 +00:00
}
2020-12-10 00:32:24 +00:00
2020-12-16 23:30:33 +00:00
let res_poise = {
let poise = poise.get_unchecked();
poise.current() < poise.maximum()
};
2020-12-10 00:32:24 +00:00
2020-12-16 23:30:33 +00:00
if res_poise {
let mut poise = poise.get_mut_unchecked();
poise.change_by(
PoiseChange {
amount: (poise.regen_rate * dt.0
+ POISE_REGEN_ACCEL * dt.0.powi(2) / 2.0)
as i32,
source: PoiseSource::Regen,
},
Vec3::zero(),
);
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt.0).min(10.0);
}
},
// Ability and glider use does not regen and sets the rate back to zero.
CharacterState::Glide { .. }
| CharacterState::BasicMelee { .. }
| CharacterState::DashMelee { .. }
| CharacterState::LeapMelee { .. }
2020-07-08 19:58:41 +00:00
| CharacterState::SpinMelee { .. }
| CharacterState::ComboMelee { .. }
2020-07-26 03:06:53 +00:00
| CharacterState::BasicRanged { .. }
| CharacterState::ChargedMelee { .. }
2020-08-08 20:53:55 +00:00
| CharacterState::ChargedRanged { .. }
| CharacterState::RepeaterRanged { .. }
| CharacterState::Shockwave { .. }
| CharacterState::BasicBeam { .. } => {
2020-01-19 19:19:44 +00:00
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}
2020-12-16 23:30:33 +00:00
if poise.get_unchecked().regen_rate != 0.0 {
poise.get_mut_unchecked().regen_rate = 0.0
}
},
2020-08-25 12:21:25 +00:00
// recover small amount of passive energy from blocking, and bonus energy from
// blocking attacks?
CharacterState::BasicBlock => {
let res = {
let energy = energy.get_unchecked();
energy.current() < energy.maximum()
};
if res {
2020-10-30 21:49:58 +00:00
energy.get_mut_unchecked().change_by(EnergyChange {
amount: -3,
source: EnergySource::Regen,
});
}
},
2020-06-13 11:35:31 +00:00
// Non-combat abilities that consume energy;
// temporarily stall energy gain, but preserve regen_rate.
CharacterState::Roll { .. }
| CharacterState::Climb { .. }
| CharacterState::Stunned { .. }
2020-12-16 23:30:33 +00:00
| CharacterState::Staggered { .. } => {},
}
}
sys_metrics.stats_ns.store(
start_time.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
}