Move addition/removal of buffs to server event.

This commit is contained in:
Sam 2020-09-30 19:40:46 -05:00
parent 7ab99a3bbf
commit c50063ad0c
8 changed files with 113 additions and 127 deletions

View File

@ -5,7 +5,7 @@ use specs_idvs::IdvStorage;
use std::time::Duration;
/// De/buff ID.
/// ID can be independant of an actual type/config of a `BuffData`.
/// ID can be independant of an actual type/config of a `BuffEffect`.
/// Therefore, information provided by `BuffId` can be incomplete/incorrect.
///
/// For example, there could be two regeneration buffs, each with
@ -19,6 +19,8 @@ pub enum BuffId {
Regeneration,
/// Lowers health/time for some period, but faster
Poison,
/// Lowers health over time for some duration
Bleeding,
/// Changes entity name as to "Cursed {}"
Cursed,
}
@ -29,6 +31,7 @@ pub enum BuffId {
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum BuffCategoryId {
Natural,
Physical,
Magical,
Divine,
Negative,
@ -39,9 +42,9 @@ pub enum BuffCategoryId {
///
/// NOTE: Contents of this enum are WIP/Placeholder
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum BuffData {
/// Periodically damages health
RepeatedHealthChange { speed: f32, accumulated: f32 },
pub enum BuffEffect {
/// Periodically damages or heals entity
RepeatedHealthChange { rate: f32, accumulated: f32 },
/// Changes name on_add/on_remove
NameChange { prefix: String },
}
@ -68,7 +71,7 @@ pub struct Buff {
pub id: BuffId,
pub cat_ids: Vec<BuffCategoryId>,
pub time: Option<Duration>,
pub data: BuffData,
pub effects: Vec<BuffEffect>,
}
/// Information about whether buff addition or removal was requested.
@ -78,8 +81,6 @@ pub enum BuffChange {
/// Adds this buff.
Add(Buff),
/// Removes all buffs with this ID.
/// TODO: Better removal, allowing to specify which ability to remove
/// directly.
Remove(BuffId),
}
@ -102,10 +103,10 @@ pub enum BuffSource {
/// Component holding all de/buffs that gets resolved each tick.
/// On each tick, remaining time of buffs get lowered and
/// buff effect of each buff is applied or not, depending on the `BuffData`
/// (specs system will decide based on `BuffData`, to simplify implementation).
/// buff effect of each buff is applied or not, depending on the `BuffEffect`
/// (specs system will decide based on `BuffEffect`, to simplify implementation).
/// TODO: Something like `once` flag for `Buff` to remove the dependence on
/// `BuffData` enum?
/// `BuffEffect` enum?
///
/// In case of one-time buffs, buff effects will be applied on addition
/// and undone on removal of the buff (by the specs system).
@ -120,17 +121,11 @@ pub enum BuffSource {
pub struct Buffs {
/// Active de/buffs.
pub buffs: Vec<Buff>,
/// Request to add/remove a buff.
/// Used for reacting on buff changes by the ECS system.
/// TODO: Can be `EventBus<T>` used instead of this?
pub changes: Vec<BuffChange>,
/// Last time since any buff change to limit syncing.
pub last_change: f64,
}
impl Buffs {
/// Adds a request for adding given `buff`.
pub fn add_buff(&mut self, buff: Buff) {
/*pub fn add_buff(&mut self, buff: Buff) {
let change = BuffChange::Add(buff);
self.changes.push(change);
self.last_change = 0.0;
@ -143,7 +138,7 @@ impl Buffs {
let change = BuffChange::Remove(id);
self.changes.push(change);
self.last_change = 0.0;
}
}*/
/// This is a primitive check if a specific buff is present.
/// (for purposes like blocking usage of abilities or something like this).

View File

@ -3,7 +3,7 @@ mod admin;
pub mod agent;
pub mod beam;
pub mod body;
mod buff;
pub mod buff;
mod character_state;
pub mod chat;
mod controller;
@ -32,7 +32,7 @@ pub use body::{
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData,
};
pub use buff::{Buff, BuffCategoryId, BuffChange, BuffData, BuffId, Buffs};
pub use buff::{Buff, BuffCategoryId, BuffChange, BuffEffect, BuffId, Buffs};
pub use character_state::{Attacking, CharacterState, StateUpdate};
pub use chat::{
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,

View File

@ -106,6 +106,10 @@ pub enum ServerEvent {
ChatCmd(EcsEntity, String),
/// Send a chat message to the player from an npc or other player
Chat(comp::UnresolvedChatMsg),
Buff {
uid: Uid,
buff: comp::BuffChange,
},
}
pub struct EventBus<E> {

View File

@ -1,5 +1,5 @@
use crate::{
comp::{BuffChange, BuffData, BuffId, Buffs, HealthChange, HealthSource, Stats},
comp::{BuffChange, BuffEffect, BuffId, Buffs, HealthChange, HealthSource, Stats},
state::DeltaTime,
};
use specs::{Entities, Join, Read, System, WriteStorage};
@ -21,58 +21,8 @@ impl<'a> System<'a> for Sys {
);
fn run(&mut self, (entities, dt, mut stats, mut buffs): Self::SystemData) {
// Increment last change timer
buffs.set_event_emission(false);
for buff in (&mut buffs).join() {
buff.last_change += f64::from(dt.0);
}
buffs.set_event_emission(true);
for (entity, mut buffs) in (&entities, &mut buffs.restrict_mut()).join() {
let buff_comp = buffs.get_mut_unchecked();
// Add/Remove de/buffs
// While adding/removing buffs, it could call respective hooks
// Currently, it is done based on enum variant
let changes = buff_comp.changes.drain(0..buff_comp.changes.len());
for change in changes {
match change {
// Hooks for on_add could be here
BuffChange::Add(new_buff) => {
match &new_buff.data {
BuffData::NameChange { prefix } => {
if let Some(stats) = stats.get_mut(entity) {
let mut pref = String::from(prefix);
pref.push_str(&stats.name);
stats.name = pref;
}
},
_ => {},
}
buff_comp.buffs.push(new_buff.clone());
},
// Hooks for on_remove could be here
BuffChange::Remove(id) => {
let some_predicate = |current_id: &BuffId| *current_id == id;
let mut i = 0;
while i != buff_comp.buffs.len() {
if some_predicate(&mut buff_comp.buffs[i].id) {
let buff = buff_comp.buffs.remove(i);
match &buff.data {
BuffData::NameChange { prefix } => {
if let Some(stats) = stats.get_mut(entity) {
stats.name = stats.name.replace(prefix, "");
}
},
_ => {},
}
} else {
i += 1;
}
}
},
}
}
let mut buffs_for_removal = Vec::new();
// Tick all de/buffs on a Buffs component.
for active_buff in &mut buff_comp.buffs {
@ -102,29 +52,32 @@ impl<'a> System<'a> for Sys {
};
// Now, execute the buff, based on it's delta
match &mut active_buff.data {
BuffData::RepeatedHealthChange { speed, accumulated } => {
*accumulated += *speed * buff_delta;
// Apply only 0.5 or higher damage
if accumulated.abs() > 5.0 {
if let Some(stats) = stats.get_mut(entity) {
let change = HealthChange {
amount: *accumulated as i32,
cause: HealthSource::Unknown,
};
stats.health.change_by(change);
}
*accumulated = 0.0;
};
},
_ => {},
};
for effect in &mut active_buff.effects {
match effect {
// Only add an effect here if it is continuous or it is not immediate
BuffEffect::RepeatedHealthChange { rate, accumulated } => {
*accumulated += *rate * buff_delta;
// Apply only 0.5 or higher damage
if accumulated.abs() > 5.0 {
if let Some(stats) = stats.get_mut(entity) {
let change = HealthChange {
amount: *accumulated as i32,
cause: HealthSource::Unknown,
};
stats.health.change_by(change);
}
*accumulated = 0.0;
};
},
_ => {},
};
}
}
// Truly mark expired buffs for removal.
// TODO: Review this, as it is ugly.
for to_remove in buffs_for_removal {
/*for to_remove in buffs_for_removal {
buff_comp.remove_buff_by_id(to_remove);
}
}*/
}
}
}

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
Loadout, Ori, Pos, Scale, Stats,
},
event::{EventBus, LocalEvent, ServerEvent},
@ -10,6 +10,7 @@ use crate::{
util::Dir,
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use std::time::Duration;
use vek::*;
pub const BLOCK_EFFICIENCY: f32 = 0.9;
@ -150,6 +151,19 @@ impl<'a> System<'a> for Sys {
cause,
},
});
// Test for server event of buff, remove before merging
server_emitter.emit(ServerEvent::Buff {
uid: *uid_b,
buff: buff::BuffChange::Add(buff::Buff {
id: buff::BuffId::Bleeding,
cat_ids: vec![buff::BuffCategoryId::Physical],
time: Some(Duration::from_millis(2000)),
effects: vec![buff::BuffEffect::RepeatedHealthChange {
rate: 50.0,
accumulated: 0.0
}],
}),
});
attack.hit_count += 1;
}
if attack.knockback != 0.0 && damage.healthchange != 0.0 {

View File

@ -7,7 +7,7 @@ use common::{
assets::Asset,
comp::{
self,
chat::{KillSource, KillType},
buff, chat::{KillSource, KillType},
object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Item,
Player, Pos, Stats,
},
@ -674,3 +674,53 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
PlayerListUpdate::LevelChange(*uid, new_level),
));
}
pub fn handle_buff(server: &mut Server, uid: Uid, buff: buff::BuffChange ) {
let ecs = &server.state.ecs();
let mut buffs_all = ecs.write_storage::<comp::Buffs>();
if let Some(entity) = ecs.entity_from_uid(uid.into()) {
if let Some(buffs) = buffs_all.get_mut(entity) {
let mut stats = ecs.write_storage::<comp::Stats>();
match buff {
buff::BuffChange::Add(new_buff) => {
for effect in &new_buff.effects {
match effect {
// Only add an effect here if it is immediate and is not continuous
buff::BuffEffect::NameChange { prefix } => {
if let Some(stats) = stats.get_mut(entity) {
let mut pref = String::from(prefix);
pref.push_str(&stats.name);
stats.name = pref;
}
},
_ => {},
}
}
buffs.buffs.push(new_buff.clone());
},
buff::BuffChange::Remove(id) => {
let some_predicate = |current_id: &buff::BuffId| *current_id == id;
let mut i = 0;
while i != buffs.buffs.len() {
if some_predicate(&mut buffs.buffs[i].id) {
let buff = buffs.buffs.remove(i);
for effect in &buff.effects {
match effect {
// Only remove an effect here if its effect was not continuously applied
buff::BuffEffect::NameChange { prefix } => {
if let Some(stats) = stats.get_mut(entity) {
stats.name = stats.name.replace(prefix, "");
}
},
_ => {},
}
}
} else {
i += 1;
}
}
},
}
}
}
}

View File

@ -8,7 +8,7 @@ use entity_creation::{
handle_loaded_character_data, handle_shockwave, handle_shoot,
};
use entity_manipulation::{
handle_damage, handle_destroy, handle_explosion, handle_knockback, handle_land_on_ground,
handle_buff, handle_damage, handle_destroy, handle_explosion, handle_knockback, handle_land_on_ground,
handle_level_up, handle_respawn,
};
use group_manip::handle_group;
@ -133,6 +133,10 @@ impl Server {
ServerEvent::Chat(msg) => {
chat_messages.push(msg);
},
ServerEvent::Buff {
uid,
buff,
} => handle_buff(self, uid, buff),
}
}

View File

@ -93,39 +93,6 @@ impl StateExt for State {
loadout: comp::Loadout,
body: comp::Body,
) -> EcsEntityBuilder {
// NOTE: This is placeholder code for testing and does not
// belongs here really
let mut buffs = comp::Buffs::default();
// Damages slightly each NPC spawned for 20 seconds
buffs.add_buff(comp::Buff {
id: comp::BuffId::Poison,
cat_ids: vec![],
time: Some(std::time::Duration::from_secs(20)),
data: comp::BuffData::RepeatedHealthChange {
speed: -10.0,
accumulated: 0.0,
},
});
// Prepends "Cursed " to each NPC's name for 35 secs
buffs.add_buff(comp::Buff {
id: comp::BuffId::Cursed,
cat_ids: vec![],
time: Some(std::time::Duration::from_secs(35)),
data: comp::BuffData::NameChange {
prefix: String::from("Cursed "),
},
});
// Adds super-slow regen to each NPC spawned, indefinitely
buffs.add_buff(comp::Buff {
id: comp::BuffId::Regeneration,
cat_ids: vec![],
time: None,
data: comp::BuffData::RepeatedHealthChange {
speed: 1.0,
accumulated: 0.0,
},
});
self.ecs_mut()
.create_entity_synced()
.with(pos)
@ -144,7 +111,6 @@ impl StateExt for State {
.with(comp::Gravity(1.0))
.with(comp::CharacterState::default())
.with(loadout)
.with(buffs)
}
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {