Changed buff effects so they did not need to mutably change buffs every tick. Buff system now no longer mutably accesses buffs component.

This commit is contained in:
Sam 2023-03-08 23:10:24 -05:00
parent b1b41e95f6
commit 9efac9957d
11 changed files with 138 additions and 119 deletions

View File

@ -9,7 +9,7 @@ ItemDef(
data: (
strength: 100.0,
duration: Some(1),
),
),
cat_ids: [Natural],
)),
Buff((
@ -17,7 +17,6 @@ ItemDef(
data: (
strength: 0.33,
duration: Some(45),
delay: Some(1)
),
cat_ids: [Natural],
)),

View File

@ -9,7 +9,7 @@ ItemDef(
data: (
strength: 75.0,
duration: Some(1),
),
),
cat_ids: [Natural],
)),
Buff((
@ -17,7 +17,6 @@ ItemDef(
data: (
strength: 0.33,
duration: Some(45),
delay: Some(1)
),
cat_ids: [Natural],
)),

View File

@ -9,7 +9,7 @@ ItemDef(
data: (
strength: 50.0,
duration: Some(1),
),
),
cat_ids: [Natural],
)),
Buff((
@ -17,7 +17,6 @@ ItemDef(
data: (
strength: 0.33,
duration: Some(45),
delay: Some(1)
),
cat_ids: [Natural],
)),

View File

@ -365,6 +365,7 @@ impl Attack {
buff_change: BuffChange::Add(b.to_buff(
time,
attacker.map(|a| a.uid),
target.stats,
applied_damage,
strength_modifier,
)),
@ -532,6 +533,7 @@ impl Attack {
buff_change: BuffChange::Add(b.to_buff(
time,
attacker.map(|a| a.uid),
target.stats,
accumulated_damage,
strength_modifier,
)),
@ -1029,7 +1031,14 @@ impl MulAssign<f32> for CombatBuffStrength {
#[cfg(not(target_arch = "wasm32"))]
impl CombatBuff {
fn to_buff(self, time: Time, uid: Option<Uid>, damage: f32, strength_modifier: f32) -> Buff {
fn to_buff(
self,
time: Time,
uid: Option<Uid>,
stats: Option<&Stats>,
damage: f32,
strength_modifier: f32,
) -> Buff {
// TODO: Generate BufCategoryId vec (probably requires damage overhaul?)
let source = if let Some(uid) = uid {
BuffSource::Character { by: uid }
@ -1046,6 +1055,7 @@ impl CombatBuff {
Vec::new(),
source,
time,
stats,
)
}
}

View File

@ -1,5 +1,5 @@
#![allow(clippy::nonstandard_macro_braces)] //tmp as of false positive !?
use crate::{resources::Time, uid::Uid};
use crate::{comp::Stats, resources::Time, uid::Uid};
use core::cmp::Ordering;
#[cfg(not(target_arch = "wasm32"))]
use hashbrown::HashMap;
@ -183,16 +183,11 @@ pub enum BuffEffect {
/// Periodically damages or heals entity
HealthChangeOverTime {
rate: f32,
accumulated: f32,
kind: ModifierKind,
instance: u64,
},
/// Periodically consume entity energy
EnergyChangeOverTime {
rate: f32,
accumulated: f32,
kind: ModifierKind,
},
EnergyChangeOverTime { rate: f32, kind: ModifierKind },
/// Changes maximum health by a certain amount
MaxHealthModifier { value: f32, kind: ModifierKind },
/// Changes maximum energy by a certain amount
@ -204,7 +199,6 @@ pub enum BuffEffect {
rate: f32,
kind: ModifierKind,
target_fraction: f32,
achieved_fraction: Option<f32>,
},
/// Modifies move speed of target
MovementSpeed(f32),
@ -215,7 +209,7 @@ pub enum BuffEffect {
/// Reduces poise damage taken after armor is accounted for by this fraction
PoiseReduction(f32),
/// Reduces amount healed by consumables
HealReduction { rate: f32 },
HealReduction(f32),
}
/// Actual de/buff.
@ -271,6 +265,7 @@ impl Buff {
cat_ids: Vec<BuffCategory>,
source: BuffSource,
time: Time,
stats: Option<&Stats>,
) -> Self {
// Normalized nonlinear scaling
let nn_scaling = |a| a / (a + 0.5);
@ -278,21 +273,25 @@ impl Buff {
let effects = match kind {
BuffKind::Bleeding => vec![BuffEffect::HealthChangeOverTime {
rate: -data.strength,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
}],
BuffKind::Regeneration | BuffKind::Saturation | BuffKind::Potion => {
BuffKind::Regeneration | BuffKind::Saturation => {
vec![BuffEffect::HealthChangeOverTime {
rate: data.strength,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
}]
},
BuffKind::Potion => {
vec![BuffEffect::HealthChangeOverTime {
rate: data.strength * dbg!(stats.map_or(1.0, |s| s.heal_multiplier)),
kind: ModifierKind::Additive,
instance,
}]
},
BuffKind::CampfireHeal => vec![BuffEffect::HealthChangeOverTime {
rate: data.strength,
accumulated: 0.0,
kind: ModifierKind::Fractional,
instance,
}],
@ -301,18 +300,15 @@ impl Buff {
rate: -1.0,
kind: ModifierKind::Additive,
target_fraction: 1.0 - data.strength,
achieved_fraction: None,
},
BuffEffect::HealthChangeOverTime {
rate: -1.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
},
],
BuffKind::EnergyRegen => vec![BuffEffect::EnergyChangeOverTime {
rate: data.strength,
accumulated: 0.0,
kind: ModifierKind::Additive,
}],
BuffKind::IncreaseMaxEnergy => vec![BuffEffect::MaxEnergyModifier {
@ -332,20 +328,17 @@ impl Buff {
)],
BuffKind::Burning => vec![BuffEffect::HealthChangeOverTime {
rate: -data.strength,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
}],
BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime {
rate: -data.strength,
accumulated: 0.0,
kind: ModifierKind::Additive,
}],
BuffKind::Crippled => vec![
BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)),
BuffEffect::HealthChangeOverTime {
rate: -data.strength * 4.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
},
@ -354,7 +347,6 @@ impl Buff {
BuffEffect::MovementSpeed(1.0 + data.strength),
BuffEffect::HealthChangeOverTime {
rate: data.strength * 10.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
instance,
},
@ -371,9 +363,7 @@ impl Buff {
],
BuffKind::Fortitude => vec![BuffEffect::PoiseReduction(data.strength)],
BuffKind::Parried => vec![BuffEffect::AttackSpeed(0.5)],
BuffKind::PotionSickness => vec![BuffEffect::HealReduction {
rate: data.strength,
}],
BuffKind::PotionSickness => vec![BuffEffect::HealReduction(data.strength)],
};
let start_time = Time(time.0 + data.delay.unwrap_or(0.0));
Buff {
@ -495,7 +485,9 @@ impl Buffs {
self.kinds.entry(kind).or_default().push(id);
self.buffs.insert(id, buff);
self.sort_kind(kind);
self.delay_queueable_buffs(kind, current_time);
if kind.queues() {
self.delay_queueable_buffs(kind, current_time);
}
id
}
@ -548,6 +540,7 @@ impl Buffs {
fn delay_queueable_buffs(&mut self, kind: BuffKind, current_time: Time) {
let mut next_start_time: Option<Time> = None;
debug_assert!(kind.queues());
if let Some(buffs) = self.kinds.get(&kind) {
buffs.iter().for_each(|id| {
if let Some(buff) = self.buffs.get_mut(id) {

View File

@ -70,6 +70,7 @@ impl CharacterBehavior for Data {
Vec::new(),
BuffSource::Character { by: *data.uid },
*data.time,
Some(data.stats),
);
output_events.emit_server(ServerEvent::Buff {
entity: data.entity,

View File

@ -4,7 +4,7 @@ use common::{
aura::{AuraChange, AuraKey, AuraKind, AuraTarget},
buff::{Buff, BuffCategory, BuffChange, BuffSource},
group::Group,
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos,
Alignment, Aura, Auras, BuffKind, Buffs, CharacterState, Health, Player, Pos, Stats,
},
event::{Emitter, EventBus, ServerEvent},
resources::{DeltaTime, Time},
@ -32,6 +32,7 @@ pub struct ReadData<'a> {
healths: ReadStorage<'a, Health>,
groups: ReadStorage<'a, Group>,
uids: ReadStorage<'a, Uid>,
stats: ReadStorage<'a, Stats>,
}
#[derive(Default)]
@ -97,10 +98,16 @@ impl<'a> System<'a> for Sys {
.and_then(|l| read_data.healths.get(target).map(|r| (l, r)))
.and_then(|l| read_data.uids.get(target).map(|r| (l, r)))
.map(|((target_pos, health), target_uid)| {
(target, target_pos, health, target_uid)
(
target,
target_pos,
health,
target_uid,
read_data.stats.get(target),
)
})
});
target_iter.for_each(|(target, target_pos, health, target_uid)| {
target_iter.for_each(|(target, target_pos, health, target_uid, stats)| {
let mut target_buffs = match buffs.get_mut(target) {
Some(buff) => buff,
None => return,
@ -132,6 +139,7 @@ impl<'a> System<'a> for Sys {
target,
health,
&mut target_buffs,
stats,
&read_data,
&mut server_emitter,
);
@ -158,6 +166,7 @@ fn activate_aura(
target: EcsEntity,
health: &Health,
target_buffs: &mut Buffs,
stats: Option<&Stats>,
read_data: &ReadData,
server_emitter: &mut Emitter<ServerEvent>,
) {
@ -255,6 +264,7 @@ fn activate_aura(
vec![category, BuffCategory::FromAura(true)],
source,
*read_data.time,
stats,
)),
});
}

View File

@ -38,6 +38,7 @@ pub struct ReadData<'a> {
uid_allocator: Read<'a, UidAllocator>,
time: Read<'a, Time>,
msm: ReadExpect<'a, MaterialStatManifest>,
buffs: ReadStorage<'a, Buffs>,
}
#[derive(Default)]
@ -45,7 +46,6 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadData<'a>,
WriteStorage<'a, Buffs>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Body>,
WriteStorage<'a, LightEmitter>,
@ -57,12 +57,11 @@ impl<'a> System<'a> for Sys {
fn run(
job: &mut Job<Self>,
(read_data, mut buffs, mut stats, mut bodies, mut light_emitters): Self::SystemData,
(read_data, mut stats, mut bodies, mut light_emitters): Self::SystemData,
) {
let mut server_emitter = read_data.server_bus.emitter();
let dt = read_data.dt.0;
// Set to false to avoid spamming server
buffs.set_event_emission(false);
stats.set_event_emission(false);
// Put out underwater campfires. Logically belongs here since this system also
@ -123,9 +122,9 @@ impl<'a> System<'a> for Sys {
}
}
for (entity, mut buff_comp, mut stat, health, energy, physics_state) in (
for (entity, buff_comp, mut stat, health, energy, physics_state) in (
&read_data.entities,
&mut buffs,
&read_data.buffs,
&mut stats,
&read_data.healths,
&read_data.energies,
@ -148,6 +147,7 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
)),
});
}
@ -164,6 +164,7 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
)),
});
}
@ -180,6 +181,7 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
)),
});
// When standing on IceSpike also apply Frozen
@ -191,6 +193,7 @@ impl<'a> System<'a> for Sys {
Vec::new(),
BuffSource::World,
*read_data.time,
Some(&stat),
)),
});
}
@ -210,6 +213,7 @@ impl<'a> System<'a> for Sys {
vec![BuffCategory::Natural],
BuffSource::World,
*read_data.time,
Some(&stat),
)),
});
} else if matches!(
@ -253,7 +257,6 @@ impl<'a> System<'a> for Sys {
stat.reset_temp_modifiers();
// Iterator over the lists of buffs by kind
let buff_comp = &mut *buff_comp;
let mut buff_kinds = buff_comp
.kinds
.iter()
@ -270,7 +273,7 @@ impl<'a> System<'a> for Sys {
active_buff_ids.push(buff_ids[0]);
}
for buff_id in active_buff_ids.into_iter() {
if let Some(buff) = buff_comp.buffs.get_mut(&buff_id) {
if let Some(buff) = buff_comp.buffs.get(&buff_id) {
// Skip the effect of buffs whose start delay hasn't expired.
if buff.start_time.0 > read_data.time.0 {
continue;
@ -283,11 +286,11 @@ impl<'a> System<'a> for Sys {
};
// Now, execute the buff, based on it's delta
for effect in &mut buff.effects {
for effect in &buff.effects {
execute_effect(
effect,
buff.kind,
buff.end_time,
buff.start_time,
&read_data,
&mut stat,
health,
@ -297,6 +300,7 @@ impl<'a> System<'a> for Sys {
&mut server_emitter,
dt,
*read_data.time,
expired_buffs.contains(&buff_id),
);
}
}
@ -324,15 +328,14 @@ impl<'a> System<'a> for Sys {
}
}
// Turned back to true
buffs.set_event_emission(true);
stats.set_event_emission(true);
}
}
fn execute_effect(
effect: &mut BuffEffect,
effect: &BuffEffect,
buff_kind: BuffKind,
buff_end_time: Option<Time>,
buff_start_time: Time,
read_data: &ReadData,
stat: &mut Stats,
health: &Health,
@ -342,28 +345,36 @@ fn execute_effect(
server_emitter: &mut Emitter<ServerEvent>,
dt: f32,
time: Time,
buff_will_expire: bool,
) {
match effect {
BuffEffect::HealthChangeOverTime {
rate,
accumulated,
kind,
instance,
} => {
*accumulated += *rate * dt;
// Apply health change only once per second, per health, or
// when a buff is removed
if accumulated.abs() > rate.abs().min(1.0)
|| buff_end_time.map_or(false, |end| end.0 < time.0)
{
let (cause, by) = if *accumulated != 0.0 {
// Apply health change if buff wasn't just added, only once per second or when
// buff is about to be removed
let time_passed = (time.0 - buff_start_time.0) as f32;
let one_second = (time_passed % 1.0) < dt;
if time_passed > dt && (one_second || buff_will_expire) {
let amount = if one_second {
// If buff not expiring this tick, then 1 second has passed
*rate
} else {
// If buff expiring this tick, only a fraction of the second has passed since
// last tick
((time.0 - buff_start_time.0) % 1.0) as f32 * rate
};
let (cause, by) = if amount != 0.0 {
(Some(DamageSource::Buff(buff_kind)), buff_owner)
} else {
(None, None)
};
let mut amount = match *kind {
ModifierKind::Additive => *accumulated,
ModifierKind::Fractional => health.maximum() * *accumulated,
let amount = match *kind {
ModifierKind::Additive => amount,
ModifierKind::Fractional => health.maximum() * amount,
};
let damage_contributor = by.and_then(|uid| {
read_data
@ -373,9 +384,6 @@ fn execute_effect(
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
})
});
if amount > 0.0 && matches!(buff_kind, BuffKind::Potion) {
amount *= stat.heal_multiplier;
}
server_emitter.emit(ServerEvent::HealthChange {
entity,
change: HealthChange {
@ -387,29 +395,31 @@ fn execute_effect(
instance: *instance,
},
});
*accumulated = 0.0;
};
},
BuffEffect::EnergyChangeOverTime {
rate,
accumulated,
kind,
} => {
*accumulated += *rate * dt;
// Apply energy change only once per second, per energy, or
// when a buff is removed
if accumulated.abs() > rate.abs().min(10.0)
|| buff_end_time.map_or(false, |end| end.0 < time.0)
{
BuffEffect::EnergyChangeOverTime { rate, kind } => {
// Apply energy change if buff wasn't just added, only once per second or when
// buff is about to be removed
let time_passed = (time.0 - buff_start_time.0) as f32;
let one_second = (time_passed % 1.0) < dt;
if time_passed > dt && (one_second || buff_will_expire) {
let amount = if one_second {
// If buff not expiring this tick, then 1 second has passed
*rate
} else {
// If buff expiring this tick, only a fraction of the second has passed since
// last tick
((time.0 - buff_start_time.0) % 1.0) as f32 * rate
};
let amount = match *kind {
ModifierKind::Additive => *accumulated,
ModifierKind::Fractional => energy.maximum() * *accumulated,
ModifierKind::Additive => amount,
ModifierKind::Fractional => energy.maximum() * amount,
};
server_emitter.emit(ServerEvent::EnergyChange {
entity,
change: amount,
});
*accumulated = 0.0;
};
},
BuffEffect::MaxHealthModifier { value, kind } => match kind {
@ -436,60 +446,46 @@ fn execute_effect(
rate,
kind,
target_fraction,
achieved_fraction,
} => {
// Current fraction uses information from last tick, which is
// necessary as buffs from this tick are not guaranteed to have
// finished applying
let current_fraction = health.maximum() / health.base_max();
let potential_amount = (time.0 - buff_start_time.0) as f32 * rate;
// If achieved_fraction not initialized, initialize it to health
// fraction
if achieved_fraction.is_none() {
*achieved_fraction = Some(current_fraction)
}
if let Some(achieved_fraction) = achieved_fraction {
// Percentage change that should be applied to max_health
let health_tick = match kind {
// Percentage change that should be applied to max_health
let potential_fraction = 1.0
+ match kind {
ModifierKind::Additive => {
// `rate * dt` is amount of health, dividing by base max
// creates fraction
*rate * dt / health.base_max()
potential_amount / health.base_max()
},
ModifierKind::Fractional => {
// `rate * dt` is the fraction
*rate * dt
potential_amount
},
};
let potential_fraction = *achieved_fraction + health_tick;
// Potential progress towards target fraction, if
// target_fraction ~ 1.0 then set progress to 1.0 to avoid
// divide by zero
let progress = if (1.0 - *target_fraction).abs() > f32::EPSILON {
(1.0 - potential_fraction) / (1.0 - *target_fraction)
} else {
1.0
};
// Potential progress towards target fraction, if
// target_fraction ~ 1.0 then set progress to 1.0 to avoid
// divide by zero
let progress = if (1.0 - *target_fraction).abs() > f32::EPSILON {
(1.0 - potential_fraction) / (1.0 - *target_fraction)
} else {
1.0
};
// Change achieved_fraction depending on what other buffs have
// occurred
let achieved_fraction = if progress > 1.0 {
// If potential fraction already beyond target fraction,
// simply multiply max_health_modifier by the target
// fraction, and set achieved fraction to target_fraction
*target_fraction
} else {
// Else have not achieved target yet, use potential_fraction
potential_fraction
};
// Change achieved_fraction depending on what other buffs have
// occurred
if progress > 1.0 {
// If potential fraction already beyond target fraction,
// simply multiply max_health_modifier by the target
// fraction, and set achieved fraction to target_fraction
*achieved_fraction = *target_fraction;
} else {
// Else have not achieved target yet, update
// achieved_fraction
*achieved_fraction = potential_fraction;
}
// Apply achieved_fraction to max_health_modifier
stat.max_health_modifiers.mult_mod *= *achieved_fraction;
}
// Apply achieved_fraction to max_health_modifier
stat.max_health_modifiers.mult_mod *= achieved_fraction;
},
BuffEffect::MovementSpeed(speed) => {
stat.move_speed_modifier *= *speed;
@ -504,8 +500,8 @@ fn execute_effect(
BuffEffect::PoiseReduction(pr) => {
stat.poise_reduction = stat.poise_reduction.max(*pr).min(1.0);
},
BuffEffect::HealReduction { rate } => {
stat.heal_multiplier *= 1.0 - *rate;
BuffEffect::HealReduction(red) => {
stat.heal_multiplier *= 1.0 - *red;
},
};
}

View File

@ -3537,10 +3537,18 @@ fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity)
if let Some(buffkind) = parse_buffkind(kind) {
let ecs = &server.state.ecs();
let mut buffs_all = ecs.write_storage::<comp::Buffs>();
let stats = ecs.read_storage::<comp::Stats>();
let time = ecs.read_resource::<Time>();
if let Some(mut buffs) = buffs_all.get_mut(target) {
buffs.insert(
Buff::new(buffkind, data, vec![], BuffSource::Command, *time),
Buff::new(
buffkind,
data,
vec![],
BuffSource::Command,
*time,
stats.get(target),
),
*time,
);
}

View File

@ -1318,12 +1318,14 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<
BuffSource::World
};
let time = ecs.read_resource::<Time>();
let stats = ecs.read_storage::<comp::Stats>();
let buff = buff::Buff::new(
BuffKind::Parried,
data,
vec![buff::BuffCategory::Physical],
source,
*time,
stats.get(attacker),
);
server_eventbus.emit_now(ServerEvent::Buff {
entity: attacker,

View File

@ -223,6 +223,7 @@ impl StateExt for State {
},
Effect::Buff(buff) => {
let time = self.ecs().read_resource::<Time>();
let stats = self.ecs().read_storage::<comp::Stats>();
self.ecs()
.write_storage::<comp::Buffs>()
.get_mut(entity)
@ -234,6 +235,7 @@ impl StateExt for State {
buff.cat_ids,
comp::BuffSource::Item,
*time,
stats.get(entity),
),
*time,
)