mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Separated out health from stats component.
This commit is contained in:
parent
b8f722af8d
commit
c48c022f7e
@ -583,16 +583,16 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||
// Get the stats component from the entity
|
||||
// Get the health component from the entity
|
||||
|
||||
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||
// If we're dead, exit before sending the message
|
||||
if self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Stats>()
|
||||
.read_storage::<comp::Health>()
|
||||
.get(self.entity)
|
||||
.map_or(false, |s| s.is_dead)
|
||||
.map_or(false, |h| h.is_dead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -731,9 +731,9 @@ impl Client {
|
||||
if self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Stats>()
|
||||
.read_storage::<comp::Health>()
|
||||
.get(self.entity)
|
||||
.map_or(false, |s| s.is_dead)
|
||||
.map_or(false, |h| h.is_dead)
|
||||
{
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Respawn));
|
||||
}
|
||||
|
129
common/src/comp/health.rs
Normal file
129
common/src/comp/health.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use crate::{comp::Body, sync::Uid};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
/// Specifies what and how much changed current health
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HealthChange {
|
||||
pub amount: i32,
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum HealthSource {
|
||||
Attack { by: Uid }, // TODO: Implement weapon
|
||||
Projectile { owner: Option<Uid> },
|
||||
Explosion { owner: Option<Uid> },
|
||||
Energy { owner: Option<Uid> },
|
||||
Buff { owner: Option<Uid> },
|
||||
Suicide,
|
||||
World,
|
||||
Revive,
|
||||
Command,
|
||||
LevelUp,
|
||||
Item,
|
||||
Healing { by: Option<Uid> },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Health {
|
||||
base_max: u32,
|
||||
current: u32,
|
||||
maximum: u32,
|
||||
pub last_change: (f64, HealthChange),
|
||||
pub is_dead: bool,
|
||||
}
|
||||
|
||||
impl Health {
|
||||
pub fn new(body: Body, level: u32) -> Self {
|
||||
let mut health = Health::empty();
|
||||
|
||||
health.update_max_hp(Some(body), level);
|
||||
health.set_to(health.maximum(), HealthSource::Revive);
|
||||
|
||||
health
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Health {
|
||||
current: 0,
|
||||
maximum: 0,
|
||||
base_max: 0,
|
||||
last_change: (0.0, HealthChange {
|
||||
amount: 0,
|
||||
cause: HealthSource::Revive,
|
||||
}),
|
||||
is_dead: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&self) -> u32 { self.current }
|
||||
|
||||
pub fn maximum(&self) -> u32 { self.maximum }
|
||||
|
||||
pub fn set_to(&mut self, amount: u32, cause: HealthSource) {
|
||||
let amount = amount.min(self.maximum);
|
||||
self.last_change = (0.0, HealthChange {
|
||||
amount: amount as i32 - self.current as i32,
|
||||
cause,
|
||||
});
|
||||
self.current = amount;
|
||||
}
|
||||
|
||||
pub fn change_by(&mut self, change: HealthChange) {
|
||||
self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum);
|
||||
self.last_change = (0.0, change);
|
||||
}
|
||||
|
||||
// This function changes the modified max health value, not the base health
|
||||
// value. The modified health value takes into account buffs and other temporary
|
||||
// changes to max health.
|
||||
pub fn set_maximum(&mut self, amount: u32) {
|
||||
self.maximum = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
// This is private because max hp is based on the level
|
||||
fn set_base_max(&mut self, amount: u32) {
|
||||
self.base_max = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
pub fn reset_max(&mut self) { self.maximum = self.base_max; }
|
||||
|
||||
pub fn should_die(&self) -> bool { self.current == 0 }
|
||||
|
||||
pub fn revive(&mut self) {
|
||||
self.set_to(self.maximum(), HealthSource::Revive);
|
||||
self.is_dead = false;
|
||||
}
|
||||
|
||||
// TODO: Delete this once stat points will be a thing
|
||||
pub fn update_max_hp(&mut self, body: Option<Body>, level: u32) {
|
||||
if let Some(body) = body {
|
||||
self.set_base_max(body.base_health() + body.base_health_increase() * level);
|
||||
self.set_maximum(body.base_health() + body.base_health_increase() * level);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_max_health(mut self, amount: u32) -> Self {
|
||||
self.maximum = amount;
|
||||
self.current = amount;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Health {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Dying {
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
impl Component for Dying {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
@ -9,6 +9,7 @@ pub mod chat;
|
||||
mod controller;
|
||||
mod energy;
|
||||
pub mod group;
|
||||
mod health;
|
||||
mod inputs;
|
||||
mod inventory;
|
||||
mod last;
|
||||
@ -45,6 +46,7 @@ pub use controller::{
|
||||
};
|
||||
pub use energy::{Energy, EnergyChange, EnergySource};
|
||||
pub use group::Group;
|
||||
pub use health::{Health, HealthChange, HealthSource};
|
||||
pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
@ -59,5 +61,5 @@ pub use player::Player;
|
||||
pub use projectile::Projectile;
|
||||
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
pub use stats::{Exp, HealthChange, HealthSource, Level, Stats};
|
||||
pub use stats::{Exp, Level, Stats};
|
||||
pub use visual::{LightAnimation, LightEmitter};
|
||||
|
@ -1,45 +1,12 @@
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{body::humanoid::Species, skills::SkillSet, Body},
|
||||
sync::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
/// Specifies what and how much changed current health
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HealthChange {
|
||||
pub amount: i32,
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum HealthSource {
|
||||
Attack { by: Uid }, // TODO: Implement weapon
|
||||
Projectile { owner: Option<Uid> },
|
||||
Explosion { owner: Option<Uid> },
|
||||
Energy { owner: Option<Uid> },
|
||||
Buff { owner: Option<Uid> },
|
||||
Suicide,
|
||||
World,
|
||||
Revive,
|
||||
Command,
|
||||
LevelUp,
|
||||
Item,
|
||||
Healing { by: Option<Uid> },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Health {
|
||||
base_max: u32,
|
||||
current: u32,
|
||||
maximum: u32,
|
||||
pub last_change: (f64, HealthChange),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct Exp {
|
||||
current: u32,
|
||||
@ -51,41 +18,6 @@ pub struct Level {
|
||||
amount: u32,
|
||||
}
|
||||
|
||||
impl Health {
|
||||
pub fn current(&self) -> u32 { self.current }
|
||||
|
||||
pub fn maximum(&self) -> u32 { self.maximum }
|
||||
|
||||
pub fn set_to(&mut self, amount: u32, cause: HealthSource) {
|
||||
let amount = amount.min(self.maximum);
|
||||
self.last_change = (0.0, HealthChange {
|
||||
amount: amount as i32 - self.current as i32,
|
||||
cause,
|
||||
});
|
||||
self.current = amount;
|
||||
}
|
||||
|
||||
pub fn change_by(&mut self, change: HealthChange) {
|
||||
self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum);
|
||||
self.last_change = (0.0, change);
|
||||
}
|
||||
|
||||
// This function changes the modified max health value, not the base health
|
||||
// value. The modified health value takes into account buffs and other temporary
|
||||
// changes to max health.
|
||||
pub fn set_maximum(&mut self, amount: u32) {
|
||||
self.maximum = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
// This is private because max hp is based on the level
|
||||
fn set_base_max(&mut self, amount: u32) {
|
||||
self.base_max = amount;
|
||||
self.current = self.current.min(self.maximum);
|
||||
}
|
||||
|
||||
pub fn reset_max(&mut self) { self.maximum = self.base_max; }
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum StatChangeError {
|
||||
Underflow,
|
||||
@ -139,35 +71,15 @@ impl Level {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
pub name: String,
|
||||
pub health: Health,
|
||||
pub level: Level,
|
||||
pub exp: Exp,
|
||||
pub skill_set: SkillSet,
|
||||
pub endurance: u32,
|
||||
pub fitness: u32,
|
||||
pub willpower: u32,
|
||||
pub is_dead: bool,
|
||||
pub body_type: Body,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn should_die(&self) -> bool { self.health.current == 0 }
|
||||
|
||||
pub fn revive(&mut self) {
|
||||
self.health
|
||||
.set_to(self.health.maximum(), HealthSource::Revive);
|
||||
self.is_dead = false;
|
||||
}
|
||||
|
||||
// TODO: Delete this once stat points will be a thing
|
||||
pub fn update_max_hp(&mut self, body: Body) {
|
||||
self.health
|
||||
.set_base_max(body.base_health() + body.base_health_increase() * self.level.amount);
|
||||
self.health
|
||||
.set_maximum(body.base_health() + body.base_health_increase() * self.level.amount);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new(name: String, body: Body) -> Self {
|
||||
let species = if let comp::Body::Humanoid(hbody) = body {
|
||||
@ -189,17 +101,8 @@ impl Stats {
|
||||
None => (0, 0, 0),
|
||||
};
|
||||
|
||||
let mut stats = Self {
|
||||
Self {
|
||||
name,
|
||||
health: Health {
|
||||
current: 0,
|
||||
maximum: 0,
|
||||
base_max: 0,
|
||||
last_change: (0.0, HealthChange {
|
||||
amount: 0,
|
||||
cause: HealthSource::Revive,
|
||||
}),
|
||||
},
|
||||
level: Level { amount: 1 },
|
||||
exp: Exp {
|
||||
current: 0,
|
||||
@ -209,17 +112,8 @@ impl Stats {
|
||||
endurance,
|
||||
fitness,
|
||||
willpower,
|
||||
is_dead: false,
|
||||
body_type: body,
|
||||
};
|
||||
|
||||
stats.update_max_hp(body);
|
||||
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), HealthSource::Revive);
|
||||
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an empty `Stats` instance - used during character loading from
|
||||
@ -227,15 +121,6 @@ impl Stats {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
name: "".to_owned(),
|
||||
health: Health {
|
||||
current: 0,
|
||||
maximum: 0,
|
||||
base_max: 0,
|
||||
last_change: (0.0, HealthChange {
|
||||
amount: 0,
|
||||
cause: HealthSource::Revive,
|
||||
}),
|
||||
},
|
||||
level: Level { amount: 1 },
|
||||
exp: Exp {
|
||||
current: 0,
|
||||
@ -245,27 +130,11 @@ impl Stats {
|
||||
endurance: 0,
|
||||
fitness: 0,
|
||||
willpower: 0,
|
||||
is_dead: false,
|
||||
body_type: comp::Body::Humanoid(comp::body::humanoid::Body::random()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_max_health(mut self, amount: u32) -> Self {
|
||||
self.health.maximum = amount;
|
||||
self.health.current = amount;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Stats {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Dying {
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
impl Component for Dying {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ pub enum ServerEvent {
|
||||
CreateNpc {
|
||||
pos: comp::Pos,
|
||||
stats: comp::Stats,
|
||||
health: comp::Health,
|
||||
loadout: comp::Loadout,
|
||||
body: comp::Body,
|
||||
agent: Option<comp::Agent>,
|
||||
|
@ -15,6 +15,7 @@ sum_type! {
|
||||
Stats(comp::Stats),
|
||||
Buffs(comp::Buffs),
|
||||
Energy(comp::Energy),
|
||||
Health(comp::Health),
|
||||
LightEmitter(comp::LightEmitter),
|
||||
Item(comp::Item),
|
||||
Scale(comp::Scale),
|
||||
@ -45,6 +46,7 @@ sum_type! {
|
||||
Stats(PhantomData<comp::Stats>),
|
||||
Buffs(PhantomData<comp::Buffs>),
|
||||
Energy(PhantomData<comp::Energy>),
|
||||
Health(PhantomData<comp::Health>),
|
||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||
Item(PhantomData<comp::Item>),
|
||||
Scale(PhantomData<comp::Scale>),
|
||||
@ -75,6 +77,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Health(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -103,6 +106,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Health(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -131,6 +135,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
|
||||
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
|
||||
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),
|
||||
EcsCompPhantom::Health(_) => sync::handle_remove::<comp::Health>(entity, world),
|
||||
EcsCompPhantom::LightEmitter(_) => {
|
||||
sync::handle_remove::<comp::LightEmitter>(entity, world)
|
||||
},
|
||||
|
@ -114,6 +114,7 @@ impl State {
|
||||
ecs.register::<comp::Stats>();
|
||||
ecs.register::<comp::Buffs>();
|
||||
ecs.register::<comp::Energy>();
|
||||
ecs.register::<comp::Health>();
|
||||
ecs.register::<comp::CanBuild>();
|
||||
ecs.register::<comp::LightEmitter>();
|
||||
ecs.register::<comp::Item>();
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
group::Invite,
|
||||
item::{tool::ToolKind, ItemKind},
|
||||
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
|
||||
GroupManip, LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale, Stats,
|
||||
GroupManip, Health, LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale,
|
||||
UnresolvedChatMsg, Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
@ -46,7 +46,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Vel>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
ReadStorage<'a, Uid>,
|
||||
@ -76,7 +76,7 @@ impl<'a> System<'a> for Sys {
|
||||
velocities,
|
||||
orientations,
|
||||
scales,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
physics_states,
|
||||
uids,
|
||||
@ -261,8 +261,8 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
},
|
||||
Activity::Follow { target, chaser } => {
|
||||
if let (Some(tgt_pos), _tgt_stats) =
|
||||
(positions.get(*target), stats.get(*target))
|
||||
if let (Some(tgt_pos), _tgt_health) =
|
||||
(positions.get(*target), healths.get(*target))
|
||||
{
|
||||
let dist = pos.0.distance(tgt_pos.0);
|
||||
// Follow, or return to idle
|
||||
@ -329,9 +329,9 @@ impl<'a> System<'a> for Sys {
|
||||
_ => Tactic::Melee,
|
||||
};
|
||||
|
||||
if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = (
|
||||
if let (Some(tgt_pos), Some(tgt_health), tgt_alignment) = (
|
||||
positions.get(*target),
|
||||
stats.get(*target),
|
||||
healths.get(*target),
|
||||
alignments.get(*target).copied().unwrap_or(
|
||||
uids.get(*target)
|
||||
.copied()
|
||||
@ -346,7 +346,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Don't attack entities we are passive towards
|
||||
// TODO: This is here, it's a bit of a hack
|
||||
if let Some(alignment) = alignment {
|
||||
if alignment.passive_towards(tgt_alignment) || tgt_stats.is_dead {
|
||||
if alignment.passive_towards(tgt_alignment) || tgt_health.is_dead {
|
||||
do_idle = true;
|
||||
break 'activity;
|
||||
}
|
||||
@ -354,9 +354,9 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
||||
|
||||
let damage = stats
|
||||
let damage = healths
|
||||
.get(entity)
|
||||
.map(|s| s.health.current() as f32 / s.health.maximum() as f32)
|
||||
.map(|h| h.current() as f32 / h.maximum() as f32)
|
||||
.unwrap_or(0.5);
|
||||
|
||||
// Flee
|
||||
@ -557,9 +557,9 @@ impl<'a> System<'a> for Sys {
|
||||
if choose_target {
|
||||
// Search for new targets (this looks expensive, but it's only run occasionally)
|
||||
// TODO: Replace this with a better system that doesn't consider *all* entities
|
||||
let closest_entity = (&entities, &positions, &stats, alignments.maybe(), char_states.maybe())
|
||||
let closest_entity = (&entities, &positions, &healths, alignments.maybe(), char_states.maybe())
|
||||
.join()
|
||||
.filter(|(e, e_pos, e_stats, e_alignment, char_state)| {
|
||||
.filter(|(e, e_pos, e_health, e_alignment, char_state)| {
|
||||
let mut search_dist = SEARCH_DIST;
|
||||
let mut listen_dist = LISTEN_DIST;
|
||||
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
||||
@ -573,7 +573,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Within listen distance
|
||||
|| e_pos.0.distance_squared(pos.0) < listen_dist.powf(2.0))
|
||||
&& *e != entity
|
||||
&& !e_stats.is_dead
|
||||
&& !e_health.is_dead
|
||||
&& alignment
|
||||
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
||||
.unwrap_or(false)
|
||||
@ -602,20 +602,20 @@ impl<'a> System<'a> for Sys {
|
||||
// last!) ---
|
||||
|
||||
// Attack a target that's attacking us
|
||||
if let Some(my_stats) = stats.get(entity) {
|
||||
if let Some(my_health) = healths.get(entity) {
|
||||
// Only if the attack was recent
|
||||
if my_stats.health.last_change.0 < 3.0 {
|
||||
if my_health.last_change.0 < 3.0 {
|
||||
if let comp::HealthSource::Attack { by }
|
||||
| comp::HealthSource::Projectile { owner: Some(by) }
|
||||
| comp::HealthSource::Energy { owner: Some(by) }
|
||||
| comp::HealthSource::Buff { owner: Some(by) }
|
||||
| comp::HealthSource::Explosion { owner: Some(by) } =
|
||||
my_stats.health.last_change.1.cause
|
||||
my_health.last_change.1.cause
|
||||
{
|
||||
if !agent.activity.is_attack() {
|
||||
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
|
||||
{
|
||||
if stats.get(attacker).map_or(false, |a| !a.is_dead) {
|
||||
if healths.get(attacker).map_or(false, |a| !a.is_dead) {
|
||||
match agent.activity {
|
||||
Activity::Attack { target, .. } if target == attacker => {},
|
||||
_ => {
|
||||
@ -658,12 +658,9 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// Attack owner's attacker
|
||||
let owner_stats = stats.get(owner)?;
|
||||
if owner_stats.health.last_change.0 < 5.0
|
||||
&& owner_stats.health.last_change.1.amount < 0
|
||||
{
|
||||
if let comp::HealthSource::Attack { by } =
|
||||
owner_stats.health.last_change.1.cause
|
||||
let owner_health = healths.get(owner)?;
|
||||
if owner_health.last_change.0 < 5.0 && owner_health.last_change.1.amount < 0 {
|
||||
if let comp::HealthSource::Attack { by } = owner_health.last_change.1.cause
|
||||
{
|
||||
if !agent.activity.is_attack() {
|
||||
let attacker = uid_allocator.retrieve_entity_internal(by.id())?;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource,
|
||||
HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats,
|
||||
group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, Health,
|
||||
HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
@ -30,7 +30,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
@ -53,7 +53,7 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
@ -124,7 +124,7 @@ impl<'a> System<'a> for Sys {
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
health_b,
|
||||
body_b,
|
||||
) in (
|
||||
&entities,
|
||||
@ -135,7 +135,7 @@ impl<'a> System<'a> for Sys {
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
)
|
||||
.join()
|
||||
@ -152,7 +152,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Check if it is a hit
|
||||
let hit = entity != b
|
||||
&& !stats_b.is_dead
|
||||
&& !health_b.is_dead
|
||||
// Collision shapes
|
||||
&& (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, pos_b.0, rad_b, height_b)
|
||||
|| last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, (pos_maybe.0).0, rad_b, height_b)}));
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange,
|
||||
HealthSource, Loadout, ModifierKind, Stats,
|
||||
BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, Health, HealthChange,
|
||||
HealthSource, Loadout, ModifierKind,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
@ -19,19 +19,20 @@ impl<'a> System<'a> for Sys {
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
WriteStorage<'a, Stats>,
|
||||
WriteStorage<'a, Health>,
|
||||
WriteStorage<'a, Buffs>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, dt, server_bus, uids, loadouts, mut stats, mut buffs): Self::SystemData,
|
||||
(entities, dt, server_bus, uids, loadouts, mut healths, mut buffs): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
// Set to false to avoid spamming server
|
||||
buffs.set_event_emission(false);
|
||||
stats.set_event_emission(false);
|
||||
for (entity, buff_comp, uid, stat) in (&entities, &mut buffs, &uids, &mut stats).join() {
|
||||
healths.set_event_emission(false);
|
||||
for (entity, buff_comp, uid, health) in (&entities, &mut buffs, &uids, &mut healths).join()
|
||||
{
|
||||
let mut expired_buffs = Vec::<BuffId>::new();
|
||||
for (id, buff) in buff_comp.buffs.iter_mut() {
|
||||
// Tick the buff and subtract delta from it
|
||||
@ -63,8 +64,8 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
// Call to reset stats to base values
|
||||
stat.health.reset_max();
|
||||
// Call to reset health to base values
|
||||
health.reset_max();
|
||||
|
||||
// Iterator over the lists of buffs by kind
|
||||
for buff_ids in buff_comp.kinds.values() {
|
||||
@ -104,14 +105,10 @@ impl<'a> System<'a> for Sys {
|
||||
},
|
||||
BuffEffect::MaxHealthModifier { value, kind } => match kind {
|
||||
ModifierKind::Multiplicative => {
|
||||
stat.health.set_maximum(
|
||||
(stat.health.maximum() as f32 * *value) as u32,
|
||||
);
|
||||
health.set_maximum((health.maximum() as f32 * *value) as u32);
|
||||
},
|
||||
ModifierKind::Additive => {
|
||||
stat.health.set_maximum(
|
||||
(stat.health.maximum() as f32 + *value) as u32,
|
||||
);
|
||||
health.set_maximum((health.maximum() as f32 + *value) as u32);
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -127,8 +124,8 @@ impl<'a> System<'a> for Sys {
|
||||
});
|
||||
}
|
||||
|
||||
// Remove stats that don't persist on death
|
||||
if stat.is_dead {
|
||||
// Remove buffs that don't persist on death
|
||||
if health.is_dead {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity,
|
||||
buff_change: BuffChange::RemoveByCategory {
|
||||
@ -141,6 +138,6 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
// Turned back to true
|
||||
buffs.set_event_emission(true);
|
||||
stats.set_event_emission(true);
|
||||
healths.set_event_emission(true);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
Attacking, Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy,
|
||||
Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
|
||||
Health, Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
@ -58,7 +58,7 @@ pub struct JoinData<'a> {
|
||||
pub dt: &'a DeltaTime,
|
||||
pub controller: &'a Controller,
|
||||
pub inputs: &'a ControllerInputs,
|
||||
pub stats: &'a Stats,
|
||||
pub health: &'a Health,
|
||||
pub energy: &'a Energy,
|
||||
pub loadout: &'a Loadout,
|
||||
pub body: &'a Body,
|
||||
@ -85,7 +85,7 @@ pub type JoinTuple<'a> = (
|
||||
RestrictedMut<'a, Energy>,
|
||||
RestrictedMut<'a, Loadout>,
|
||||
&'a mut Controller,
|
||||
&'a Stats,
|
||||
&'a Health,
|
||||
&'a Body,
|
||||
&'a PhysicsState,
|
||||
Option<&'a Attacking>,
|
||||
@ -123,7 +123,7 @@ impl<'a> JoinData<'a> {
|
||||
loadout: j.7.get_unchecked(),
|
||||
controller: j.8,
|
||||
inputs: &j.8.inputs,
|
||||
stats: j.9,
|
||||
health: j.9,
|
||||
body: j.10,
|
||||
physics: j.11,
|
||||
attacking: j.12,
|
||||
@ -155,7 +155,7 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Energy>,
|
||||
WriteStorage<'a, Loadout>,
|
||||
WriteStorage<'a, Controller>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
ReadStorage<'a, Attacking>,
|
||||
@ -182,7 +182,7 @@ impl<'a> System<'a> for Sys {
|
||||
mut energies,
|
||||
mut loadouts,
|
||||
mut controllers,
|
||||
stats,
|
||||
healths,
|
||||
bodies,
|
||||
physics_states,
|
||||
attacking_storage,
|
||||
@ -206,7 +206,7 @@ impl<'a> System<'a> for Sys {
|
||||
&mut energies.restrict_mut(),
|
||||
&mut loadouts.restrict_mut(),
|
||||
&mut controllers,
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
&physics_states,
|
||||
attacking_storage.maybe(),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{buff, group, Attacking, Body, CharacterState, Loadout, Ori, Pos, Scale, Stats},
|
||||
comp::{buff, group, Attacking, Body, CharacterState, Health, Loadout, Ori, Pos, Scale},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
@ -28,7 +28,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
@ -47,7 +47,7 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
@ -75,14 +75,14 @@ impl<'a> System<'a> for Sys {
|
||||
attack.applied = true;
|
||||
|
||||
// Go through all other entities
|
||||
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
|
||||
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, health_b, body_b) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
)
|
||||
.join()
|
||||
@ -99,7 +99,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Check if it is a hit
|
||||
if entity != b
|
||||
&& !stats_b.is_dead
|
||||
&& !health_b.is_dead
|
||||
// Spherical wedge shaped attack field
|
||||
&& pos.0.distance_squared(pos_b.0) < (rad_b + scale * attack.range).powi(2)
|
||||
&& ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan()
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Body, CharacterState, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale,
|
||||
Shockwave, ShockwaveHitEntities, Stats,
|
||||
group, Body, CharacterState, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos,
|
||||
Scale, Shockwave, ShockwaveHitEntities,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
@ -31,7 +31,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
@ -55,7 +55,7 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
@ -133,7 +133,7 @@ impl<'a> System<'a> for Sys {
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
health_b,
|
||||
body_b,
|
||||
physics_state_b,
|
||||
) in (
|
||||
@ -145,7 +145,7 @@ impl<'a> System<'a> for Sys {
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
&physics_states,
|
||||
)
|
||||
@ -179,7 +179,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Check if it is a hit
|
||||
let hit = entity != b
|
||||
&& !stats_b.is_dead
|
||||
&& !health_b.is_dead
|
||||
// Collision shapes
|
||||
&& {
|
||||
// TODO: write code to collide rect with the arc strip so that we can do
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{CharacterState, Energy, EnergyChange, EnergySource, HealthSource, Stats},
|
||||
comp::{CharacterState, Energy, EnergyChange, EnergySource, Health, HealthSource, Stats},
|
||||
event::{EventBus, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
@ -20,42 +20,59 @@ impl<'a> System<'a> for Sys {
|
||||
ReadExpect<'a, SysMetrics>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
WriteStorage<'a, Stats>,
|
||||
WriteStorage<'a, Health>,
|
||||
WriteStorage<'a, Energy>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, dt, server_event_bus, sys_metrics, character_states, mut stats, mut energies): Self::SystemData,
|
||||
(
|
||||
entities,
|
||||
dt,
|
||||
server_event_bus,
|
||||
sys_metrics,
|
||||
character_states,
|
||||
mut stats,
|
||||
mut healths,
|
||||
mut energies,
|
||||
): 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
|
||||
stats.set_event_emission(false); // avoid unnecessary syncing
|
||||
for stat in (&mut stats).join() {
|
||||
stat.health.last_change.0 += f64::from(dt.0);
|
||||
healths.set_event_emission(false); // avoid unnecessary syncing
|
||||
for health in (&mut healths).join() {
|
||||
health.last_change.0 += f64::from(dt.0);
|
||||
}
|
||||
stats.set_event_emission(true);
|
||||
healths.set_event_emission(true);
|
||||
|
||||
// Update stats
|
||||
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
|
||||
for (entity, mut stats, mut health) in (
|
||||
&entities,
|
||||
&mut stats.restrict_mut(),
|
||||
&mut healths.restrict_mut(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let (set_dead, level_up) = {
|
||||
let stat = stats.get_unchecked();
|
||||
let health = health.get_unchecked();
|
||||
(
|
||||
stat.should_die() && !stat.is_dead,
|
||||
health.should_die() && !health.is_dead,
|
||||
stat.exp.current() >= stat.exp.maximum(),
|
||||
)
|
||||
};
|
||||
|
||||
if set_dead {
|
||||
let stat = stats.get_mut_unchecked();
|
||||
let health = health.get_mut_unchecked();
|
||||
server_event_emitter.emit(ServerEvent::Destroy {
|
||||
entity,
|
||||
cause: stat.health.last_change.1.cause,
|
||||
cause: health.last_change.1.cause,
|
||||
});
|
||||
|
||||
stat.is_dead = true;
|
||||
health.is_dead = true;
|
||||
}
|
||||
|
||||
if level_up {
|
||||
@ -67,9 +84,9 @@ impl<'a> System<'a> for Sys {
|
||||
server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level()));
|
||||
}
|
||||
|
||||
stat.update_max_hp(stat.body_type);
|
||||
stat.health
|
||||
.set_to(stat.health.maximum(), HealthSource::LevelUp);
|
||||
let health = health.get_mut_unchecked();
|
||||
health.update_max_hp(Some(stat.body_type), stat.level.level());
|
||||
health.set_to(health.maximum(), HealthSource::LevelUp);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,9 +400,9 @@ fn handle_kill(
|
||||
server
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage::<comp::Stats>()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(target)
|
||||
.map(|s| s.health.set_to(0, reason));
|
||||
.map(|h| h.set_to(0, reason));
|
||||
}
|
||||
|
||||
fn handle_time(
|
||||
@ -471,13 +471,13 @@ fn handle_health(
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) {
|
||||
if let Some(stats) = server
|
||||
if let Some(health) = server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::Stats>()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(target)
|
||||
{
|
||||
stats.health.set_to(hp * 10, comp::HealthSource::Command);
|
||||
health.set_to(hp * 10, comp::HealthSource::Command);
|
||||
} else {
|
||||
server.notify_client(
|
||||
client,
|
||||
@ -656,6 +656,7 @@ fn handle_spawn(
|
||||
.create_npc(
|
||||
pos,
|
||||
comp::Stats::new(get_npc_name(id).into(), body),
|
||||
comp::Health::new(body, 1),
|
||||
LoadoutBuilder::build_loadout(body, alignment, None, false)
|
||||
.build(),
|
||||
body,
|
||||
@ -762,9 +763,11 @@ fn handle_spawn_training_dummy(
|
||||
// Level 0 will prevent exp gain from kill
|
||||
stats.level.set_level(0);
|
||||
|
||||
let health = comp::Health::new(body, 0);
|
||||
|
||||
server
|
||||
.state
|
||||
.create_npc(pos, stats, comp::Loadout::default(), body)
|
||||
.create_npc(pos, stats, health, comp::Loadout::default(), body)
|
||||
.with(comp::Vel(vel))
|
||||
.with(comp::MountState::Unmounted)
|
||||
.build();
|
||||
@ -924,12 +927,12 @@ fn handle_kill_npcs(
|
||||
_action: &ChatCommand,
|
||||
) {
|
||||
let ecs = server.state.ecs();
|
||||
let mut stats = ecs.write_storage::<comp::Stats>();
|
||||
let mut healths = ecs.write_storage::<comp::Health>();
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
let mut count = 0;
|
||||
for (stats, ()) in (&mut stats, !&players).join() {
|
||||
for (health, ()) in (&mut healths, !&players).join() {
|
||||
count += 1;
|
||||
stats.health.set_to(0, comp::HealthSource::Command);
|
||||
health.set_to(0, comp::HealthSource::Command);
|
||||
}
|
||||
let text = if count > 0 {
|
||||
format!("Destroyed {} NPCs.", count)
|
||||
@ -1702,6 +1705,8 @@ fn handle_set_level(
|
||||
PlayerListUpdate::LevelChange(uid, lvl),
|
||||
));
|
||||
|
||||
let body_type: Option<comp::Body>;
|
||||
|
||||
if let Some(stats) = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
@ -1709,13 +1714,20 @@ fn handle_set_level(
|
||||
.get_mut(player)
|
||||
{
|
||||
stats.level.set_level(lvl);
|
||||
|
||||
stats.update_max_hp(stats.body_type);
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), comp::HealthSource::LevelUp);
|
||||
body_type = Some(stats.body_type);
|
||||
} else {
|
||||
error_msg = Some(ChatType::CommandError.server_msg("Player has no stats!"));
|
||||
body_type = None;
|
||||
}
|
||||
|
||||
if let Some(health) = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(player)
|
||||
{
|
||||
health.update_max_hp(body_type, lvl);
|
||||
health.set_to(health.maximum(), comp::HealthSource::LevelUp);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
|
@ -3,8 +3,8 @@ use common::{
|
||||
character::CharacterId,
|
||||
comp::{
|
||||
self, beam, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body,
|
||||
Gravity, Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel,
|
||||
WaypointArea,
|
||||
Gravity, Health, Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats,
|
||||
Vel, WaypointArea,
|
||||
},
|
||||
outcome::Outcome,
|
||||
util::Dir,
|
||||
@ -37,6 +37,7 @@ pub fn handle_create_npc(
|
||||
server: &mut Server,
|
||||
pos: Pos,
|
||||
stats: Stats,
|
||||
health: Health,
|
||||
loadout: Loadout,
|
||||
body: Body,
|
||||
agent: impl Into<Option<Agent>>,
|
||||
@ -55,7 +56,7 @@ pub fn handle_create_npc(
|
||||
|
||||
let entity = server
|
||||
.state
|
||||
.create_npc(pos, stats, loadout, body)
|
||||
.create_npc(pos, stats, health, loadout, body)
|
||||
.with(scale)
|
||||
.with(alignment);
|
||||
|
||||
|
@ -8,8 +8,8 @@ use common::{
|
||||
comp::{
|
||||
self, buff,
|
||||
chat::{KillSource, KillType},
|
||||
object, Alignment, Body, Energy, EnergyChange, Group, HealthChange, HealthSource, Item,
|
||||
Player, Pos, Stats,
|
||||
object, Alignment, Body, Energy, EnergyChange, Group, Health, HealthChange, HealthSource,
|
||||
Item, Player, Pos, Stats,
|
||||
},
|
||||
lottery::Lottery,
|
||||
msg::{PlayerListUpdate, ServerGeneral},
|
||||
@ -28,11 +28,10 @@ use tracing::error;
|
||||
use vek::Vec3;
|
||||
|
||||
pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||
let state = &server.state;
|
||||
let ecs = state.ecs();
|
||||
let ecs = &server.state.ecs();
|
||||
if let Some(entity) = ecs.entity_from_uid(uid.into()) {
|
||||
if let Some(stats) = ecs.write_storage::<Stats>().get_mut(entity) {
|
||||
stats.health.change_by(change);
|
||||
if let Some(health) = ecs.write_storage::<Health>().get_mut(entity) {
|
||||
health.change_by(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,7 +452,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
|
||||
let state = &server.state;
|
||||
if vel.z <= -30.0 {
|
||||
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().get_mut(entity) {
|
||||
if let Some(health) = state.ecs().write_storage::<comp::Health>().get_mut(entity) {
|
||||
let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0;
|
||||
let damage = Damage {
|
||||
source: DamageSource::Falling,
|
||||
@ -461,7 +460,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
|
||||
};
|
||||
let loadouts = state.ecs().read_storage::<comp::Loadout>();
|
||||
let change = damage.modify_damage(false, loadouts.get(entity), None);
|
||||
stats.health.change_by(change);
|
||||
health.change_by(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -483,9 +482,9 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
||||
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<comp::Stats>()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(entity)
|
||||
.map(|stats| stats.revive());
|
||||
.map(|health| health.revive());
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<comp::Pos>()
|
||||
@ -550,19 +549,19 @@ pub fn handle_explosion(
|
||||
for effect in explosion.effects {
|
||||
match effect {
|
||||
RadiusEffect::Damages(damages) => {
|
||||
for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in (
|
||||
for (entity_b, pos_b, ori_b, character_b, health_b, loadout_b) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::Pos>(),
|
||||
&ecs.read_storage::<comp::Ori>(),
|
||||
ecs.read_storage::<comp::CharacterState>().maybe(),
|
||||
&mut ecs.write_storage::<comp::Stats>(),
|
||||
&mut ecs.write_storage::<comp::Health>(),
|
||||
ecs.read_storage::<comp::Loadout>().maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let distance_squared = pos.distance_squared(pos_b.0);
|
||||
// Check if it is a hit
|
||||
if !stats_b.is_dead
|
||||
if !health_b.is_dead
|
||||
// RADIUS
|
||||
&& distance_squared < explosion.radius.powi(2)
|
||||
{
|
||||
@ -592,7 +591,7 @@ pub fn handle_explosion(
|
||||
let change = damage.modify_damage(block, loadout_b, owner);
|
||||
|
||||
if change.amount != 0 {
|
||||
stats_b.health.change_by(change);
|
||||
health_b.change_by(change);
|
||||
if let Some(owner) = owner_entity {
|
||||
if let Some(energy) =
|
||||
ecs.write_storage::<comp::Energy>().get_mut(owner)
|
||||
|
@ -67,10 +67,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
return;
|
||||
};
|
||||
|
||||
// Grab the stats from the player and check if the player is dead.
|
||||
let stats = state.ecs().read_storage::<comp::Stats>();
|
||||
if let Some(entity_stats) = stats.get(entity) {
|
||||
if entity_stats.is_dead {
|
||||
// Grab the health from the player and check if the player is dead.
|
||||
let healths = state.ecs().read_storage::<comp::Health>();
|
||||
if let Some(entity_health) = healths.get(entity) {
|
||||
if entity_health.is_dead {
|
||||
debug!("Failed to pick up item as the player is dead");
|
||||
return; // If dead, don't continue
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ impl Server {
|
||||
ServerEvent::CreateNpc {
|
||||
pos,
|
||||
stats,
|
||||
health,
|
||||
loadout,
|
||||
body,
|
||||
agent,
|
||||
@ -116,7 +117,7 @@ impl Server {
|
||||
scale,
|
||||
drop_item,
|
||||
} => handle_create_npc(
|
||||
self, pos, stats, loadout, body, agent, alignment, scale, drop_item,
|
||||
self, pos, stats, health, loadout, body, agent, alignment, scale, drop_item,
|
||||
),
|
||||
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
|
||||
ServerEvent::ClientDisconnect(entity) => {
|
||||
|
@ -318,11 +318,11 @@ pub fn convert_stats_from_database(stats: &Stats, alias: String) -> common::comp
|
||||
new_stats.level.set_level(stats.level as u32);
|
||||
new_stats.exp.update_maximum(stats.level as u32);
|
||||
new_stats.exp.set_current(stats.exp as u32);
|
||||
new_stats.update_max_hp(new_stats.body_type);
|
||||
/*new_stats.update_max_hp(new_stats.body_type);
|
||||
new_stats.health.set_to(
|
||||
new_stats.health.maximum(),
|
||||
common::comp::HealthSource::Revive,
|
||||
);
|
||||
);*/
|
||||
new_stats.endurance = stats.endurance as u32;
|
||||
new_stats.fitness = stats.fitness as u32;
|
||||
new_stats.willpower = stats.willpower as u32;
|
||||
|
@ -26,6 +26,7 @@ pub trait StateExt {
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
stats: comp::Stats,
|
||||
health: comp::Health,
|
||||
loadout: comp::Loadout,
|
||||
body: comp::Body,
|
||||
) -> EcsEntityBuilder;
|
||||
@ -74,9 +75,9 @@ impl StateExt for State {
|
||||
match effect {
|
||||
Effect::Health(change) => {
|
||||
self.ecs()
|
||||
.write_storage::<comp::Stats>()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(entity)
|
||||
.map(|stats| stats.health.change_by(change));
|
||||
.map(|health| health.change_by(change));
|
||||
},
|
||||
Effect::Xp(xp) => {
|
||||
self.ecs()
|
||||
@ -91,6 +92,7 @@ impl StateExt for State {
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
stats: comp::Stats,
|
||||
health: comp::Health,
|
||||
loadout: comp::Loadout,
|
||||
body: comp::Body,
|
||||
) -> EcsEntityBuilder {
|
||||
@ -107,6 +109,7 @@ impl StateExt for State {
|
||||
.with(comp::Controller::default())
|
||||
.with(body)
|
||||
.with(stats)
|
||||
.with(health)
|
||||
.with(comp::Alignment::Npc)
|
||||
.with(comp::Energy::new(body.base_energy()))
|
||||
.with(comp::Gravity(1.0))
|
||||
|
743
server/src/sys/message.rs
Normal file
743
server/src/sys/message.rs
Normal file
@ -0,0 +1,743 @@
|
||||
use super::SysTimer;
|
||||
use crate::{
|
||||
alias_validator::AliasValidator,
|
||||
character_creator,
|
||||
client::Client,
|
||||
login_provider::LoginProvider,
|
||||
metrics::{NetworkRequestMetrics, PlayerMetrics},
|
||||
persistence::character_loader::CharacterLoader,
|
||||
EditableSettings, Settings,
|
||||
};
|
||||
use common::{
|
||||
comp::{
|
||||
Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Health, Ori,
|
||||
Player, Pos, Stats, UnresolvedChatMsg, Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{
|
||||
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneral, ClientInGame,
|
||||
ClientRegister, DisconnectReason, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||
ServerGeneral, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG,
|
||||
},
|
||||
span,
|
||||
state::{BlockChange, Time},
|
||||
sync::Uid,
|
||||
terrain::{TerrainChunkSize, TerrainGrid},
|
||||
vol::{ReadVol, RectVolSize},
|
||||
};
|
||||
use futures_executor::block_on;
|
||||
use futures_timer::Delay;
|
||||
use futures_util::{select, FutureExt};
|
||||
use hashbrown::HashMap;
|
||||
use specs::{
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage,
|
||||
};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
impl Sys {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
chat_modes: &ReadStorage<'_, ChatMode>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
ClientGeneral::ChatMsg(message) => {
|
||||
if client.registered {
|
||||
match validate_chat_msg(&message) {
|
||||
Ok(()) => {
|
||||
if let Some(from) = uids.get(entity) {
|
||||
let mode = chat_modes.get(entity).cloned().unwrap_or_default();
|
||||
let msg = mode.new_message(*from, message);
|
||||
new_chat_msgs.push((Some(entity), msg));
|
||||
} else {
|
||||
error!("Could not send message. Missing player uid");
|
||||
}
|
||||
},
|
||||
Err(ChatMsgValidationError::TooLong) => {
|
||||
let max = MAX_BYTES_CHAT_MSG;
|
||||
let len = message.len();
|
||||
warn!(?len, ?max, "Received a chat message that's too long")
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::Disconnect => {
|
||||
client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested));
|
||||
},
|
||||
ClientGeneral::Terminate => {
|
||||
debug!(?entity, "Client send message to termitate session");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["gracefully"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
},
|
||||
_ => unreachable!("not a client_general msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_in_game_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
terrain: &ReadExpect<'_, TerrainGrid>,
|
||||
network_metrics: &ReadExpect<'_, NetworkRequestMetrics>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
stats: &mut WriteStorage<'_, Stats>,
|
||||
healths: &mut WriteStorage<'_, Health>,
|
||||
block_changes: &mut Write<'_, BlockChange>,
|
||||
positions: &mut WriteStorage<'_, Pos>,
|
||||
velocities: &mut WriteStorage<'_, Vel>,
|
||||
orientations: &mut WriteStorage<'_, Ori>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
controllers: &mut WriteStorage<'_, Controller>,
|
||||
settings: &Read<'_, Settings>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
if client.in_game.is_none() {
|
||||
debug!(?entity, "client is not in_game, ignoring msg");
|
||||
trace!(?msg, "ignored msg content");
|
||||
if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
match msg {
|
||||
// Go back to registered state (char selection screen)
|
||||
ClientGeneral::ExitInGame => {
|
||||
client.in_game = None;
|
||||
server_emitter.emit(ServerEvent::ExitIngame { entity });
|
||||
client.send_msg(ServerGeneral::ExitInGameSuccess);
|
||||
},
|
||||
ClientGeneral::SetViewDistance(view_distance) => {
|
||||
players.get_mut(entity).map(|player| {
|
||||
player.view_distance = Some(
|
||||
settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance.min(max))
|
||||
.unwrap_or(view_distance),
|
||||
)
|
||||
});
|
||||
|
||||
//correct client if its VD is to high
|
||||
if settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance > max)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.send_msg(ServerGeneral::SetViewDistance(
|
||||
settings.max_view_distance.unwrap_or(0),
|
||||
));
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControllerInputs(inputs) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.inputs.update_with_new(inputs);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControlEvent(event) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
// Skip respawn if client entity is alive
|
||||
if let ControlEvent::Respawn = event {
|
||||
if healths.get(entity).map_or(true, |h| !h.is_dead) {
|
||||
//Todo: comment why return!
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.events.push(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControlAction(event) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.actions.push(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::PlayerPhysics { pos, vel, ori } => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if force_updates.get(entity).is_none()
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
{
|
||||
let _ = positions.insert(entity, pos);
|
||||
let _ = velocities.insert(entity, vel);
|
||||
let _ = orientations.insert(entity, ori);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::BreakBlock(pos) => {
|
||||
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
|
||||
block_changes.set(pos, block.into_vacant());
|
||||
}
|
||||
},
|
||||
ClientGeneral::PlaceBlock(pos, block) => {
|
||||
if can_build.get(entity).is_some() {
|
||||
block_changes.try_set(pos, block);
|
||||
}
|
||||
},
|
||||
ClientGeneral::TerrainChunkRequest { key } => {
|
||||
let in_vd = if let (Some(view_distance), Some(pos)) = (
|
||||
players.get(entity).and_then(|p| p.view_distance),
|
||||
positions.get(entity),
|
||||
) {
|
||||
pos.0.xy().map(|e| e as f64).distance(
|
||||
key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
|
||||
) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
|
||||
* TerrainChunkSize::RECT_SIZE.x as f64
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if in_vd {
|
||||
match terrain.get_key(key) {
|
||||
Some(chunk) => {
|
||||
network_metrics.chunks_served_from_memory.inc();
|
||||
client.send_msg(ServerGeneral::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Ok(Box::new(chunk.clone())),
|
||||
})
|
||||
},
|
||||
None => {
|
||||
network_metrics.chunks_generation_triggered.inc();
|
||||
server_emitter.emit(ServerEvent::ChunkRequest(entity, key))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
},
|
||||
ClientGeneral::UnlockSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill(skill));
|
||||
},
|
||||
ClientGeneral::RefundSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.refund_skill(skill));
|
||||
},
|
||||
ClientGeneral::UnlockSkillGroup(skill_group_type) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill_group(skill_group_type));
|
||||
},
|
||||
_ => unreachable!("not a client_in_game msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_character_screen_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
character_loader: &ReadExpect<'_, CharacterLoader>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
// Request spectator state
|
||||
ClientGeneral::Spectate if client.registered => {
|
||||
client.in_game = Some(ClientInGame::Spectator)
|
||||
},
|
||||
ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"),
|
||||
ClientGeneral::Character(character_id)
|
||||
if client.registered && client.in_game.is_none() =>
|
||||
{
|
||||
if let Some(player) = players.get(entity) {
|
||||
// Send a request to load the character's component data from the
|
||||
// DB. Once loaded, persisted components such as stats and inventory
|
||||
// will be inserted for the entity
|
||||
character_loader.load_character_data(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
|
||||
// Start inserting non-persisted/default components for the entity
|
||||
// while we load the DB data
|
||||
server_emitter.emit(ServerEvent::InitCharacterData {
|
||||
entity,
|
||||
character_id,
|
||||
});
|
||||
|
||||
// Give the player a welcome message
|
||||
if !editable_settings.server_description.is_empty() {
|
||||
client.send_msg(
|
||||
ChatType::CommandInfo
|
||||
.server_msg(String::from(&*editable_settings.server_description)),
|
||||
);
|
||||
}
|
||||
|
||||
if !client.login_msg_sent {
|
||||
if let Some(player_uid) = uids.get(entity) {
|
||||
new_chat_msgs.push((None, UnresolvedChatMsg {
|
||||
chat_type: ChatType::Online(*player_uid),
|
||||
message: "".to_string(),
|
||||
}));
|
||||
|
||||
client.login_msg_sent = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.send_msg(ServerGeneral::CharacterDataLoadError(String::from(
|
||||
"Failed to fetch player entity",
|
||||
)))
|
||||
}
|
||||
}
|
||||
ClientGeneral::Character(_) => {
|
||||
let registered = client.registered;
|
||||
let in_game = client.in_game;
|
||||
debug!(?registered, ?in_game, "dropped Character msg from client");
|
||||
},
|
||||
ClientGeneral::RequestCharacterList => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.load_character_list(entity, player.uuid().to_string())
|
||||
}
|
||||
},
|
||||
ClientGeneral::CreateCharacter { alias, tool, body } => {
|
||||
if let Err(error) = alias_validator.validate(&alias) {
|
||||
debug!(?error, ?alias, "denied alias as it contained a banned word");
|
||||
client.send_msg(ServerGeneral::CharacterActionError(error.to_string()));
|
||||
} else if let Some(player) = players.get(entity) {
|
||||
character_creator::create_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
alias,
|
||||
tool,
|
||||
body,
|
||||
character_loader,
|
||||
);
|
||||
}
|
||||
},
|
||||
ClientGeneral::DeleteCharacter(character_id) => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.delete_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => unreachable!("not a client_character_screen msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
PingMsg::Ping => client.send_msg(PingMsg::Pong),
|
||||
PingMsg::Pong => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_register_msg(
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
new_players: &mut Vec<specs::Entity>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
login_provider: &mut WriteExpect<'_, LoginProvider>,
|
||||
admins: &mut WriteStorage<'_, Admin>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
msg: ClientRegister,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&msg.token_or_username,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
) {
|
||||
Err(err) => {
|
||||
client
|
||||
.register_stream
|
||||
.send(ServerRegisterAnswer::Err(err))?;
|
||||
return Ok(());
|
||||
},
|
||||
Ok((username, uuid)) => (username, uuid),
|
||||
};
|
||||
|
||||
const INITIAL_VD: Option<u32> = Some(5); //will be changed after login
|
||||
let player = Player::new(username, None, INITIAL_VD, uuid);
|
||||
let is_admin = editable_settings.admins.contains(&uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
client
|
||||
.register_stream
|
||||
.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !client.registered && client.in_game.is_none() {
|
||||
// Add Player component to this client
|
||||
let _ = players.insert(entity, player);
|
||||
player_metrics.players_connected.inc();
|
||||
|
||||
// Give the Admin component to the player if their name exists in
|
||||
// admin list
|
||||
if is_admin {
|
||||
let _ = admins.insert(entity, Admin);
|
||||
}
|
||||
|
||||
// Tell the client its request was successful.
|
||||
client.registered = true;
|
||||
client.register_stream.send(ServerRegisterAnswer::Ok(()))?;
|
||||
|
||||
// Send initial player list
|
||||
client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
|
||||
player_list.clone(),
|
||||
)));
|
||||
|
||||
// Add to list to notify all clients of the new player
|
||||
new_players.push(entity);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///We needed to move this to a async fn, if we would use a async closures
|
||||
/// the compiler generates to much recursion and fails to compile this
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_messages(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
new_players: &mut Vec<specs::Entity>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
cnt: &mut u64,
|
||||
character_loader: &ReadExpect<'_, CharacterLoader>,
|
||||
terrain: &ReadExpect<'_, TerrainGrid>,
|
||||
network_metrics: &ReadExpect<'_, NetworkRequestMetrics>,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
stats: &mut WriteStorage<'_, Stats>,
|
||||
healths: &mut WriteStorage<'_, Health>,
|
||||
chat_modes: &ReadStorage<'_, ChatMode>,
|
||||
login_provider: &mut WriteExpect<'_, LoginProvider>,
|
||||
block_changes: &mut Write<'_, BlockChange>,
|
||||
admins: &mut WriteStorage<'_, Admin>,
|
||||
positions: &mut WriteStorage<'_, Pos>,
|
||||
velocities: &mut WriteStorage<'_, Vel>,
|
||||
orientations: &mut WriteStorage<'_, Ori>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
controllers: &mut WriteStorage<'_, Controller>,
|
||||
settings: &Read<'_, Settings>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let (mut b1, mut b2, mut b3, mut b4, mut b5) = (
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
);
|
||||
loop {
|
||||
/*
|
||||
waiting for 1 of the 5 streams to return a massage asynchronous.
|
||||
If so, handle that msg type. This code will be refactored soon
|
||||
*/
|
||||
|
||||
let q1 = Client::internal_recv(&mut b1, &mut client.general_stream);
|
||||
let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream);
|
||||
let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream);
|
||||
let q4 = Client::internal_recv(&mut b4, &mut client.ping_stream);
|
||||
let q5 = Client::internal_recv(&mut b5, &mut client.register_stream);
|
||||
|
||||
let (m1, m2, m3, m4, m5) = select!(
|
||||
msg = q1.fuse() => (Some(msg), None, None, None, None),
|
||||
msg = q2.fuse() => (None, Some(msg), None, None, None),
|
||||
msg = q3.fuse() => (None, None, Some(msg), None, None),
|
||||
msg = q4.fuse() => (None, None, None, Some(msg), None),
|
||||
msg = q5.fuse() => (None, None, None, None,Some(msg)),
|
||||
);
|
||||
*cnt += 1;
|
||||
if let Some(msg) = m1 {
|
||||
client.network_error |= b1;
|
||||
Self::handle_client_msg(
|
||||
server_emitter,
|
||||
new_chat_msgs,
|
||||
entity,
|
||||
client,
|
||||
player_metrics,
|
||||
uids,
|
||||
chat_modes,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m2 {
|
||||
client.network_error |= b2;
|
||||
Self::handle_client_in_game_msg(
|
||||
server_emitter,
|
||||
entity,
|
||||
client,
|
||||
terrain,
|
||||
network_metrics,
|
||||
can_build,
|
||||
force_updates,
|
||||
stats,
|
||||
healths,
|
||||
block_changes,
|
||||
positions,
|
||||
velocities,
|
||||
orientations,
|
||||
players,
|
||||
controllers,
|
||||
settings,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m3 {
|
||||
client.network_error |= b3;
|
||||
Self::handle_client_character_screen_msg(
|
||||
server_emitter,
|
||||
new_chat_msgs,
|
||||
entity,
|
||||
client,
|
||||
character_loader,
|
||||
uids,
|
||||
players,
|
||||
editable_settings,
|
||||
alias_validator,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m4 {
|
||||
client.network_error |= b4;
|
||||
Self::handle_ping_msg(client, msg?)?;
|
||||
}
|
||||
if let Some(msg) = m5 {
|
||||
client.network_error |= b5;
|
||||
Self::handle_register_msg(
|
||||
player_list,
|
||||
new_players,
|
||||
entity,
|
||||
client,
|
||||
player_metrics,
|
||||
login_provider,
|
||||
admins,
|
||||
players,
|
||||
editable_settings,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This system will handle new messages from clients
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
Read<'a, Time>,
|
||||
ReadExpect<'a, CharacterLoader>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
ReadExpect<'a, NetworkRequestMetrics>,
|
||||
ReadExpect<'a, PlayerMetrics>,
|
||||
Write<'a, SysTimer<Self>>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, CanBuild>,
|
||||
ReadStorage<'a, ForceUpdate>,
|
||||
WriteStorage<'a, Stats>,
|
||||
WriteStorage<'a, Health>,
|
||||
ReadStorage<'a, ChatMode>,
|
||||
WriteExpect<'a, LoginProvider>,
|
||||
Write<'a, BlockChange>,
|
||||
WriteStorage<'a, Admin>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
WriteStorage<'a, Player>,
|
||||
WriteStorage<'a, Client>,
|
||||
WriteStorage<'a, Controller>,
|
||||
Read<'a, Settings>,
|
||||
ReadExpect<'a, EditableSettings>,
|
||||
ReadExpect<'a, AliasValidator>,
|
||||
);
|
||||
|
||||
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
|
||||
#[allow(clippy::single_char_pattern)] // TODO: Pending review in #587
|
||||
#[allow(clippy::single_match)] // TODO: Pending review in #587
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
server_event_bus,
|
||||
time,
|
||||
character_loader,
|
||||
terrain,
|
||||
network_metrics,
|
||||
player_metrics,
|
||||
mut timer,
|
||||
uids,
|
||||
can_build,
|
||||
force_updates,
|
||||
mut stats,
|
||||
mut healths,
|
||||
chat_modes,
|
||||
mut accounts,
|
||||
mut block_changes,
|
||||
mut admins,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
mut players,
|
||||
mut clients,
|
||||
mut controllers,
|
||||
settings,
|
||||
editable_settings,
|
||||
alias_validator,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
span!(_guard, "run", "message::Sys::run");
|
||||
timer.start();
|
||||
|
||||
let mut server_emitter = server_event_bus.emitter();
|
||||
|
||||
let mut new_chat_msgs = Vec::new();
|
||||
|
||||
// Player list to send new players.
|
||||
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
||||
.join()
|
||||
.map(|(uid, player, stats, admin)| {
|
||||
(*uid, PlayerInfo {
|
||||
is_online: true,
|
||||
is_admin: admin.is_some(),
|
||||
player_alias: player.alias.clone(),
|
||||
character: stats.map(|stats| CharacterInfo {
|
||||
name: stats.name.clone(),
|
||||
level: stats.level.level(),
|
||||
}),
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
// List of new players to update player lists of all clients.
|
||||
let mut new_players = Vec::new();
|
||||
|
||||
for (entity, client) in (&entities, &mut clients).join() {
|
||||
let mut cnt = 0;
|
||||
|
||||
let network_err: Result<(), crate::error::Error> = block_on(async {
|
||||
//TIMEOUT 0.02 ms for msg handling
|
||||
let work_future = Self::handle_messages(
|
||||
&mut server_emitter,
|
||||
&mut new_chat_msgs,
|
||||
&player_list,
|
||||
&mut new_players,
|
||||
entity,
|
||||
client,
|
||||
&mut cnt,
|
||||
&character_loader,
|
||||
&terrain,
|
||||
&network_metrics,
|
||||
&player_metrics,
|
||||
&uids,
|
||||
&can_build,
|
||||
&force_updates,
|
||||
&mut stats,
|
||||
&mut healths,
|
||||
&chat_modes,
|
||||
&mut accounts,
|
||||
&mut block_changes,
|
||||
&mut admins,
|
||||
&mut positions,
|
||||
&mut velocities,
|
||||
&mut orientations,
|
||||
&mut players,
|
||||
&mut controllers,
|
||||
&settings,
|
||||
&editable_settings,
|
||||
&alias_validator,
|
||||
);
|
||||
select!(
|
||||
_ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()),
|
||||
err = work_future.fuse() => err,
|
||||
)
|
||||
});
|
||||
|
||||
// Network error
|
||||
if network_err.is_err() {
|
||||
debug!(?entity, "postbox error with client, disconnecting");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["network_error"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
} else if cnt > 0 {
|
||||
// Update client ping.
|
||||
client.last_ping = time.0
|
||||
} else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64
|
||||
// Timeout
|
||||
{
|
||||
info!(?entity, "timeout error with client, disconnecting");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["timeout"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
} else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 {
|
||||
// Try pinging the client if the timeout is nearing.
|
||||
client.send_msg(PingMsg::Ping);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new players.
|
||||
// Tell all clients to add them to the player list.
|
||||
for entity in new_players {
|
||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
||||
let msg =
|
||||
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
player_alias: player.alias.clone(),
|
||||
is_online: true,
|
||||
is_admin: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
}));
|
||||
for client in (&mut clients).join().filter(|c| c.registered) {
|
||||
client.send_msg(msg.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new chat messages.
|
||||
for (entity, msg) in new_chat_msgs {
|
||||
// Handle chat commands.
|
||||
if msg.message.starts_with("/") {
|
||||
if let (Some(entity), true) = (entity, msg.message.len() > 1) {
|
||||
let argv = String::from(&msg.message[1..]);
|
||||
server_emitter.emit(ServerEvent::ChatCmd(entity, argv));
|
||||
}
|
||||
} else {
|
||||
// Send chat message
|
||||
server_emitter.emit(ServerEvent::Chat(msg));
|
||||
}
|
||||
}
|
||||
|
||||
timer.end()
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use super::super::SysTimer;
|
||||
use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, Settings};
|
||||
use common::{
|
||||
comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Pos, Stats, Vel},
|
||||
comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, Stats, Vel},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{ClientGeneral, PresenceKind, ServerGeneral},
|
||||
span,
|
||||
@ -24,6 +24,7 @@ impl Sys {
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
stats: &mut WriteStorage<'_, Stats>,
|
||||
healths: &ReadStorage<'_, Health>,
|
||||
block_changes: &mut Write<'_, BlockChange>,
|
||||
positions: &mut WriteStorage<'_, Pos>,
|
||||
velocities: &mut WriteStorage<'_, Vel>,
|
||||
@ -78,7 +79,7 @@ impl Sys {
|
||||
if matches!(presence.kind, PresenceKind::Character(_)) {
|
||||
// Skip respawn if client entity is alive
|
||||
if let ControlEvent::Respawn = event {
|
||||
if stats.get(entity).map_or(true, |s| !s.is_dead) {
|
||||
if healths.get(entity).map_or(true, |h| !h.is_dead) {
|
||||
//Todo: comment why return!
|
||||
return Ok(());
|
||||
}
|
||||
@ -98,7 +99,7 @@ impl Sys {
|
||||
ClientGeneral::PlayerPhysics { pos, vel, ori } => {
|
||||
if matches!(presence.kind, PresenceKind::Character(_))
|
||||
&& force_updates.get(entity).is_none()
|
||||
&& stats.get(entity).map_or(true, |s| !s.is_dead)
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
{
|
||||
let _ = positions.insert(entity, pos);
|
||||
let _ = velocities.insert(entity, vel);
|
||||
@ -176,6 +177,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, CanBuild>,
|
||||
ReadStorage<'a, ForceUpdate>,
|
||||
WriteStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
Write<'a, BlockChange>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
@ -197,6 +199,7 @@ impl<'a> System<'a> for Sys {
|
||||
can_build,
|
||||
force_updates,
|
||||
mut stats,
|
||||
healths,
|
||||
mut block_changes,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
@ -226,6 +229,7 @@ impl<'a> System<'a> for Sys {
|
||||
&can_build,
|
||||
&force_updates,
|
||||
&mut stats,
|
||||
&healths,
|
||||
&mut block_changes,
|
||||
&mut positions,
|
||||
&mut velocities,
|
||||
|
@ -1,9 +1,9 @@
|
||||
use super::SysTimer;
|
||||
use common::{
|
||||
comp::{
|
||||
BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item,
|
||||
LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave,
|
||||
Stats, Sticky, Vel,
|
||||
BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group,
|
||||
Health, Item, LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale,
|
||||
Shockwave, Stats, Sticky, Vel,
|
||||
},
|
||||
msg::EcsCompPacket,
|
||||
span,
|
||||
@ -46,6 +46,7 @@ pub struct TrackedComps<'a> {
|
||||
pub stats: ReadStorage<'a, Stats>,
|
||||
pub buffs: ReadStorage<'a, Buffs>,
|
||||
pub energy: ReadStorage<'a, Energy>,
|
||||
pub health: ReadStorage<'a, Health>,
|
||||
pub can_build: ReadStorage<'a, CanBuild>,
|
||||
pub light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
pub item: ReadStorage<'a, Item>,
|
||||
@ -94,6 +95,10 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.health
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.can_build
|
||||
.get(entity)
|
||||
.cloned()
|
||||
@ -164,6 +169,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
|
||||
pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>,
|
||||
pub energy: ReadExpect<'a, UpdateTracker<Energy>>,
|
||||
pub health: ReadExpect<'a, UpdateTracker<Health>>,
|
||||
pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>,
|
||||
pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
pub item: ReadExpect<'a, UpdateTracker<Item>>,
|
||||
@ -195,6 +201,7 @@ impl<'a> ReadTrackers<'a> {
|
||||
.with_component(&comps.uid, &*self.stats, &comps.stats, filter)
|
||||
.with_component(&comps.uid, &*self.buffs, &comps.buffs, filter)
|
||||
.with_component(&comps.uid, &*self.energy, &comps.energy, filter)
|
||||
.with_component(&comps.uid, &*self.health, &comps.health, filter)
|
||||
.with_component(&comps.uid, &*self.can_build, &comps.can_build, filter)
|
||||
.with_component(
|
||||
&comps.uid,
|
||||
@ -233,6 +240,7 @@ pub struct WriteTrackers<'a> {
|
||||
stats: WriteExpect<'a, UpdateTracker<Stats>>,
|
||||
buffs: WriteExpect<'a, UpdateTracker<Buffs>>,
|
||||
energy: WriteExpect<'a, UpdateTracker<Energy>>,
|
||||
health: WriteExpect<'a, UpdateTracker<Health>>,
|
||||
can_build: WriteExpect<'a, UpdateTracker<CanBuild>>,
|
||||
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
item: WriteExpect<'a, UpdateTracker<Item>>,
|
||||
@ -258,6 +266,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers.stats.record_changes(&comps.stats);
|
||||
trackers.buffs.record_changes(&comps.buffs);
|
||||
trackers.energy.record_changes(&comps.energy);
|
||||
trackers.health.record_changes(&comps.health);
|
||||
trackers.can_build.record_changes(&comps.can_build);
|
||||
trackers.light_emitter.record_changes(&comps.light_emitter);
|
||||
trackers.item.record_changes(&comps.item);
|
||||
@ -296,6 +305,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(player, "Players");
|
||||
log_counts!(stats, "Stats");
|
||||
log_counts!(energy, "Energies");
|
||||
log_vounts!(health, "Healths");
|
||||
log_counts!(light_emitter, "Light emitters");
|
||||
log_counts!(item, "Items");
|
||||
log_counts!(scale, "Scales");
|
||||
@ -319,6 +329,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Stats>();
|
||||
world.register_tracker::<Buffs>();
|
||||
world.register_tracker::<Energy>();
|
||||
world.register_tracker::<Health>();
|
||||
world.register_tracker::<CanBuild>();
|
||||
world.register_tracker::<LightEmitter>();
|
||||
world.register_tracker::<Item>();
|
||||
|
@ -146,11 +146,7 @@ impl<'a> System<'a> for Sys {
|
||||
LoadoutBuilder::build_loadout(body, alignment, main_tool, entity.is_giant)
|
||||
.build();
|
||||
|
||||
stats.update_max_hp(stats.body_type);
|
||||
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), comp::HealthSource::Revive);
|
||||
let health = comp::Health::new(stats.body_type, stats.level.level());
|
||||
|
||||
let can_speak = match body {
|
||||
comp::Body::Humanoid(_) => alignment == comp::Alignment::Npc,
|
||||
@ -174,6 +170,7 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::CreateNpc {
|
||||
pos: Pos(entity.pos),
|
||||
stats,
|
||||
health,
|
||||
loadout,
|
||||
agent: if entity.has_agency {
|
||||
Some(comp::Agent::new(entity.pos, can_speak, &body))
|
||||
|
@ -143,6 +143,7 @@ fn matches_ability_stage() {
|
||||
speed_increase: 0.05,
|
||||
max_speed_increase: 1.8,
|
||||
is_interruptible: true,
|
||||
ability_key: states::utils::AbilityKey::Mouse1,
|
||||
},
|
||||
stage: 1,
|
||||
combo: 0,
|
||||
@ -203,6 +204,7 @@ fn ignores_different_ability_stage() {
|
||||
speed_increase: 0.05,
|
||||
max_speed_increase: 1.8,
|
||||
is_interruptible: true,
|
||||
ability_key: states::utils::AbilityKey::Mouse1,
|
||||
},
|
||||
stage: 1,
|
||||
combo: 0,
|
||||
|
@ -3,7 +3,7 @@ use crate::ecs::{
|
||||
ExpFloater, MyEntity, MyExpFloaterList,
|
||||
};
|
||||
use common::{
|
||||
comp::{HealthSource, Pos, Stats},
|
||||
comp::{Health, HealthSource, Pos, Stats},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
};
|
||||
@ -25,19 +25,30 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Health>,
|
||||
WriteStorage<'a, HpFloaterList>,
|
||||
);
|
||||
|
||||
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
|
||||
fn run(
|
||||
&mut self,
|
||||
(entities, my_entity, dt, mut my_exp_floater_list, uids, pos, stats, mut hp_floater_lists): Self::SystemData,
|
||||
(
|
||||
entities,
|
||||
my_entity,
|
||||
dt,
|
||||
mut my_exp_floater_list,
|
||||
uids,
|
||||
pos,
|
||||
stats,
|
||||
healths,
|
||||
mut hp_floater_lists,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
// Add hp floater lists to all entities with stats and a position
|
||||
// Add hp floater lists to all entities with health and a position
|
||||
// Note: necessary in order to know last_hp
|
||||
for (entity, last_hp) in (&entities, &stats, &pos, !&hp_floater_lists)
|
||||
for (entity, last_hp) in (&entities, &healths, &pos, !&hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, s, _, _)| (e, s.health.current()))
|
||||
.map(|(e, h, _, _)| (e, h.current()))
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let _ = hp_floater_lists.insert(entity, HpFloaterList {
|
||||
@ -49,9 +60,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Add hp floaters to all entities that have been damaged
|
||||
let my_uid = uids.get(my_entity.0);
|
||||
for (entity, health, hp_floater_list) in (&entities, &stats, &mut hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, s, fl)| (e, s.health, fl))
|
||||
for (entity, health, hp_floater_list) in (&entities, &healths, &mut hp_floater_lists).join()
|
||||
{
|
||||
// Increment timer for time since last damaged by me
|
||||
hp_floater_list
|
||||
@ -64,8 +73,8 @@ impl<'a> System<'a> for Sys {
|
||||
if hp_floater_list.last_hp != health.current() {
|
||||
hp_floater_list.last_hp = health.current();
|
||||
// TODO: What if multiple health changes occurred since last check here
|
||||
// Also, If we make stats store a vec of the last_changes (from say the last
|
||||
// frame), what if the client receives the stats component from
|
||||
// Also, If we make health store a vec of the last_changes (from say the last
|
||||
// frame), what if the client receives the health component from
|
||||
// two different server ticks at once, then one will be lost
|
||||
// (tbf this is probably a rare occurance and the results
|
||||
// would just be a transient glitch in the display of these damage numbers)
|
||||
@ -101,8 +110,8 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove floater lists on entities without stats or without position
|
||||
for entity in (&entities, !&stats, &hp_floater_lists)
|
||||
// Remove floater lists on entities without health or without position
|
||||
for entity in (&entities, !&healths, &hp_floater_lists)
|
||||
.join()
|
||||
.map(|(e, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -322,6 +322,7 @@ impl<'a> Widget for Group<'a> {
|
||||
|
||||
let client_state = self.client.state();
|
||||
let stats = client_state.ecs().read_storage::<common::comp::Stats>();
|
||||
let healths = client_state.ecs().read_storage::<common::comp::Health>();
|
||||
let energy = client_state.ecs().read_storage::<common::comp::Energy>();
|
||||
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
|
||||
let uid_allocator = client_state
|
||||
@ -338,80 +339,84 @@ impl<'a> Widget for Group<'a> {
|
||||
self.show.group = true;
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let stats = entity.and_then(|entity| stats.get(entity));
|
||||
let health = entity.and_then(|entity| healths.get(entity));
|
||||
let energy = entity.and_then(|entity| energy.get(entity));
|
||||
let buffs = entity.and_then(|entity| buffs.get(entity));
|
||||
|
||||
let is_leader = uid == leader;
|
||||
|
||||
if let Some(stats) = stats {
|
||||
let char_name = stats.name.to_string();
|
||||
let health_perc = stats.health.current() as f64 / stats.health.maximum() as f64;
|
||||
|
||||
// change panel positions when debug info is shown
|
||||
let back = if i == 0 {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.top_left_with_margins_on(ui.window, offset, 20.0)
|
||||
} else {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.down_from(state.ids.member_panels_bg[i - 1], 45.0)
|
||||
};
|
||||
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
|
||||
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
|
||||
let health_col = match (health_perc * 100.0) as u8 {
|
||||
0..=20 => crit_hp_color,
|
||||
21..=40 => LOW_HP_COLOR,
|
||||
_ => HP_COLOR,
|
||||
};
|
||||
let is_leader = uid == leader;
|
||||
// Don't show panel for the player!
|
||||
// Panel BG
|
||||
back.w_h(152.0, 36.0)
|
||||
.color(if is_leader {
|
||||
Some(ERROR_COLOR)
|
||||
if let Some(health) = health {
|
||||
let health_perc = health.current() as f64 / health.maximum() as f64;
|
||||
// change panel positions when debug info is shown
|
||||
let back = if i == 0 {
|
||||
Image::new(self.imgs.member_bg)
|
||||
.top_left_with_margins_on(ui.window, offset, 20.0)
|
||||
} else {
|
||||
Some(TEXT_COLOR)
|
||||
})
|
||||
.set(state.ids.member_panels_bg[i], ui);
|
||||
// Health
|
||||
Image::new(self.imgs.bar_content)
|
||||
.w_h(148.0 * health_perc, 22.0)
|
||||
.color(Some(health_col))
|
||||
.top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
|
||||
.set(state.ids.member_health[i], ui);
|
||||
if stats.is_dead {
|
||||
// Death Text
|
||||
Text::new(&self.localized_strings.get("hud.group.dead"))
|
||||
.mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0)
|
||||
.font_size(20)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(KILL_COLOR)
|
||||
.set(state.ids.dead_txt[i], ui);
|
||||
} else {
|
||||
// Health Text
|
||||
let txt = format!(
|
||||
"{}/{}",
|
||||
stats.health.current() / 10 as u32,
|
||||
stats.health.maximum() / 10 as u32,
|
||||
);
|
||||
// Change font size depending on health amount
|
||||
let font_size = match stats.health.maximum() {
|
||||
0..=999 => 14,
|
||||
1000..=9999 => 13,
|
||||
10000..=99999 => 12,
|
||||
_ => 11,
|
||||
Image::new(self.imgs.member_bg)
|
||||
.down_from(state.ids.member_panels_bg[i - 1], 45.0)
|
||||
};
|
||||
// Change text offset depending on health amount
|
||||
let txt_offset = match stats.health.maximum() {
|
||||
0..=999 => 4.0,
|
||||
1000..=9999 => 4.5,
|
||||
10000..=99999 => 5.0,
|
||||
_ => 5.5,
|
||||
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
|
||||
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
|
||||
let health_col = match (health_perc * 100.0) as u8 {
|
||||
0..=20 => crit_hp_color,
|
||||
21..=40 => LOW_HP_COLOR,
|
||||
_ => HP_COLOR,
|
||||
};
|
||||
Text::new(&txt)
|
||||
.mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset)
|
||||
.font_size(font_size)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(1.0, 1.0, 1.0, 0.5))
|
||||
.set(state.ids.health_txt[i], ui);
|
||||
};
|
||||
// Don't show panel for the player!
|
||||
// Panel BG
|
||||
back.w_h(152.0, 36.0)
|
||||
.color(if is_leader {
|
||||
Some(ERROR_COLOR)
|
||||
} else {
|
||||
Some(TEXT_COLOR)
|
||||
})
|
||||
.set(state.ids.member_panels_bg[i], ui);
|
||||
// Health
|
||||
Image::new(self.imgs.bar_content)
|
||||
.w_h(148.0 * health_perc, 22.0)
|
||||
.color(Some(health_col))
|
||||
.top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
|
||||
.set(state.ids.member_health[i], ui);
|
||||
if health.is_dead {
|
||||
// Death Text
|
||||
Text::new(&self.localized_strings.get("hud.group.dead"))
|
||||
.mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0)
|
||||
.font_size(20)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(KILL_COLOR)
|
||||
.set(state.ids.dead_txt[i], ui);
|
||||
} else {
|
||||
// Health Text
|
||||
let txt = format!(
|
||||
"{}/{}",
|
||||
health.current() / 10 as u32,
|
||||
health.maximum() / 10 as u32,
|
||||
);
|
||||
// Change font size depending on health amount
|
||||
let font_size = match health.maximum() {
|
||||
0..=999 => 14,
|
||||
1000..=9999 => 13,
|
||||
10000..=99999 => 12,
|
||||
_ => 11,
|
||||
};
|
||||
// Change text offset depending on health amount
|
||||
let txt_offset = match health.maximum() {
|
||||
0..=999 => 4.0,
|
||||
1000..=9999 => 4.5,
|
||||
10000..=99999 => 5.0,
|
||||
_ => 5.5,
|
||||
};
|
||||
Text::new(&txt)
|
||||
.mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset)
|
||||
.font_size(font_size)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(1.0, 1.0, 1.0, 0.5))
|
||||
.set(state.ids.health_txt[i], ui);
|
||||
};
|
||||
}
|
||||
|
||||
// Panel Frame
|
||||
Image::new(self.imgs.member_frame)
|
||||
.w_h(152.0, 36.0)
|
||||
|
@ -753,6 +753,7 @@ impl Hud {
|
||||
let ecs = client.state().ecs();
|
||||
let pos = ecs.read_storage::<comp::Pos>();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let healths = ecs.read_storage::<comp::Health>();
|
||||
let buffs = ecs.read_storage::<comp::Buffs>();
|
||||
let energy = ecs.read_storage::<comp::Energy>();
|
||||
let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>();
|
||||
@ -767,11 +768,10 @@ impl Hud {
|
||||
.get(client.entity())
|
||||
.map_or(0, |stats| stats.level.level());
|
||||
//self.input = client.read_storage::<comp::ControllerInputs>();
|
||||
if let Some(stats) = stats.get(me) {
|
||||
if let Some(health) = healths.get(me) {
|
||||
// Hurt Frame
|
||||
let hp_percentage =
|
||||
stats.health.current() as f32 / stats.health.maximum() as f32 * 100.0;
|
||||
if hp_percentage < 10.0 && !stats.is_dead {
|
||||
let hp_percentage = health.current() as f32 / health.maximum() as f32 * 100.0;
|
||||
if hp_percentage < 10.0 && !health.is_dead {
|
||||
let hurt_fade =
|
||||
(self.pulse * (10.0 - hp_percentage as f32) * 0.1/* speed factor */).sin()
|
||||
* 0.5
|
||||
@ -792,7 +792,7 @@ impl Hud {
|
||||
.set(self.ids.alpha_text, ui_widgets);
|
||||
|
||||
// Death Frame
|
||||
if stats.is_dead {
|
||||
if health.is_dead {
|
||||
Image::new(self.imgs.death_bg)
|
||||
.wh_of(ui_widgets.window)
|
||||
.middle_of(ui_widgets.window)
|
||||
@ -801,7 +801,7 @@ impl Hud {
|
||||
.set(self.ids.death_bg, ui_widgets);
|
||||
}
|
||||
// Crosshair
|
||||
let show_crosshair = (info.is_aiming || info.is_first_person) && !stats.is_dead;
|
||||
let show_crosshair = (info.is_aiming || info.is_first_person) && !health.is_dead;
|
||||
self.crosshair_opacity = Lerp::lerp(
|
||||
self.crosshair_opacity,
|
||||
if show_crosshair { 1.0 } else { 0.0 },
|
||||
@ -850,11 +850,11 @@ impl Hud {
|
||||
// Render Player SCT numbers
|
||||
let mut player_sct_bg_id_walker = self.ids.player_sct_bgs.walk();
|
||||
let mut player_sct_id_walker = self.ids.player_scts.walk();
|
||||
if let (Some(HpFloaterList { floaters, .. }), Some(stats)) = (
|
||||
if let (Some(HpFloaterList { floaters, .. }), Some(health)) = (
|
||||
hp_floater_lists
|
||||
.get(me)
|
||||
.filter(|fl| !fl.floaters.is_empty()),
|
||||
stats.get(me),
|
||||
healths.get(me),
|
||||
) {
|
||||
if global_state.settings.gameplay.sct_player_batch {
|
||||
let number_speed = 100.0; // Player Batched Numbers Speed
|
||||
@ -871,7 +871,7 @@ impl Hud {
|
||||
let hp_damage = floaters.iter().fold(0, |acc, f| f.hp_change.min(0) + acc);
|
||||
// Divide by 10 to stay in the same dimension as the HP display
|
||||
let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs();
|
||||
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
|
||||
let max_hp_frac = hp_damage.abs() as f32 / health.maximum() as f32;
|
||||
let timer = floaters
|
||||
.last()
|
||||
.expect("There must be at least one floater")
|
||||
@ -927,8 +927,7 @@ impl Hud {
|
||||
&mut self.ids.player_scts,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
);
|
||||
let max_hp_frac =
|
||||
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
|
||||
let max_hp_frac = floater.hp_change.abs() as f32 / health.maximum() as f32;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
@ -1152,11 +1151,12 @@ impl Hud {
|
||||
let speech_bubbles = &self.speech_bubbles;
|
||||
|
||||
// Render overhead name tags and health bars
|
||||
for (pos, info, bubble, stats, _, height_offset, hpfl, in_group) in (
|
||||
for (pos, info, bubble, _, health, _, height_offset, hpfl, in_group) in (
|
||||
&entities,
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&buffs,
|
||||
energy.maybe(),
|
||||
scales.maybe(),
|
||||
@ -1166,12 +1166,24 @@ impl Hud {
|
||||
)
|
||||
.join()
|
||||
.filter(|t| {
|
||||
let stats = t.3;
|
||||
let health = t.4;
|
||||
let entity = t.0;
|
||||
entity != me && !stats.is_dead
|
||||
entity != me && !health.is_dead
|
||||
})
|
||||
.filter_map(
|
||||
|(entity, pos, interpolated, stats, buffs, energy, scale, body, hpfl, uid)| {
|
||||
|(
|
||||
entity,
|
||||
pos,
|
||||
interpolated,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
energy,
|
||||
scale,
|
||||
body,
|
||||
hpfl,
|
||||
uid,
|
||||
)| {
|
||||
// Use interpolated position if available
|
||||
let pos = interpolated.map_or(pos.0, |i| i.pos);
|
||||
let in_group = client.group_members().contains_key(uid);
|
||||
@ -1183,7 +1195,7 @@ impl Hud {
|
||||
let display_overhead_info =
|
||||
(info.target_entity.map_or(false, |e| e == entity)
|
||||
|| info.selected_entity.map_or(false, |s| s.0 == entity)
|
||||
|| overhead::show_healthbar(stats)
|
||||
|| overhead::show_healthbar(health)
|
||||
|| in_group)
|
||||
&& dist_sqr
|
||||
< (if in_group {
|
||||
@ -1201,6 +1213,7 @@ impl Hud {
|
||||
let info = display_overhead_info.then(|| overhead::Info {
|
||||
name: &stats.name,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
energy,
|
||||
});
|
||||
@ -1216,6 +1229,7 @@ impl Hud {
|
||||
info,
|
||||
bubble,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
|
||||
hpfl,
|
||||
@ -1292,7 +1306,7 @@ impl Hud {
|
||||
});
|
||||
// Divide by 10 to stay in the same dimension as the HP display
|
||||
let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs();
|
||||
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
|
||||
let max_hp_frac = hp_damage.abs() as f32 / health.maximum() as f32;
|
||||
let timer = floaters
|
||||
.last()
|
||||
.expect("There must be at least one floater")
|
||||
@ -1364,7 +1378,7 @@ impl Hud {
|
||||
.next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator());
|
||||
// Calculate total change
|
||||
let max_hp_frac =
|
||||
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
|
||||
floater.hp_change.abs() as f32 / health.maximum() as f32;
|
||||
// Increase font size based on fraction of maximum health
|
||||
// "flashes" by having a larger size in the first 100ms
|
||||
let font_size = 30
|
||||
@ -1897,6 +1911,7 @@ impl Hud {
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let healths = ecs.read_storage::<comp::Health>();
|
||||
let loadouts = ecs.read_storage::<comp::Loadout>();
|
||||
let energies = ecs.read_storage::<comp::Energy>();
|
||||
let character_states = ecs.read_storage::<comp::CharacterState>();
|
||||
@ -1904,6 +1919,7 @@ impl Hud {
|
||||
let inventories = ecs.read_storage::<comp::Inventory>();
|
||||
if let (
|
||||
Some(stats),
|
||||
Some(health),
|
||||
Some(loadout),
|
||||
Some(energy),
|
||||
Some(_character_state),
|
||||
@ -1911,6 +1927,7 @@ impl Hud {
|
||||
Some(inventory),
|
||||
) = (
|
||||
stats.get(entity),
|
||||
healths.get(entity),
|
||||
loadouts.get(entity),
|
||||
energies.get(entity),
|
||||
character_states.get(entity),
|
||||
@ -1924,6 +1941,7 @@ impl Hud {
|
||||
&self.fonts,
|
||||
&self.rot_imgs,
|
||||
&stats,
|
||||
&health,
|
||||
&loadout,
|
||||
&energy,
|
||||
//&character_state,
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
settings::GameplaySettings,
|
||||
ui::{fonts::ConrodVoxygenFonts, Ingameable},
|
||||
};
|
||||
use common::comp::{BuffKind, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats};
|
||||
use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stats};
|
||||
use conrod_core::{
|
||||
color,
|
||||
position::Align,
|
||||
@ -58,12 +58,13 @@ widget_ids! {
|
||||
pub struct Info<'a> {
|
||||
pub name: &'a str,
|
||||
pub stats: &'a Stats,
|
||||
pub health: &'a Health,
|
||||
pub buffs: &'a Buffs,
|
||||
pub energy: Option<&'a Energy>,
|
||||
}
|
||||
|
||||
/// Determines whether to show the healthbar
|
||||
pub fn show_healthbar(stats: &Stats) -> bool { stats.health.current() != stats.health.maximum() }
|
||||
pub fn show_healthbar(health: &Health) -> bool { health.current() != health.maximum() }
|
||||
|
||||
/// ui widget containing everything that goes over a character's head
|
||||
/// (Speech bubble, Name, Level, HP/energy bars, etc.)
|
||||
@ -141,7 +142,7 @@ impl<'a> Ingameable for Overhead<'a> {
|
||||
} else {
|
||||
0
|
||||
}
|
||||
+ if show_healthbar(info.stats) {
|
||||
+ if show_healthbar(info.health) {
|
||||
5 + if info.energy.is_some() { 1 } else { 0 }
|
||||
} else {
|
||||
0
|
||||
@ -171,17 +172,17 @@ impl<'a> Widget for Overhead<'a> {
|
||||
if let Some(Info {
|
||||
name,
|
||||
stats,
|
||||
health,
|
||||
buffs,
|
||||
energy,
|
||||
}) = self.info
|
||||
{
|
||||
// Used to set healthbar colours based on hp_percentage
|
||||
let hp_percentage =
|
||||
stats.health.current() as f64 / stats.health.maximum() as f64 * 100.0;
|
||||
let hp_percentage = health.current() as f64 / health.maximum() as f64 * 100.0;
|
||||
// Compare levels to decide if a skull is shown
|
||||
let level_comp = stats.level.level() as i64 - self.own_level as i64;
|
||||
let health_current = (stats.health.current() / 10) as f64;
|
||||
let health_max = (stats.health.maximum() / 10) as f64;
|
||||
let health_current = (health.current() / 10) as f64;
|
||||
let health_max = (health.maximum() / 10) as f64;
|
||||
let name_y = if (health_current - health_max).abs() < 1e-6 {
|
||||
MANA_BAR_Y + 20.0
|
||||
} else if level_comp > 9 && !self.in_group {
|
||||
@ -302,7 +303,7 @@ impl<'a> Widget for Overhead<'a> {
|
||||
.parent(id)
|
||||
.set(state.ids.name, ui);
|
||||
|
||||
if show_healthbar(stats) {
|
||||
if show_healthbar(health) {
|
||||
// Show HP Bar
|
||||
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer
|
||||
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
|
||||
@ -332,7 +333,7 @@ impl<'a> Widget for Overhead<'a> {
|
||||
.parent(id)
|
||||
.set(state.ids.health_bar, ui);
|
||||
let mut txt = format!("{}/{}", health_cur_txt, health_max_txt);
|
||||
if stats.is_dead {
|
||||
if health.is_dead {
|
||||
txt = self.voxygen_i18n.get("hud.group.dead").to_string()
|
||||
};
|
||||
Text::new(&txt)
|
||||
|
@ -20,7 +20,7 @@ use common::comp::{
|
||||
tool::{Tool, ToolKind},
|
||||
Hands, ItemKind,
|
||||
},
|
||||
Energy, Inventory, Loadout, Stats,
|
||||
Energy, Health, Inventory, Loadout, Stats,
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
@ -124,6 +124,7 @@ pub struct Skillbar<'a> {
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
stats: &'a Stats,
|
||||
health: &'a Health,
|
||||
loadout: &'a Loadout,
|
||||
energy: &'a Energy,
|
||||
// character_state: &'a CharacterState,
|
||||
@ -148,6 +149,7 @@ impl<'a> Skillbar<'a> {
|
||||
fonts: &'a ConrodVoxygenFonts,
|
||||
rot_imgs: &'a ImgsRot,
|
||||
stats: &'a Stats,
|
||||
health: &'a Health,
|
||||
loadout: &'a Loadout,
|
||||
energy: &'a Energy,
|
||||
// character_state: &'a CharacterState,
|
||||
@ -167,6 +169,7 @@ impl<'a> Skillbar<'a> {
|
||||
fonts,
|
||||
rot_imgs,
|
||||
stats,
|
||||
health,
|
||||
loadout,
|
||||
energy,
|
||||
common: widget::CommonBuilder::default(),
|
||||
@ -216,11 +219,10 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
|
||||
let exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64);
|
||||
|
||||
let mut hp_percentage =
|
||||
self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0;
|
||||
let mut hp_percentage = self.health.current() as f64 / self.health.maximum() as f64 * 100.0;
|
||||
let mut energy_percentage =
|
||||
self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0;
|
||||
if self.stats.is_dead {
|
||||
if self.health.is_dead {
|
||||
hp_percentage = 0.0;
|
||||
energy_percentage = 0.0;
|
||||
};
|
||||
@ -293,7 +295,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.set(state.ids.level_down, ui);
|
||||
}
|
||||
// Death message
|
||||
if self.stats.is_dead {
|
||||
if self.health.is_dead {
|
||||
if let Some(key) = self
|
||||
.global_state
|
||||
.settings
|
||||
@ -400,12 +402,12 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
if let BarNumbers::Values = bar_values {
|
||||
let mut hp_txt = format!(
|
||||
"{}/{}",
|
||||
(self.stats.health.current() / 10).max(1) as u32, /* Don't show 0 health for
|
||||
* living players */
|
||||
(self.stats.health.maximum() / 10) as u32
|
||||
(self.health.current() / 10).max(1) as u32, /* Don't show 0 health for
|
||||
* living players */
|
||||
(self.health.maximum() / 10) as u32
|
||||
);
|
||||
let mut energy_txt = format!("{}", energy_percentage as u32);
|
||||
if self.stats.is_dead {
|
||||
if self.health.is_dead {
|
||||
hp_txt = self.localized_strings.get("hud.group.dead").to_string();
|
||||
energy_txt = self.localized_strings.get("hud.group.dead").to_string();
|
||||
};
|
||||
@ -438,7 +440,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
if let BarNumbers::Percent = bar_values {
|
||||
let mut hp_txt = format!("{}%", hp_percentage as u32);
|
||||
let mut energy_txt = format!("{}", energy_percentage as u32);
|
||||
if self.stats.is_dead {
|
||||
if self.health.is_dead {
|
||||
hp_txt = self.localized_strings.get("hud.group.dead").to_string();
|
||||
energy_txt = self.localized_strings.get("hud.group.dead").to_string();
|
||||
};
|
||||
|
@ -26,8 +26,8 @@ use anim::{
|
||||
use common::{
|
||||
comp::{
|
||||
item::{ItemKind, ToolKind},
|
||||
Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout, Ori, PhysicsState,
|
||||
Pos, Scale, Stats, Vel,
|
||||
Body, CharacterState, Health, Item, Last, LightAnimation, LightEmitter, Loadout, Ori,
|
||||
PhysicsState, Pos, Scale, Vel,
|
||||
},
|
||||
span,
|
||||
state::{DeltaTime, State},
|
||||
@ -545,7 +545,7 @@ impl FigureMgr {
|
||||
character,
|
||||
last_character,
|
||||
physics,
|
||||
stats,
|
||||
health,
|
||||
loadout,
|
||||
item,
|
||||
),
|
||||
@ -559,7 +559,7 @@ impl FigureMgr {
|
||||
ecs.read_storage::<CharacterState>().maybe(),
|
||||
ecs.read_storage::<Last<CharacterState>>().maybe(),
|
||||
&ecs.read_storage::<PhysicsState>(),
|
||||
ecs.read_storage::<Stats>().maybe(),
|
||||
ecs.read_storage::<Health>().maybe(),
|
||||
ecs.read_storage::<Loadout>().maybe(),
|
||||
ecs.read_storage::<Item>().maybe(),
|
||||
)
|
||||
@ -662,11 +662,11 @@ impl FigureMgr {
|
||||
};
|
||||
|
||||
// Change in health as color!
|
||||
let col = stats
|
||||
.map(|s| {
|
||||
let col = health
|
||||
.map(|h| {
|
||||
vek::Rgba::broadcast(1.0)
|
||||
+ vek::Rgba::new(2.0, 2.0, 2., 0.00).map(|c| {
|
||||
(c / (1.0 + DAMAGE_FADE_COEFFICIENT * s.health.last_change.0)) as f32
|
||||
(c / (1.0 + DAMAGE_FADE_COEFFICIENT * h.last_change.0)) as f32
|
||||
})
|
||||
})
|
||||
.unwrap_or(vek::Rgba::broadcast(1.0))
|
||||
@ -2749,13 +2749,13 @@ impl FigureMgr {
|
||||
&ecs.read_storage::<Pos>(),
|
||||
ecs.read_storage::<Ori>().maybe(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
ecs.read_storage::<Stats>().maybe(),
|
||||
ecs.read_storage::<Health>().maybe(),
|
||||
ecs.read_storage::<Loadout>().maybe(),
|
||||
ecs.read_storage::<Scale>().maybe(),
|
||||
)
|
||||
.join()
|
||||
// Don't render dead entities
|
||||
.filter(|(_, _, _, _, stats, _, _)| stats.map_or(true, |s| !s.is_dead))
|
||||
.filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
|
||||
.for_each(|(entity, pos, _, body, _, loadout, _)| {
|
||||
if let Some((locals, bone_consts, model, _)) = self.get_model_for_render(
|
||||
tick,
|
||||
@ -2803,13 +2803,13 @@ impl FigureMgr {
|
||||
&ecs.read_storage::<Pos>(),
|
||||
ecs.read_storage::<Ori>().maybe(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
ecs.read_storage::<Stats>().maybe(),
|
||||
ecs.read_storage::<Health>().maybe(),
|
||||
ecs.read_storage::<Loadout>().maybe(),
|
||||
ecs.read_storage::<Scale>().maybe(),
|
||||
)
|
||||
.join()
|
||||
// Don't render dead entities
|
||||
.filter(|(_, _, _, _, stats, _, _)| stats.map_or(true, |s| !s.is_dead))
|
||||
.filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
|
||||
{
|
||||
let is_player = entity == player_entity;
|
||||
|
||||
@ -2853,10 +2853,9 @@ impl FigureMgr {
|
||||
ecs.read_storage::<Pos>().get(player_entity),
|
||||
ecs.read_storage::<Body>().get(player_entity),
|
||||
) {
|
||||
let stats_storage = state.read_storage::<Stats>();
|
||||
let stats = stats_storage.get(player_entity);
|
||||
|
||||
if stats.map_or(false, |s| s.is_dead) {
|
||||
let healths = state.read_storage::<Health>();
|
||||
let health = healths.get(player_entity);
|
||||
if health.map_or(false, |h| h.is_dead) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -604,10 +604,10 @@ impl Scene {
|
||||
.maybe(),
|
||||
scene_data.state.ecs().read_storage::<comp::Scale>().maybe(),
|
||||
&scene_data.state.ecs().read_storage::<comp::Body>(),
|
||||
&scene_data.state.ecs().read_storage::<comp::Stats>(),
|
||||
&scene_data.state.ecs().read_storage::<comp::Health>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, _, _, _, stats)| !stats.is_dead)
|
||||
.filter(|(_, _, _, _, health)| !health.is_dead)
|
||||
.filter(|(pos, _, _, _, _)| {
|
||||
(pos.0.distance_squared(player_pos) as f32)
|
||||
< (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powf(2.0)
|
||||
|
Loading…
Reference in New Issue
Block a user