Added invulnerability debuff. Currently tied to campfire spawned with '/campfire' command.

This commit is contained in:
Sam 2021-02-28 15:02:03 -05:00
parent 9ce61ae814
commit a88ad7b971
18 changed files with 231 additions and 154 deletions

View File

@ -47,6 +47,13 @@ pub struct AttackerInfo<'a> {
pub energy: Option<&'a Energy>, pub energy: Option<&'a Energy>,
} }
#[cfg(not(target_arch = "wasm32"))]
pub struct TargetInfo<'a> {
pub entity: EcsEntity,
pub inventory: Option<&'a Inventory>,
pub stats: Option<&'a Stats>,
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive #[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
pub struct Attack { pub struct Attack {
@ -99,9 +106,8 @@ impl Attack {
pub fn apply_attack( pub fn apply_attack(
&self, &self,
target_group: GroupTarget, target_group: GroupTarget,
attacker_info: Option<AttackerInfo>, attacker: Option<AttackerInfo>,
target_entity: EcsEntity, target: TargetInfo,
target_inventory: Option<&Inventory>,
dir: Dir, dir: Dir,
target_dodging: bool, target_dodging: bool,
// Currently just modifies damage, maybe look into modifying strength of other effects? // Currently just modifies damage, maybe look into modifying strength of other effects?
@ -116,9 +122,10 @@ impl Attack {
.filter(|d| d.target.map_or(true, |t| t == target_group)) .filter(|d| d.target.map_or(true, |t| t == target_group))
.filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging)) .filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
{ {
let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats);
let change = damage.damage.calculate_health_change( let change = damage.damage.calculate_health_change(
target_inventory, damage_reduction,
attacker_info.map(|a| a.uid), attacker.map(|a| a.uid),
is_crit, is_crit,
self.crit_multiplier, self.crit_multiplier,
strength_modifier, strength_modifier,
@ -127,7 +134,7 @@ impl Attack {
accumulated_damage += applied_damage; accumulated_damage += applied_damage;
if change.amount != 0 { if change.amount != 0 {
emit(ServerEvent::Damage { emit(ServerEvent::Damage {
entity: target_entity, entity: target.entity,
change, change,
}); });
for effect in damage.effects.iter() { for effect in damage.effects.iter() {
@ -136,13 +143,13 @@ impl Attack {
let impulse = kb.calculate_impulse(dir); let impulse = kb.calculate_impulse(dir);
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
emit(ServerEvent::Knockback { emit(ServerEvent::Knockback {
entity: target_entity, entity: target.entity,
impulse, impulse,
}); });
} }
}, },
CombatEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::EnergyChange { emit(ServerEvent::EnergyChange {
entity: attacker_entity, entity: attacker_entity,
change: EnergyChange { change: EnergyChange {
@ -155,19 +162,19 @@ impl Attack {
CombatEffect::Buff(b) => { CombatEffect::Buff(b) => {
if thread_rng().gen::<f32>() < b.chance { if thread_rng().gen::<f32>() < b.chance {
emit(ServerEvent::Buff { emit(ServerEvent::Buff {
entity: target_entity, entity: target.entity,
buff_change: BuffChange::Add( buff_change: BuffChange::Add(
b.to_buff(attacker_info.map(|a| a.uid), applied_damage), b.to_buff(attacker.map(|a| a.uid), applied_damage),
), ),
}); });
} }
}, },
CombatEffect::Lifesteal(l) => { CombatEffect::Lifesteal(l) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
let change = HealthChange { let change = HealthChange {
amount: (applied_damage * l) as i32, amount: (applied_damage * l) as i32,
cause: HealthSource::Heal { cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid), by: attacker.map(|a| a.uid),
}, },
}; };
if change.amount != 0 { if change.amount != 0 {
@ -179,10 +186,10 @@ impl Attack {
} }
}, },
CombatEffect::Poise(p) => { CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(*p, target_inventory); let change = PoiseChange::from_value(*p, target.inventory);
if change.amount != 0 { if change.amount != 0 {
emit(ServerEvent::PoiseChange { emit(ServerEvent::PoiseChange {
entity: target_entity, entity: target.entity,
change, change,
kb_dir: *dir, kb_dir: *dir,
}); });
@ -192,18 +199,18 @@ impl Attack {
let change = HealthChange { let change = HealthChange {
amount: *h as i32, amount: *h as i32,
cause: HealthSource::Heal { cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid), by: attacker.map(|a| a.uid),
}, },
}; };
if change.amount != 0 { if change.amount != 0 {
emit(ServerEvent::Damage { emit(ServerEvent::Damage {
entity: target_entity, entity: target.entity,
change, change,
}); });
} }
}, },
CombatEffect::Combo(c) => { CombatEffect::Combo(c) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::ComboChange { emit(ServerEvent::ComboChange {
entity: attacker_entity, entity: attacker_entity,
change: *c, change: *c,
@ -227,7 +234,7 @@ impl Attack {
entity, entity,
energy: Some(e), energy: Some(e),
.. ..
}) = attacker_info }) = attacker
{ {
let sufficient_energy = e.current() as f32 >= *r; let sufficient_energy = e.current() as f32 >= *r;
if sufficient_energy { if sufficient_energy {
@ -252,13 +259,13 @@ impl Attack {
let impulse = kb.calculate_impulse(dir); let impulse = kb.calculate_impulse(dir);
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
emit(ServerEvent::Knockback { emit(ServerEvent::Knockback {
entity: target_entity, entity: target.entity,
impulse, impulse,
}); });
} }
}, },
CombatEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::EnergyChange { emit(ServerEvent::EnergyChange {
entity: attacker_entity, entity: attacker_entity,
change: EnergyChange { change: EnergyChange {
@ -271,19 +278,19 @@ impl Attack {
CombatEffect::Buff(b) => { CombatEffect::Buff(b) => {
if thread_rng().gen::<f32>() < b.chance { if thread_rng().gen::<f32>() < b.chance {
emit(ServerEvent::Buff { emit(ServerEvent::Buff {
entity: target_entity, entity: target.entity,
buff_change: BuffChange::Add( buff_change: BuffChange::Add(
b.to_buff(attacker_info.map(|a| a.uid), accumulated_damage), b.to_buff(attacker.map(|a| a.uid), accumulated_damage),
), ),
}); });
} }
}, },
CombatEffect::Lifesteal(l) => { CombatEffect::Lifesteal(l) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
let change = HealthChange { let change = HealthChange {
amount: (accumulated_damage * l) as i32, amount: (accumulated_damage * l) as i32,
cause: HealthSource::Heal { cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid), by: attacker.map(|a| a.uid),
}, },
}; };
if change.amount != 0 { if change.amount != 0 {
@ -295,10 +302,10 @@ impl Attack {
} }
}, },
CombatEffect::Poise(p) => { CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(p, target_inventory); let change = PoiseChange::from_value(p, target.inventory);
if change.amount != 0 { if change.amount != 0 {
emit(ServerEvent::PoiseChange { emit(ServerEvent::PoiseChange {
entity: target_entity, entity: target.entity,
change, change,
kb_dir: *dir, kb_dir: *dir,
}); });
@ -308,18 +315,18 @@ impl Attack {
let change = HealthChange { let change = HealthChange {
amount: h as i32, amount: h as i32,
cause: HealthSource::Heal { cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid), by: attacker.map(|a| a.uid),
}, },
}; };
if change.amount != 0 { if change.amount != 0 {
emit(ServerEvent::Damage { emit(ServerEvent::Damage {
entity: target_entity, entity: target.entity,
change, change,
}); });
} }
}, },
CombatEffect::Combo(c) => { CombatEffect::Combo(c) => {
if let Some(attacker_entity) = attacker_info.map(|a| a.entity) { if let Some(attacker_entity) = attacker.map(|a| a.entity) {
emit(ServerEvent::ComboChange { emit(ServerEvent::ComboChange {
entity: attacker_entity, entity: attacker_entity,
change: c, change: c,
@ -423,40 +430,49 @@ pub struct Damage {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
impl Damage { impl Damage {
/// Returns the total damage reduction provided by all equipped items /// Returns the total damage reduction provided by all equipped items
pub fn compute_damage_reduction(inventory: &Inventory) -> f32 { pub fn compute_damage_reduction(inventory: Option<&Inventory>, stats: Option<&Stats>) -> f32 {
let protection = inventory let invetory_dr = if let Some(inventory) = inventory {
.equipped_items() let protection = inventory
.filter_map(|item| { .equipped_items()
if let ItemKind::Armor(armor) = &item.kind() { .filter_map(|item| {
Some(armor.get_protection()) if let ItemKind::Armor(armor) = &item.kind() {
} else { Some(armor.get_protection())
None } else {
} None
}) }
.map(|protection| match protection { })
Protection::Normal(protection) => Some(protection), .map(|protection| match protection {
Protection::Invincible => None, Protection::Normal(protection) => Some(protection),
}) Protection::Invincible => None,
.sum::<Option<f32>>(); })
.sum::<Option<f32>>();
const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0;
match protection { match protection {
Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()),
None => 1.0, None => 1.0,
} }
} else {
0.0
};
let stats_dr = if let Some(stats) = stats {
stats.damage_reduction
} else {
0.0
};
1.0 - (1.0 - invetory_dr) * (1.0 - stats_dr)
} }
pub fn calculate_health_change( pub fn calculate_health_change(
self, self,
inventory: Option<&Inventory>, damage_reduction: f32,
uid: Option<Uid>, uid: Option<Uid>,
is_crit: bool, is_crit: bool,
crit_mult: f32, crit_mult: f32,
damage_modifier: f32, damage_modifier: f32,
) -> HealthChange { ) -> HealthChange {
let mut damage = self.value * damage_modifier; let mut damage = self.value * damage_modifier;
let damage_reduction = inventory.map_or(0.0, Damage::compute_damage_reduction);
match self.source { match self.source {
DamageSource::Melee => { DamageSource::Melee => {
// Critical hit // Critical hit
@ -705,7 +721,7 @@ pub fn combat_rating(
let defensive_weighting = 1.0; let defensive_weighting = 1.0;
let offensive_weighting = 1.0; let offensive_weighting = 1.0;
let defensive_rating = health.maximum() as f32 let defensive_rating = health.maximum() as f32
/ (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001) / (1.0 - Damage::compute_damage_reduction(Some(inventory), Some(stats))).max(0.00001)
/ 100.0; / 100.0;
let offensive_rating = offensive_rating(inventory, &stats.skill_set, msm).max(0.1) let offensive_rating = offensive_rating(inventory, &stats.skill_set, msm).max(0.1)
+ 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32; + 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32;

View File

@ -91,10 +91,12 @@ pub struct Auras {
} }
impl Auras { impl Auras {
pub fn new(aura: Aura) -> Self { pub fn new(auras: Vec<Aura>) -> Self {
let mut auras: SlotMap<AuraKey, Aura> = SlotMap::with_key(); let mut auras_comp: SlotMap<AuraKey, Aura> = SlotMap::with_key();
auras.insert(aura); for aura in auras {
Self { auras } auras_comp.insert(aura);
}
Self { auras: auras_comp }
} }
pub fn insert(&mut self, aura: Aura) { self.auras.insert(aura); } pub fn insert(&mut self, aura: Aura) { self.auras.insert(aura); }

View File

@ -31,6 +31,8 @@ pub enum BuffKind {
IncreaseMaxEnergy, IncreaseMaxEnergy,
/// Raises maximum health /// Raises maximum health
IncreaseMaxHealth, IncreaseMaxHealth,
/// Makes you immune to attacks
Invulnerability,
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -46,6 +48,7 @@ impl BuffKind {
BuffKind::CampfireHeal => true, BuffKind::CampfireHeal => true,
BuffKind::IncreaseMaxEnergy => true, BuffKind::IncreaseMaxEnergy => true,
BuffKind::IncreaseMaxHealth => true, BuffKind::IncreaseMaxHealth => true,
BuffKind::Invulnerability => true,
} }
} }
@ -98,9 +101,16 @@ pub enum BuffEffect {
kind: ModifierKind, kind: ModifierKind,
}, },
/// Changes maximum health by a certain amount /// Changes maximum health by a certain amount
MaxHealthModifier { value: f32, kind: ModifierKind }, MaxHealthModifier {
value: f32,
kind: ModifierKind,
},
/// Changes maximum stamina by a certain amount /// Changes maximum stamina by a certain amount
MaxEnergyModifier { value: f32, kind: ModifierKind }, MaxEnergyModifier {
value: f32,
kind: ModifierKind,
},
ImmuneToAttacks,
} }
/// Actual de/buff. /// Actual de/buff.
@ -203,6 +213,7 @@ impl Buff {
}], }],
data.duration, data.duration,
), ),
BuffKind::Invulnerability => (vec![BuffEffect::ImmuneToAttacks], data.duration),
}; };
Buff { Buff {
kind, kind,

View File

@ -23,7 +23,10 @@ impl Error for StatChangeError {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Stats { pub struct Stats {
pub name: String, pub name: String,
// TODO: Make skillset a separate component, probably too heavy for something that will
// potentially be updated every tick (especially as more buffs are added)
pub skill_set: SkillSet, pub skill_set: SkillSet,
pub damage_reduction: f32,
} }
impl Stats { impl Stats {
@ -31,6 +34,7 @@ impl Stats {
Self { Self {
name, name,
skill_set: SkillSet::default(), skill_set: SkillSet::default(),
damage_reduction: 0.0,
} }
} }
@ -40,6 +44,7 @@ impl Stats {
Self { Self {
name: "".to_owned(), name: "".to_owned(),
skill_set: SkillSet::default(), skill_set: SkillSet::default(),
damage_reduction: 0.0,
} }
} }
} }

View File

@ -1,8 +1,8 @@
use common::{ use common::{
combat::AttackerInfo, combat::{AttackerInfo, TargetInfo},
comp::{ comp::{
Beam, BeamSegment, Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, Pos, Beam, BeamSegment, Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, Pos,
Scale, Scale, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
@ -33,6 +33,7 @@ pub struct ReadData<'a> {
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
groups: ReadStorage<'a, Group>, groups: ReadStorage<'a, Group>,
energies: ReadStorage<'a, Energy>, energies: ReadStorage<'a, Energy>,
stats: ReadStorage<'a, Stats>,
} }
/// This system is responsible for handling beams that heal or do damage /// This system is responsible for handling beams that heal or do damage
@ -157,11 +158,16 @@ impl<'a> System<'a> for Sys {
energy: read_data.energies.get(entity), energy: read_data.energies.get(entity),
}); });
let target_info = TargetInfo {
entity: target,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
};
beam_segment.properties.attack.apply_attack( beam_segment.properties.attack.apply_attack(
target_group, target_group,
attacker_info, attacker_info,
target, target_info,
read_data.inventories.get(target),
ori.look_dir(), ori.look_dir(),
false, false,
1.0, 1.0,

View File

@ -1,7 +1,7 @@
use common::{ use common::{
comp::{ comp::{
Buff, BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, Energy, Health, Buff, BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, Energy, Health,
HealthChange, HealthSource, Inventory, ModifierKind, HealthChange, HealthSource, Inventory, ModifierKind, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::DeltaTime, resources::DeltaTime,
@ -27,17 +27,29 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Health>, WriteStorage<'a, Health>,
WriteStorage<'a, Energy>, WriteStorage<'a, Energy>,
WriteStorage<'a, Buffs>, WriteStorage<'a, Buffs>,
WriteStorage<'a, Stats>,
); );
fn run(&mut self, (read_data, mut healths, mut energies, mut buffs): Self::SystemData) { fn run(
&mut self,
(read_data, mut healths, mut energies, mut buffs, mut stats): Self::SystemData,
) {
let mut server_emitter = read_data.server_bus.emitter(); let mut server_emitter = read_data.server_bus.emitter();
let dt = read_data.dt.0; let dt = read_data.dt.0;
// Set to false to avoid spamming server // Set to false to avoid spamming server
buffs.set_event_emission(false); buffs.set_event_emission(false);
healths.set_event_emission(false); healths.set_event_emission(false);
energies.set_event_emission(false); energies.set_event_emission(false);
for (entity, mut buff_comp, mut health, mut energy) in healths.set_event_emission(false);
(&read_data.entities, &mut buffs, &mut healths, &mut energies).join() stats.set_event_emission(false);
for (entity, mut buff_comp, mut health, mut energy, mut stat) in (
&read_data.entities,
&mut buffs,
&mut healths,
&mut energies,
&mut stats,
)
.join()
{ {
let (buff_comp_kinds, buff_comp_buffs) = buff_comp.parts(); let (buff_comp_kinds, buff_comp_buffs) = buff_comp.parts();
let mut expired_buffs = Vec::<BuffId>::new(); let mut expired_buffs = Vec::<BuffId>::new();
@ -61,13 +73,12 @@ impl<'a> System<'a> for Sys {
} }
} }
if let Some(inventory) = read_data.inventories.get(entity) { let damage_reduction =
let damage_reduction = Damage::compute_damage_reduction(inventory); Damage::compute_damage_reduction(read_data.inventories.get(entity), Some(&stat));
if (damage_reduction - 1.0).abs() < f32::EPSILON { if (damage_reduction - 1.0).abs() < f32::EPSILON {
for (id, buff) in buff_comp.buffs.iter() { for (id, buff) in buff_comp.buffs.iter() {
if !buff.kind.is_buff() { if !buff.kind.is_buff() {
expired_buffs.push(*id); expired_buffs.push(*id);
}
} }
} }
} }
@ -77,6 +88,7 @@ impl<'a> System<'a> for Sys {
energy.last_set(); energy.last_set();
health.reset_max(); health.reset_max();
energy.reset_max(); energy.reset_max();
stat.damage_reduction = 0.0;
// Iterator over the lists of buffs by kind // Iterator over the lists of buffs by kind
let buff_comp = &mut *buff_comp; let buff_comp = &mut *buff_comp;
@ -147,6 +159,9 @@ impl<'a> System<'a> for Sys {
energy.set_maximum(new_max); energy.set_maximum(new_max);
}, },
}, },
BuffEffect::ImmuneToAttacks => {
stat.damage_reduction = 1.0;
},
}; };
} }
} }
@ -176,6 +191,7 @@ impl<'a> System<'a> for Sys {
buffs.set_event_emission(true); buffs.set_event_emission(true);
healths.set_event_emission(true); healths.set_event_emission(true);
energies.set_event_emission(true); energies.set_event_emission(true);
stats.set_event_emission(true);
} }
} }

View File

@ -1,6 +1,6 @@
use common::{ use common::{
combat::AttackerInfo, combat::{AttackerInfo, TargetInfo},
comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale}, comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
span, span,
@ -29,6 +29,7 @@ pub struct ReadData<'a> {
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
server_bus: Read<'a, EventBus<ServerEvent>>, server_bus: Read<'a, EventBus<ServerEvent>>,
metrics: ReadExpect<'a, SysMetrics>, metrics: ReadExpect<'a, SysMetrics>,
stats: ReadStorage<'a, Stats>,
} }
/// This system is responsible for handling accepted inputs like moving or /// This system is responsible for handling accepted inputs like moving or
@ -114,11 +115,16 @@ impl<'a> System<'a> for Sys {
energy: read_data.energies.get(attacker), energy: read_data.energies.get(attacker),
}); });
let target_info = TargetInfo {
entity: target,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
};
melee_attack.attack.apply_attack( melee_attack.attack.apply_attack(
target_group, target_group,
attacker_info, attacker_info,
target, target_info,
read_data.inventories.get(target),
dir, dir,
is_dodge, is_dodge,
1.0, 1.0,

View File

@ -1,7 +1,8 @@
use common::{ use common::{
combat::AttackerInfo, combat::{AttackerInfo, TargetInfo},
comp::{ comp::{
projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Vel, projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile,
Stats, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
@ -30,6 +31,7 @@ pub struct ReadData<'a> {
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
groups: ReadStorage<'a, Group>, groups: ReadStorage<'a, Group>,
energies: ReadStorage<'a, Energy>, energies: ReadStorage<'a, Energy>,
stats: ReadStorage<'a, Stats>,
} }
/// This system is responsible for handling projectile effect triggers /// This system is responsible for handling projectile effect triggers
@ -93,7 +95,7 @@ impl<'a> System<'a> for Sys {
for effect in projectile.hit_entity.drain(..) { for effect in projectile.hit_entity.drain(..) {
match effect { match effect {
projectile::Effect::Attack(attack) => { projectile::Effect::Attack(attack) => {
if let Some(target_entity) = read_data if let Some(target) = read_data
.uid_allocator .uid_allocator
.retrieve_entity_internal(other.into()) .retrieve_entity_internal(other.into())
{ {
@ -110,11 +112,16 @@ impl<'a> System<'a> for Sys {
} }
}); });
let target_info = TargetInfo {
entity: target,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
};
attack.apply_attack( attack.apply_attack(
target_group, target_group,
attacker_info, attacker_info,
target_entity, target_info,
read_data.inventories.get(target_entity),
ori.look_dir(), ori.look_dir(),
false, false,
1.0, 1.0,

View File

@ -1,8 +1,8 @@
use common::{ use common::{
combat::AttackerInfo, combat::{AttackerInfo, TargetInfo},
comp::{ comp::{
Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale, Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale,
Shockwave, ShockwaveHitEntities, Shockwave, ShockwaveHitEntities, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
@ -34,6 +34,7 @@ pub struct ReadData<'a> {
groups: ReadStorage<'a, Group>, groups: ReadStorage<'a, Group>,
physics_states: ReadStorage<'a, PhysicsState>, physics_states: ReadStorage<'a, PhysicsState>,
energies: ReadStorage<'a, Energy>, energies: ReadStorage<'a, Energy>,
stats: ReadStorage<'a, Stats>,
} }
/// This system is responsible for handling accepted inputs like moving or /// This system is responsible for handling accepted inputs like moving or
@ -176,11 +177,16 @@ impl<'a> System<'a> for Sys {
energy: read_data.energies.get(entity), energy: read_data.energies.get(entity),
}); });
let target_info = TargetInfo {
entity: target,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
};
shockwave.properties.attack.apply_attack( shockwave.properties.attack.apply_attack(
target_group, target_group,
attacker_info, attacker_info,
target, target_info,
read_data.inventories.get(target),
dir, dir,
false, false,
1.0, 1.0,

View File

@ -975,17 +975,30 @@ fn handle_spawn_campfire(
animated: true, animated: true,
}) })
.with(WaypointArea::default()) .with(WaypointArea::default())
.with(comp::Auras::new(Aura::new( .with(comp::Auras::new(vec![
AuraKind::Buff { Aura::new(
kind: BuffKind::CampfireHeal, AuraKind::Buff {
data: BuffData::new(0.02, Some(Duration::from_secs(1))), kind: BuffKind::CampfireHeal,
category: BuffCategory::Natural, data: BuffData::new(0.02, Some(Duration::from_secs(1))),
source: BuffSource::World, category: BuffCategory::Natural,
}, source: BuffSource::World,
5.0, },
None, 5.0,
AuraTarget::All, None,
))) AuraTarget::All,
),
Aura::new(
AuraKind::Buff {
kind: BuffKind::Invulnerability,
data: BuffData::new(1.0, Some(Duration::from_secs(1))),
category: BuffCategory::Natural,
source: BuffSource::World,
},
100.0,
None,
AuraTarget::All,
),
]))
.build(); .build();
server.notify_client( server.notify_client(

View File

@ -191,7 +191,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
}) })
.with(WaypointArea::default()) .with(WaypointArea::default())
.with(comp::Mass(10_f32.powi(10))) .with(comp::Mass(10_f32.powi(10)))
.with(comp::Auras::new(Aura::new( .with(comp::Auras::new(vec![Aura::new(
AuraKind::Buff { AuraKind::Buff {
kind: BuffKind::CampfireHeal, kind: BuffKind::CampfireHeal,
data: BuffData::new(0.02, Some(Duration::from_secs(1))), data: BuffData::new(0.02, Some(Duration::from_secs(1))),
@ -201,6 +201,6 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
5.0, 5.0,
None, None,
AuraTarget::All, AuraTarget::All,
))) )]))
.build(); .build();
} }

View File

@ -511,14 +511,16 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
if vel.z <= -30.0 { if vel.z <= -30.0 {
let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0; let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0;
let inventories = state.ecs().read_storage::<Inventory>(); let inventories = state.ecs().read_storage::<Inventory>();
let stats = state.ecs().read_storage::<Stats>();
// Handle health change // Handle health change
if let Some(mut health) = state.ecs().write_storage::<comp::Health>().get_mut(entity) { if let Some(mut health) = state.ecs().write_storage::<comp::Health>().get_mut(entity) {
let damage = Damage { let damage = Damage {
source: DamageSource::Falling, source: DamageSource::Falling,
value: falldmg, value: falldmg,
}; };
let change = let damage_reduction =
damage.calculate_health_change(inventories.get(entity), None, false, 0.0, 1.0); Damage::compute_damage_reduction(inventories.get(entity), stats.get(entity));
let change = damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0);
health.change_by(change); health.change_by(change);
} }
// Handle poise change // Handle poise change
@ -674,14 +676,15 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
}, },
RadiusEffect::Attack(attack) => { RadiusEffect::Attack(attack) => {
let energies = &ecs.read_storage::<comp::Energy>(); let energies = &ecs.read_storage::<comp::Energy>();
for (entity_b, pos_b, _health_b, inventory_b_maybe) in ( for (entity_b, pos_b, _health_b, inventory_b_maybe, stats_b_maybe) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<comp::Pos>(), &ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::Health>(), &ecs.read_storage::<comp::Health>(),
ecs.read_storage::<comp::Inventory>().maybe(), ecs.read_storage::<comp::Inventory>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(),
) )
.join() .join()
.filter(|(_, _, h, _)| !h.is_dead) .filter(|(_, _, h, _, _)| !h.is_dead)
{ {
// Check if it is a hit // Check if it is a hit
let distance_squared = pos.distance_squared(pos_b.0); let distance_squared = pos.distance_squared(pos_b.0);
@ -714,13 +717,18 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
energy: energies.get(entity), energy: energies.get(entity),
}); });
let target_info = combat::TargetInfo {
entity: entity_b,
inventory: inventory_b_maybe,
stats: stats_b_maybe,
};
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>(); let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
attack.apply_attack( attack.apply_attack(
target_group, target_group,
attacker_info, attacker_info,
entity_b, target_info,
inventory_b_maybe,
dir, dir,
false, false,
strength, strength,

View File

@ -4,6 +4,7 @@ use crate::{
}; };
use common::{ use common::{
character::CharacterId, character::CharacterId,
combat,
comp::{ comp::{
self, self,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
@ -89,8 +90,12 @@ impl StateExt for State {
}, },
Effect::Damage(damage) => { Effect::Damage(damage) => {
let inventories = self.ecs().read_storage::<Inventory>(); let inventories = self.ecs().read_storage::<Inventory>();
let stats = self.ecs().read_storage::<comp::Stats>();
let change = damage.calculate_health_change( let change = damage.calculate_health_change(
inventories.get(entity), combat::Damage::compute_damage_reduction(
inventories.get(entity),
stats.get(entity),
),
source, source,
false, false,
0.0, 0.0,

View File

@ -457,7 +457,8 @@ impl<'a> Widget for Bag<'a> {
}); });
let protection_txt = format!( let protection_txt = format!(
"{}%", "{}%",
(100.0 * Damage::compute_damage_reduction(inventory)) as i32 (100.0 * Damage::compute_damage_reduction(Some(inventory), Some(self.stats)))
as i32
); );
let health_txt = format!("{}", (self.health.maximum() as f32 / 10.0) as usize); let health_txt = format!("{}", (self.health.maximum() as f32 / 10.0) as usize);
let stamina_txt = format!("{}", (self.energy.maximum() as f32 / 10.0) as usize); let stamina_txt = format!("{}", (self.energy.maximum() as f32 / 10.0) as usize);

View File

@ -3,7 +3,7 @@ use super::{
BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR, BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR,
}; };
use crate::{ use crate::{
hud::{get_buff_info, BuffPosition}, hud::{get_buff_image, get_buff_info, BuffPosition},
i18n::Localization, i18n::Localization,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
GlobalState, GlobalState,
@ -201,15 +201,7 @@ impl<'a> Widget for BuffsBar<'a> {
max_duration max_duration
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0) .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
}) as u32; // Percentage to determine which frame of the timer overlay is displayed }) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.kind { let buff_img = get_buff_image(buff.kind, self.imgs);
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
BuffKind::Saturation { .. } => self.imgs.buff_saturation_0,
BuffKind::Potion { .. } => self.imgs.buff_potion_0,
BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0,
BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0,
BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0,
_ => self.imgs.missing_icon,
};
let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 11 slots // Sort buffs into rows of 11 slots
let x = i % 6; let x = i % 6;
@ -437,16 +429,7 @@ impl<'a> Widget for BuffsBar<'a> {
max_duration max_duration
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0) .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
}) as u32; }) as u32;
let buff_img = match buff.kind { let buff_img = get_buff_image(buff.kind, &self.imgs);
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
BuffKind::Saturation { .. } => self.imgs.buff_saturation_0,
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
BuffKind::Potion { .. } => self.imgs.buff_potion_0,
BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0,
BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0,
BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0,
};
let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 6 slots // Sort buffs into rows of 6 slots
let x = i % 6; let x = i % 6;

View File

@ -6,7 +6,7 @@ use super::{
}; };
use crate::{ use crate::{
hud::get_buff_info, hud::{get_buff_image, get_buff_info},
i18n::Localization, i18n::Localization,
settings::Settings, settings::Settings,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
@ -504,20 +504,7 @@ impl<'a> Widget for Group<'a> {
cur.as_secs_f32() / max.as_secs_f32() * 1000.0 cur.as_secs_f32() / max.as_secs_f32() * 1000.0
}) })
}) as u32; // Percentage to determine which frame of the timer overlay is displayed }) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.kind { let buff_img = get_buff_image(buff.kind, &self.imgs);
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
BuffKind::Saturation { .. } => self.imgs.buff_saturation_0,
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
BuffKind::Potion { .. } => self.imgs.buff_potion_0,
BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0,
BuffKind::IncreaseMaxEnergy { .. } => {
self.imgs.buff_energyplus_0
},
BuffKind::IncreaseMaxHealth { .. } => {
self.imgs.buff_healthplus_0
},
};
let buff_widget = Image::new(buff_img).w_h(15.0, 15.0); let buff_widget = Image::new(buff_img).w_h(15.0, 15.0);
let buff_widget = if let Some(id) = prev_id { let buff_widget = if let Some(id) = prev_id {
buff_widget.right_from(id, 1.0) buff_widget.right_from(id, 1.0)

View File

@ -3170,3 +3170,17 @@ pub fn cr_color(combat_rating: f32) -> Color {
_ => XP_COLOR, _ => XP_COLOR,
} }
} }
pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
match buff {
BuffKind::Regeneration { .. } => imgs.buff_plus_0,
BuffKind::Saturation { .. } => imgs.buff_saturation_0,
BuffKind::Bleeding { .. } => imgs.debuff_bleed_0,
BuffKind::Cursed { .. } => imgs.debuff_skull_0,
BuffKind::Potion { .. } => imgs.buff_potion_0,
BuffKind::CampfireHeal { .. } => imgs.buff_campfire_heal_0,
BuffKind::IncreaseMaxEnergy { .. } => imgs.buff_energyplus_0,
BuffKind::IncreaseMaxHealth { .. } => imgs.buff_healthplus_0,
BuffKind::Invulnerability => imgs.buff_plus_0,
}
}

View File

@ -4,12 +4,12 @@ use super::{
TEXT_COLOR, TEXT_COLOR,
}; };
use crate::{ use crate::{
hud::get_buff_info, hud::{get_buff_image, get_buff_info},
i18n::Localization, i18n::Localization,
settings::GameplaySettings, settings::GameplaySettings,
ui::{fonts::Fonts, Ingameable}, ui::{fonts::Fonts, Ingameable},
}; };
use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType}; use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
use conrod_core::{ use conrod_core::{
color, color,
position::Align, position::Align,
@ -236,16 +236,7 @@ impl<'a> Widget for Overhead<'a> {
cur.as_secs_f32() / max.as_secs_f32() * 1000.0 cur.as_secs_f32() / max.as_secs_f32() * 1000.0
}) })
}) as u32; // Percentage to determine which frame of the timer overlay is displayed }) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.kind { let buff_img = get_buff_image(buff.kind, &self.imgs);
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
BuffKind::Saturation { .. } => self.imgs.buff_saturation_0,
BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffKind::Cursed { .. } => self.imgs.debuff_skull_0,
BuffKind::Potion { .. } => self.imgs.buff_potion_0,
BuffKind::CampfireHeal { .. } => self.imgs.buff_campfire_heal_0,
BuffKind::IncreaseMaxEnergy { .. } => self.imgs.buff_energyplus_0,
BuffKind::IncreaseMaxHealth { .. } => self.imgs.buff_healthplus_0,
};
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 5 slots // Sort buffs into rows of 5 slots
let x = i % 5; let x = i % 5;