mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'xMAC94x/FeuerzeugBierflasche' into 'master'
xMAC94x/feuerzeugBierflasche See merge request veloren/veloren!1478
This commit is contained in:
commit
121364821a
@ -188,13 +188,14 @@ https://account.veloren.net.
|
||||
"hud.chat.pvp_ranged_kill_msg": "[{attacker}] hat [{victim}] aus der Ferne getötet",
|
||||
"hud.chat.pvp_explosion_kill_msg": "[{attacker}] hat [{victim}] hochgejagt",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{attacker}] hat [{victim}] mit Magie erledigt",
|
||||
"hud.chat.pvp_buff_kill_msg" : "[{attacker}] hat [{victim}] mit Magie erledigt",
|
||||
"hud.chat.pvp_other_kill_msg" : "[{attacker}] hat [{victim}] mit Magie erledigt",
|
||||
|
||||
|
||||
"hud.chat.npc_melee_kill_msg": "{attacker} tötete [{victim}]",
|
||||
"hud.chat.npc_ranged_kill_msg": "{attacker} tötete [{victim}]",
|
||||
"hud.chat.npc_explosion_kill_msg": "{attacker} hat [{victim}] hochgejagt",
|
||||
"hud.chat.npc_buff_kill_msg": "[{attacker}] tötete [{victim}]",
|
||||
"hud.chat.npc_energy_kill_msg": "[{attacker}] hat [{victim}] mit Magie erledigt",
|
||||
"hud.chat.npc_other_kill_msg": "[{attacker}] tötete [{victim}]",
|
||||
|
||||
// SCT outputs
|
||||
"hud.sct.experience": "{amount} Erf",
|
||||
|
@ -184,13 +184,14 @@ https://account.veloren.net."#,
|
||||
"hud.chat.pvp_ranged_kill_msg": "[{attacker}] shot [{victim}]",
|
||||
"hud.chat.pvp_explosion_kill_msg": "[{attacker}] blew up [{victim}]",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{attacker}] killed [{victim}] with magic",
|
||||
"hud.chat.pvp_buff_kill_msg": "[{attacker}] killed [{victim}]",
|
||||
"hud.chat.pvp_other_kill_msg": "[{attacker}] killed [{victim}]",
|
||||
|
||||
|
||||
"hud.chat.npc_melee_kill_msg": "{attacker} killed [{victim}]",
|
||||
"hud.chat.npc_ranged_kill_msg": "{attacker} shot [{victim}]",
|
||||
"hud.chat.npc_explosion_kill_msg": "{attacker} blew up [{victim}]",
|
||||
"hud.chat.npc_buff_kill_msg": "[{attacker}] killed [{victim}]",
|
||||
"hud.chat.npc_energy_kill_msg": "[{attacker}] killed [{victim}] with magic",
|
||||
"hud.chat.npc_other_kill_msg": "[{attacker}] killed [{victim}]",
|
||||
|
||||
"hud.chat.loot_msg": "You picked up [{item}]",
|
||||
"hud.chat.loot_fail": "Your Inventory is full!",
|
||||
|
@ -184,13 +184,14 @@ bir hesap oluşturabilirsin."#,
|
||||
"hud.chat.pvp_ranged_kill_msg": "[{victim}], [{attacker}] tarafından vuruldu.",
|
||||
"hud.chat.pvp_explosion_kill_msg": "[{victim}], [{attacker}] tarafından havaya uçuruldu.",
|
||||
"hud.chat.pvp_energy_kill_msg": "[{victim}], [{attacker}] tarafından büyü ile mağlup edildi.",
|
||||
"hud.chat.pvp_buff_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.",
|
||||
"hud.chat.pvp_other_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.",
|
||||
|
||||
|
||||
"hud.chat.npc_melee_kill_msg": "[{victim}], [{attacker}] tarafından mağlup edildi.",
|
||||
"hud.chat.npc_ranged_kill_msg": "[{victim}], [{attacker}] tarafından vuruldu.",
|
||||
"hud.chat.npc_explosion_kill_msg": "[{victim}], [{attacker}] tarafından havaya uçuruldu.",
|
||||
"hud.chat.npc_buff_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.",
|
||||
"hud.chat.npc_energy_kill_msg": "[{victim}], [{attacker}] tarafından büyü ile mağlup edildi.",
|
||||
"hud.chat.npc_other_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.",
|
||||
|
||||
"hud.chat.loot_msg": "[{item}] topladın.",
|
||||
"hud.chat.loot_fail": "Envanterin dolu!",
|
||||
@ -584,6 +585,49 @@ Koruma
|
||||
"Yardım edin! Yardım edin! Baskı altındayım!",
|
||||
"Ah, artık sistemin doğasında var olan şiddeti görüyoruz.",
|
||||
"Bu bana göre bir çizik bile değil!",
|
||||
"Yapma şunu!",
|
||||
"Ben sana ne yaptım ki?!",
|
||||
"Lütfen bana saldırmayı kes!",
|
||||
"Hey! Onu nereye yönelttiğine dikkat et!",
|
||||
"Aşağılık herif, gözüm bile görmesin!",
|
||||
"Durdur şunu! Git buradan!",
|
||||
"Şimdi beni kızdırmaya başladın!",
|
||||
"Hey! Sen kim olduğunu zannediyorsun ki?!",
|
||||
"Bunun için kelleni alacağım!",
|
||||
"Yapma, lütfen! Değerli hiçbir şeyim yok bile!",
|
||||
"Kardeşimi üzerine salacağım, o benden bile büyük!",
|
||||
"Olamaaaz, Seni anneme söyleyeceğim!",
|
||||
"Lanet olsun sana!",
|
||||
"Lütfen yapma şunu.",
|
||||
"Bunu yapman pek kibarca değildi!",
|
||||
"Evet silahın çalışıyor, şimdi kaldırabilir misin?",
|
||||
"Bağışlayın beni!",
|
||||
"Lütfen, benim bir ailem var!",
|
||||
"Ölmek için çok gencim!",
|
||||
"Bunu konuşarak çözebilir miyiz?",
|
||||
"Şiddet hiçbir zaman çare değildir!",
|
||||
"Günüm gittikçe kötüleşiyor...",
|
||||
"Hey, bu acıttı!",
|
||||
"Eek!",
|
||||
"Ne kadar da kaba!",
|
||||
"Dur, sana yalvarırım!",
|
||||
"Lanet olsun sana!",
|
||||
"Bu eğlenceli bile değil.",
|
||||
"Ne cürret?!",
|
||||
"Bunu sana ödeteceğim!",
|
||||
"Yapmaya devam edersen bunun için pişman olacaksın!",
|
||||
"Sana zarar vermek zorunda bırakma beni!",
|
||||
"Bir yanlış anlaşılma olmalı!",
|
||||
"Bunu yapmak zorunda değilsin!",
|
||||
"Defol!",
|
||||
"Bu gerçekten acıttı!",
|
||||
"Bunu neden yaptın ki?",
|
||||
"Ruhlar tarafından, dur!",
|
||||
"Beni başkasıyla karıştırmış olmalısın!",
|
||||
"Bunu haketmiyorum!",
|
||||
"Lütfen bunu bir daha yapma.",
|
||||
"Muhafızlar, şu canavarı göle atın!",
|
||||
"Tarrasque'ımı üzerine salarım!",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
@ -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));
|
||||
}
|
||||
@ -1733,7 +1733,7 @@ impl Client {
|
||||
alias_of_uid(attacker_uid),
|
||||
alias_of_uid(victim)
|
||||
),
|
||||
KillSource::Player(attacker_uid, KillType::Buff) => format!(
|
||||
KillSource::Player(attacker_uid, KillType::Other) => format!(
|
||||
"[{}] killed [{}]",
|
||||
alias_of_uid(attacker_uid),
|
||||
alias_of_uid(victim)
|
||||
@ -1752,7 +1752,7 @@ impl Client {
|
||||
attacker_name,
|
||||
alias_of_uid(victim)
|
||||
),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Buff) => {
|
||||
KillSource::NonPlayer(attacker_name, KillType::Other) => {
|
||||
format!("{} killed [{}]", attacker_name, alias_of_uid(victim))
|
||||
},
|
||||
KillSource::Environment(environment) => {
|
||||
@ -1780,7 +1780,7 @@ impl Client {
|
||||
KillSource::Player(attacker_uid, KillType::Energy) => message
|
||||
.replace("{attacker}", &alias_of_uid(attacker_uid))
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::Player(attacker_uid, KillType::Buff) => message
|
||||
KillSource::Player(attacker_uid, KillType::Other) => message
|
||||
.replace("{attacker}", &alias_of_uid(attacker_uid))
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Melee) => message
|
||||
@ -1795,7 +1795,7 @@ impl Client {
|
||||
KillSource::NonPlayer(attacker_name, KillType::Energy) => message
|
||||
.replace("{attacker}", attacker_name)
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::NonPlayer(attacker_name, KillType::Buff) => message
|
||||
KillSource::NonPlayer(attacker_name, KillType::Other) => message
|
||||
.replace("{attacker}", attacker_name)
|
||||
.replace("{victim}", &alias_of_uid(victim)),
|
||||
KillSource::Environment(environment) => message
|
||||
|
@ -51,6 +51,7 @@ pub enum ChatCommand {
|
||||
Group,
|
||||
Health,
|
||||
Help,
|
||||
Home,
|
||||
JoinFaction,
|
||||
Jump,
|
||||
Kick,
|
||||
@ -98,6 +99,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::Group,
|
||||
ChatCommand::Health,
|
||||
ChatCommand::Help,
|
||||
ChatCommand::Home,
|
||||
ChatCommand::JoinFaction,
|
||||
ChatCommand::Jump,
|
||||
ChatCommand::Kick,
|
||||
@ -267,6 +269,7 @@ impl ChatCommand {
|
||||
"Display information about commands",
|
||||
NoAdmin,
|
||||
),
|
||||
ChatCommand::Home => cmd(vec![], "Return to the home town", NoAdmin),
|
||||
ChatCommand::JoinFaction => ChatCommandData::new(
|
||||
vec![Any("faction", Optional)],
|
||||
"Join/leave the specified faction",
|
||||
@ -427,6 +430,7 @@ impl ChatCommand {
|
||||
ChatCommand::Health => "health",
|
||||
ChatCommand::JoinFaction => "join_faction",
|
||||
ChatCommand::Help => "help",
|
||||
ChatCommand::Home => "home",
|
||||
ChatCommand::Jump => "jump",
|
||||
ChatCommand::Kick => "kick",
|
||||
ChatCommand::Kill => "kill",
|
||||
|
@ -6,56 +6,40 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_EFFICIENCY: f32 = 0.9;
|
||||
|
||||
/// Each section of this struct determines what damage is applied to a
|
||||
/// particular target, using some identifier
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Damages {
|
||||
/// Targets enemies, and all other creatures not in your group
|
||||
pub enemy: Option<Damage>,
|
||||
/// Targets people in the same group as you, and any pets you have
|
||||
pub group: Option<Damage>,
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum GroupTarget {
|
||||
InGroup,
|
||||
OutOfGroup,
|
||||
}
|
||||
|
||||
impl Damages {
|
||||
pub fn new(enemy: Option<Damage>, group: Option<Damage>) -> Self { Damages { enemy, group } }
|
||||
|
||||
pub fn get_damage(self, same_group: bool) -> Option<Damage> {
|
||||
if same_group { self.group } else { self.enemy }
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub enum DamageSource {
|
||||
Melee,
|
||||
Healing,
|
||||
Projectile,
|
||||
Explosion,
|
||||
Falling,
|
||||
Shockwave,
|
||||
Energy,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Damage {
|
||||
Melee(f32),
|
||||
Healing(f32),
|
||||
Projectile(f32),
|
||||
Explosion(f32),
|
||||
Falling(f32),
|
||||
Shockwave(f32),
|
||||
Energy(f32),
|
||||
pub struct Damage {
|
||||
pub source: DamageSource,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub fn modify_damage(
|
||||
self,
|
||||
block: bool,
|
||||
loadout: Option<&Loadout>,
|
||||
uid: Option<Uid>,
|
||||
) -> HealthChange {
|
||||
match self {
|
||||
Damage::Melee(damage) => {
|
||||
let mut damage = damage;
|
||||
pub fn modify_damage(self, loadout: Option<&Loadout>, uid: Option<Uid>) -> HealthChange {
|
||||
let mut damage = self.value;
|
||||
match self.source {
|
||||
DamageSource::Melee => {
|
||||
// Critical hit
|
||||
let mut critdamage = 0.0;
|
||||
if rand::random() {
|
||||
critdamage = damage * 0.3;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
@ -67,71 +51,73 @@ impl Damage {
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Attack { by: uid.unwrap() },
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
}
|
||||
},
|
||||
Damage::Projectile(damage) => {
|
||||
let mut damage = damage;
|
||||
DamageSource::Projectile => {
|
||||
// Critical hit
|
||||
if rand::random() {
|
||||
damage *= 1.2;
|
||||
}
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Projectile { owner: uid },
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
}
|
||||
},
|
||||
Damage::Explosion(damage) => {
|
||||
let mut damage = damage;
|
||||
// Block
|
||||
if block {
|
||||
damage *= 1.0 - BLOCK_EFFICIENCY
|
||||
}
|
||||
DamageSource::Explosion => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Explosion { owner: uid },
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
}
|
||||
},
|
||||
Damage::Shockwave(damage) => {
|
||||
let mut damage = damage;
|
||||
DamageSource::Shockwave => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Attack { by: uid.unwrap() },
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
}
|
||||
},
|
||||
Damage::Energy(damage) => {
|
||||
let mut damage = damage;
|
||||
DamageSource::Energy => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Energy { owner: uid },
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
}
|
||||
},
|
||||
Damage::Healing(heal) => HealthChange {
|
||||
amount: heal as i32,
|
||||
cause: HealthSource::Healing { by: uid },
|
||||
DamageSource::Healing => HealthChange {
|
||||
amount: damage as i32,
|
||||
cause: HealthSource::Heal { by: uid },
|
||||
},
|
||||
Damage::Falling(damage) => {
|
||||
let mut damage = damage;
|
||||
DamageSource::Falling => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
if (damage_reduction - 1.0).abs() < f32::EPSILON {
|
||||
@ -142,8 +128,20 @@ impl Damage {
|
||||
cause: HealthSource::World,
|
||||
}
|
||||
},
|
||||
DamageSource::Other => HealthChange {
|
||||
amount: -damage as i32,
|
||||
cause: HealthSource::Damage {
|
||||
kind: self.source,
|
||||
by: uid,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpolate_damage(&mut self, frac: f32, min: f32) {
|
||||
let new_damage = min + frac * (self.value - min);
|
||||
self.value = new_damage;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -452,6 +452,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
swing_duration: *swing_duration,
|
||||
recover_duration: *recover_duration,
|
||||
is_interruptible: *is_interruptible,
|
||||
ability_key: key,
|
||||
},
|
||||
auto_charge: false,
|
||||
timer: Duration::default(),
|
||||
@ -489,6 +490,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
speed_increase: 1.0 - *speed_increase,
|
||||
max_speed_increase: *max_speed_increase - 1.0,
|
||||
is_interruptible: *is_interruptible,
|
||||
ability_key: key,
|
||||
},
|
||||
stage: 1,
|
||||
combo: 0,
|
||||
@ -552,6 +554,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
is_interruptible: *is_interruptible,
|
||||
forward_speed: *forward_speed,
|
||||
num_spins: *num_spins,
|
||||
ability_key: key,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
spins_remaining: *num_spins - 1,
|
||||
@ -583,6 +586,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
charge_duration: *charge_duration,
|
||||
swing_duration: *swing_duration,
|
||||
recover_duration: *recover_duration,
|
||||
ability_key: key,
|
||||
},
|
||||
stage_section: StageSection::Charge,
|
||||
timer: Duration::default(),
|
||||
@ -619,6 +623,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
projectile_gravity: *projectile_gravity,
|
||||
initial_projectile_speed: *initial_projectile_speed,
|
||||
max_projectile_speed: *max_projectile_speed,
|
||||
ability_key: key,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{sync::Uid, Damages};
|
||||
use crate::{sync::Uid, Damage, GroupTarget};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -8,7 +8,7 @@ use std::time::Duration;
|
||||
pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub speed: f32,
|
||||
pub damages: Damages,
|
||||
pub damages: Vec<(Option<GroupTarget>, Damage)>,
|
||||
pub lifesteal_eff: f32,
|
||||
pub energy_regen: u32,
|
||||
pub energy_cost: u32,
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
event::{LocalEvent, ServerEvent},
|
||||
states::*,
|
||||
sys::character_behavior::JoinData,
|
||||
Damages, Knockback,
|
||||
Damage, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage, VecStorage};
|
||||
@ -155,9 +155,9 @@ impl Component for CharacterState {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Attacking {
|
||||
pub damages: Damages,
|
||||
pub damages: Vec<(Option<GroupTarget>, Damage)>,
|
||||
pub range: f32,
|
||||
pub max_angle: f32,
|
||||
pub applied: bool,
|
||||
|
@ -51,7 +51,7 @@ pub enum KillType {
|
||||
Projectile,
|
||||
Explosion,
|
||||
Energy,
|
||||
Buff,
|
||||
Other,
|
||||
// Projectile(String), TODO: add projectile name when available
|
||||
}
|
||||
|
||||
|
@ -47,9 +47,9 @@ impl Energy {
|
||||
self.current = amount;
|
||||
}
|
||||
|
||||
pub fn change_by(&mut self, amount: i32, cause: EnergySource) {
|
||||
self.current = ((self.current as i32 + amount).max(0) as u32).min(self.maximum);
|
||||
self.last_change = Some((amount, 0.0, cause));
|
||||
pub fn change_by(&mut self, change: EnergyChange) {
|
||||
self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum);
|
||||
self.last_change = Some((change.amount, 0.0, change.source));
|
||||
}
|
||||
|
||||
pub fn try_change_by(
|
||||
@ -62,7 +62,10 @@ impl Energy {
|
||||
} else if self.current as i32 + amount > self.maximum as i32 {
|
||||
Err(StatChangeError::Overflow)
|
||||
} else {
|
||||
self.change_by(amount, cause);
|
||||
self.change_by(EnergyChange {
|
||||
amount,
|
||||
source: cause,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -73,6 +76,11 @@ impl Energy {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnergyChange {
|
||||
pub amount: i32,
|
||||
pub source: EnergySource,
|
||||
}
|
||||
|
||||
impl Component for Energy {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
131
common/src/comp/health.rs
Normal file
131
common/src/comp/health.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use crate::{comp::Body, sync::Uid, DamageSource};
|
||||
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 {
|
||||
Damage { kind: DamageSource, by: Option<Uid> },
|
||||
Heal { by: Option<Uid> },
|
||||
//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 Dead {
|
||||
pub cause: HealthSource,
|
||||
}
|
||||
|
||||
impl Component for Dead {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
@ -7,8 +7,9 @@ use crate::{
|
||||
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile,
|
||||
},
|
||||
effect::Effect,
|
||||
states::combo_melee,
|
||||
Damage, Damages, Explosion, Knockback,
|
||||
Damage, DamageSource, Explosion, GroupTarget, Knockback, RadiusEffect,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -302,10 +303,10 @@ impl Tool {
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(40.0 * self.base_power())),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Projectile,
|
||||
value: 40.0 * self.base_power(),
|
||||
}),
|
||||
projectile::Effect::Knockback(Knockback::Away(10.0)),
|
||||
projectile::Effect::RewardEnergy(50),
|
||||
projectile::Effect::Vanish,
|
||||
@ -357,10 +358,10 @@ impl Tool {
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(40.0 * self.base_power())),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Projectile,
|
||||
value: 40.0 * self.base_power(),
|
||||
}),
|
||||
projectile::Effect::Knockback(Knockback::Away(10.0)),
|
||||
projectile::Effect::Vanish,
|
||||
projectile::Effect::Buff {
|
||||
@ -419,24 +420,46 @@ impl Tool {
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![
|
||||
projectile::Effect::Explode(Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Entity(
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 50.0 * self.base_power(),
|
||||
}),
|
||||
),
|
||||
RadiusEffect::Entity(
|
||||
Some(GroupTarget::InGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Healing,
|
||||
value: 140.0 * self.base_power(),
|
||||
}),
|
||||
),
|
||||
],
|
||||
radius: 3.0 + 2.5 * self.base_power(),
|
||||
max_damage: (50.0 * self.base_power()) as u32,
|
||||
min_damage: (20.0 * self.base_power()) as u32,
|
||||
max_heal: (140.0 * self.base_power()) as u32,
|
||||
min_heal: (50.0 * self.base_power()) as u32,
|
||||
terrain_destruction_power: 0.0,
|
||||
energy_regen: 0,
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Explode(Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Entity(
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 50.0 * self.base_power(),
|
||||
}),
|
||||
),
|
||||
RadiusEffect::Entity(
|
||||
Some(GroupTarget::InGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Healing,
|
||||
value: 140.0 * self.base_power(),
|
||||
}),
|
||||
),
|
||||
],
|
||||
radius: 3.0 + 2.5 * self.base_power(),
|
||||
max_damage: (50.0 * self.base_power()) as u32,
|
||||
min_damage: (20.0 * self.base_power()) as u32,
|
||||
max_heal: (140.0 * self.base_power()) as u32,
|
||||
min_heal: (50.0 * self.base_power()) as u32,
|
||||
terrain_destruction_power: 0.0,
|
||||
energy_regen: 0,
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
@ -462,24 +485,28 @@ impl Tool {
|
||||
projectile: Projectile {
|
||||
hit_solid: vec![
|
||||
projectile::Effect::Explode(Explosion {
|
||||
effects: vec![RadiusEffect::Entity(
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 100.0 * self.base_power(),
|
||||
}),
|
||||
)],
|
||||
radius: 5.0,
|
||||
max_damage: (100.0 * self.base_power()) as u32,
|
||||
min_damage: 0,
|
||||
max_heal: 0,
|
||||
min_heal: 0,
|
||||
terrain_destruction_power: 0.0,
|
||||
energy_regen: 50,
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Explode(Explosion {
|
||||
effects: vec![RadiusEffect::Entity(
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 100.0 * self.base_power(),
|
||||
}),
|
||||
)],
|
||||
radius: 5.0,
|
||||
max_damage: (100.0 * self.base_power()) as u32,
|
||||
min_damage: 0,
|
||||
max_heal: 0,
|
||||
min_heal: 0,
|
||||
terrain_destruction_power: 0.0,
|
||||
energy_regen: 50,
|
||||
}),
|
||||
projectile::Effect::Vanish,
|
||||
|
@ -9,6 +9,7 @@ pub mod chat;
|
||||
mod controller;
|
||||
mod energy;
|
||||
pub mod group;
|
||||
mod health;
|
||||
mod inputs;
|
||||
mod inventory;
|
||||
mod last;
|
||||
@ -43,8 +44,9 @@ pub use controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||
InventoryManip, MountState, Mounting,
|
||||
};
|
||||
pub use energy::{Energy, EnergySource};
|
||||
pub use energy::{Energy, EnergyChange, EnergySource};
|
||||
pub use group::Group;
|
||||
pub use health::{Health, HealthChange, HealthSource};
|
||||
pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
@ -62,5 +64,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,4 +1,4 @@
|
||||
use crate::{comp::Buff, sync::Uid, Damages, Explosion, Knockback};
|
||||
use crate::{comp::Buff, sync::Uid, Damage, Explosion, GroupTarget, Knockback};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -6,7 +6,7 @@ use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Effect {
|
||||
Damages(Damages),
|
||||
Damage(Option<GroupTarget>, Damage),
|
||||
Knockback(Knockback),
|
||||
RewardEnergy(u32),
|
||||
Explode(Explosion),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{sync::Uid, Damages, Knockback};
|
||||
use crate::{sync::Uid, Damage, GroupTarget, Knockback};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -9,7 +9,7 @@ pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub vertical_angle: f32,
|
||||
pub speed: f32,
|
||||
pub damages: Damages,
|
||||
pub damages: Vec<(Option<GroupTarget>, Damage)>,
|
||||
pub knockback: Knockback,
|
||||
pub requires_ground: bool,
|
||||
pub duration: Duration,
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use crate::comp;
|
||||
use crate::{combat, comp};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An effect that may be applied to an entity
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Effect {
|
||||
Health(comp::HealthChange),
|
||||
Xp(i64),
|
||||
Damage(combat::Damage),
|
||||
}
|
||||
|
||||
impl Effect {
|
||||
@ -13,6 +14,21 @@ impl Effect {
|
||||
match self {
|
||||
Effect::Health(c) => format!("{:+} health", c.amount),
|
||||
Effect::Xp(n) => format!("{:+} exp", n),
|
||||
Effect::Damage(d) => format!("{:+}", d.value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify_strength(&mut self, modifier: f32) {
|
||||
match self {
|
||||
Effect::Health(change) => {
|
||||
change.amount = (change.amount as f32 * modifier) as i32;
|
||||
},
|
||||
Effect::Xp(amount) => {
|
||||
*amount = (*amount as f32 * modifier) as i64;
|
||||
},
|
||||
Effect::Damage(damage) => {
|
||||
damage.interpolate_damage(modifier, 0.0);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +31,10 @@ pub enum ServerEvent {
|
||||
pos: Vec3<f32>,
|
||||
explosion: Explosion,
|
||||
owner: Option<Uid>,
|
||||
friendly_damage: bool,
|
||||
reagent: Option<Reagent>,
|
||||
},
|
||||
Damage {
|
||||
uid: Uid,
|
||||
entity: EcsEntity,
|
||||
change: comp::HealthChange,
|
||||
},
|
||||
Destroy {
|
||||
@ -93,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>,
|
||||
@ -110,6 +110,10 @@ pub enum ServerEvent {
|
||||
entity: EcsEntity,
|
||||
buff_change: comp::BuffChange,
|
||||
},
|
||||
EnergyChange {
|
||||
entity: EcsEntity,
|
||||
change: comp::EnergyChange,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EventBus<E> {
|
||||
|
@ -1,12 +1,15 @@
|
||||
use crate::{combat::GroupTarget, effect::Effect};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Explosion {
|
||||
pub effects: Vec<RadiusEffect>,
|
||||
pub radius: f32,
|
||||
pub max_damage: u32,
|
||||
pub min_damage: u32,
|
||||
pub max_heal: u32,
|
||||
pub min_heal: u32,
|
||||
pub terrain_destruction_power: f32,
|
||||
pub energy_regen: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum RadiusEffect {
|
||||
TerrainDestruction(f32),
|
||||
Entity(Option<GroupTarget>, Effect),
|
||||
}
|
||||
|
@ -53,6 +53,6 @@ pub mod util;
|
||||
pub mod vol;
|
||||
pub mod volumes;
|
||||
|
||||
pub use combat::{Damage, Damages, Knockback};
|
||||
pub use explosion::Explosion;
|
||||
pub use combat::{Damage, DamageSource, GroupTarget, Knockback};
|
||||
pub use explosion::{Explosion, RadiusEffect};
|
||||
pub use loadout_builder::LoadoutBuilder;
|
||||
|
@ -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>();
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::{
|
||||
comp::{beam, humanoid, Body, CharacterState, EnergySource, Ori, Pos, StateUpdate},
|
||||
comp::{
|
||||
beam, humanoid, Body, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate,
|
||||
},
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sync::Uid,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages,
|
||||
Damage, DamageSource, GroupTarget,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -107,12 +109,14 @@ impl CharacterBehavior for Data {
|
||||
if ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
&& (self.static_data.energy_drain == 0 || update.energy.current() > 0)
|
||||
{
|
||||
let damage = Damage::Energy(
|
||||
self.static_data.base_dps as f32 / self.static_data.tick_rate,
|
||||
);
|
||||
let heal = Damage::Healing(
|
||||
self.static_data.base_hps as f32 / self.static_data.tick_rate,
|
||||
);
|
||||
let damage = Damage {
|
||||
source: DamageSource::Energy,
|
||||
value: self.static_data.base_dps as f32 / self.static_data.tick_rate,
|
||||
};
|
||||
let heal = Damage {
|
||||
source: DamageSource::Healing,
|
||||
value: self.static_data.base_hps as f32 / self.static_data.tick_rate,
|
||||
};
|
||||
let energy_regen =
|
||||
(self.static_data.energy_regen as f32 / self.static_data.tick_rate) as u32;
|
||||
let energy_cost =
|
||||
@ -122,7 +126,10 @@ impl CharacterBehavior for Data {
|
||||
let properties = beam::Properties {
|
||||
angle: self.static_data.max_angle.to_radians(),
|
||||
speed,
|
||||
damages: Damages::new(Some(damage), Some(heal)),
|
||||
damages: vec![
|
||||
(Some(GroupTarget::OutOfGroup), damage),
|
||||
(Some(GroupTarget::InGroup), heal),
|
||||
],
|
||||
lifesteal_eff: self.static_data.lifesteal_eff,
|
||||
energy_regen,
|
||||
energy_cost,
|
||||
@ -146,10 +153,10 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and ability key is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else {
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
timer: Duration::default(),
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -76,10 +76,10 @@ impl CharacterBehavior for Data {
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: self.static_data.base_damage as f32,
|
||||
})],
|
||||
range: self.static_data.range,
|
||||
max_angle: 180_f32.to_radians(),
|
||||
applied: false,
|
||||
@ -133,7 +133,10 @@ impl CharacterBehavior for Data {
|
||||
if let Some(attack) = data.attacking {
|
||||
if attack.applied && attack.hit_count > 0 {
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
update.energy.change_by(50, EnergySource::HitEnemy);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: 50,
|
||||
source: EnergySource::HitEnemy,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate},
|
||||
states::utils::{StageSection, *},
|
||||
sys::character_behavior::*,
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -32,6 +32,8 @@ pub struct StaticData {
|
||||
pub swing_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -58,7 +60,7 @@ impl CharacterBehavior for Data {
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Charge => {
|
||||
if data.inputs.secondary.is_pressed()
|
||||
if ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
&& update.energy.current() >= self.static_data.energy_cost
|
||||
&& self.timer < self.static_data.charge_duration
|
||||
{
|
||||
@ -77,11 +79,11 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
} else if data.inputs.secondary.is_pressed()
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else if ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
&& update.energy.current() >= self.static_data.energy_cost
|
||||
{
|
||||
// Maintains charge
|
||||
@ -94,10 +96,10 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing
|
||||
update.character = CharacterState::ChargedMelee(Data {
|
||||
@ -109,16 +111,21 @@ impl CharacterBehavior for Data {
|
||||
},
|
||||
StageSection::Swing => {
|
||||
if !self.exhausted {
|
||||
let damage = self.static_data.initial_damage as f32
|
||||
+ (self.static_data.max_damage - self.static_data.initial_damage) as f32
|
||||
* self.charge_amount;
|
||||
let mut damage = Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: self.static_data.max_damage as f32,
|
||||
};
|
||||
damage.interpolate_damage(
|
||||
self.charge_amount,
|
||||
self.static_data.initial_damage as f32,
|
||||
);
|
||||
let knockback = self.static_data.initial_knockback
|
||||
+ (self.static_data.max_knockback - self.static_data.initial_knockback)
|
||||
* self.charge_amount;
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(Some(Damage::Melee(damage)), None),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), damage)],
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.max_angle.to_radians(),
|
||||
applied: false,
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
projectile, Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile,
|
||||
StateUpdate,
|
||||
projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter,
|
||||
Projectile, StateUpdate,
|
||||
},
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -37,6 +37,8 @@ pub struct StaticData {
|
||||
pub projectile_gravity: Option<Gravity>,
|
||||
pub initial_projectile_speed: f32,
|
||||
pub max_projectile_speed: f32,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -80,14 +82,15 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
},
|
||||
StageSection::Charge => {
|
||||
if !data.inputs.secondary.is_pressed() && !self.exhausted {
|
||||
if !ability_key_is_pressed(data, self.static_data.ability_key) && !self.exhausted {
|
||||
let charge_frac = (self.timer.as_secs_f32()
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
let damage = self.static_data.initial_damage as f32
|
||||
+ (charge_frac
|
||||
* (self.static_data.max_damage - self.static_data.initial_damage)
|
||||
as f32);
|
||||
let mut damage = Damage {
|
||||
source: DamageSource::Projectile,
|
||||
value: self.static_data.max_damage as f32,
|
||||
};
|
||||
damage.interpolate_damage(charge_frac, self.static_data.initial_damage as f32);
|
||||
let knockback = self.static_data.initial_knockback as f32
|
||||
+ (charge_frac
|
||||
* (self.static_data.max_knockback - self.static_data.initial_knockback)
|
||||
@ -96,17 +99,14 @@ impl CharacterBehavior for Data {
|
||||
let mut projectile = Projectile {
|
||||
hit_solid: vec![projectile::Effect::Stick],
|
||||
hit_entity: vec![
|
||||
projectile::Effect::Damages(Damages::new(
|
||||
Some(Damage::Projectile(damage)),
|
||||
None,
|
||||
)),
|
||||
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), damage),
|
||||
projectile::Effect::Knockback(Knockback::Away(knockback)),
|
||||
projectile::Effect::Vanish,
|
||||
projectile::Effect::Buff {
|
||||
buff: Buff::new(
|
||||
BuffKind::Bleeding,
|
||||
BuffData {
|
||||
strength: damage / 5.0,
|
||||
strength: damage.value / 5.0,
|
||||
duration: Some(Duration::from_secs(5)),
|
||||
},
|
||||
vec![BuffCategory::Physical],
|
||||
@ -140,7 +140,7 @@ impl CharacterBehavior for Data {
|
||||
..*self
|
||||
});
|
||||
} else if self.timer < self.static_data.charge_duration
|
||||
&& data.inputs.secondary.is_pressed()
|
||||
&& ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
{
|
||||
// Charges
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
@ -152,11 +152,11 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
} else if data.inputs.secondary.is_pressed() {
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else if ability_key_is_pressed(data, self.static_data.ability_key) {
|
||||
// Holds charge
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
timer: self
|
||||
@ -167,10 +167,10 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -54,6 +54,8 @@ pub struct StaticData {
|
||||
pub max_speed_increase: f32,
|
||||
/// Whether the state can be interrupted by other abilities
|
||||
pub is_interruptible: bool,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
/// A sequence of attacks that can incrementally become faster and more
|
||||
/// damaging.
|
||||
@ -84,7 +86,9 @@ impl CharacterBehavior for Data {
|
||||
let stage_index = (self.stage - 1) as usize;
|
||||
|
||||
// Allows for other states to interrupt this state
|
||||
if self.static_data.is_interruptible && !data.inputs.primary.is_pressed() {
|
||||
if self.static_data.is_interruptible
|
||||
&& !ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
{
|
||||
handle_interrupt(data, &mut update);
|
||||
match update.character {
|
||||
CharacterState::ComboMelee(_) => {},
|
||||
@ -127,7 +131,10 @@ impl CharacterBehavior for Data {
|
||||
* self.static_data.stage_data[stage_index].damage_increase,
|
||||
);
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(Some(Damage::Melee(damage as f32)), None),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: damage as f32,
|
||||
})],
|
||||
range: self.static_data.stage_data[stage_index].range,
|
||||
max_angle: self.static_data.stage_data[stage_index].angle.to_radians(),
|
||||
applied: false,
|
||||
@ -177,7 +184,7 @@ impl CharacterBehavior for Data {
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.stage_data[stage_index].base_recover_duration {
|
||||
// Recovers
|
||||
if data.inputs.primary.is_pressed() {
|
||||
if ability_key_is_pressed(data, self.static_data.ability_key) {
|
||||
// Checks if state will transition to next stage after recover
|
||||
update.character = CharacterState::ComboMelee(Data {
|
||||
static_data: self.static_data.clone(),
|
||||
@ -255,7 +262,10 @@ impl CharacterBehavior for Data {
|
||||
next_stage: self.next_stage,
|
||||
});
|
||||
data.updater.remove::<Attacking>(data.entity);
|
||||
update.energy.change_by(energy, EnergySource::HitEnemy);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: energy,
|
||||
source: EnergySource::HitEnemy,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -39,6 +39,8 @@ pub struct StaticData {
|
||||
pub recover_duration: Duration,
|
||||
/// Whether the state can be interrupted by other abilities
|
||||
pub is_interruptible: bool,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -66,7 +68,9 @@ impl CharacterBehavior for Data {
|
||||
handle_move(data, &mut update, 0.1);
|
||||
|
||||
// Allows for other states to interrupt this state
|
||||
if self.static_data.is_interruptible && !data.inputs.secondary.is_pressed() {
|
||||
if self.static_data.is_interruptible
|
||||
&& !ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
{
|
||||
handle_interrupt(data, &mut update);
|
||||
match update.character {
|
||||
CharacterState::DashMelee(_) => {},
|
||||
@ -90,7 +94,7 @@ impl CharacterBehavior for Data {
|
||||
} else {
|
||||
// Transitions to charge section of stage
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
auto_charge: !data.inputs.secondary.is_pressed(),
|
||||
auto_charge: !ability_key_is_pressed(data, self.static_data.ability_key),
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Charge,
|
||||
..*self
|
||||
@ -100,7 +104,7 @@ impl CharacterBehavior for Data {
|
||||
StageSection::Charge => {
|
||||
if (self.static_data.infinite_charge
|
||||
|| self.timer < self.static_data.charge_duration)
|
||||
&& (data.inputs.secondary.is_pressed()
|
||||
&& (ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
|| (self.auto_charge && self.timer < self.static_data.charge_duration))
|
||||
&& update.energy.current() > 0
|
||||
{
|
||||
@ -122,16 +126,20 @@ impl CharacterBehavior for Data {
|
||||
let charge_frac = (self.timer.as_secs_f32()
|
||||
/ self.static_data.charge_duration.as_secs_f32())
|
||||
.min(1.0);
|
||||
let damage = (self.static_data.max_damage as f32
|
||||
- self.static_data.base_damage as f32)
|
||||
* charge_frac
|
||||
+ self.static_data.base_damage as f32;
|
||||
let mut damage = Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: self.static_data.max_damage as f32,
|
||||
};
|
||||
damage.interpolate_damage(
|
||||
charge_frac,
|
||||
self.static_data.base_damage as f32,
|
||||
);
|
||||
let knockback = (self.static_data.max_knockback
|
||||
- self.static_data.base_knockback)
|
||||
* charge_frac
|
||||
+ self.static_data.base_knockback;
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(Some(Damage::Melee(damage)), None),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), damage)],
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.angle.to_radians(),
|
||||
applied: false,
|
||||
@ -172,10 +180,10 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// Consumes energy if there's enough left and charge has not stopped
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
EnergySource::Ability,
|
||||
);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32,
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else {
|
||||
// Transitions to swing section of stage
|
||||
update.character = CharacterState::DashMelee(Data {
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
comp::{Attacking, CharacterState, StateUpdate},
|
||||
states::utils::{StageSection, *},
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -133,10 +133,10 @@ impl CharacterBehavior for Data {
|
||||
if !self.exhausted {
|
||||
// Hit attempt, when animation plays
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: self.static_data.base_damage as f32,
|
||||
})],
|
||||
range: self.static_data.range,
|
||||
max_angle: self.static_data.max_angle.to_radians(),
|
||||
applied: false,
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -70,10 +70,10 @@ impl CharacterBehavior for Data {
|
||||
vertical_angle: self.static_data.shockwave_vertical_angle,
|
||||
speed: self.static_data.shockwave_speed,
|
||||
duration: self.static_data.shockwave_duration,
|
||||
damages: Damages::new(
|
||||
Some(Damage::Shockwave(self.static_data.damage as f32)),
|
||||
None,
|
||||
),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Shockwave,
|
||||
value: self.static_data.damage as f32,
|
||||
})],
|
||||
knockback: self.static_data.knockback,
|
||||
requires_ground: self.static_data.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
|
||||
comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate},
|
||||
states::utils::*,
|
||||
sys::{
|
||||
character_behavior::{CharacterBehavior, JoinData},
|
||||
phys::GRAVITY,
|
||||
},
|
||||
Damage, Damages, Knockback,
|
||||
Damage, DamageSource, GroupTarget, Knockback,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
@ -38,6 +38,8 @@ pub struct StaticData {
|
||||
pub forward_speed: f32,
|
||||
/// Number of spins
|
||||
pub num_spins: u32,
|
||||
/// What key is used to press ability
|
||||
pub ability_key: AbilityKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -65,7 +67,9 @@ impl CharacterBehavior for Data {
|
||||
}
|
||||
|
||||
// Allows for other states to interrupt this state
|
||||
if self.static_data.is_interruptible && !data.inputs.ability3.is_pressed() {
|
||||
if self.static_data.is_interruptible
|
||||
&& !ability_key_is_pressed(data, self.static_data.ability_key)
|
||||
{
|
||||
handle_interrupt(data, &mut update);
|
||||
match update.character {
|
||||
CharacterState::SpinMelee(_) => {},
|
||||
@ -104,10 +108,10 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Attacking {
|
||||
damages: Damages::new(
|
||||
Some(Damage::Melee(self.static_data.base_damage as f32)),
|
||||
None,
|
||||
),
|
||||
damages: vec![(Some(GroupTarget::OutOfGroup), Damage {
|
||||
source: DamageSource::Melee,
|
||||
value: self.static_data.base_damage as f32,
|
||||
})],
|
||||
range: self.static_data.range,
|
||||
max_angle: 180_f32.to_radians(),
|
||||
applied: false,
|
||||
@ -137,7 +141,8 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
} else if update.energy.current() >= self.static_data.energy_cost
|
||||
&& (self.spins_remaining != 0
|
||||
|| (self.static_data.is_infinite && data.inputs.secondary.is_pressed()))
|
||||
|| (self.static_data.is_infinite
|
||||
&& ability_key_is_pressed(data, self.static_data.ability_key)))
|
||||
{
|
||||
let new_spins_remaining = if self.static_data.is_infinite {
|
||||
self.spins_remaining
|
||||
@ -151,10 +156,10 @@ impl CharacterBehavior for Data {
|
||||
..*self
|
||||
});
|
||||
// Consumes energy if there's enough left and RMB is held down
|
||||
update.energy.change_by(
|
||||
-(self.static_data.energy_cost as i32),
|
||||
EnergySource::Ability,
|
||||
);
|
||||
update.energy.change_by(EnergyChange {
|
||||
amount: -(self.static_data.energy_cost as i32),
|
||||
source: EnergySource::Ability,
|
||||
});
|
||||
} else {
|
||||
// Transitions to recover section of stage
|
||||
update.character = CharacterState::SpinMelee(Data {
|
||||
|
@ -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,16 @@ 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 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
|
||||
if my_health.last_change.0 < 3.0 {
|
||||
if let comp::HealthSource::Damage { by: Some(by), .. } =
|
||||
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 +654,10 @@ 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::Damage { by: Some(by), .. } =
|
||||
owner_health.last_change.1.cause
|
||||
{
|
||||
if !agent.activity.is_attack() {
|
||||
let attacker = uid_allocator.retrieve_entity_internal(by.id())?;
|
||||
|
@ -1,19 +1,17 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Beam, BeamSegment, Body, CharacterState, Energy, EnergySource, HealthChange,
|
||||
HealthSource, Last, Loadout, Ori, Pos, Scale, Stats,
|
||||
group, Beam, BeamSegment, Body, Energy, EnergyChange, EnergySource, Health, HealthChange,
|
||||
HealthSource, Last, Loadout, Ori, Pos, Scale,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
sync::{Uid, UidAllocator},
|
||||
Damage,
|
||||
GroupTarget,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling beams that heal or do damage
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
@ -30,11 +28,10 @@ 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>,
|
||||
WriteStorage<'a, Energy>,
|
||||
ReadStorage<'a, Energy>,
|
||||
WriteStorage<'a, BeamSegment>,
|
||||
WriteStorage<'a, Beam>,
|
||||
);
|
||||
@ -53,11 +50,10 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
mut energies,
|
||||
energies,
|
||||
mut beam_segments,
|
||||
mut beams,
|
||||
): Self::SystemData,
|
||||
@ -68,8 +64,8 @@ impl<'a> System<'a> for Sys {
|
||||
let dt = dt.0;
|
||||
|
||||
// Beams
|
||||
for (entity, uid, pos, ori, beam_segment) in
|
||||
(&entities, &uids, &positions, &orientations, &beam_segments).join()
|
||||
for (entity, pos, ori, beam_segment) in
|
||||
(&entities, &positions, &orientations, &beam_segments).join()
|
||||
{
|
||||
let creation_time = match beam_segment.creation {
|
||||
Some(time) => time,
|
||||
@ -116,26 +112,14 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
// Go through all other effectable entities
|
||||
for (
|
||||
b,
|
||||
uid_b,
|
||||
pos_b,
|
||||
last_pos_b_maybe,
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
body_b,
|
||||
) in (
|
||||
for (b, uid_b, pos_b, last_pos_b_maybe, scale_b_maybe, health_b, body_b) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
// TODO: make sure that these are maintained on the client and remove `.maybe()`
|
||||
last_positions.maybe(),
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
)
|
||||
.join()
|
||||
@ -152,7 +136,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)}));
|
||||
@ -163,63 +147,74 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(Some(*uid_b) == beam_segment.owner);
|
||||
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
} else {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
// If owner, shouldn't heal or damage
|
||||
if Some(*uid_b) == beam_segment.owner {
|
||||
continue;
|
||||
}
|
||||
|
||||
let damage = if let Some(damage) = beam_segment.damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
// TODO: investigate whether this calculation is proper for beams
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner);
|
||||
|
||||
if !matches!(damage, Damage::Healing(_)) {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change,
|
||||
});
|
||||
if beam_segment.lifesteal_eff > 0.0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: beam_segment.owner.unwrap_or(*uid),
|
||||
change: HealthChange {
|
||||
amount: (-change.amount as f32 * beam_segment.lifesteal_eff)
|
||||
as i32,
|
||||
cause: HealthSource::Healing {
|
||||
by: beam_segment.owner,
|
||||
},
|
||||
},
|
||||
});
|
||||
for (target, damage) in beam_segment.damages.iter() {
|
||||
if let Some(target) = target {
|
||||
if *target != target_group {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) {
|
||||
energy_mut.change_by(
|
||||
beam_segment.energy_regen as i32,
|
||||
EnergySource::HitEnemy,
|
||||
);
|
||||
}
|
||||
} else if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) {
|
||||
if energy_mut
|
||||
.try_change_by(
|
||||
-(beam_segment.energy_cost as i32), // Stamina use
|
||||
EnergySource::Ability,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change,
|
||||
});
|
||||
|
||||
// Modify damage
|
||||
let change = damage.modify_damage(loadouts.get(b), beam_segment.owner);
|
||||
|
||||
match target {
|
||||
Some(GroupTarget::OutOfGroup) => {
|
||||
server_emitter.emit(ServerEvent::Damage { entity: b, change });
|
||||
if let Some(entity) = beam_owner {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
entity,
|
||||
change: HealthChange {
|
||||
amount: (-change.amount as f32
|
||||
* beam_segment.lifesteal_eff)
|
||||
as i32,
|
||||
cause: HealthSource::Heal {
|
||||
by: beam_segment.owner,
|
||||
},
|
||||
},
|
||||
});
|
||||
server_emitter.emit(ServerEvent::EnergyChange {
|
||||
entity,
|
||||
change: EnergyChange {
|
||||
amount: beam_segment.energy_regen as i32,
|
||||
source: EnergySource::HitEnemy,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
Some(GroupTarget::InGroup) => {
|
||||
if let Some(energy) = beam_owner.and_then(|o| energies.get(o)) {
|
||||
if energy.current() > beam_segment.energy_cost {
|
||||
server_emitter.emit(ServerEvent::EnergyChange {
|
||||
entity: beam_owner.unwrap(), /* If it's able to get an energy
|
||||
* component, the entity exists */
|
||||
change: EnergyChange {
|
||||
amount: -(beam_segment.energy_cost as i32), // Stamina use
|
||||
source: EnergySource::Ability,
|
||||
},
|
||||
});
|
||||
server_emitter
|
||||
.emit(ServerEvent::Damage { entity: b, change });
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
|
||||
// Adds entities that were hit to the hit_entities list on the beam, sees if
|
||||
// it needs to purge the hit_entities list
|
||||
hit_entities.push(*uid_b);
|
||||
}
|
||||
// Adds entities that were hit to the hit_entities list on the beam, sees if it
|
||||
// needs to purge the hit_entities list
|
||||
hit_entities.push(*uid_b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
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,
|
||||
sync::Uid,
|
||||
DamageSource,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
@ -17,21 +17,20 @@ impl<'a> System<'a> for Sys {
|
||||
Entities<'a>,
|
||||
Read<'a, DeltaTime>,
|
||||
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, 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, health) in (&entities, &mut buffs, &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 +62,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() {
|
||||
@ -88,12 +87,15 @@ impl<'a> System<'a> for Sys {
|
||||
|| buff.time.map_or(false, |dur| dur == Duration::default())
|
||||
{
|
||||
let cause = if *accumulated > 0.0 {
|
||||
HealthSource::Healing { by: buff_owner }
|
||||
HealthSource::Heal { by: buff_owner }
|
||||
} else {
|
||||
HealthSource::Buff { owner: buff_owner }
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Other,
|
||||
by: buff_owner,
|
||||
}
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid,
|
||||
entity,
|
||||
change: HealthChange {
|
||||
amount: *accumulated as i32,
|
||||
cause,
|
||||
@ -104,14 +106,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 +125,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 +139,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,18 +1,17 @@
|
||||
use crate::{
|
||||
comp::{buff, group, Attacking, Body, CharacterState, Loadout, Ori, Pos, Scale, Stats},
|
||||
comp::{buff, group, Attacking, Body, Health, Loadout, Ori, Pos, Scale},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
sync::Uid,
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
/// attacking
|
||||
pub struct Sys;
|
||||
@ -28,10 +27,9 @@ 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>,
|
||||
WriteStorage<'a, Attacking>,
|
||||
);
|
||||
|
||||
@ -47,10 +45,9 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
mut attacking_storage,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
@ -75,17 +72,8 @@ 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 (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&bodies,
|
||||
)
|
||||
.join()
|
||||
for (b, pos_b, scale_b_maybe, health_b, body_b) in
|
||||
(&entities, &positions, scales.maybe(), &healths, &bodies).join()
|
||||
{
|
||||
// 2D versions
|
||||
let pos2 = Vec2::from(pos.0);
|
||||
@ -99,7 +87,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()
|
||||
@ -110,23 +98,22 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(false);
|
||||
|
||||
let damage = if let Some(damage) = attack.damages.get_damage(same_group) {
|
||||
damage
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
} else {
|
||||
continue;
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
for (target, damage) in attack.damages.iter() {
|
||||
if let Some(target) = target {
|
||||
if *target != target_group {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let change = damage.modify_damage(block, loadouts.get(b), Some(*uid));
|
||||
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change,
|
||||
});
|
||||
let change = damage.modify_damage(loadouts.get(b), Some(*uid));
|
||||
|
||||
server_emitter.emit(ServerEvent::Damage { entity: b, change });
|
||||
// Apply bleeding buff on melee hits with 10% chance
|
||||
// TODO: Don't have buff uniformly applied on all melee attacks
|
||||
if change.amount < 0 && thread_rng().gen::<f32>() < 0.1 {
|
||||
@ -144,15 +131,13 @@ impl<'a> System<'a> for Sys {
|
||||
)),
|
||||
});
|
||||
}
|
||||
attack.hit_count += 1;
|
||||
}
|
||||
|
||||
if change.amount != 0 {
|
||||
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
||||
let impulse = attack.knockback.calculate_impulse(kb_dir);
|
||||
if !impulse.is_approx_zero() {
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
}
|
||||
|
||||
attack.hit_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
buff::{BuffChange, BuffSource},
|
||||
projectile, Energy, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, Pos,
|
||||
Projectile, Vel,
|
||||
projectile, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState,
|
||||
Pos, Projectile, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
metrics::SysMetrics,
|
||||
span,
|
||||
state::DeltaTime,
|
||||
sync::UidAllocator,
|
||||
GroupTarget,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
@ -32,7 +33,6 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
WriteStorage<'a, Projectile>,
|
||||
WriteStorage<'a, Energy>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, Group>,
|
||||
);
|
||||
@ -51,7 +51,6 @@ impl<'a> System<'a> for Sys {
|
||||
velocities,
|
||||
mut orientations,
|
||||
mut projectiles,
|
||||
mut energies,
|
||||
loadouts,
|
||||
groups,
|
||||
): Self::SystemData,
|
||||
@ -85,6 +84,13 @@ impl<'a> System<'a> for Sys {
|
||||
.retrieve_entity_internal(other.into())
|
||||
.and_then(|e| groups.get(e))
|
||||
);
|
||||
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
} else {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
if projectile.ignore_group
|
||||
// Skip if in the same group
|
||||
&& same_group
|
||||
@ -98,43 +104,54 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
for effect in projectile.hit_entity.drain(..) {
|
||||
match effect {
|
||||
projectile::Effect::Damages(damages) => {
|
||||
projectile::Effect::Damage(target, damage) => {
|
||||
if Some(other) == projectile.owner {
|
||||
continue;
|
||||
}
|
||||
let damage = if let Some(damage) = damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let other_entity_loadout = uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
.and_then(|e| loadouts.get(e));
|
||||
let change =
|
||||
damage.modify_damage(false, other_entity_loadout, projectile.owner);
|
||||
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage { uid: other, change });
|
||||
if let Some(target) = target {
|
||||
if target != target_group {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(other_entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
{
|
||||
let other_entity_loadout = loadouts.get(other_entity);
|
||||
let change =
|
||||
damage.modify_damage(other_entity_loadout, projectile.owner);
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
entity: other_entity,
|
||||
change,
|
||||
});
|
||||
}
|
||||
},
|
||||
projectile::Effect::Knockback(knockback) => {
|
||||
if let Some(entity) =
|
||||
if let Some(other_entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
{
|
||||
let impulse = knockback.calculate_impulse(ori.0);
|
||||
if !impulse.is_approx_zero() {
|
||||
local_emitter
|
||||
.emit(LocalEvent::ApplyImpulse { entity, impulse });
|
||||
local_emitter.emit(LocalEvent::ApplyImpulse {
|
||||
entity: other_entity,
|
||||
impulse,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
projectile::Effect::RewardEnergy(energy) => {
|
||||
if let Some(energy_mut) = projectile
|
||||
if let Some(entity_owner) = projectile
|
||||
.owner
|
||||
.and_then(|o| uid_allocator.retrieve_entity_internal(o.into()))
|
||||
.and_then(|o| energies.get_mut(o))
|
||||
.and_then(|u| uid_allocator.retrieve_entity_internal(u.into()))
|
||||
{
|
||||
energy_mut.change_by(energy as i32, EnergySource::HitEnemy);
|
||||
server_emitter.emit(ServerEvent::EnergyChange {
|
||||
entity: entity_owner,
|
||||
change: EnergyChange {
|
||||
amount: energy as i32,
|
||||
source: EnergySource::HitEnemy,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
projectile::Effect::Explode(e) => {
|
||||
@ -142,7 +159,6 @@ impl<'a> System<'a> for Sys {
|
||||
pos: pos.0,
|
||||
explosion: e,
|
||||
owner: projectile.owner,
|
||||
friendly_damage: false,
|
||||
reagent: None,
|
||||
})
|
||||
},
|
||||
@ -157,6 +173,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO: Change to effect after !1472 merges
|
||||
projectile::Effect::Buff { buff, chance } => {
|
||||
if let Some(entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
@ -187,7 +204,6 @@ impl<'a> System<'a> for Sys {
|
||||
pos: pos.0,
|
||||
explosion: e,
|
||||
owner: projectile.owner,
|
||||
friendly_damage: false,
|
||||
reagent: None,
|
||||
})
|
||||
},
|
||||
|
@ -1,18 +1,17 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Body, CharacterState, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale,
|
||||
Shockwave, ShockwaveHitEntities, Stats,
|
||||
group, Body, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, Shockwave,
|
||||
ShockwaveHitEntities,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
sync::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
/// attacking
|
||||
pub struct Sys;
|
||||
@ -31,10 +30,9 @@ 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>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
WriteStorage<'a, Shockwave>,
|
||||
WriteStorage<'a, ShockwaveHitEntities>,
|
||||
@ -55,10 +53,9 @@ impl<'a> System<'a> for Sys {
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
healths,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
physics_states,
|
||||
mut shockwaves,
|
||||
mut shockwave_hit_lists,
|
||||
@ -130,10 +127,8 @@ impl<'a> System<'a> for Sys {
|
||||
uid_b,
|
||||
pos_b,
|
||||
last_pos_b_maybe,
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
health_b,
|
||||
body_b,
|
||||
physics_state_b,
|
||||
) in (
|
||||
@ -142,10 +137,8 @@ impl<'a> System<'a> for Sys {
|
||||
&positions,
|
||||
// TODO: make sure that these are maintained on the client and remove `.maybe()`
|
||||
last_positions.maybe(),
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&healths,
|
||||
&bodies,
|
||||
&physics_states,
|
||||
)
|
||||
@ -177,9 +170,15 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(Some(*uid_b) == shockwave.owner);
|
||||
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
} else {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
// 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
|
||||
@ -192,24 +191,19 @@ impl<'a> System<'a> for Sys {
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground);
|
||||
|
||||
if hit {
|
||||
let damage = if let Some(damage) = shockwave.damages.get_damage(same_group) {
|
||||
damage
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
for (target, damage) in shockwave.damages.iter() {
|
||||
if let Some(target) = target {
|
||||
if *target != target_group {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
let owner_uid = shockwave.owner.unwrap_or(*uid);
|
||||
let change = damage.modify_damage(loadouts.get(b), Some(owner_uid));
|
||||
|
||||
let owner_uid = shockwave.owner.unwrap_or(*uid);
|
||||
let change = damage.modify_damage(block, loadouts.get(b), Some(owner_uid));
|
||||
|
||||
if change.amount != 0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change,
|
||||
});
|
||||
server_emitter.emit(ServerEvent::Damage { entity: b, change });
|
||||
shockwave_hit_list.hit_entities.push(*uid_b);
|
||||
|
||||
let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
|
||||
let impulse = shockwave.knockback.calculate_impulse(kb_dir);
|
||||
if !impulse.is_approx_zero() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{CharacterState, Energy, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,11 +113,12 @@ impl<'a> System<'a> for Sys {
|
||||
if res {
|
||||
let mut energy = energy.get_mut_unchecked();
|
||||
// Have to account for Calc I differential equations due to acceleration
|
||||
energy.change_by(
|
||||
(energy.regen_rate * dt.0 + ENERGY_REGEN_ACCEL * dt.0.powf(2.0) / 2.0)
|
||||
energy.change_by(EnergyChange {
|
||||
amount: (energy.regen_rate * dt.0
|
||||
+ ENERGY_REGEN_ACCEL * dt.0.powf(2.0) / 2.0)
|
||||
as i32,
|
||||
EnergySource::Regen,
|
||||
);
|
||||
source: EnergySource::Regen,
|
||||
});
|
||||
energy.regen_rate =
|
||||
(energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0);
|
||||
}
|
||||
@ -130,9 +148,10 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
if res {
|
||||
energy
|
||||
.get_mut_unchecked()
|
||||
.change_by(-3, EnergySource::Regen);
|
||||
energy.get_mut_unchecked().change_by(EnergyChange {
|
||||
amount: -3,
|
||||
source: EnergySource::Regen,
|
||||
});
|
||||
}
|
||||
},
|
||||
// Non-combat abilities that consume energy;
|
||||
|
@ -4,12 +4,13 @@
|
||||
|
||||
use crate::{
|
||||
settings::{BanRecord, EditableSetting},
|
||||
Server, StateExt,
|
||||
Server, SpawnPoint, StateExt,
|
||||
};
|
||||
use chrono::{NaiveTime, Timelike};
|
||||
use common::{
|
||||
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
||||
comp::{self, ChatType, Item, LightEmitter, WaypointArea},
|
||||
effect::Effect,
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
||||
npc::{self, get_npc_name},
|
||||
@ -18,7 +19,7 @@ use common::{
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
|
||||
util::Dir,
|
||||
vol::RectVolSize,
|
||||
Explosion, LoadoutBuilder,
|
||||
Damage, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
};
|
||||
use rand::Rng;
|
||||
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
@ -82,6 +83,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
||||
ChatCommand::Group => handle_group,
|
||||
ChatCommand::Health => handle_health,
|
||||
ChatCommand::Help => handle_help,
|
||||
ChatCommand::Home => handle_home,
|
||||
ChatCommand::JoinFaction => handle_join_faction,
|
||||
ChatCommand::Jump => handle_jump,
|
||||
ChatCommand::Kick => handle_kick,
|
||||
@ -359,6 +361,29 @@ fn handle_goto(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_home(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
_args: String,
|
||||
_action: &ChatCommand,
|
||||
) {
|
||||
if server
|
||||
.state
|
||||
.read_component_copied::<comp::Pos>(target)
|
||||
.is_some()
|
||||
{
|
||||
let home_pos = server.state.ecs().read_resource::<SpawnPoint>().0;
|
||||
server.state.write_component(target, comp::Pos(home_pos));
|
||||
server.state.write_component(target, comp::ForceUpdate);
|
||||
} else {
|
||||
server.notify_client(
|
||||
client,
|
||||
ChatType::CommandError.server_msg("You have no position."),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_kill(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
@ -369,16 +394,19 @@ fn handle_kill(
|
||||
let reason = if client == target {
|
||||
comp::HealthSource::Suicide
|
||||
} else if let Some(uid) = server.state.read_storage::<Uid>().get(client) {
|
||||
comp::HealthSource::Attack { by: *uid }
|
||||
comp::HealthSource::Damage {
|
||||
kind: DamageSource::Other,
|
||||
by: Some(*uid),
|
||||
}
|
||||
} else {
|
||||
comp::HealthSource::Command
|
||||
};
|
||||
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(
|
||||
@ -447,13 +475,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,
|
||||
@ -632,6 +660,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,
|
||||
@ -738,9 +767,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();
|
||||
@ -900,12 +931,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)
|
||||
@ -1129,16 +1160,20 @@ fn handle_explosion(
|
||||
.emit_now(ServerEvent::Explosion {
|
||||
pos: pos.0,
|
||||
explosion: Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Entity(
|
||||
None,
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 100.0 * power,
|
||||
}),
|
||||
),
|
||||
RadiusEffect::TerrainDestruction(power),
|
||||
],
|
||||
radius: 3.0 * power,
|
||||
max_damage: (100.0 * power) as u32,
|
||||
min_damage: 0,
|
||||
max_heal: 0,
|
||||
min_heal: 0,
|
||||
terrain_destruction_power: power,
|
||||
energy_regen: 0,
|
||||
},
|
||||
owner: ecs.read_storage::<Uid>().get(target).copied(),
|
||||
friendly_damage: true,
|
||||
reagent: None,
|
||||
})
|
||||
},
|
||||
@ -1671,6 +1706,8 @@ fn handle_set_level(
|
||||
PlayerListUpdate::LevelChange(uid, lvl),
|
||||
));
|
||||
|
||||
let body_type: Option<comp::Body>;
|
||||
|
||||
if let Some(stats) = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
@ -1678,13 +1715,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,17 +8,18 @@ use common::{
|
||||
comp::{
|
||||
self, buff,
|
||||
chat::{KillSource, KillType},
|
||||
object, Alignment, Body, Group, HealthChange, HealthSource, Item, Player, Pos, Stats,
|
||||
object, Alignment, Body, Energy, EnergyChange, Group, Health, HealthChange, HealthSource,
|
||||
Item, Player, Pos, Stats,
|
||||
},
|
||||
effect::Effect,
|
||||
lottery::Lottery,
|
||||
msg::{PlayerListUpdate, ServerGeneral},
|
||||
outcome::Outcome,
|
||||
state::BlockChange,
|
||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||
sys::melee::BLOCK_ANGLE,
|
||||
terrain::{Block, TerrainGrid},
|
||||
vol::ReadVol,
|
||||
Damage, Explosion,
|
||||
Damage, DamageSource, Explosion, GroupTarget, RadiusEffect,
|
||||
};
|
||||
use comp::item::Reagent;
|
||||
use rand::prelude::*;
|
||||
@ -26,13 +27,10 @@ use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt
|
||||
use tracing::error;
|
||||
use vek::Vec3;
|
||||
|
||||
pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||
let state = &server.state;
|
||||
let ecs = 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);
|
||||
}
|
||||
pub fn handle_damage(server: &Server, entity: EcsEntity, change: HealthChange) {
|
||||
let ecs = &server.state.ecs();
|
||||
if let Some(health) = ecs.write_storage::<Health>().get_mut(entity) {
|
||||
health.change_by(change);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +73,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
if let Some(_player) = state.ecs().read_storage::<Player>().get(entity) {
|
||||
if let Some(uid) = state.ecs().read_storage::<Uid>().get(entity) {
|
||||
let kill_source = match cause {
|
||||
HealthSource::Attack { by } => {
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Melee,
|
||||
by: Some(by),
|
||||
} => {
|
||||
// Get attacker entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
@ -97,7 +98,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Melee)
|
||||
}
|
||||
},
|
||||
HealthSource::Projectile { owner: Some(by) } => {
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Projectile,
|
||||
by: Some(by),
|
||||
} => {
|
||||
// Get projectile owner entity TODO: add names to projectiles and send in
|
||||
// message
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
@ -120,7 +124,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Projectile)
|
||||
}
|
||||
},
|
||||
HealthSource::Explosion { owner: Some(by) } => {
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Explosion,
|
||||
by: Some(by),
|
||||
} => {
|
||||
// Get explosion owner entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
@ -142,7 +149,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Explosion)
|
||||
}
|
||||
},
|
||||
HealthSource::Energy { owner: Some(by) } => {
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Energy,
|
||||
by: Some(by),
|
||||
} => {
|
||||
// Get energy owner entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
@ -164,7 +174,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Energy)
|
||||
}
|
||||
},
|
||||
HealthSource::Buff { owner: Some(by) } => {
|
||||
HealthSource::Damage {
|
||||
kind: DamageSource::Other,
|
||||
by: Some(by),
|
||||
} => {
|
||||
// Get energy owner entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
@ -174,29 +187,26 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
.get(char_entity)
|
||||
.is_some()
|
||||
{
|
||||
KillSource::Player(by, KillType::Buff)
|
||||
KillSource::Player(by, KillType::Other)
|
||||
} else if let Some(stats) =
|
||||
state.ecs().read_storage::<Stats>().get(char_entity)
|
||||
{
|
||||
KillSource::NonPlayer(stats.name.clone(), KillType::Buff)
|
||||
KillSource::NonPlayer(stats.name.clone(), KillType::Other)
|
||||
} else {
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Buff)
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Other)
|
||||
}
|
||||
} else {
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Buff)
|
||||
KillSource::NonPlayer("<?>".to_string(), KillType::Other)
|
||||
}
|
||||
},
|
||||
HealthSource::World => KillSource::FallDamage,
|
||||
HealthSource::Suicide => KillSource::Suicide,
|
||||
HealthSource::Projectile { owner: None }
|
||||
| HealthSource::Explosion { owner: None }
|
||||
| HealthSource::Energy { owner: None }
|
||||
| HealthSource::Buff { owner: None }
|
||||
HealthSource::Damage { .. }
|
||||
| HealthSource::Revive
|
||||
| HealthSource::Command
|
||||
| HealthSource::LevelUp
|
||||
| HealthSource::Item
|
||||
| HealthSource::Healing { by: _ }
|
||||
| HealthSource::Heal { by: _ }
|
||||
| HealthSource::Unknown => KillSource::Other,
|
||||
};
|
||||
state
|
||||
@ -207,12 +217,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
// Give EXP to the killer if entity had stats
|
||||
(|| {
|
||||
let mut stats = state.ecs().write_storage::<Stats>();
|
||||
let by = if let HealthSource::Attack { by }
|
||||
| HealthSource::Projectile { owner: Some(by) }
|
||||
| HealthSource::Energy { owner: Some(by) }
|
||||
| HealthSource::Buff { owner: Some(by) }
|
||||
| HealthSource::Explosion { owner: Some(by) } = cause
|
||||
{
|
||||
let by = if let HealthSource::Damage { by: Some(by), .. } = cause {
|
||||
by
|
||||
} else {
|
||||
return;
|
||||
@ -452,12 +457,15 @@ 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::Falling(falldmg);
|
||||
let damage = Damage {
|
||||
source: DamageSource::Falling,
|
||||
value: falldmg,
|
||||
};
|
||||
let loadouts = state.ecs().read_storage::<comp::Loadout>();
|
||||
let change = damage.modify_damage(false, loadouts.get(entity), None);
|
||||
stats.health.change_by(change);
|
||||
let change = damage.modify_damage(loadouts.get(entity), None);
|
||||
health.change_by(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -479,9 +487,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>()
|
||||
@ -506,26 +514,30 @@ pub fn handle_explosion(
|
||||
pos: Vec3<f32>,
|
||||
explosion: Explosion,
|
||||
owner: Option<Uid>,
|
||||
friendly_damage: bool,
|
||||
reagent: Option<Reagent>,
|
||||
) {
|
||||
// Go through all other entities
|
||||
let ecs = &server.state.ecs();
|
||||
|
||||
let outcome_power = if explosion.max_heal > explosion.max_damage {
|
||||
(-explosion.terrain_destruction_power).min(explosion.max_heal as f32 / -100.0)
|
||||
} else {
|
||||
explosion
|
||||
.terrain_destruction_power
|
||||
.max(explosion.max_damage as f32 / 100.0)
|
||||
};
|
||||
// Add an outcome
|
||||
// Uses radius as outcome power, makes negative if explosion has healing effect
|
||||
let outcome_power = explosion.radius
|
||||
* if explosion.effects.iter().any(
|
||||
|e| matches!(e, RadiusEffect::Entity(_, Effect::Damage(Damage { source: DamageSource::Healing, .. })))
|
||||
) {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
ecs.write_resource::<Vec<Outcome>>()
|
||||
.push(Outcome::Explosion {
|
||||
pos,
|
||||
power: outcome_power,
|
||||
radius: explosion.radius,
|
||||
is_attack: explosion.max_heal > 0 || explosion.max_damage > 0,
|
||||
is_attack: explosion
|
||||
.effects
|
||||
.iter()
|
||||
.any(|e| matches!(e, RadiusEffect::Entity(_, Effect::Damage(_)))),
|
||||
reagent,
|
||||
});
|
||||
let owner_entity = owner.and_then(|uid| {
|
||||
@ -534,129 +546,126 @@ pub fn handle_explosion(
|
||||
});
|
||||
let groups = ecs.read_storage::<comp::Group>();
|
||||
|
||||
for (entity_b, pos_b, ori_b, character_b, stats_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>(),
|
||||
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
|
||||
// RADIUS
|
||||
&& distance_squared < explosion.radius.powi(2)
|
||||
{
|
||||
// See if entities are in the same group
|
||||
let mut same_group = owner_entity
|
||||
.and_then(|e| groups.get(e))
|
||||
.map_or(false, |group_a| Some(group_a) == groups.get(entity_b));
|
||||
if let Some(entity) = owner_entity {
|
||||
if entity == entity_b {
|
||||
same_group = true;
|
||||
for effect in explosion.effects {
|
||||
match effect {
|
||||
RadiusEffect::TerrainDestruction(power) => {
|
||||
const RAYS: usize = 500;
|
||||
|
||||
// Color terrain
|
||||
let mut touched_blocks = Vec::new();
|
||||
let color_range = power * 2.7;
|
||||
for _ in 0..RAYS {
|
||||
let dir = Vec3::new(
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
)
|
||||
.normalized();
|
||||
|
||||
let _ = ecs
|
||||
.read_resource::<TerrainGrid>()
|
||||
.ray(pos, pos + dir * color_range)
|
||||
// TODO: Faster RNG
|
||||
.until(|_| rand::random::<f32>() < 0.05)
|
||||
.for_each(|_: &Block, pos| touched_blocks.push(pos))
|
||||
.cast();
|
||||
}
|
||||
}
|
||||
// Don't heal if outside group
|
||||
// Don't damage in the same group
|
||||
let is_damage = (friendly_damage || !same_group) && explosion.max_damage > 0;
|
||||
let is_heal = same_group && explosion.max_heal > 0 && !friendly_damage;
|
||||
if !is_heal && !is_damage {
|
||||
continue;
|
||||
}
|
||||
|
||||
let strength = 1.0 - distance_squared / explosion.radius.powi(2);
|
||||
let damage = if is_heal {
|
||||
Damage::Healing(
|
||||
explosion.min_heal as f32
|
||||
+ (explosion.max_heal - explosion.min_heal) as f32 * strength,
|
||||
)
|
||||
} else {
|
||||
Damage::Explosion(
|
||||
explosion.min_damage as f32
|
||||
+ (explosion.max_damage - explosion.min_damage) as f32 * strength,
|
||||
)
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
&& ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
let change = damage.modify_damage(block, loadout_b, owner);
|
||||
|
||||
if change.amount != 0 {
|
||||
stats_b.health.change_by(change);
|
||||
if let Some(owner) = owner_entity {
|
||||
if let Some(energy) = ecs.write_storage::<comp::Energy>().get_mut(owner) {
|
||||
energy
|
||||
.change_by(explosion.energy_regen as i32, comp::EnergySource::HitEnemy);
|
||||
let terrain = ecs.read_resource::<TerrainGrid>();
|
||||
let mut block_change = ecs.write_resource::<BlockChange>();
|
||||
for block_pos in touched_blocks {
|
||||
if let Ok(block) = terrain.get(block_pos) {
|
||||
let diff2 = block_pos.map(|b| b as f32).distance_squared(pos);
|
||||
let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0);
|
||||
if let Some(mut color) = block.get_color() {
|
||||
let r = color[0] as f32
|
||||
+ (fade * (color[0] as f32 * 0.5 - color[0] as f32));
|
||||
let g = color[1] as f32
|
||||
+ (fade * (color[1] as f32 * 0.3 - color[1] as f32));
|
||||
let b = color[2] as f32
|
||||
+ (fade * (color[2] as f32 * 0.3 - color[2] as f32));
|
||||
color[0] = r as u8;
|
||||
color[1] = g as u8;
|
||||
color[2] = b as u8;
|
||||
block_change.set(block_pos, Block::new(block.kind(), color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RAYS: usize = 500;
|
||||
// Destroy terrain
|
||||
for _ in 0..RAYS {
|
||||
let dir = Vec3::new(
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.15,
|
||||
)
|
||||
.normalized();
|
||||
|
||||
// Color terrain
|
||||
let mut touched_blocks = Vec::new();
|
||||
let color_range = explosion.terrain_destruction_power * 2.7;
|
||||
for _ in 0..RAYS {
|
||||
let dir = Vec3::new(
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
)
|
||||
.normalized();
|
||||
|
||||
let _ = ecs
|
||||
.read_resource::<TerrainGrid>()
|
||||
.ray(pos, pos + dir * color_range)
|
||||
// TODO: Faster RNG
|
||||
.until(|_| rand::random::<f32>() < 0.05)
|
||||
.for_each(|_: &Block, pos| touched_blocks.push(pos))
|
||||
.cast();
|
||||
}
|
||||
|
||||
let terrain = ecs.read_resource::<TerrainGrid>();
|
||||
let mut block_change = ecs.write_resource::<BlockChange>();
|
||||
for block_pos in touched_blocks {
|
||||
if let Ok(block) = terrain.get(block_pos) {
|
||||
let diff2 = block_pos.map(|b| b as f32).distance_squared(pos);
|
||||
let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0);
|
||||
if let Some(mut color) = block.get_color() {
|
||||
let r = color[0] as f32 + (fade * (color[0] as f32 * 0.5 - color[0] as f32));
|
||||
let g = color[1] as f32 + (fade * (color[1] as f32 * 0.3 - color[1] as f32));
|
||||
let b = color[2] as f32 + (fade * (color[2] as f32 * 0.3 - color[2] as f32));
|
||||
color[0] = r as u8;
|
||||
color[1] = g as u8;
|
||||
color[2] = b as u8;
|
||||
block_change.set(block_pos, Block::new(block.kind(), color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy terrain
|
||||
for _ in 0..RAYS {
|
||||
let dir = Vec3::new(
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.5,
|
||||
rand::random::<f32>() - 0.15,
|
||||
)
|
||||
.normalized();
|
||||
|
||||
let terrain = ecs.read_resource::<TerrainGrid>();
|
||||
let _ = terrain
|
||||
.ray(pos, pos + dir * explosion.terrain_destruction_power)
|
||||
// TODO: Faster RNG
|
||||
.until(|block| block.is_liquid() || rand::random::<f32>() < 0.05)
|
||||
.for_each(|block: &Block, pos| {
|
||||
if block.is_explodable() {
|
||||
block_change.set(pos, block.into_vacant());
|
||||
let terrain = ecs.read_resource::<TerrainGrid>();
|
||||
let _ = terrain
|
||||
.ray(pos, pos + dir * power)
|
||||
// TODO: Faster RNG
|
||||
.until(|block| block.is_liquid() || rand::random::<f32>() < 0.05)
|
||||
.for_each(|block: &Block, pos| {
|
||||
if block.is_explodable() {
|
||||
block_change.set(pos, block.into_vacant());
|
||||
}
|
||||
})
|
||||
.cast();
|
||||
}
|
||||
})
|
||||
.cast();
|
||||
},
|
||||
RadiusEffect::Entity(target, mut effect) => {
|
||||
for (entity_b, pos_b) in (&ecs.entities(), &ecs.read_storage::<comp::Pos>()).join()
|
||||
{
|
||||
// See if entities are in the same group
|
||||
let mut same_group = owner_entity
|
||||
.and_then(|e| groups.get(e))
|
||||
.map_or(false, |group_a| Some(group_a) == groups.get(entity_b));
|
||||
if let Some(entity) = owner_entity {
|
||||
if entity == entity_b {
|
||||
same_group = true;
|
||||
}
|
||||
}
|
||||
let target_group = if same_group {
|
||||
GroupTarget::InGroup
|
||||
} else {
|
||||
GroupTarget::OutOfGroup
|
||||
};
|
||||
|
||||
if let Some(target) = target {
|
||||
if target != target_group {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let distance_squared = pos.distance_squared(pos_b.0);
|
||||
let strength = 1.0 - distance_squared / explosion.radius.powi(2);
|
||||
|
||||
if strength > 0.0 {
|
||||
let is_alive = ecs
|
||||
.read_storage::<comp::Health>()
|
||||
.get(entity_b)
|
||||
.map_or(false, |h| !h.is_dead);
|
||||
|
||||
if is_alive {
|
||||
effect.modify_strength(strength);
|
||||
server.state().apply_effect(entity_b, effect, owner);
|
||||
// Apply energy change
|
||||
if let Some(owner) = owner_entity {
|
||||
if let Some(energy) =
|
||||
ecs.write_storage::<comp::Energy>().get_mut(owner)
|
||||
{
|
||||
energy.change_by(EnergyChange {
|
||||
amount: explosion.energy_regen as i32,
|
||||
source: comp::EnergySource::HitEnemy,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -732,3 +741,10 @@ pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::Bu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_energy_change(server: &Server, entity: EcsEntity, change: EnergyChange) {
|
||||
let ecs = &server.state.ecs();
|
||||
if let Some(energy) = ecs.write_storage::<Energy>().get_mut(entity) {
|
||||
energy.change_by(change);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{client::Client, presence::RegionSubscription, Server};
|
||||
use crate::{
|
||||
client::Client,
|
||||
presence::{Presence, RegionSubscription},
|
||||
Server,
|
||||
};
|
||||
use common::{
|
||||
comp::{self, item, Pos},
|
||||
consts::MAX_MOUNT_RANGE,
|
||||
@ -127,12 +131,16 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
|
||||
clients.insert(possesse, c).ok()?;
|
||||
//optional entities
|
||||
let mut players = ecs.write_storage::<comp::Player>();
|
||||
let mut presence = ecs.write_storage::<Presence>();
|
||||
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
||||
let mut admins = ecs.write_storage::<comp::Admin>();
|
||||
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
||||
players
|
||||
.remove(possessor)
|
||||
.map(|p| players.insert(possesse, p).ok()?);
|
||||
presence
|
||||
.remove(possessor)
|
||||
.map(|p| presence.insert(possesse, p).ok()?);
|
||||
subscriptions
|
||||
.remove(possessor)
|
||||
.map(|s| subscriptions.insert(possesse, s).ok()?);
|
||||
|
@ -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
|
||||
}
|
||||
@ -349,7 +349,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
|
||||
drop(inventories);
|
||||
if let Some(effect) = maybe_effect {
|
||||
state.apply_effect(entity, effect);
|
||||
state.apply_effect(entity, effect, None);
|
||||
}
|
||||
if let Some(event) = event {
|
||||
state.write_component(entity, comp::InventoryUpdate::new(event));
|
||||
|
@ -8,8 +8,8 @@ use entity_creation::{
|
||||
handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
};
|
||||
use entity_manipulation::{
|
||||
handle_buff, handle_damage, handle_destroy, handle_explosion, handle_knockback,
|
||||
handle_land_on_ground, handle_level_up, handle_respawn,
|
||||
handle_buff, handle_damage, handle_destroy, handle_energy_change, handle_explosion,
|
||||
handle_knockback, handle_land_on_ground, handle_level_up, handle_respawn,
|
||||
};
|
||||
use group_manip::handle_group;
|
||||
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
||||
@ -58,9 +58,8 @@ impl Server {
|
||||
pos,
|
||||
explosion,
|
||||
owner,
|
||||
friendly_damage,
|
||||
reagent,
|
||||
} => handle_explosion(&self, pos, explosion, owner, friendly_damage, reagent),
|
||||
} => handle_explosion(&self, pos, explosion, owner, reagent),
|
||||
ServerEvent::Shoot {
|
||||
entity,
|
||||
dir,
|
||||
@ -83,7 +82,7 @@ impl Server {
|
||||
ServerEvent::Knockback { entity, impulse } => {
|
||||
handle_knockback(&self, entity, impulse)
|
||||
},
|
||||
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||
ServerEvent::Damage { entity, change } => handle_damage(&self, entity, change),
|
||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||
ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip),
|
||||
@ -110,6 +109,7 @@ impl Server {
|
||||
ServerEvent::CreateNpc {
|
||||
pos,
|
||||
stats,
|
||||
health,
|
||||
loadout,
|
||||
body,
|
||||
agent,
|
||||
@ -117,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) => {
|
||||
@ -137,6 +137,9 @@ impl Server {
|
||||
entity,
|
||||
buff_change,
|
||||
} => handle_buff(self, entity, buff_change),
|
||||
ServerEvent::EnergyChange { entity, change } => {
|
||||
handle_energy_change(&self, entity, change)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -20,12 +20,13 @@ use vek::*;
|
||||
|
||||
pub trait StateExt {
|
||||
/// Updates a component associated with the entity based on the `Effect`
|
||||
fn apply_effect(&mut self, entity: EcsEntity, effect: Effect);
|
||||
fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option<Uid>);
|
||||
/// Build a non-player character
|
||||
fn create_npc(
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
stats: comp::Stats,
|
||||
health: comp::Health,
|
||||
loadout: comp::Loadout,
|
||||
body: comp::Body,
|
||||
) -> EcsEntityBuilder;
|
||||
@ -70,13 +71,13 @@ pub trait StateExt {
|
||||
}
|
||||
|
||||
impl StateExt for State {
|
||||
fn apply_effect(&mut self, entity: EcsEntity, effect: Effect) {
|
||||
fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option<Uid>) {
|
||||
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()
|
||||
@ -84,6 +85,14 @@ impl StateExt for State {
|
||||
.get_mut(entity)
|
||||
.map(|stats| stats.exp.change_by(xp));
|
||||
},
|
||||
Effect::Damage(damage) => {
|
||||
let loadouts = self.ecs().read_storage::<comp::Loadout>();
|
||||
let change = damage.modify_damage(loadouts.get(entity), source);
|
||||
self.ecs()
|
||||
.write_storage::<comp::Health>()
|
||||
.get_mut(entity)
|
||||
.map(|health| health.change_by(change));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,6 +100,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 +117,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))
|
||||
@ -239,6 +250,10 @@ impl StateExt for State {
|
||||
z_max: body.height(),
|
||||
});
|
||||
self.write_component(entity, body);
|
||||
self.write_component(
|
||||
entity,
|
||||
comp::Health::new(stats.body_type, stats.level.level()),
|
||||
);
|
||||
self.write_component(entity, stats);
|
||||
self.write_component(entity, inventory);
|
||||
self.write_component(entity, loadout);
|
||||
|
@ -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,10 @@
|
||||
use common::{
|
||||
comp::{HealthSource, Object, PhysicsState, Pos, Vel},
|
||||
effect::Effect,
|
||||
event::{EventBus, ServerEvent},
|
||||
span,
|
||||
state::DeltaTime,
|
||||
Explosion,
|
||||
Damage, DamageSource, Explosion, RadiusEffect,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
|
||||
@ -48,16 +49,20 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::Explosion {
|
||||
pos: pos.0,
|
||||
explosion: Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Entity(
|
||||
None,
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 500.0,
|
||||
}),
|
||||
),
|
||||
RadiusEffect::TerrainDestruction(4.0),
|
||||
],
|
||||
radius: 12.0,
|
||||
max_damage: 500,
|
||||
min_damage: 100,
|
||||
max_heal: 0,
|
||||
min_heal: 0,
|
||||
terrain_destruction_power: 4.0,
|
||||
energy_regen: 0,
|
||||
},
|
||||
owner: *owner,
|
||||
friendly_damage: true,
|
||||
reagent: None,
|
||||
});
|
||||
}
|
||||
@ -71,16 +76,20 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::Explosion {
|
||||
pos: pos.0,
|
||||
explosion: Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Entity(
|
||||
None,
|
||||
Effect::Damage(Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: 50.0,
|
||||
}),
|
||||
),
|
||||
RadiusEffect::TerrainDestruction(4.0),
|
||||
],
|
||||
radius: 12.0,
|
||||
max_damage: 50,
|
||||
min_damage: 10,
|
||||
max_heal: 0,
|
||||
min_heal: 0,
|
||||
terrain_destruction_power: 4.0,
|
||||
energy_regen: 0,
|
||||
},
|
||||
owner: *owner,
|
||||
friendly_damage: true,
|
||||
reagent: Some(*reagent),
|
||||
});
|
||||
}
|
||||
|
@ -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,20 +73,16 @@ 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)
|
||||
// (maybe health changes could be sent to the client as a list
|
||||
// of events)
|
||||
if match health.last_change.1.cause {
|
||||
HealthSource::Attack { by }
|
||||
| HealthSource::Projectile { owner: Some(by) }
|
||||
| HealthSource::Energy { owner: Some(by) }
|
||||
| HealthSource::Explosion { owner: Some(by) }
|
||||
| HealthSource::Buff { owner: Some(by) }
|
||||
| HealthSource::Healing { by: Some(by) } => {
|
||||
HealthSource::Damage { by: Some(by), .. }
|
||||
| HealthSource::Heal { by: Some(by) } => {
|
||||
let by_me = my_uid.map_or(false, |&uid| by == uid);
|
||||
// If the attack was by me also reset this timer
|
||||
if by_me {
|
||||
@ -101,8 +106,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<_>>()
|
||||
|
@ -373,9 +373,9 @@ impl<'a> Widget for Chat<'a> {
|
||||
.localized_strings
|
||||
.get("hud.chat.pvp_energy_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::Player(_, KillType::Buff) => self
|
||||
KillSource::Player(_, KillType::Other) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.pvp_buff_kill_msg")
|
||||
.get("hud.chat.pvp_other_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::NonPlayer(_, KillType::Melee) => self
|
||||
.localized_strings
|
||||
@ -393,9 +393,9 @@ impl<'a> Widget for Chat<'a> {
|
||||
.localized_strings
|
||||
.get("hud.chat.npc_energy_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::NonPlayer(_, KillType::Buff) => self
|
||||
KillSource::NonPlayer(_, KillType::Other) => self
|
||||
.localized_strings
|
||||
.get("hud.chat.npc_buff_kill_msg")
|
||||
.get("hud.chat.npc_other_kill_msg")
|
||||
.to_string(),
|
||||
KillSource::Environment(_) => self
|
||||
.localized_strings
|
||||
|
@ -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::should_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 should_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 should_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 should_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