From 4c104912638e0cdfdcdc41c24a5773dc077939aa Mon Sep 17 00:00:00 2001 From: BottledByte <6443024-BottledByte@users.noreply.gitlab.com> Date: Tue, 11 Aug 2020 00:54:45 +0200 Subject: [PATCH 01/25] Initial WIP implementation of the Buff system --- common/src/comp/buff.rs | 155 +++++++++++++++++++++++++++++++++++ common/src/comp/mod.rs | 2 + common/src/msg/ecs_packet.rs | 5 ++ common/src/state.rs | 1 + common/src/sys/buff.rs | 130 +++++++++++++++++++++++++++++ common/src/sys/mod.rs | 3 + server/src/state_ext.rs | 34 ++++++++ server/src/sys/sentinel.rs | 13 ++- 8 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 common/src/comp/buff.rs create mode 100644 common/src/sys/buff.rs diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs new file mode 100644 index 0000000000..b719383d1e --- /dev/null +++ b/common/src/comp/buff.rs @@ -0,0 +1,155 @@ +use crate::sync::Uid; +use serde::{Deserialize, Serialize}; +use specs::{Component, FlaggedStorage}; +use specs_idvs::IdvStorage; +use std::time::Duration; + +/// De/buff ID. +/// ID can be independant of an actual type/config of a `BuffData`. +/// Therefore, information provided by `BuffId` can be incomplete/incorrect. +/// +/// For example, there could be two regeneration buffs, each with +/// different strength, but they could use the same `BuffId`, +/// making it harder to recognize which is which. +/// +/// Also, this should be dehardcoded eventually. +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub enum BuffId { + /// Restores health/time for some period + Regeneration, + /// Lowers health/time for some period, but faster + Poison, + /// Changes entity name as to "Cursed {}" + Cursed, +} + +/// De/buff category ID. +/// Similar to `BuffId`, but to mark a category (for more generic usage, like +/// positive/negative buffs). +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub enum BuffCategoryId { + Natural, + Magical, + Divine, + Negative, + Positive, +} + +/// Data indicating and configuring behaviour of a de/buff. +/// +/// NOTE: Contents of this enum are WIP/Placeholder +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BuffData { + /// Periodically damages health + RepeatedHealthChange { speed: f32, accumulated: f32 }, + /// Changes name on_add/on_remove + NameChange { prefix: String }, +} + +/// Actual de/buff. +/// Buff can timeout after some time if `time` is Some. If `time` is None, +/// Buff will last indefinitely, until removed manually (by some action, like +/// uncursing). The `time` field might be moved into the `Buffs` component +/// (so that `Buff` does not own this information). +/// +/// Buff has an id and data, which can be independent on each other. +/// This makes it hard to create buff stacking "helpers", as the system +/// does not assume that the same id is always the same behaviour (data). +/// Therefore id=behaviour relationship has to be enforced elsewhere (if +/// desired). +/// +/// To provide more classification info when needed, +/// buff can be in one or more buff category. +/// +/// `data` is separate, to make this system more flexible +/// (at the cost of the fact that id=behaviour relationship might not apply). +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Buff { + pub id: BuffId, + pub cat_ids: Vec<BuffCategoryId>, + pub time: Option<Duration>, + pub data: BuffData, +} + +/// Information about whether buff addition or removal was requested. +/// This to implement "on_add" and "on_remove" hooks for constant buffs. +#[derive(Clone, Debug, Serialize, Deserialize)] +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), +} + +/// Source of the de/buff +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum BuffSource { + /// Applied by a character + Character { by: Uid }, + /// Applied by world, like a poisonous fumes from a swamp + World, + /// Applied by command + Command, + /// Applied by an item + Item, + /// Applied by another buff (like an after-effect) + Buff, + /// Some other source + Unknown, +} + +/// 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). +/// TODO: Something like `once` flag for `Buff` to remove the dependence on +/// `BuffData` 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). +/// Example could be decreasing max health, which, if repeated each tick, +/// would be probably an undesired effect). +/// +/// TODO: Make this net/sync-friendly. Events could help there +/// (probably replacing `changes`). Also, the "buff ticking" is really +/// not needed to be synced often, only in case that a buff begins/ends +/// (as the buff ECS system will probably run on a client too). +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +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) { + let change = BuffChange::Add(buff); + self.changes.push(change); + self.last_change = 0.0; + } + + /// Adds a request for removal of all buffs with given Id. + /// TODO: Better removal, allowing to specify which ability to remove + /// directly. + pub fn remove_buff_by_id(&mut self, id: BuffId) { + 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). + pub fn has_buff_id(&self, id: &BuffId) -> bool { self.buffs.iter().any(|buff| buff.id == *id) } +} + +impl Component for Buffs { + type Storage = FlaggedStorage<Self, IdvStorage<Self>>; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index c8204c6eed..ead6d4b371 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -3,6 +3,7 @@ mod admin; pub mod agent; pub mod beam; pub mod body; +mod buff; mod character_state; pub mod chat; mod controller; @@ -31,6 +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 character_state::{Attacking, CharacterState, StateUpdate}; pub use chat::{ ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index ce9852720f..41ab1bda71 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -13,6 +13,7 @@ sum_type! { Player(comp::Player), CanBuild(comp::CanBuild), Stats(comp::Stats), + Buffs(comp::Buffs), Energy(comp::Energy), LightEmitter(comp::LightEmitter), Item(comp::Item), @@ -42,6 +43,7 @@ sum_type! { Player(PhantomData<comp::Player>), CanBuild(PhantomData<comp::CanBuild>), Stats(PhantomData<comp::Stats>), + Buffs(PhantomData<comp::Buffs>), Energy(PhantomData<comp::Energy>), LightEmitter(PhantomData<comp::LightEmitter>), Item(PhantomData<comp::Item>), @@ -71,6 +73,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world), 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::LightEmitter(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world), @@ -98,6 +101,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world), 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::LightEmitter(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world), @@ -125,6 +129,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world), EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world), 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::LightEmitter(_) => { sync::handle_remove::<comp::LightEmitter>(entity, world) diff --git a/common/src/state.rs b/common/src/state.rs index d54a5d9433..cbf00daaf2 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -112,6 +112,7 @@ impl State { ecs.register::<comp::Body>(); ecs.register::<comp::Player>(); ecs.register::<comp::Stats>(); + ecs.register::<comp::Buffs>(); ecs.register::<comp::Energy>(); ecs.register::<comp::CanBuild>(); ecs.register::<comp::LightEmitter>(); diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs new file mode 100644 index 0000000000..32b5150f46 --- /dev/null +++ b/common/src/sys/buff.rs @@ -0,0 +1,130 @@ +use crate::{ + comp::{BuffChange, BuffData, BuffId, Buffs, HealthChange, HealthSource, Stats}, + state::DeltaTime, +}; +use specs::{Entities, Join, Read, System, WriteStorage}; +use std::time::Duration; + +/// This system modifies entity stats, changing them using buffs +/// Currently, the system is VERY, VERY CRUDE and SYNC UN-FRIENDLY. +/// It does not use events and uses `Vec`s stored in component. +/// +/// TODO: Make this production-quality system/design +pub struct Sys; +impl<'a> System<'a> for Sys { + #[allow(clippy::type_complexity)] + type SystemData = ( + Entities<'a>, + Read<'a, DeltaTime>, + WriteStorage<'a, Stats>, + WriteStorage<'a, Buffs>, + ); + + 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 { + // First, tick the buff and subtract delta from it + // and return how much "real" time the buff took (for tick independence). + // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. + let buff_delta = if let Some(remaining_time) = &mut active_buff.time { + let pre_tick = remaining_time.as_secs_f32(); + let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); + let post_tick = if let Some(dur) = new_duration { + // The buff still continues. + *remaining_time -= Duration::from_secs_f32(dt.0); + dur.as_secs_f32() + } else { + // The buff has expired. + // Mark it for removal. + // TODO: This removes by ID! better method required + buffs_for_removal.push(active_buff.id.clone()); + 0.0 + }; + pre_tick - post_tick + } else { + // The buff is indefinite, and it takes full tick (delta). + // TODO: Delta for indefinite buffs might be shorter since they can get removed + // *during a tick* and this treats it as it always happens on a *tick end*. + dt.0 + }; + + // 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; + }; + }, + _ => {}, + }; + } + // Truly mark expired buffs for removal. + // TODO: Review this, as it is ugly. + for to_remove in buffs_for_removal { + buff_comp.remove_buff_by_id(to_remove); + } + } + } +} diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index 99b2e56047..98c70fd145 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -1,5 +1,6 @@ pub mod agent; mod beam; +mod buff; pub mod character_behavior; pub mod combat; pub mod controller; @@ -23,6 +24,7 @@ pub const PHYS_SYS: &str = "phys_sys"; pub const PROJECTILE_SYS: &str = "projectile_sys"; pub const SHOCKWAVE_SYS: &str = "shockwave_sys"; pub const STATS_SYS: &str = "stats_sys"; +pub const BUFFS_SYS: &str = "buffs_sys"; pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(agent::Sys, AGENT_SYS, &[]); @@ -32,6 +34,7 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { CONTROLLER_SYS, ]); dispatch_builder.add(stats::Sys, STATS_SYS, &[]); + dispatch_builder.add(buff::Sys, BUFFS_SYS, &[]); dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_SYS, STATS_SYS]); dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]); dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]); diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index a4208f0934..0fc1b657d5 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -93,6 +93,39 @@ 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) @@ -111,6 +144,7 @@ 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 { diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index fcecccb288..68d568b66c 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,7 +1,7 @@ use super::SysTimer; use common::{ comp::{ - BeamSegment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, + BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky, Vel, }, @@ -44,6 +44,7 @@ pub struct TrackedComps<'a> { pub body: ReadStorage<'a, Body>, pub player: ReadStorage<'a, Player>, pub stats: ReadStorage<'a, Stats>, + pub buffs: ReadStorage<'a, Buffs>, pub energy: ReadStorage<'a, Energy>, pub can_build: ReadStorage<'a, CanBuild>, pub light_emitter: ReadStorage<'a, LightEmitter>, @@ -85,6 +86,10 @@ impl<'a> TrackedComps<'a> { .get(entity) .cloned() .map(|c| comps.push(c.into())); + self.buffs + .get(entity) + .cloned() + .map(|c| comps.push(c.into())); self.energy .get(entity) .cloned() @@ -157,6 +162,7 @@ pub struct ReadTrackers<'a> { pub body: ReadExpect<'a, UpdateTracker<Body>>, pub player: ReadExpect<'a, UpdateTracker<Player>>, pub stats: ReadExpect<'a, UpdateTracker<Stats>>, + pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>, pub energy: ReadExpect<'a, UpdateTracker<Energy>>, pub can_build: ReadExpect<'a, UpdateTracker<CanBuild>>, pub light_emitter: ReadExpect<'a, UpdateTracker<LightEmitter>>, @@ -187,6 +193,7 @@ impl<'a> ReadTrackers<'a> { .with_component(&comps.uid, &*self.body, &comps.body, filter) .with_component(&comps.uid, &*self.player, &comps.player, filter) .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.can_build, &comps.can_build, filter) .with_component( @@ -224,6 +231,7 @@ pub struct WriteTrackers<'a> { body: WriteExpect<'a, UpdateTracker<Body>>, player: WriteExpect<'a, UpdateTracker<Player>>, stats: WriteExpect<'a, UpdateTracker<Stats>>, + buffs: WriteExpect<'a, UpdateTracker<Buffs>>, energy: WriteExpect<'a, UpdateTracker<Energy>>, can_build: WriteExpect<'a, UpdateTracker<CanBuild>>, light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>, @@ -248,6 +256,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.body.record_changes(&comps.body); trackers.player.record_changes(&comps.player); trackers.stats.record_changes(&comps.stats); + trackers.buffs.record_changes(&comps.buffs); trackers.energy.record_changes(&comps.energy); trackers.can_build.record_changes(&comps.can_build); trackers.light_emitter.record_changes(&comps.light_emitter); @@ -283,6 +292,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { }; log_counts!(uid, "Uids"); log_counts!(body, "Bodies"); + log_counts!(buffs, "Buffs"); log_counts!(player, "Players"); log_counts!(stats, "Stats"); log_counts!(energy, "Energies"); @@ -307,6 +317,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::<Body>(); world.register_tracker::<Player>(); world.register_tracker::<Stats>(); + world.register_tracker::<Buffs>(); world.register_tracker::<Energy>(); world.register_tracker::<CanBuild>(); world.register_tracker::<LightEmitter>(); From 7ae25e1e56386e02b9123054fb6540ef19f57176 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Wed, 30 Sep 2020 19:40:46 -0500 Subject: [PATCH 02/25] Move addition/removal of buffs to server event. --- common/src/comp/buff.rs | 31 ++++---- common/src/comp/mod.rs | 4 +- common/src/event.rs | 4 + common/src/sys/buff.rs | 93 ++++++------------------ common/src/sys/combat.rs | 16 +++- server/src/events/entity_manipulation.rs | 52 ++++++++++++- server/src/events/mod.rs | 6 +- server/src/state_ext.rs | 34 --------- 8 files changed, 113 insertions(+), 127 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index b719383d1e..5505e61f17 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -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). diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index ead6d4b371..21a7dbd03c 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -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, diff --git a/common/src/event.rs b/common/src/event.rs index 542650d8a4..ac56b1fa6b 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -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> { diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 32b5150f46..987dd7b269 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -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); - } + }*/ } } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index a9327b96ef..c3359856ad 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -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 { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d4daa1b3bc..33170a9801 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -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; + } + } + }, + } + } + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 9d64de3202..15ae546a04 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -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), } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 0fc1b657d5..a4208f0934 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -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 { From ddce5b78fc446623fd656fc24fba38bf500c3c29 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Wed, 30 Sep 2020 20:35:57 -0500 Subject: [PATCH 03/25] Creatures and playrs now have buffs component, buffs that expire now only end their particular buff instead of every buff with the same type. --- common/src/comp/buff.rs | 8 +++---- common/src/sys/buff.rs | 30 ++++++++++++++---------- common/src/sys/combat.rs | 10 ++++---- server/src/events/entity_manipulation.rs | 9 +++---- server/src/events/mod.rs | 9 +++---- server/src/state_ext.rs | 2 ++ 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 5505e61f17..dcd9af6347 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -44,7 +44,7 @@ pub enum BuffCategoryId { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum BuffEffect { /// Periodically damages or heals entity - RepeatedHealthChange { rate: f32, accumulated: f32 }, + HealthChangeOverTime { rate: f32, accumulated: f32 }, /// Changes name on_add/on_remove NameChange { prefix: String }, } @@ -104,9 +104,9 @@ 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 `BuffEffect` -/// (specs system will decide based on `BuffEffect`, to simplify implementation). -/// TODO: Something like `once` flag for `Buff` to remove the dependence on -/// `BuffEffect` enum? +/// (specs system will decide based on `BuffEffect`, to simplify +/// implementation). TODO: Something like `once` flag for `Buff` to remove the +/// dependence on `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). diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 987dd7b269..047f18d1b4 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -23,13 +23,13 @@ impl<'a> System<'a> for Sys { fn run(&mut self, (entities, dt, mut stats, mut buffs): Self::SystemData) { for (entity, mut buffs) in (&entities, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); - let mut buffs_for_removal = Vec::new(); + let mut buff_indices_for_removal = Vec::new(); // Tick all de/buffs on a Buffs component. - for active_buff in &mut buff_comp.buffs { + for i in 0..buff_comp.buffs.len() { // First, tick the buff and subtract delta from it // and return how much "real" time the buff took (for tick independence). // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. - let buff_delta = if let Some(remaining_time) = &mut active_buff.time { + let buff_delta = if let Some(remaining_time) = &mut buff_comp.buffs[i].time { let pre_tick = remaining_time.as_secs_f32(); let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); let post_tick = if let Some(dur) = new_duration { @@ -38,9 +38,8 @@ impl<'a> System<'a> for Sys { dur.as_secs_f32() } else { // The buff has expired. - // Mark it for removal. - // TODO: This removes by ID! better method required - buffs_for_removal.push(active_buff.id.clone()); + // Remove it. + buff_indices_for_removal.push(i); 0.0 }; pre_tick - post_tick @@ -52,10 +51,10 @@ impl<'a> System<'a> for Sys { }; // Now, execute the buff, based on it's delta - for effect in &mut active_buff.effects { + for effect in &mut buff_comp.buffs[i].effects { match effect { // Only add an effect here if it is continuous or it is not immediate - BuffEffect::RepeatedHealthChange { rate, accumulated } => { + BuffEffect::HealthChangeOverTime { rate, accumulated } => { *accumulated += *rate * buff_delta; // Apply only 0.5 or higher damage if accumulated.abs() > 5.0 { @@ -73,11 +72,16 @@ impl<'a> System<'a> for Sys { }; } } - // Truly mark expired buffs for removal. - // TODO: Review this, as it is ugly. - /*for to_remove in buffs_for_removal { - buff_comp.remove_buff_by_id(to_remove); - }*/ + // Remove buffs that have expired. + // Since buffs are added into this vec as it iterates up through the list, it + // will be in order of increasing values. Therefore to avoid + // removing the incorrect buff, removal will start from the greatest index + // value, which is the last in this vec. + while !buff_indices_for_removal.is_empty() { + if let Some(i) = buff_indices_for_removal.pop() { + buff_comp.buffs.remove(i); + } + } } } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index c3359856ad..9945f2bc19 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, - Loadout, Ori, Pos, Scale, Stats, + buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, + HealthSource, Loadout, Ori, Pos, Scale, Stats, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, @@ -158,9 +158,9 @@ impl<'a> System<'a> for Sys { 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 + effects: vec![buff::BuffEffect::HealthChangeOverTime { + rate: 10.0, + accumulated: 0.0, }], }), }); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 33170a9801..194daac982 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -6,8 +6,8 @@ use crate::{ use common::{ assets::Asset, comp::{ - self, - buff, chat::{KillSource, KillType}, + self, buff, + chat::{KillSource, KillType}, object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Item, Player, Pos, Stats, }, @@ -675,7 +675,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { )); } -pub fn handle_buff(server: &mut Server, uid: Uid, buff: buff::BuffChange ) { +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()) { @@ -706,7 +706,8 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff: buff::BuffChange ) { 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 + // 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, ""); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 15ae546a04..359ebe0504 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -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_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}; @@ -133,10 +133,7 @@ impl Server { ServerEvent::Chat(msg) => { chat_messages.push(msg); }, - ServerEvent::Buff { - uid, - buff, - } => handle_buff(self, uid, buff), + ServerEvent::Buff { uid, buff } => handle_buff(self, uid, buff), } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index a4208f0934..06d7c523e3 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -111,6 +111,7 @@ impl StateExt for State { .with(comp::Gravity(1.0)) .with(comp::CharacterState::default()) .with(loadout) + .with(comp::Buffs::default()) } fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { @@ -202,6 +203,7 @@ impl StateExt for State { entity, comp::Alignment::Owned(self.read_component_copied(entity).unwrap()), ); + self.write_component(entity, comp::Buffs::default()); // Make sure physics components are updated self.write_component(entity, comp::ForceUpdate); From d7f1ffc9915f9abf8f8cba6b660b4b2122c59ce1 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Wed, 30 Sep 2020 21:32:38 -0500 Subject: [PATCH 04/25] When buffs expire from duration, now only they expire rather than ending all buffs of the same type. --- common/src/comp/buff.rs | 20 ++--------- common/src/event.rs | 2 +- common/src/sys/buff.rs | 26 +++++++++----- common/src/sys/combat.rs | 15 +++++--- server/src/events/entity_manipulation.rs | 46 +++++++++++++----------- server/src/events/mod.rs | 2 +- 6 files changed, 59 insertions(+), 52 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index dcd9af6347..b36535a39d 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -81,7 +81,9 @@ pub enum BuffChange { /// Adds this buff. Add(Buff), /// Removes all buffs with this ID. - Remove(BuffId), + RemoveById(BuffId), + /// Removes buff of this index + RemoveByIndex(Vec<usize>), } /// Source of the de/buff @@ -124,22 +126,6 @@ pub struct Buffs { } impl Buffs { - /// Adds a request for adding given `buff`. - /*pub fn add_buff(&mut self, buff: Buff) { - let change = BuffChange::Add(buff); - self.changes.push(change); - self.last_change = 0.0; - } - - /// Adds a request for removal of all buffs with given Id. - /// TODO: Better removal, allowing to specify which ability to remove - /// directly. - pub fn remove_buff_by_id(&mut self, id: BuffId) { - 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). pub fn has_buff_id(&self, id: &BuffId) -> bool { self.buffs.iter().any(|buff| buff.id == *id) } diff --git a/common/src/event.rs b/common/src/event.rs index ac56b1fa6b..aedbbdf14f 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -108,7 +108,7 @@ pub enum ServerEvent { Chat(comp::UnresolvedChatMsg), Buff { uid: Uid, - buff: comp::BuffChange, + buff_change: comp::BuffChange, }, } diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 047f18d1b4..98b5c8e8b6 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,8 +1,10 @@ use crate::{ comp::{BuffChange, BuffEffect, BuffId, Buffs, HealthChange, HealthSource, Stats}, + event::{EventBus, ServerEvent}, state::DeltaTime, + sync::Uid, }; -use specs::{Entities, Join, Read, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; /// This system modifies entity stats, changing them using buffs @@ -16,12 +18,15 @@ impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, Read<'a, DeltaTime>, + Read<'a, EventBus<ServerEvent>>, + ReadStorage<'a, Uid>, WriteStorage<'a, Stats>, WriteStorage<'a, Buffs>, ); - fn run(&mut self, (entities, dt, mut stats, mut buffs): Self::SystemData) { - for (entity, mut buffs) in (&entities, &mut buffs.restrict_mut()).join() { + fn run(&mut self, (entities, dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { + let mut server_emitter = server_bus.emitter(); + for (entity, uid, mut buffs) in (&entities, &uids, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); let mut buff_indices_for_removal = Vec::new(); // Tick all de/buffs on a Buffs component. @@ -72,16 +77,21 @@ impl<'a> System<'a> for Sys { }; } } + server_emitter.emit(ServerEvent::Buff { + uid: *uid, + buff_change: BuffChange::RemoveByIndex(buff_indices_for_removal), + }); // Remove buffs that have expired. - // Since buffs are added into this vec as it iterates up through the list, it - // will be in order of increasing values. Therefore to avoid - // removing the incorrect buff, removal will start from the greatest index + // Since buffs are added into this vec as it iterates up through the + // list, it will be in order of increasing values. + // Therefore to avoid removing the incorrect buff, + // removal will start from the greatest index // value, which is the last in this vec. - while !buff_indices_for_removal.is_empty() { + /*while !buff_indices_for_removal.is_empty() { if let Some(i) = buff_indices_for_removal.pop() { buff_comp.buffs.remove(i); } - } + }*/ } } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 9945f2bc19..cd7e1d0602 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -154,14 +154,19 @@ impl<'a> System<'a> for Sys { // Test for server event of buff, remove before merging server_emitter.emit(ServerEvent::Buff { uid: *uid_b, - buff: buff::BuffChange::Add(buff::Buff { + buff_change: 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::HealthChangeOverTime { - rate: 10.0, - accumulated: 0.0, - }], + effects: vec![ + buff::BuffEffect::HealthChangeOverTime { + rate: 10.0, + accumulated: 0.0, + }, + buff::BuffEffect::NameChange { + prefix: String::from("Injured "), + }, + ], }), }); attack.hit_count += 1; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 194daac982..37d6d5c7e6 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -675,13 +675,14 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { )); } -pub fn handle_buff(server: &mut Server, uid: Uid, buff: buff::BuffChange) { +pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: 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 { + let mut buff_indices_for_removal = Vec::new(); + match buff_change { buff::BuffChange::Add(new_buff) => { for effect in &new_buff.effects { match effect { @@ -698,30 +699,35 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff: buff::BuffChange) { } buffs.buffs.push(new_buff.clone()); }, - buff::BuffChange::Remove(id) => { + buff::BuffChange::RemoveByIndex(indices) => { + buff_indices_for_removal = indices; + }, + buff::BuffChange::RemoveById(id) => { let some_predicate = |current_id: &buff::BuffId| *current_id == id; - let mut i = 0; - while i != buffs.buffs.len() { + for i in 0..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; + buff_indices_for_removal.push(i); } } }, } + while !buff_indices_for_removal.is_empty() { + if let Some(i) = buff_indices_for_removal.pop() { + 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.replacen(prefix, "", 1); + } + }, + _ => {}, + } + } + } + } } } } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 359ebe0504..49a133a5a8 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -133,7 +133,7 @@ impl Server { ServerEvent::Chat(msg) => { chat_messages.push(msg); }, - ServerEvent::Buff { uid, buff } => handle_buff(self, uid, buff), + ServerEvent::Buff { uid, buff_change } => handle_buff(self, uid, buff_change), } } From 4f6be340ce15e458b67f15dfb8b1d5ae6a0c8c84 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Thu, 1 Oct 2020 12:33:35 -0500 Subject: [PATCH 05/25] Added builder function for buffs --- common/src/comp/buff.rs | 48 +++++++++++++++++++++++++++++++++++----- common/src/sys/buff.rs | 13 +---------- common/src/sys/combat.rs | 19 +++++----------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index b36535a39d..8aa6abe5a9 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -16,13 +16,15 @@ use std::time::Duration; #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffId { /// Restores health/time for some period - Regeneration, - /// Lowers health/time for some period, but faster - Poison, + /// Has fields: strength (f32) + Regeneration(f32), /// Lowers health over time for some duration - Bleeding, - /// Changes entity name as to "Cursed {}" - Cursed, + /// Has fields: strength (f32) + Bleeding(f32), + /// Adds a prefix to the entity name + /// Currently placeholder buff to show other stuff is possible + /// Has fields: prefix (String) + Prefix(String), } /// De/buff category ID. @@ -131,6 +133,40 @@ impl Buffs { pub fn has_buff_id(&self, id: &BuffId) -> bool { self.buffs.iter().any(|buff| buff.id == *id) } } +impl Buff { + pub fn new(id: BuffId, time: Option<Duration>, cat_ids: Vec<BuffCategoryId>) -> Self { + let effects = match id { + BuffId::Bleeding(strength) => vec![ + BuffEffect::HealthChangeOverTime { + rate: -strength, + accumulated: 0.0, + }, + // This effect is for testing purposes + BuffEffect::NameChange { + prefix: String::from("Injured "), + }, + ], + BuffId::Regeneration(strength) => vec![BuffEffect::HealthChangeOverTime { + rate: strength, + accumulated: 0.0, + }], + BuffId::Prefix(ref prefix) => { + let mut prefix = prefix.clone(); + prefix.push(' '); + vec![BuffEffect::NameChange { + prefix, + }] + }, + }; + Buff { + id: id.clone(), + cat_ids, + time, + effects, + } + } +} + impl Component for Buffs { type Storage = FlaggedStorage<Self, IdvStorage<Self>>; } diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 98b5c8e8b6..78ecaff28d 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -28,7 +28,7 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_bus.emitter(); for (entity, uid, mut buffs) in (&entities, &uids, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); - let mut buff_indices_for_removal = Vec::new(); + let mut buff_indices_for_removal = Vec::<usize>::new(); // Tick all de/buffs on a Buffs component. for i in 0..buff_comp.buffs.len() { // First, tick the buff and subtract delta from it @@ -81,17 +81,6 @@ impl<'a> System<'a> for Sys { uid: *uid, buff_change: BuffChange::RemoveByIndex(buff_indices_for_removal), }); - // Remove buffs that have expired. - // Since buffs are added into this vec as it iterates up through the - // list, it will be in order of increasing values. - // Therefore to avoid removing the incorrect buff, - // removal will start from the greatest index - // value, which is the last in this vec. - /*while !buff_indices_for_removal.is_empty() { - if let Some(i) = buff_indices_for_removal.pop() { - buff_comp.buffs.remove(i); - } - }*/ } } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index cd7e1d0602..b9a9bc8e99 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -154,20 +154,11 @@ impl<'a> System<'a> for Sys { // Test for server event of buff, remove before merging server_emitter.emit(ServerEvent::Buff { uid: *uid_b, - buff_change: 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::HealthChangeOverTime { - rate: 10.0, - accumulated: 0.0, - }, - buff::BuffEffect::NameChange { - prefix: String::from("Injured "), - }, - ], - }), + buff_change: buff::BuffChange::Add(buff::Buff::new( + buff::BuffId::Bleeding(50.0), + Some(Duration::from_millis(5000)), + vec![buff::BuffCategoryId::Physical], + )), }); attack.hit_count += 1; } From ddebad6a735284e9239656595b0dc289aaff4e5f Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Fri, 2 Oct 2020 12:15:10 -0500 Subject: [PATCH 06/25] Separated buffs into active and inactive buffs. There can only be 1 active buff at a time of a particular buff id. If a new buff is stronger than an active buff, it moves the active buff to inactive buffs. When active buffs are removed, it checks inactive buffs for any buffs of the same id and moves the strongest one to active buffs. --- common/src/comp/buff.rs | 32 ++--- common/src/sys/buff.rs | 36 ++++- common/src/sys/combat.rs | 4 +- server/src/events/entity_manipulation.rs | 166 +++++++++++++++++++---- 4 files changed, 183 insertions(+), 55 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 8aa6abe5a9..24c13a40d7 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -13,7 +13,7 @@ use std::time::Duration; /// making it harder to recognize which is which. /// /// Also, this should be dehardcoded eventually. -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffId { /// Restores health/time for some period /// Has fields: strength (f32) @@ -21,10 +21,9 @@ pub enum BuffId { /// Lowers health over time for some duration /// Has fields: strength (f32) Bleeding(f32), - /// Adds a prefix to the entity name + /// Prefixes an entity's name with "Cursed" /// Currently placeholder buff to show other stuff is possible - /// Has fields: prefix (String) - Prefix(String), + Cursed, } /// De/buff category ID. @@ -85,7 +84,7 @@ pub enum BuffChange { /// Removes all buffs with this ID. RemoveById(BuffId), /// Removes buff of this index - RemoveByIndex(Vec<usize>), + RemoveByIndex(Vec<usize>, Vec<usize>), } /// Source of the de/buff @@ -124,13 +123,18 @@ pub enum BuffSource { #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Buffs { /// Active de/buffs. - pub buffs: Vec<Buff>, + pub active_buffs: Vec<Buff>, + /// Inactive de/buffs (used so that only 1 buff of a particular type is + /// active at any time) + pub inactive_buffs: Vec<Buff>, } impl Buffs { - /// This is a primitive check if a specific buff is present. + /// This is a primitive check if a specific buff is present and active. /// (for purposes like blocking usage of abilities or something like this). - pub fn has_buff_id(&self, id: &BuffId) -> bool { self.buffs.iter().any(|buff| buff.id == *id) } + pub fn has_buff_id(&self, id: &BuffId) -> bool { + self.active_buffs.iter().any(|buff| buff.id == *id) + } } impl Buff { @@ -150,16 +154,12 @@ impl Buff { rate: strength, accumulated: 0.0, }], - BuffId::Prefix(ref prefix) => { - let mut prefix = prefix.clone(); - prefix.push(' '); - vec![BuffEffect::NameChange { - prefix, - }] - }, + BuffId::Cursed => vec![BuffEffect::NameChange { + prefix: String::from("Cursed "), + }], }; Buff { - id: id.clone(), + id, cat_ids, time, effects, diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 78ecaff28d..5f0dcb65d2 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{BuffChange, BuffEffect, BuffId, Buffs, HealthChange, HealthSource, Stats}, + comp::{BuffChange, BuffEffect, Buffs, HealthChange, HealthSource, Stats}, event::{EventBus, ServerEvent}, state::DeltaTime, sync::Uid, @@ -28,13 +28,14 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_bus.emitter(); for (entity, uid, mut buffs) in (&entities, &uids, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); - let mut buff_indices_for_removal = Vec::<usize>::new(); + let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = + (Vec::<usize>::new(), Vec::<usize>::new()); // Tick all de/buffs on a Buffs component. - for i in 0..buff_comp.buffs.len() { + for i in 0..buff_comp.active_buffs.len() { // First, tick the buff and subtract delta from it // and return how much "real" time the buff took (for tick independence). // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. - let buff_delta = if let Some(remaining_time) = &mut buff_comp.buffs[i].time { + let buff_delta = if let Some(remaining_time) = &mut buff_comp.active_buffs[i].time { let pre_tick = remaining_time.as_secs_f32(); let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); let post_tick = if let Some(dur) = new_duration { @@ -44,7 +45,7 @@ impl<'a> System<'a> for Sys { } else { // The buff has expired. // Remove it. - buff_indices_for_removal.push(i); + active_buff_indices_for_removal.push(i); 0.0 }; pre_tick - post_tick @@ -56,7 +57,9 @@ impl<'a> System<'a> for Sys { }; // Now, execute the buff, based on it's delta - for effect in &mut buff_comp.buffs[i].effects { + for effect in &mut buff_comp.active_buffs[i].effects { + #[allow(clippy::single_match)] + // Remove clippy when more effects are added here match effect { // Only add an effect here if it is continuous or it is not immediate BuffEffect::HealthChangeOverTime { rate, accumulated } => { @@ -77,9 +80,28 @@ impl<'a> System<'a> for Sys { }; } } + for i in 0..buff_comp.inactive_buffs.len() { + // First, tick the buff and subtract delta from it + // and return how much "real" time the buff took (for tick independence). + // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. + if let Some(remaining_time) = &mut buff_comp.inactive_buffs[i].time { + let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); + if new_duration.is_some() { + // The buff still continues. + *remaining_time -= Duration::from_secs_f32(dt.0); + } else { + // The buff has expired. + // Remove it. + inactive_buff_indices_for_removal.push(i); + }; + } + } server_emitter.emit(ServerEvent::Buff { uid: *uid, - buff_change: BuffChange::RemoveByIndex(buff_indices_for_removal), + buff_change: BuffChange::RemoveByIndex( + active_buff_indices_for_removal, + inactive_buff_indices_for_removal, + ), }); } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index b9a9bc8e99..cee3067994 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -155,8 +155,8 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Buff { uid: *uid_b, buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffId::Bleeding(50.0), - Some(Duration::from_millis(5000)), + buff::BuffId::Bleeding(-damage.healthchange), + Some(Duration::from_millis(10000)), vec![buff::BuffCategoryId::Physical], )), }); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 37d6d5c7e6..3cfb11af64 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -24,6 +24,7 @@ use common::{ use comp::item::Reagent; use rand::prelude::*; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; +use std::mem::discriminant; use tracing::error; use vek::Vec3; @@ -681,53 +682,158 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) 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>(); - let mut buff_indices_for_removal = Vec::new(); + let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = + (Vec::new(), Vec::new()); match buff_change { 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; + if buffs.active_buffs.is_empty() { + add_buff_effects(new_buff.clone(), stats.get_mut(entity)); + buffs.active_buffs.push(new_buff); + } else { + for i in 0..buffs.active_buffs.len() { + let active_buff = &buffs.active_buffs[i]; + // Checks if new buff has the same id as an already active buff. If it + // doesn't, new buff added to active buffs. If it does, compares the new + // buff and the active buff, and decides to either add new buff to + // inactive buffs, or move active buff to + // inactive buffs and add new buff to active + // buffs. + if discriminant(&active_buff.id) == discriminant(&new_buff.id) { + if determine_replace_active_buff( + active_buff.clone(), + new_buff.clone(), + ) { + active_buff_indices_for_removal.push(i); + add_buff_effects(new_buff.clone(), stats.get_mut(entity)); + buffs.active_buffs.push(new_buff.clone()); + } else { + buffs.inactive_buffs.push(new_buff.clone()); } - }, - _ => {}, + } else { + add_buff_effects(new_buff.clone(), stats.get_mut(entity)); + buffs.active_buffs.push(new_buff.clone()); + } } } - buffs.buffs.push(new_buff.clone()); }, - buff::BuffChange::RemoveByIndex(indices) => { - buff_indices_for_removal = indices; + buff::BuffChange::RemoveByIndex(active_indices, inactive_indices) => { + active_buff_indices_for_removal = active_indices; + inactive_buff_indices_for_removal = inactive_indices; }, buff::BuffChange::RemoveById(id) => { let some_predicate = |current_id: &buff::BuffId| *current_id == id; - for i in 0..buffs.buffs.len() { - if some_predicate(&mut buffs.buffs[i].id) { - buff_indices_for_removal.push(i); + for i in 0..buffs.active_buffs.len() { + if some_predicate(&mut buffs.active_buffs[i].id) { + active_buff_indices_for_removal.push(i); + } + } + for i in 0..buffs.inactive_buffs.len() { + if some_predicate(&mut buffs.inactive_buffs[i].id) { + inactive_buff_indices_for_removal.push(i); } } }, } - while !buff_indices_for_removal.is_empty() { - if let Some(i) = buff_indices_for_removal.pop() { - 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.replacen(prefix, "", 1); - } - }, - _ => {}, + let mut removed_active_buff_ids = Vec::new(); + while !active_buff_indices_for_removal.is_empty() { + if let Some(i) = active_buff_indices_for_removal.pop() { + let buff = buffs.active_buffs.remove(i); + removed_active_buff_ids.push(buff.id); + remove_buff_effects(buff, stats.get_mut(entity)); + } + } + while !inactive_buff_indices_for_removal.is_empty() { + if let Some(i) = inactive_buff_indices_for_removal.pop() { + buffs.inactive_buffs.remove(i); + } + } + // Checks after buffs are removed so that it doesn't grab incorrect + // index + for buff_id in removed_active_buff_ids { + // Checks to verify that there are no active buffs with the same id + if buffs + .active_buffs + .iter() + .any(|buff| discriminant(&buff.id) == discriminant(&buff_id)) + { + continue; + } + let mut new_active_buff = None::<buff::Buff>; + let mut replacement_buff_index = 0; + for i in 0..buffs.inactive_buffs.len() { + let inactive_buff = buffs.inactive_buffs[i].clone(); + if discriminant(&buff_id) == discriminant(&inactive_buff.id) { + if let Some(ref buff) = new_active_buff { + if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) { + new_active_buff = Some(inactive_buff); + replacement_buff_index = i; + } + } else { + new_active_buff = Some(inactive_buff); + replacement_buff_index = i; } } } + if new_active_buff.is_some() { + let buff = buffs.inactive_buffs.remove(replacement_buff_index); + add_buff_effects(buff.clone(), stats.get_mut(entity)); + buffs.active_buffs.push(buff.clone()); + } } } } } + +fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool { + use buff::BuffId; + match new_buff.id { + BuffId::Bleeding(new_strength) => { + if let BuffId::Bleeding(active_strength) = active_buff.id { + new_strength > active_strength + } else { + false + } + }, + BuffId::Regeneration(new_strength) => { + if let BuffId::Regeneration(active_strength) = active_buff.id { + new_strength > active_strength + } else { + false + } + }, + BuffId::Cursed => false, + } +} + +fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { + for effect in &buff.effects { + #[allow(clippy::single_match)] // Remove clippy when there are more buff effects here + match effect { + // Only add an effect here if it is immediate and is not continuous + buff::BuffEffect::NameChange { prefix } => { + if let Some(ref mut stats) = stats { + let mut pref = String::from(prefix); + pref.push_str(&stats.name); + stats.name = pref; + } + }, + _ => {}, + } + } +} + +fn remove_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { + for effect in &buff.effects { + #[allow(clippy::single_match)] // Remove clippy when there are more buff effects here + match effect { + // Only remove an effect here if its effect was not continuously + // applied + buff::BuffEffect::NameChange { prefix } => { + if let Some(ref mut stats) = stats { + stats.name = stats.name.replacen(prefix, "", 1); + } + }, + _ => {}, + } + } +} From 1d008dca33ba3d790da8602b213ce0d638200ce2 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Fri, 2 Oct 2020 14:09:19 -0500 Subject: [PATCH 07/25] Added functionality to remove buffs by category. --- common/src/comp/buff.rs | 14 ++++-- server/src/events/entity_manipulation.rs | 55 ++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 24c13a40d7..8a8338c605 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -29,14 +29,14 @@ pub enum BuffId { /// De/buff category ID. /// Similar to `BuffId`, but to mark a category (for more generic usage, like /// positive/negative buffs). -#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffCategoryId { Natural, Physical, Magical, Divine, - Negative, - Positive, + Debuff, + Buff, } /// Data indicating and configuring behaviour of a de/buff. @@ -83,8 +83,14 @@ pub enum BuffChange { Add(Buff), /// Removes all buffs with this ID. RemoveById(BuffId), - /// Removes buff of this index + /// Removes buffs of these indices (first vec is for active buffs, second is + /// for inactive buffs) RemoveByIndex(Vec<usize>, Vec<usize>), + /// Removes buffs of these categories (first vec is of categories of which + /// all are required, second vec is of categories of which at least one is + /// required) Note that this functionality is currently untested and + /// should be tested when doing so is possible + RemoveByCategory(Vec<BuffCategoryId>, Vec<BuffCategoryId>), } /// Source of the de/buff diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 3cfb11af64..8ac6ed2ed2 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -684,8 +684,9 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) let mut stats = ecs.write_storage::<comp::Stats>(); let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = (Vec::new(), Vec::new()); + use buff::BuffChange; match buff_change { - buff::BuffChange::Add(new_buff) => { + BuffChange::Add(new_buff) => { if buffs.active_buffs.is_empty() { add_buff_effects(new_buff.clone(), stats.get_mut(entity)); buffs.active_buffs.push(new_buff); @@ -716,11 +717,11 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } }, - buff::BuffChange::RemoveByIndex(active_indices, inactive_indices) => { + BuffChange::RemoveByIndex(active_indices, inactive_indices) => { active_buff_indices_for_removal = active_indices; inactive_buff_indices_for_removal = inactive_indices; }, - buff::BuffChange::RemoveById(id) => { + BuffChange::RemoveById(id) => { let some_predicate = |current_id: &buff::BuffId| *current_id == id; for i in 0..buffs.active_buffs.len() { if some_predicate(&mut buffs.active_buffs[i].id) { @@ -733,6 +734,54 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } }, + BuffChange::RemoveByCategory(all_required, any_required) => { + for i in 0..buffs.active_buffs.len() { + let mut required_met = true; + for required in &all_required { + if !buffs.active_buffs[i] + .cat_ids + .iter() + .any(|cat| cat == required) + { + required_met = false; + break; + } + } + let mut any_met = any_required.is_empty(); + for any in &any_required { + if !buffs.active_buffs[i].cat_ids.iter().any(|cat| cat == any) { + any_met = true; + break; + } + } + if required_met && any_met { + active_buff_indices_for_removal.push(i); + } + } + for i in 0..buffs.inactive_buffs.len() { + let mut required_met = true; + for required in &all_required { + if !buffs.inactive_buffs[i] + .cat_ids + .iter() + .any(|cat| cat == required) + { + required_met = false; + break; + } + } + let mut any_met = any_required.is_empty(); + for any in &any_required { + if !buffs.inactive_buffs[i].cat_ids.iter().any(|cat| cat == any) { + any_met = true; + break; + } + } + if required_met && any_met { + inactive_buff_indices_for_removal.push(i); + } + } + }, } let mut removed_active_buff_ids = Vec::new(); while !active_buff_indices_for_removal.is_empty() { From 3bcec0c40909e709c2223a16d3da833f5f6c882b Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sat, 3 Oct 2020 13:48:56 -0500 Subject: [PATCH 08/25] Server event used to deal damage/heal with buffs. Buff kills now award xp. --- assets/voxygen/i18n/en.ron | 1 + client/src/lib.rs | 14 ++++++++ common/src/comp/buff.rs | 19 +++++++---- common/src/comp/chat.rs | 1 + common/src/comp/mod.rs | 2 +- common/src/comp/stats.rs | 1 + common/src/sys/agent.rs | 1 + common/src/sys/buff.rs | 33 ++++++++++++------- common/src/sys/combat.rs | 5 ++- server/src/events/entity_manipulation.rs | 42 +++++++++++++++++++++--- voxygen/src/hud/chat.rs | 8 +++++ 11 files changed, 102 insertions(+), 25 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 04a12e119f..55355503b8 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -184,6 +184,7 @@ 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}] used magic to kill [{victim}]", + "hud.chat.pvp_buff_kill_msg": "[{attacker}] killed [{victim}]", "hud.chat.npc_melee_kill_msg": "{attacker} killed [{victim}]", "hud.chat.npc_ranged_kill_msg": "{attacker} shot [{victim}]", diff --git a/client/src/lib.rs b/client/src/lib.rs index 01c24a81c5..ad47263340 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1715,6 +1715,11 @@ impl Client { alias_of_uid(attacker_uid), alias_of_uid(victim) ), + KillSource::Player(attacker_uid, KillType::Buff) => format!( + "[{}] killed [{}]", + alias_of_uid(attacker_uid), + alias_of_uid(victim) + ), KillSource::NonPlayer(attacker_name, KillType::Melee) => { format!("{} killed [{}]", attacker_name, alias_of_uid(victim)) }, @@ -1729,6 +1734,9 @@ impl Client { attacker_name, alias_of_uid(victim) ), + KillSource::NonPlayer(attacker_name, KillType::Buff) => { + format!("{} killed [{}]", attacker_name, alias_of_uid(victim)) + }, KillSource::Environment(environment) => { format!("[{}] died in {}", alias_of_uid(victim), environment) }, @@ -1754,6 +1762,9 @@ 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 + .replace("{attacker}", &alias_of_uid(attacker_uid)) + .replace("{victim}", &alias_of_uid(victim)), KillSource::NonPlayer(attacker_name, KillType::Melee) => message .replace("{attacker}", attacker_name) .replace("{victim}", &alias_of_uid(victim)), @@ -1766,6 +1777,9 @@ 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 + .replace("{attacker}", attacker_name) + .replace("{victim}", &alias_of_uid(victim)), KillSource::Environment(environment) => message .replace("{name}", &alias_of_uid(victim)) .replace("{environment}", environment), diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 8a8338c605..9b0c947368 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -16,11 +16,9 @@ use std::time::Duration; #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffId { /// Restores health/time for some period - /// Has fields: strength (f32) - Regeneration(f32), + Regeneration { strength: f32 }, /// Lowers health over time for some duration - /// Has fields: strength (f32) - Bleeding(f32), + Bleeding { strength: f32 }, /// Prefixes an entity's name with "Cursed" /// Currently placeholder buff to show other stuff is possible Cursed, @@ -73,6 +71,7 @@ pub struct Buff { pub cat_ids: Vec<BuffCategoryId>, pub time: Option<Duration>, pub effects: Vec<BuffEffect>, + pub source: BuffSource, } /// Information about whether buff addition or removal was requested. @@ -144,9 +143,14 @@ impl Buffs { } impl Buff { - pub fn new(id: BuffId, time: Option<Duration>, cat_ids: Vec<BuffCategoryId>) -> Self { + pub fn new( + id: BuffId, + time: Option<Duration>, + cat_ids: Vec<BuffCategoryId>, + source: BuffSource, + ) -> Self { let effects = match id { - BuffId::Bleeding(strength) => vec![ + BuffId::Bleeding { strength } => vec![ BuffEffect::HealthChangeOverTime { rate: -strength, accumulated: 0.0, @@ -156,7 +160,7 @@ impl Buff { prefix: String::from("Injured "), }, ], - BuffId::Regeneration(strength) => vec![BuffEffect::HealthChangeOverTime { + BuffId::Regeneration { strength } => vec![BuffEffect::HealthChangeOverTime { rate: strength, accumulated: 0.0, }], @@ -169,6 +173,7 @@ impl Buff { cat_ids, time, effects, + source, } } } diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 4774b18683..0b748db9d4 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -51,6 +51,7 @@ pub enum KillType { Projectile, Explosion, Energy, + Buff, // Projectile(String), TODO: add projectile name when available } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 21a7dbd03c..2098e44dc0 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -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, BuffEffect, BuffId, Buffs}; +pub use buff::{Buff, BuffCategoryId, BuffChange, BuffEffect, BuffId, BuffSource, Buffs}; pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use chat::{ ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index e1e129b6b3..6c9feb5e8a 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -20,6 +20,7 @@ pub enum HealthSource { Projectile { owner: Option<Uid> }, Explosion { owner: Option<Uid> }, Energy { owner: Option<Uid> }, + Buff { owner: Option<Uid> }, Suicide, World, Revive, diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index a86e6ec529..06d39cb1c8 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -601,6 +601,7 @@ impl<'a> System<'a> for Sys { 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 { diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 5f0dcb65d2..203fe30e0e 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,10 +1,10 @@ use crate::{ - comp::{BuffChange, BuffEffect, Buffs, HealthChange, HealthSource, Stats}, + comp::{BuffChange, BuffEffect, BuffSource, Buffs, HealthChange, HealthSource}, event::{EventBus, ServerEvent}, state::DeltaTime, sync::Uid, }; -use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; +use specs::{Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; /// This system modifies entity stats, changing them using buffs @@ -16,17 +16,15 @@ pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( - Entities<'a>, Read<'a, DeltaTime>, Read<'a, EventBus<ServerEvent>>, ReadStorage<'a, Uid>, - WriteStorage<'a, Stats>, WriteStorage<'a, Buffs>, ); - fn run(&mut self, (entities, dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { + fn run(&mut self, (dt, server_bus, uids, mut buffs): Self::SystemData) { let mut server_emitter = server_bus.emitter(); - for (entity, uid, mut buffs) in (&entities, &uids, &mut buffs.restrict_mut()).join() { + for (uid, mut buffs) in (&uids, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = (Vec::<usize>::new(), Vec::<usize>::new()); @@ -56,6 +54,12 @@ impl<'a> System<'a> for Sys { dt.0 }; + let buff_owner = + if let BuffSource::Character { by: owner } = buff_comp.active_buffs[i].source { + Some(owner) + } else { + None + }; // Now, execute the buff, based on it's delta for effect in &mut buff_comp.active_buffs[i].effects { #[allow(clippy::single_match)] @@ -66,13 +70,18 @@ impl<'a> System<'a> for Sys { *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 { + let cause = if *accumulated > 0.0 { + HealthSource::Healing { by: buff_owner } + } else { + HealthSource::Buff { owner: buff_owner } + }; + server_emitter.emit(ServerEvent::Damage { + uid: *uid, + change: HealthChange { amount: *accumulated as i32, - cause: HealthSource::Unknown, - }; - stats.health.change_by(change); - } + cause, + }, + }); *accumulated = 0.0; }; }, diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index cee3067994..f577a95fcf 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -155,9 +155,12 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Buff { uid: *uid_b, buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffId::Bleeding(-damage.healthchange), + buff::BuffId::Bleeding { + strength: -damage.healthchange, + }, Some(Duration::from_millis(10000)), vec![buff::BuffCategoryId::Physical], + buff::BuffSource::Character { by: *uid }, )), }); attack.hit_count += 1; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8ac6ed2ed2..9d106d3d61 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -166,11 +166,34 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc KillSource::NonPlayer("<?>".to_string(), KillType::Energy) } }, + HealthSource::Buff { owner: 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) + if state + .ecs() + .read_storage::<Player>() + .get(char_entity) + .is_some() + { + KillSource::Player(by, KillType::Buff) + } else if let Some(stats) = + state.ecs().read_storage::<Stats>().get(char_entity) + { + KillSource::NonPlayer(stats.name.clone(), KillType::Buff) + } else { + KillSource::NonPlayer("<?>".to_string(), KillType::Buff) + } + } else { + KillSource::NonPlayer("<?>".to_string(), KillType::Buff) + } + }, 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::Revive | HealthSource::Command | HealthSource::LevelUp @@ -190,6 +213,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc 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 { by @@ -836,15 +860,25 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool { use buff::BuffId; match new_buff.id { - BuffId::Bleeding(new_strength) => { - if let BuffId::Bleeding(active_strength) = active_buff.id { + BuffId::Bleeding { + strength: new_strength, + } => { + if let BuffId::Bleeding { + strength: active_strength, + } = active_buff.id + { new_strength > active_strength } else { false } }, - BuffId::Regeneration(new_strength) => { - if let BuffId::Regeneration(active_strength) = active_buff.id { + BuffId::Regeneration { + strength: new_strength, + } => { + if let BuffId::Regeneration { + strength: active_strength, + } = active_buff.id + { new_strength > active_strength } else { false diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 8194ddf445..b9424d366a 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -373,6 +373,10 @@ impl<'a> Widget for Chat<'a> { .localized_strings .get("hud.chat.pvp_energy_kill_msg") .to_string(), + KillSource::Player(_, KillType::Buff) => self + .localized_strings + .get("hud.chat.pvp_buff_kill_msg") + .to_string(), KillSource::NonPlayer(_, KillType::Melee) => self .localized_strings .get("hud.chat.npc_melee_kill_msg") @@ -389,6 +393,10 @@ impl<'a> Widget for Chat<'a> { .localized_strings .get("hud.chat.npc_energy_kill_msg") .to_string(), + KillSource::NonPlayer(_, KillType::Buff) => self + .localized_strings + .get("hud.chat.npc_buff_kill_msg") + .to_string(), KillSource::Environment(_) => self .localized_strings .get("hud.chat.environmental_kill_msg") From a414ddf83059f985a3f1db8f0c9d0ab47e0cedb3 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sat, 3 Oct 2020 19:35:25 -0500 Subject: [PATCH 09/25] Moved duration to inside BuffId enum to future-proof for when buffs are persisted. --- common/src/comp/buff.rs | 62 ++++++++++++++---------- common/src/sys/combat.rs | 2 +- server/src/events/entity_manipulation.rs | 6 ++- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 9b0c947368..4193771b28 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -16,12 +16,18 @@ use std::time::Duration; #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffId { /// Restores health/time for some period - Regeneration { strength: f32 }, + Regeneration { + strength: f32, + duration: Option<Duration>, + }, /// Lowers health over time for some duration - Bleeding { strength: f32 }, + Bleeding { + strength: f32, + duration: Option<Duration>, + }, /// Prefixes an entity's name with "Cursed" /// Currently placeholder buff to show other stuff is possible - Cursed, + Cursed { duration: Option<Duration> }, } /// De/buff category ID. @@ -143,30 +149,34 @@ impl Buffs { } impl Buff { - pub fn new( - id: BuffId, - time: Option<Duration>, - cat_ids: Vec<BuffCategoryId>, - source: BuffSource, - ) -> Self { - let effects = match id { - BuffId::Bleeding { strength } => vec![ - BuffEffect::HealthChangeOverTime { - rate: -strength, + pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { + let (effects, time) = match id { + BuffId::Bleeding { strength, duration } => ( + vec![ + BuffEffect::HealthChangeOverTime { + rate: -strength, + accumulated: 0.0, + }, + // This effect is for testing purposes + BuffEffect::NameChange { + prefix: String::from("Injured "), + }, + ], + duration, + ), + BuffId::Regeneration { strength, duration } => ( + vec![BuffEffect::HealthChangeOverTime { + rate: strength, accumulated: 0.0, - }, - // This effect is for testing purposes - BuffEffect::NameChange { - prefix: String::from("Injured "), - }, - ], - BuffId::Regeneration { strength } => vec![BuffEffect::HealthChangeOverTime { - rate: strength, - accumulated: 0.0, - }], - BuffId::Cursed => vec![BuffEffect::NameChange { - prefix: String::from("Cursed "), - }], + }], + duration, + ), + BuffId::Cursed { duration } => ( + vec![BuffEffect::NameChange { + prefix: String::from("Cursed "), + }], + duration, + ), }; Buff { id, diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index f577a95fcf..be54208631 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -157,8 +157,8 @@ impl<'a> System<'a> for Sys { buff_change: buff::BuffChange::Add(buff::Buff::new( buff::BuffId::Bleeding { strength: -damage.healthchange, + duration: Some(Duration::from_millis(10000)), }, - Some(Duration::from_millis(10000)), vec![buff::BuffCategoryId::Physical], buff::BuffSource::Character { by: *uid }, )), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 9d106d3d61..d61cf9f6cf 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -862,9 +862,11 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) match new_buff.id { BuffId::Bleeding { strength: new_strength, + duration: _, } => { if let BuffId::Bleeding { strength: active_strength, + duration: _, } = active_buff.id { new_strength > active_strength @@ -874,9 +876,11 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) }, BuffId::Regeneration { strength: new_strength, + duration: _, } => { if let BuffId::Regeneration { strength: active_strength, + duration: _, } = active_buff.id { new_strength > active_strength @@ -884,7 +888,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) false } }, - BuffId::Cursed => false, + BuffId::Cursed { duration: _ } => false, } } From 8d9ac68c803490fdafc6442c469c2e341e37266e Mon Sep 17 00:00:00 2001 From: Monty Marz <m.marzouq@gmx.de> Date: Sun, 11 Oct 2020 23:52:16 +0200 Subject: [PATCH 10/25] skillbar update part 1 skillbar rework part 2 --- assets/voxygen/element/icons/m1.png | Bin 605 -> 2015 bytes assets/voxygen/element/icons/m2.png | Bin 605 -> 2015 bytes .../voxygen/element/skillbar/bar_content.png | Bin 90 -> 1410 bytes assets/voxygen/element/skillbar/bg.png | Bin 0 -> 2430 bytes .../voxygen/element/skillbar/energybar_bg.png | Bin 193 -> 0 bytes assets/voxygen/element/skillbar/frame.png | Bin 0 -> 3383 bytes .../voxygen/element/skillbar/healthbar_bg.png | Bin 190 -> 0 bytes .../element/skillbar/skillbar_slot.png | Bin 610 -> 0 bytes .../element/skillbar/skillbar_slot_active.png | Bin 3533 -> 0 bytes .../element/skillbar/skillbar_slot_big.png | Bin 243 -> 0 bytes .../element/skillbar/skillbar_slot_l.png | Bin 607 -> 0 bytes .../skillbar/skillbar_slot_l_active.png | Bin 3533 -> 0 bytes .../element/skillbar/skillbar_slot_r.png | Bin 608 -> 0 bytes .../skillbar/skillbar_slot_r_active.png | Bin 3533 -> 0 bytes .../element/skillbar/xp_bar_content.png | Bin 111 -> 0 bytes .../voxygen/element/skillbar/xp_bar_left.png | Bin 170 -> 0 bytes .../voxygen/element/skillbar/xp_bar_mid.png | Bin 133 -> 0 bytes .../voxygen/element/skillbar/xp_bar_right.png | Bin 159 -> 0 bytes voxygen/src/hud/group.rs | 4 +- voxygen/src/hud/img_ids.rs | 20 +- voxygen/src/hud/mod.rs | 7 +- voxygen/src/hud/overhead.rs | 4 +- voxygen/src/hud/settings_window.rs | 46 +- voxygen/src/hud/skillbar.rs | 1038 +++++++---------- 24 files changed, 420 insertions(+), 699 deletions(-) create mode 100644 assets/voxygen/element/skillbar/bg.png delete mode 100644 assets/voxygen/element/skillbar/energybar_bg.png create mode 100644 assets/voxygen/element/skillbar/frame.png delete mode 100644 assets/voxygen/element/skillbar/healthbar_bg.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_active.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_big.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_l_active.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r.png delete mode 100644 assets/voxygen/element/skillbar/skillbar_slot_r_active.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_content.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_left.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_mid.png delete mode 100644 assets/voxygen/element/skillbar/xp_bar_right.png diff --git a/assets/voxygen/element/icons/m1.png b/assets/voxygen/element/icons/m1.png index 774ae38bc55be16656992abd96f8e9556f1a0eae..1b5f1136516eda947db7fb75fd3cecfe7963996a 100644 GIT binary patch literal 2015 zcmcIlOKjXk7+xxxrYVttB0!uhmqU4YJu~)tZPQKhKH4l|lO<V{hXm?)X1r^%>v3%- zyNM8}Jy9Wqih2MPj@&8^oRE;9ClCk;(GwspJw#lPf&@1ZV!W>e)P!7EtzYARzW@8b z{~OP>`rO6osmG==nap%`w!9#&_lx7f$q8{@c;@9-#N}i-yAo$Ir%tEGJ(>5nAI)S= zobVgV$#U&^oA6E+Q|_{CuM>*sOy=DAUWmyWOC*;y{h%Q4e)_pA`LrM}<!Yc7mRQT5 z-HO=a)?9;Ztr3gL=VzpIJzD^DSc0Wqryaz0uORnvZE>FttFqJ&N!ALolO~jwYjvr_ zBPJPHpb!9%l((|b&~*bmEg^t_3UVq$3N&n>+b}N;f3nCHQO{l|Ul`^RPX)P^B%!UU z-EKGA)v`Qls?f456(AKMMMNm^W{}{X62xal2xS(N$PW{r2U3cNUA~?aWWjV$LMI%< z2JuiPA(-03p$fAgEol^3s~sNN>5QY}q_QEh4Ef#_IBsl)OkH3xUylf@Y_K3XGfEb= zc*5frKY-i#^*<O=GENw-NA12ml&GxDIs!Wuv9QlCv`Z1g36C0_w?~btk1|RKW|4Gu z$qy*+#!sY-h7(vBC#)c+T~na=!v;dO4n^0V0>}nn5Ly#Hpg6%t3+B}KHjffotJ&2c zPH;e2wOo(|sjTl)+b{_-v>aA+%e54SbXT#Ut|=y=76jPJLyPoziTPzt)>9?Y`2*Wh zP6WnSO`Cem({h@nfIOm#W#~w8%^X!s$^bBQW+FE?LYt3#v2t*GKWfT~3Pc86>baCC zF3>$CPj#TU*n^7ZVnz*&ED8jxy}Tvf=24APp{}-nyjm*NBkuWa@giQhSd^-jk^wEl zP*4`i5+%0BqYf5E^E<f7)G%nKwih%+xwC#Odb~MYNQ><9@UHDkeRtZJq>G{;lhh52 z%EMv*E;1jjpaE>R#RTXfE*XTy+)KJRVop=E<Ud`g9z;II8;4UrnnV6a>O)yc3kOXm zzObsC7Ne%$qCr#CfA?$87$b8z!uDx5<Dm>i+#CxsbAXpY-tZvN6@o3Mu)J6luI>>< zhsZ#>$xKG*fJNKi!x}l*#3n+TA^fjdhGMwF!PxTzn5O4*fKsYk+QGAn6(^5CxmeOH zr{X|pmK;#37-bV(C_1nL_RZ|;FepboyzYMFzOVU6M6pGrx8uD@JdAfY76`#3v3H%A z&n=35@3m^#X_#;A+;FCDN%!6R+vMAS&A+~T`u4<6I$Ztz$BBoX?UQd@fA_t|KYVDW z+MK;}<Ms>E<zH{EuHkQ=Ye+AytzVIT{^i(Lj~pi}mo~0DU}JS>_t^II)t9QhufO@@ zod-T!`rwP5`VaWyusI2?b(%N7TRHyct7o6wd~^HCv!9&$K3l)@<*g*$zBARzT=}Eo HrK^7fg7Sc; delta 595 zcmV-Z0<8Vt58VVIiBL{Q4GJ0x0000DNk~Le0000f0000v2nGNE0IRkFZIK}te*hq3 zNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe02gnPU&TfM00HAkL_t(|+U(k` zZo@DT2k?JBo**z%-k>;T4(c<EkBn|oVaf~xO8W+d<wiEEao?a)w2|SGsY8kK9Vbhg zRk~x9lKPi(d{%w6ZnqnH;*NR4f9kuyUKp5-(K(K50^Dpiv*?5n)$tm$YPC#v3Ao$s zW<e^YAf>EgEfZu7`11N;JaFJS#@A}Cp_GDB3UM5_LaWGT;QO1|sQUxfs{m=54$7$3 z8l3Z1sA~lL{F-&;gkjhO&EL`5>V7n^2#w<yob%eu+SM#ZQS@+N5y}{=e^nE^I?aqR zEE$Y3L{U`Ard<RsnZ>Sb+IWH>Bq2w|@r3m%008*DKPl3k8hE~30U-Z8bc>vQy3dy@ z0?$F<IXRZzNkS6xzaRt80rrPc<!7#Ftdr|ifb;QW^aXPRJB#_4!2{31_4}vvgKX&T z(^UN3DWBN)eN4O~(&{E8e<2A;NJ0{lkc1>8AqhDODTIKO5=oLwauPN*(t4`t7D7NN z1+DcW*-X<k?>82kq8T#JX4aK@G&BP?^%#qpTnyR;1bSM@*-c>>jyjDnJ+0{L()Kpx zIp=vRZ@b-2icFFO00#HF8=+YXt<v-Rb4V%k1=|MfLzbXL>{L;~DFFljgV73|F<R@j hJK*N~qj6dM4gjen^JZ@eh?f8W002ovPDHLkV1k@13sL|8 diff --git a/assets/voxygen/element/icons/m2.png b/assets/voxygen/element/icons/m2.png index aeb57b3d46b0f57d2e9465da5e1e1d20d788bbf1..0e346644b9b8843c72fa9d5dda8b51d88932a9d7 100644 GIT binary patch literal 2015 zcmcIlO=uid9N!2vX*7ObLIfSBB9`Xuym|AnGt+L`{c5w)jbXcxs8D?K-kaSqyEEg= zY<3etV!?waHF#+)DkAh`=%o}9sd`XEp$H0sc*wN~#gm6#e6wGPlEhqGc4pqp{NL~Y z`~UvGdGGDTg>zGrGn1)QYN|Y6tn%w4{CI5Qetxc=y!a}=91Z4Qj8drw9#4+3)Rn8# zsnqzmTU(BoE9WiZx6+vUHcNL~0gq0lPMz)sn5?i^uvy*pvf|w@Zi#|Rv*MCgft8@Z z8t(jh$S$le)X4e@F{yZZPB_)II6#ZVSm?HzUSxH%Vh`8i<K(d<3cZkcB`fBVgu-%V zQ7HH!6ZA9$1VSWa%(Selst#ub1QC>=CdmlMx&>8B&Ip5_$g_piv8u(hgIxSBD>mXd zuq3I|>7+YK+7IiJY?`J7k%SQ75g=OgV%!B@bZm%FWDyD7Aa;FEND#5@x8tnHnf6O) z1tVB58py;2le##N<TOl58U|J>2Zy#=qv$9ut@11bzIy^kwY7jrRTlZ}kg(D!^WtN} zWI@A^{ixyZ!)^5X9}FoOB@EhOvnLNF5^J&+$BuZc+~XHmg^=Oc4{N^P95!lkm{CA- z8VS!WxgPa9(eZ@QU;-=Rm}SMJYe43IxrUIX%9e^wKx9GK53O(?P#ojK1^d)>)(#U| zsaRz%im^vnxtJ9>skG}-i)n-sg~`CysS2>IW&qY489+xdO>9F-h|}Z6=NEm_PLxRI z_iamk!ZAi_S~z1WnyOJ?Yl;ROs>;B~$R<#2%~51SacnI!M0+80`Im#6yHOKXlp_+= zAQ@&N4#8}InTh~bAqG&PDm4(zs0tE=oxBCV>4z1ca&?v6&&!3vV(2?=lfQ_n=kh|i zRM2Hp*8xh)qCkn|_+blkqq!|yXHwwR6Wendyxe&=;yqp)d`K7AYlBhK6?*QpFiAd& ztVj|!Fe(lnyL-rdxPtnyod)Be2e_mk7Wq!x!6D1lc}xD&h0=cHBfNSr^}{*jf22N; zl{BzdXMBZ~#H1K0S&RBjk^bGU9b*j5mE*Qgx)}{*$m9A*kh#6ROkqZMWTFCsO$HdZ z9I#b~098ghQVnJ>Li;S5?he+_!3H)EQgj4t#ngdrs}Nwv;b4lI(IBN%HI@Bm=S#T^ zg2jA6F>|GyEE|O!ER^)3fzIZ0atZF5+0&t4j&yL{y~tgw`A|f@MI^(~-o$T4yBqVk z;340;-kSO8O}_8FQ7-0c#t*;0fAXOX_+TNLc<0NHUcdL`*R#ea+qdUGy}5mD;+LbF zPamJZGqsto(&?{G%wGPc{q3}M<+EpwZ2tVx2iHITYYu<^)RFeAb??2KPh9=8KDKf9 z(zTcW_z-P;w>h(V`Nyr=jq|@<zkO!%&a+F}#amOGYY*Rd=J~DQ*Ks!1`FwGE`_Xsf PWD72r7K&T>S1$bxxCnzZ delta 595 zcmV-Z0<8Vt58VVIiBL{Q4GJ0x0000DNk~Le0000f0000v2nGNE0IRkFZIK}te*hq3 zNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe02gnPU&TfM00HAkL_t(|+U(k) zZi7G=2k<|XC#bxHH>hsio#GkhXKK5ol5<Fs&CX13ZXU%s(0v0EvYG0dg^BG5r7axW z(XPw)Ss?uQa|gM6=j?Vn7*vIEf6M7Q=o;Y02KI*FnO+zK!6>>UNt*X_h9IjZP6z=h z<tWG?2zsGj7chz<NGZWN$9z6FzHzbrFh2L?>An~0bpgXL9E1Y^#u$oi0j!or=il7k z^+G+Hz`c5VH#={&?1j3c0Ea%zH&jY_bb`9=K|FN)*@g-s4u+rGOi>5{f6h5BS)B8B znw5L;6i=ktgu1wbA|xSyT8uFOz+&bD&#!+A$ruA;4B4hYwkfPbttFXl3i#6r{OP2I z(wHP9A^$6K@Mq}Z5&m>CXtih!ni)bDGaqGHnj!U{V@UbaNxcATr?Hs%zj{z>Nq+rx zXyOo(kc1>8Aqh!HLK2dYe}p8YuCgo<$1&10ZT54nB1xx4CP@ON6!JVjy7oOsig}(x zDYciPPO|Rdg<*&&ip~zzK;4aNrr3Pl7lZZz0l;Z(Z#dawWR_R{I)FalG2{VMxAE5N zwN+#s#{dAOR44Ehau2H8XRXHMuPaT{dct-AyO4X(Ce~K9XD~W;DYQ5}TW+dzeG9t| hdWIKQm%!=z902Ez>Q8=CK0p8f002ovPDHLkV1g=-1VjJ; diff --git a/assets/voxygen/element/skillbar/bar_content.png b/assets/voxygen/element/skillbar/bar_content.png index 61d1f93094ef1999a7905daaa25625201fb048a1..5f276782385bb3e7a642ebe9d0bef871425685d5 100644 GIT binary patch literal 1410 zcmbVM$!^<57-pKZ$buZ&0zr_|fX*RBOU@#Rf(B*`NAe<|bPZd8-Q<vrh9ilv$YD6+ zNcJ(e9C8j&^wbB)3*?Y<ZY_#FN3Xr5KeZ5c;nrvf3}^f1`}hCV?#|ZjoA+)yj&r-a z-Pwcr3XCtVe+K{8g&e?iBj5hfIL@uF%W=i|>F2wS^ZA{mcW4iz@43{eFJj$S{xr=2 z?KrDz(_F|SWl>)ZlPq-qeD~f(NgTQdjfg~fTaA+K(?ac^?)2p8k!;28+C#KD<v@@s zE6_9@XNFHhcgD+MT^?f>%^>zDbk|FP=rGzvZCxl7_{5WhFw|`MG^p1D@*QG?5lkAG zGLHtF)H!XU#pi;wA|CL)&gMcE>_T^BZO$>COeX%M=IdgJX{*)3gki=!KzQaPvtsIH zW@X9HQAQR?ZWEoMl2P>au?=0&bZ$bLSG1W~xCtDFry|GHC#6X<U@R+Keq4-a!efcm zSfwhn2C%fk<|A#j8R>tauCC7s0M|xQCF4R}(zFs`?8XZ)W8shs(WZBjE4-(SJ}#u% zcmZmzl-A_DT_|C7(bIao4AkyYWyGk@(1U{{i}l2ObLN5S2&+Q3jG9M@M_N5r<Mk#d z^jpF>A#-S?<79Ah9!hDCQ4Z)@a1IJ76ALRY1;>&Pbdd@$I7!7&;XE6<sB$B3>#;6C zFlbjhd){rgcMCm8#&BWwwjQDGMmwObAn=$^XX-`~?`Fn|OsZ}tbip}(lEj=+)~8L; z@EVO;?2%@jdO}j_#r<YKXcER+Y;G>R?`U~kroi%j{6F9SP$W=-V*JTGWnq@N!?zOy zQ9W6d&c1rSSd9}jOBXI=Sq7mi%lNC<T^uK$?14G$WTb%fKk{M@GkRbrqEPEY@Ybby zhe3H<7VO*({7(~ScV~)w35SkRE~{1ro2uEV3_4r^t?b(4U;cnL_*J*F-n;%p{`K+W f+V5}h(_g9c+vb<OtM|=^vP*V1b~<k#J$v~#pS-PR delta 71 zcmZqTj+!9p$i~3Hz$Ea+5J)i=2e~^jtUD+3637wpba4#fn3??J|NsB>%k<;-86+q( YXc!6wKL{1C1j;jby85}Sb4q9e0Mg<WrvLx| diff --git a/assets/voxygen/element/skillbar/bg.png b/assets/voxygen/element/skillbar/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..cd8fbaf0c4dfde7c093c1efba6a7049c8ea5871c GIT binary patch literal 2430 zcmcImTWs4@7(VHyt+ZV=G-*_bX`TlNG#novJ9ca}+tMUkBQ2$LK`RVRIkt}zYl&@a zw@C{^8A!k$5JEzNp^C?e7gzzo3lA%o2OhWyc!7yAKx{A#O-QgOCOFP*x2{Z+7X7hL zj=%H$-+wtiIXpSBd)>gD0|0<^`SI)&0I^eaJlVg7UO%*t7U}IaZ~Tz}fWZyXHwIo? z7y)4I^LBAMoG$Fq47V1?rmK^9qvlaG0HZq^9ySh=kkLumc2ey5500~pZKl|Li2^Km z8B(#w=Y6twexhj1A2d{x-Lah+ZD^FBMncRqYE>uD8Y#BLtI=^Z&9h7k5*|#k>Bu27 zU6^Dtu1^>_4mkrt#3*W9kR?fm4=@NK$ioCLAWo1qC~1PibUrNg=9`u_mEG0xMSrE( zN*H<?&)4hqcwLOUewi0kRplY#5#lI<3+9{<H#jHQ+GWU+!0>G^v|WdZ7_siogejIP zZHG|vdbCc^NfS*N-@qO(#9<Ut7g#8)46W6A=pf9^QZF67%Mu60IgjvDByeYZgXCt3 z6K?G~^D1uW1{L=@+Io+#P-q%Ghu(}|ZKcOFcv2-b${kRw(9-8=8K2<L^^2}s?G|dX z>ybf195MIpvmMi|2lq#cItxe^ha|;DRpSI|LQzO+qNs_|Hi$F`+t32-0~3e%8Zbgy zBB4R$3b1S1*4&Li3k5Cj1R-_|lFz1CDk^T<rlyDqWMG0h1&RhI63OCpL$){xA;A)4 z3+p;*>7whit}zp3B3j>WThlctM=zTi!MXt@Q{W^W>Kw5omD4Q+aVjK8Qj-c+M6t{E zpl{QigR7UbMygE8DC+`DB2(gY$x=C0l?aDX(&Ut+fK&@9B%uf_bIEVUt-5}}HEDJ! z|E2kSCNt@~mR+R}f~nnOOg@*91yz<g6sH8Hp;@k9!?e-t8ZHywbIMWMQyDbeaXX+j zp6hI+z2x!EsA@B<?$odmZHg3YMBPA4wli%nlja)>s7+h15K6j2m$YGlYlU^}lXRJu z<bS)6zn=I2&#o+860L-&LV+VXR5(MF6i!bf%o&2BTBc%IYLce%7E14M8x`!734O!z z?9B&rW9@#OV6NVPH&ch@gW;nWYrDhvtDEmqH+OeRn(pDKHG@t~$8fn<-R<2il*CDd zL>Y0qsLGtIOOV5sCAA8efTn3msyN#6TeYuUJ(f!=2xiAJqMFX71tFP9!%R-jCeg03 zw2*_Bt!#0$CGab2Z6Pl^PN>_~6g{J&VefFHKYGV2acISTdU!s2=AC`?<X*^U)5XU3 zfBf+Fn|<k_+(UQ#bo%$h3&Zcfutk67o-HFMwuP^UcmMp7<$Uz-Cy#Ce%>%<RuzDo2 zHwHZGz^0vtzuff1$(I!{f3o@Mp4gh$#?q_9iyy~g7dGzvW<RsUEcFd#VzVRB{qx7< z^(6|4K<i68o3Uj^hc@?}`}z<VghT&q9e!na^>h=%rTFIlMFpJw5wsSJ3@&cYt!X|* zmE7BR*X=J3ilTVp__31%i${)px2|spd>ixwW+}2JSVaxcIcohdvi5^p`|RPEEQ;@5 z+)$#|Kf$s0K+`(&+-Fb5z~5hh^641(Ykdi{jV9sLj#cOO+u*B<Tm_VVJKv1aC?mTt azpDDn*<VN3$_vpyq<n57`_@?L>Hh$$TI6y7 literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/skillbar/energybar_bg.png b/assets/voxygen/element/skillbar/energybar_bg.png deleted file mode 100644 index c05ee0e667fc5b91509ad0f04b5ff3acdc898595..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^DL^d3!3HF&56qYfq!^2X+?^QKos)S967zI%42fuc zd-EVCvmp=b#f=_}@Ai9bWW8a@;$!upeQx=sK!X=a{0WO$m>1;bFHAnX{uJAc;<&rd z&hGksCeEYbf3ksxM01NFZld9N<++-9_e<kLD-|_Yb#1-&<A>qYg}d1gh`R>}7;klO RmIOM3!PC{xWt~$(695hOMr!~7 diff --git a/assets/voxygen/element/skillbar/frame.png b/assets/voxygen/element/skillbar/frame.png new file mode 100644 index 0000000000000000000000000000000000000000..bd380548af95113394d8e1486036a314e22fdb44 GIT binary patch literal 3383 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 KQSOe`IQ|RUKF@go literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/skillbar/healthbar_bg.png b/assets/voxygen/element/skillbar/healthbar_bg.png deleted file mode 100644 index 3696ab4fd328362ca1b9bf51a9b53a8c627c0dbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^DL^d3!3HF&56qYfq!^2X+?^QKos)S9<WzXNIEF;D zzP-7T_ppJ0bKtxQj642MF>jorWR~Dx!15_xwUnh!eX3se+Rfj^(x$rfsHFZb*{*W( z+lM*-uicHhto*@kf&mMsGah38^QU!f$LCC1@_ar=+SS?(wQB7To^v}Hh%5d{nP(1k O1B0ilpUXO@geCyWfJ3SP diff --git a/assets/voxygen/element/skillbar/skillbar_slot.png b/assets/voxygen/element/skillbar/skillbar_slot.png deleted file mode 100644 index 9a132d9882ae1e5c55e3ac659f050a688b876236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gPdP)<h;3K|Lk000e1NJLTq000yK000yS1^@s6jfou%00009a7bBm000XU z000XU0RWnu7ytkPifKbbP*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-ocFJ!n$_6vgr1 zd#O|;rGptnI=mq|hy_s*M;nD8R0y#`5*<ulnlzA?hJ4`CQE)9d__0`ZaB<ev!Br3h zKR}!uofKW9#36wMA%b^0oO{pZ-ap}9J5Krwr${=3UNN7GO65wl_|<~6l)#B<-0!Xx z3hU<9ulE9Yx7v=AzW=MG`er{Sh;Sk9_67tuaqiZjJ0Q4^OuSXC6FkSsUa4Fm_=rqx ztiK`CER`#Skm+p{FA`=DHD`2fM#o#dgzzk~^`uiLERA)&PPl_?(jLZ>I|HOQJJ+rv zixqj7T~QFJ8ER=8Sk=-I=_|_1&B-6dih`k<ww74rl5L5qmJX$ICHk9fzj5aDcvCvZ zfiI)ck7F#}Wq)@x`u%t`+J8doGp{<+{PrH_f0Ejrdf7)T-{!^ZsaLzl%R3zT)UEca zAce5e;QKR{${c>nq3dIAlfI`P*w|tH4G$i2yvg#7Ie#CV<@V2+3BNxC+j4t&%MI!P z001CkNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe02gnPU&TfM002@+L_t(| z+U(V#5x_7E1i@1#1sqlX#f7Q^hXk0*6QnC(;eV(LpsMJk0&s8G(*dh0RRUODY%4Dg wH{gH+4)_!JEp49ILX|ju0WSam0RR630D>hE=YZX<z5oCK07*qoM6N<$f`Wbn{{R30 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_active.png b/assets/voxygen/element/skillbar/skillbar_slot_active.png deleted file mode 100644 index 1cbfb65d5c9b6d3c9458b66bf3fa221172fb9233..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_big.png b/assets/voxygen/element/skillbar/skillbar_slot_big.png deleted file mode 100644 index c7f27c4897d7f7c54a47785e770cf3f538eaac76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9GG!XV7ZFl&wkP*5S+ zBgmJ5p-Pp3p`n?9;pcxK{gQ#9)PRBERRRNp)eHs(@%%~gN8NyGD?MEtLn>~)xnaoH zV8G*iamyoyxZ?kZOW*P)bTufY>3p$uJ^5+MMc+$u$8KiLD2_U)GWP(hx!tzZnJTXF zeTN@Znmd~xw$-sc!z`fS(7?dR#KM7KJg{%bd~#|#|1x1crA48q^J5pqi|1JaZT<hB Znc-KZ@Me+gOLBl*22WQ%mvv4FO#t>3OgsPp diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l.png b/assets/voxygen/element/skillbar/skillbar_slot_l.png deleted file mode 100644 index 5e786baebf6c5f06d7e39057cd31dfea421b4c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmV-l0-*hgP)<h;3K|Lk000e1NJLTq000yK000yS1^@s6jfou%00009a7bBm000XU z000XU0RWnu7ytkPifKbbP*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-ocFJ!n$_6vgr1 zd#O|;rGptnI=mq|hy_s*M;nD8R0y#`5*<ulnlzA?hJ4`CQE)9d__0`ZaB<ev!Br3h zKR}!uofKW9#36wMA%b^0oO{pZ-ap}9J5Krwr${=3UNN7GO65wl_|<~6l)#B<-0!Xx z3hU<9ulE9Yx7v=AzW=MG`er{Sh;Sk9_67tuaqiZjJ0Q4^OuSXC6FkSsUa4Fm_=rqx ztiK`CER`#Skm+p{FA`=DHD`2fM#o#dgzzk~^`uiLERA)&PPl_?(jLZ>I|HOQJJ+rv zixqj7T~QFJ8ER=8Sk=-I=_|_1&B-6dih`k<ww74rl5L5qmJX$ICHk9fzj5aDcvCvZ zfiI)ck7F#}Wq)@x`u%t`+J8doGp{<+{PrH_f0Ejrdf7)T-{!^ZsaLzl%R3zT)UEca zAce5e;QKR{${c>nq3dIAlfI`P*w|tH4G$i2yvg#7Ie#CV<@V2+3BNxC+j4t&%MI!P z001CkNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe02gnPU&TfM002)(L_t(| z+U(Ua6~HhI#89~iw1z*ptY(ldX$ZuxlLg?9ccB8T%;cu+5bB3b?iwlqtOD$y55+au tV1o_*1n#9B6<eqh)feyr009600|01#5JT0CO{@R_002ovPDHLkV1hRI1<?Qi diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l_active.png b/assets/voxygen/element/skillbar/skillbar_slot_l_active.png deleted file mode 100644 index d146f3d20fba27262ca8c6d7394a58e34f6b569c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r.png b/assets/voxygen/element/skillbar/skillbar_slot_r.png deleted file mode 100644 index d6d603095812271a478a7d85890489b21fc32937..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmV-m0-ybfP)<h;3K|Lk000e1NJLTq000yK000yS1^@s6jfou%00009a7bBm000XU z000XU0RWnu7ytkPifKbbP*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-ocFJ!n$_6vgr1 zd#O|;rGptnI=mq|hy_s*M;nD8R0y#`5*<ulnlzA?hJ4`CQE)9d__0`ZaB<ev!Br3h zKR}!uofKW9#36wMA%b^0oO{pZ-ap}9J5Krwr${=3UNN7GO65wl_|<~6l)#B<-0!Xx z3hU<9ulE9Yx7v=AzW=MG`er{Sh;Sk9_67tuaqiZjJ0Q4^OuSXC6FkSsUa4Fm_=rqx ztiK`CER`#Skm+p{FA`=DHD`2fM#o#dgzzk~^`uiLERA)&PPl_?(jLZ>I|HOQJJ+rv zixqj7T~QFJ8ER=8Sk=-I=_|_1&B-6dih`k<ww74rl5L5qmJX$ICHk9fzj5aDcvCvZ zfiI)ck7F#}Wq)@x`u%t`+J8doGp{<+{PrH_f0Ejrdf7)T-{!^ZsaLzl%R3zT)UEca zAce5e;QKR{${c>nq3dIAlfI`P*w|tH4G$i2yvg#7Ie#CV<@V2+3BNxC+j4t&%MI!P z001CkNK#Dz0D2_=0Dyx40Qvs_0D$QL0Cg|`0P0`>06Lfe02gnPU&TfM002-)L_t(| z+U(Ua5x_7E1i@1fTx<T|(u#rHctXIMR5}1#{)busnaNEmK!jQcd%0_=1W*mwhdwkn u;D7@T_!D@Rc6Dr_N_1bq1^@v6{{sMadk{rN!E>Ad0000<MNUMnLSTZc5d(Yx diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r_active.png b/assets/voxygen/element/skillbar/skillbar_slot_r_active.png deleted file mode 100644 index dd4eef2233ca605a9c97f8802a77d8241fbbdc60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 diff --git a/assets/voxygen/element/skillbar/xp_bar_content.png b/assets/voxygen/element/skillbar/xp_bar_content.png deleted file mode 100644 index f44d2432d7c06d0c52dd75b4ee4cbbdf9ae9aa5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrB!3HFm1il!81d4;)ofy`glX(f`XnMLhhH%Ve zlw)}KpW&s;|Nl%+emr7edjI<^1Jj2;9~qcl{d&Q`^!n!u2HqKh4hgrmWdJoXc)I$z JtaD0e0stA7BV+&o diff --git a/assets/voxygen/element/skillbar/xp_bar_left.png b/assets/voxygen/element/skillbar/xp_bar_left.png deleted file mode 100644 index 4693d465bb8990578f44297e543de2e341363e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^DL~A{!3HFqWPaBHDaPU;cPEB*=VV@jWIbIRLp(a) zUNhu6V8Fq8Vf`0||NoYs;Y?~bo8aW!CRXwzKj@6YpMw&4@8tHDd+0HXxT(4sMP)NE zOj_SxR{XO+U0=bUYl#GFP!E&lvl+7s_k;sQ&oLzY%k<QfW<7Z##zjwk@iZyk*|m;0 Qfc7zXy85}Sb4q9e05oMet^fc4 diff --git a/assets/voxygen/element/skillbar/xp_bar_mid.png b/assets/voxygen/element/skillbar/xp_bar_mid.png deleted file mode 100644 index 6558458ce2f39500115c7da71c5bd522535f8c38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0YJ>f!3HFiS#H_@DaPU;cPEB*=VV?2Irg3|jv*eM zZ?7HXWKa-bJ~$`hz}Ne0zCEzxOI+S0@X15f^5SMw*$3sjcP}@tIm_xK#u2HI(im`5 gx_O7g32Elo_hn3_oykgdK%*EuUHx3vIVCg!0D#OYUH||9 diff --git a/assets/voxygen/element/skillbar/xp_bar_right.png b/assets/voxygen/element/skillbar/xp_bar_right.png deleted file mode 100644 index c45a6ba988d358b82928e1886d6892353e366fc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^DL~A{!3HFqWPaBHDaPU;cPEB*=VV?2Igy?&jv*eM zZ?A3SJ>bCObTNMl<DUOh<~`7h-L+&-z=tUFX#zs5Ifon>U%c0Uw2^Vu?#mOH)7To` z{oAu#F-@prCQFi9;|xus>Rb;m=NONqwV~H<^K@@cTgH&L?W%xe{_)#JKuZ`rUHx3v IIVCg!0F(MS-v9sr diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 26060b3084..26b853cd08 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -1,6 +1,6 @@ use super::{ img_ids::Imgs, Show, BLACK, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, LOW_HP_COLOR, - MANA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, + STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ @@ -404,7 +404,7 @@ impl<'a> Widget for Group<'a> { // Stamina Image::new(self.imgs.bar_content) .w_h(100.0 * stam_perc, 8.0) - .color(Some(MANA_COLOR)) + .color(Some(STAMINA_COLOR)) .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0) .set(state.ids.member_stam[i], ui); } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index eecd9d18da..a955e2685f 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -147,22 +147,12 @@ image_ids! { // Skillbar level_up: "voxygen.element.misc_bg.level_up", - level_down: "voxygen.element.misc_bg.level_down", - xp_bar_mid: "voxygen.element.skillbar.xp_bar_mid", - xp_bar_left: "voxygen.element.skillbar.xp_bar_left", - xp_bar_right: "voxygen.element.skillbar.xp_bar_right", - healthbar_bg: "voxygen.element.skillbar.healthbar_bg", - energybar_bg: "voxygen.element.skillbar.energybar_bg", + level_down:"voxygen.element.misc_bg.level_down", bar_content: "voxygen.element.skillbar.bar_content", - skillbar_slot_big: "voxygen.element.skillbar.skillbar_slot_big", - skillbar_slot_big_bg: "voxygen.element.skillbar.skillbar_slot_big", - skillbar_slot_big_act: "voxygen.element.skillbar.skillbar_slot_big", - skillbar_slot: "voxygen.element.skillbar.skillbar_slot", - skillbar_slot_act: "voxygen.element.skillbar.skillbar_slot_active", - skillbar_slot_l: "voxygen.element.skillbar.skillbar_slot_l", - skillbar_slot_r: "voxygen.element.skillbar.skillbar_slot_r", - skillbar_slot_l_act: "voxygen.element.skillbar.skillbar_slot_l_active", - skillbar_slot_r_act: "voxygen.element.skillbar.skillbar_slot_r_active", + skillbar_bg: "voxygen.element.skillbar.bg", + skillbar_frame: "voxygen.element.skillbar.frame", + m1_ico: "voxygen.element.icons.m1", + m2_ico: "voxygen.element.icons.m2", // Other Icons/Art skull: "voxygen.element.icons.skull", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 0350b89dd1..409c13bc2f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -91,7 +91,7 @@ const BLACK: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0); const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0); const LOW_HP_COLOR: Color = Color::Rgba(0.93, 0.59, 0.03, 1.0); const CRITICAL_HP_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0); -const MANA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9); +const STAMINA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9); //const TRANSPARENT: Color = Color::Rgba(0.0, 0.0, 0.0, 0.0); //const FOCUS_COLOR: Color = Color::Rgba(1.0, 0.56, 0.04, 1.0); //const RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0); @@ -2031,10 +2031,7 @@ impl Hud { }, settings_window::Event::CrosshairType(crosshair_type) => { events.push(Event::CrosshairType(crosshair_type)); - }, - settings_window::Event::ToggleXpBar(xp_bar) => { - events.push(Event::ToggleXpBar(xp_bar)); - }, + }, settings_window::Event::ToggleBarNumbers(bar_numbers) => { events.push(Event::ToggleBarNumbers(bar_numbers)); }, diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index a69bccbea6..e24e4eb853 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -1,6 +1,6 @@ use super::{ img_ids::Imgs, DEFAULT_NPC, FACTION_COLOR, GROUP_COLOR, GROUP_MEMBER, HP_COLOR, LOW_HP_COLOR, - MANA_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR, + REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR, }; use crate::{ i18n::VoxygenLocalization, @@ -254,7 +254,7 @@ impl<'a> Widget for Overhead<'a> { Rectangle::fill_with( [72.0 * energy_factor * BARSIZE, MANA_BAR_HEIGHT], - MANA_COLOR, + STAMINA_COLOR, ) .x_y( ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 6fb2473742..e6c0af70b5 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -1,6 +1,5 @@ use super::{ - img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, XpBar, - CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MANA_COLOR, MENU_BG, + img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MENU_BG, STAMINA_COLOR, TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ @@ -258,8 +257,7 @@ pub struct State { pub enum Event { ToggleHelp, ToggleDebug, - ToggleTips(bool), - ToggleXpBar(XpBar), + ToggleTips(bool), ToggleBarNumbers(BarNumbers), ToggleShortcutNumbers(ShortcutNumbers), ChangeTab(SettingsTab), @@ -795,41 +793,7 @@ impl<'a> Widget for SettingsWindow<'a> { .font_size(self.fonts.cyri.scale(18)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) - .set(state.ids.hotbar_title, ui); - // Show xp bar - if Button::image(match self.global_state.settings.gameplay.xp_bar { - XpBar::Always => self.imgs.checkbox_checked, - XpBar::OnGain => self.imgs.checkbox, - }) - .w_h(18.0, 18.0) - .hover_image(match self.global_state.settings.gameplay.xp_bar { - XpBar::Always => self.imgs.checkbox_checked_mo, - XpBar::OnGain => self.imgs.checkbox_mo, - }) - .press_image(match self.global_state.settings.gameplay.xp_bar { - XpBar::Always => self.imgs.checkbox_checked, - XpBar::OnGain => self.imgs.checkbox_press, - }) - .down_from(state.ids.hotbar_title, 8.0) - .set(state.ids.show_xpbar_button, ui) - .was_clicked() - { - match self.global_state.settings.gameplay.xp_bar { - XpBar::Always => events.push(Event::ToggleXpBar(XpBar::OnGain)), - XpBar::OnGain => events.push(Event::ToggleXpBar(XpBar::Always)), - } - } - Text::new( - &self - .localized_strings - .get("hud.settings.toggle_bar_experience"), - ) - .right_from(state.ids.show_xpbar_button, 10.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .graphics_for(state.ids.show_xpbar_button) - .color(TEXT_COLOR) - .set(state.ids.show_xpbar_text, ui); + .set(state.ids.hotbar_title, ui); // Show Shortcut Numbers if Button::image(match self.global_state.settings.gameplay.shortcut_numbers { ShortcutNumbers::On => self.imgs.checkbox_checked, @@ -844,7 +808,7 @@ impl<'a> Widget for SettingsWindow<'a> { ShortcutNumbers::On => self.imgs.checkbox_checked, ShortcutNumbers::Off => self.imgs.checkbox_press, }) - .down_from(state.ids.show_xpbar_button, 8.0) + .down_from(state.ids.hotbar_title, 8.0) .set(state.ids.show_shortcuts_button, ui) .was_clicked() { @@ -1692,7 +1656,7 @@ impl<'a> Widget for SettingsWindow<'a> { 0..=14 => CRITICAL_HP_COLOR, 15..=29 => LOW_HP_COLOR, 30..=50 => HP_COLOR, - _ => MANA_COLOR, + _ => STAMINA_COLOR, }; Text::new(&format!("FPS: {:.0}", self.fps)) .color(fps_col) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 21e1aa2bd4..f27bb47257 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -2,8 +2,8 @@ use super::{ hotbar, img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slots, BarNumbers, ShortcutNumbers, Show, XpBar, BLACK, CRITICAL_HP_COLOR, HP_COLOR, - LOW_HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR, + slots, BarNumbers, ShortcutNumbers, Show, BLACK, CRITICAL_HP_COLOR, HP_COLOR, + LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ i18n::VoxygenLocalization, @@ -27,27 +27,51 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; +use inline_tweak::*; use std::time::{Duration, Instant}; use vek::*; widget_ids! { struct Ids { + // Death message death_message_1, death_message_2, death_message_1_bg, death_message_2_bg, - level_text, - next_level_text, - xp_bar_mid, - xp_bar_mid_top, - xp_bar_left, - xp_bar_left_top, - xp_bar_right, - xp_bar_right_top, - xp_bar_filling, - xp_bar_filling_top, - hotbar_align, - xp_bar_subdivision, + death_bg, + // Level up message + level_up, + level_down, + level_align, + level_message, + level_message_bg, + // Hurt BG + hurt_bg, + // Skillbar + alignment, + bg, + frame, + m1_ico, + m2_ico, + // Level + level_bg, + level, + // Exp-Bar + exp_alignment, + exp_filling, + // HP-Bar + hp_alignment, + hp_filling, + hp_txt_alignment, + hp_txt_bg, + hp_txt, + // Stamina-Bar + stamina_alignment, + stamina_filling, + stamina_txt_alignment, + stamina_txt_bg, + stamina_txt, + // Slots m1_slot, m1_slot_bg, m1_text, @@ -91,29 +115,9 @@ widget_ids! { slot10, slot10_text, slot10_text_bg, - healthbar_bg, - healthbar_filling, - health_text, - health_text_bg, - energybar_bg, - energybar_filling, - energy_text, - energy_text_bg, - level_up, - level_down, - level_align, - level_message, - level_message_bg, - death_bg, - hurt_bg, } } -pub enum ResourceType { - Mana, - /*Rage, - *Focus, */ -} #[derive(WidgetCommon)] pub struct Skillbar<'a> { global_state: &'a GlobalState, @@ -133,8 +137,7 @@ pub struct Skillbar<'a> { localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, #[conrod(common_builder)] - common: widget::CommonBuilder, - current_resource: ResourceType, + common: widget::CommonBuilder, show: &'a Show, } @@ -167,8 +170,7 @@ impl<'a> Skillbar<'a> { rot_imgs, stats, loadout, - energy, - current_resource: ResourceType::Mana, + energy, common: widget::CommonBuilder::default(), character_state, pulse, @@ -184,11 +186,8 @@ impl<'a> Skillbar<'a> { } pub struct State { - ids: Ids, - - last_xp_value: u32, - last_level: u32, - last_update_xp: Instant, + ids: Ids, + last_level: u32, last_update_level: Instant, } @@ -199,11 +198,8 @@ impl<'a> Widget for Skillbar<'a> { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { State { - ids: Ids::new(id_gen), - - last_xp_value: 0, - last_level: 1, - last_update_xp: Instant::now(), + ids: Ids::new(id_gen), + last_level: 1, last_update_level: Instant::now(), } } @@ -214,8 +210,7 @@ impl<'a> Widget for Skillbar<'a> { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - let level = (self.stats.level.level()).to_string(); - let next_level = (self.stats.level.level() + 1).to_string(); + let level = if self.stats.level.level() > 999 {"A".to_string()} else {(self.stats.level.level()).to_string()}; let exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64); @@ -226,8 +221,7 @@ impl<'a> Widget for Skillbar<'a> { if self.stats.is_dead { hp_percentage = 0.0; energy_percentage = 0.0; - }; - let scale = 2.0; + }; let bar_values = self.global_state.settings.gameplay.bar_numbers; let shortcuts = self.global_state.settings.gameplay.shortcut_numbers; @@ -339,410 +333,147 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.death_message_2, ui); } } - // Experience-Bar - match self.global_state.settings.gameplay.xp_bar { - XpBar::Always => { - // Constant Display of the Exp Bar at the bottom of the screen - Image::new(self.imgs.xp_bar_mid) - .w_h(80.0 * scale, 10.0 * scale) - .mid_bottom_with_margin_on(ui.window, 2.0) - .set(state.ids.xp_bar_mid, ui); - Image::new(self.imgs.xp_bar_right) - .w_h(100.0 * scale, 10.0 * scale) - .right_from(state.ids.xp_bar_mid, 0.0) - .set(state.ids.xp_bar_right, ui); - Image::new(self.imgs.xp_bar_left) - .w_h(100.0 * scale, 10.0 * scale) - .left_from(state.ids.xp_bar_mid, 0.0) - .set(state.ids.xp_bar_left, ui); - Image::new(self.imgs.bar_content) - .w_h(260.0 * scale * exp_percentage, 5.0 * scale) + // Skillbar + // Alignment and BG + Rectangle::fill_with([524.0, 80.0], color::TRANSPARENT) + .mid_bottom_with_margin_on(ui.window, 10.0) + .set(state.ids.alignment, ui); + Image::new(self.imgs.skillbar_bg) + .w_h(480.0, 80.0) + .color(Some(UI_MAIN)) + .middle_of(state.ids.alignment) + .set(state.ids.bg, ui); + // Level + let lvl_size = match self.stats.level.level() { + 11..=99 => tweak!(13), + 100..=999 => tweak!(10), + _ => tweak!(14), + }; + Text::new(&level) + .mid_top_with_margin_on(state.ids.bg, 3.0) + .font_size(self.fonts.cyri.scale(lvl_size)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.level, ui); + // Exp-Bar + Rectangle::fill_with([476.0, 8.0], color::TRANSPARENT) + .mid_bottom_with_margin_on(state.ids.bg, 4.0) + .set(state.ids.exp_alignment, ui); + Image::new(self.imgs.bar_content) + .w_h(476.0 * exp_percentage, 8.0) .color(Some(XP_COLOR)) - .top_left_with_margins_on(state.ids.xp_bar_left, 2.0 * scale, 10.0 * scale) - .set(state.ids.xp_bar_filling, ui); - // Level Display - if self.stats.level.level() < 10 { - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left, - 3.5 * scale, - 4.0 * scale, - ) - .font_size(self.fonts.cyri.scale(10)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right, - 3.5 * scale, - 4.0 * scale, - ) - .font_size(self.fonts.cyri.scale(10)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.next_level_text, ui); - } else if self.stats.level.level() < 100 { - // Change offset and fontsize for levels > 9 - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left, - 3.5 * scale, - 3.0 * scale, - ) - .font_size(self.fonts.cyri.scale(9)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right, - 3.5 * scale, - 3.0 * scale, - ) - .font_size(self.fonts.cyri.scale(9)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.next_level_text, ui); - } else { - // Change offset and fontsize for levels > 9 - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left, - 3.5 * scale, - 2.5 * scale, - ) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right, - 3.5 * scale, - 2.5 * scale, - ) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - .set(state.ids.next_level_text, ui); - } - // M1 Slot - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .top_left_with_margins_on(state.ids.xp_bar_mid, -40.0 * scale, 0.0) - .set(state.ids.m1_slot, ui); - }, - XpBar::OnGain => { - // Displays the Exp Bar at the top of the screen when exp is gained and fades it - // out afterwards - const FADE_IN_XP: f32 = 1.0; - const FADE_HOLD_XP: f32 = 3.0; - const FADE_OUT_XP: f32 = 2.0; - let current_xp = self.stats.exp.current(); - // Check if no other popup is displayed and a new one is needed - if state.last_update_xp.elapsed() - > Duration::from_secs_f32(FADE_IN_XP + FADE_HOLD_XP + FADE_OUT_XP) - && state.last_xp_value != current_xp - { - // Update last_value - state.update(|s| s.last_xp_value = current_xp); - state.update(|s| s.last_update_xp = Instant::now()); - } - - let seconds_xp = state.last_update_xp.elapsed().as_secs_f32(); - let fade_xp = if current_xp == 0 { - 0.0 - } else if seconds_xp < FADE_IN_XP { - seconds_xp / FADE_IN_XP - } else if seconds_xp < FADE_IN_XP + FADE_HOLD_XP { - 1.0 - } else { - (1.0 - (seconds_xp - FADE_IN_XP - FADE_HOLD_XP) / FADE_OUT_XP).max(0.0) - }; - // Hotbar parts - Image::new(self.imgs.xp_bar_mid) - .w_h(80.0 * scale * 1.5, 10.0 * scale * 1.5) - .mid_top_with_margin_on(ui.window, 20.0) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_xp))) - .set(state.ids.xp_bar_mid_top, ui); - Image::new(self.imgs.xp_bar_right) - .w_h(100.0 * scale * 1.5, 10.0 * scale * 1.5) - .right_from(state.ids.xp_bar_mid_top, 0.0) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_xp))) - .set(state.ids.xp_bar_right_top, ui); - Image::new(self.imgs.xp_bar_left) - .w_h(100.0 * scale * 1.5, 10.0 * scale * 1.5) - .left_from(state.ids.xp_bar_mid_top, 0.0) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_xp))) - .set(state.ids.xp_bar_left_top, ui); - Image::new(self.imgs.bar_content) - .w_h(260.0 * scale * 1.5 * exp_percentage, 6.0 * scale * 1.5) - .color(Some(Color::Rgba(0.59, 0.41, 0.67, fade_xp))) - .top_left_with_margins_on( - state.ids.xp_bar_left_top, - 2.0 * scale * 1.5, - 10.0 * scale * 1.5, - ) - .set(state.ids.xp_bar_filling_top, ui); - // Level Display - if self.stats.level.level() < 10 { - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left_top, - 3.0 * scale * 1.5, - 4.0 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(17)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right_top, - 3.0 * scale * 1.5, - 4.0 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(15)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.next_level_text, ui); - } else if self.stats.level.level() < 100 { - // Change offset and fontsize for levels > 9 - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left_top, - 3.0 * scale * 1.5, - 3.0 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(15)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right_top, - 3.0 * scale * 1.5, - 3.0 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(15)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.next_level_text, ui); - } else { - // Change offset and fontsize for levels > 9 - Text::new(&level) - .bottom_left_with_margins_on( - state.ids.xp_bar_left_top, - 3.0 * scale * 1.5, - 2.75 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(12)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.level_text, ui); - Text::new(&next_level) - .bottom_right_with_margins_on( - state.ids.xp_bar_right_top, - 3.0 * scale * 1.5, - 2.75 * scale * 1.5, - ) - .font_size(self.fonts.cyri.scale(12)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, fade_xp)) - .set(state.ids.next_level_text, ui); - } - // Alignment for hotbar - Rectangle::fill_with([80.0 * scale, 1.0], color::TRANSPARENT) - .mid_bottom_with_margin_on(ui.window, 9.0) - .set(state.ids.hotbar_align, ui); - // M1 Slot - - match self.character_state { - CharacterState::BasicMelee { .. } => { - if self.controller.primary.is_pressed() { - let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .top_left_with_margins_on( - state.ids.hotbar_align, - -40.0 * scale, - 0.0, - ) - .set(state.ids.m1_slot, ui); - Image::new(self.imgs.skillbar_slot_big_act) - .w_h(40.0 * scale, 40.0 * scale) - .middle_of(state.ids.m1_slot) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse))) - .floating(true) - .set(state.ids.m1_slot_act, ui); - } else { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .top_left_with_margins_on( - state.ids.hotbar_align, - -40.0 * scale, - 0.0, - ) - .set(state.ids.m1_slot, ui); - } - }, - _ => { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .top_left_with_margins_on(state.ids.hotbar_align, -40.0 * scale, 0.0) - .set(state.ids.m1_slot, ui); - }, - } - }, + .bottom_left_with_margins_on(state.ids.exp_alignment, 0.0, 0.0) + .set(state.ids.exp_filling, ui); + // Health and Stamina bar + // Alignment + Rectangle::fill_with([240.0, 17.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.alignment, 0.0, 0.0) + .set(state.ids.hp_alignment, ui); + Rectangle::fill_with([240.0, 17.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.alignment, 0.0, 0.0) + .set(state.ids.stamina_alignment, ui); + let health_col = match hp_percentage as u8 { + 0..=20 => crit_hp_color, + 21..=40 => LOW_HP_COLOR, + _ => HP_COLOR, + }; + // Content + Image::new(self.imgs.bar_content) + .w_h(216.0 * hp_percentage / 100.0, 14.0) + .color(Some(health_col)) + .top_right_with_margins_on(state.ids.hp_alignment, 4.0, 0.0) + .set(state.ids.hp_filling, ui); + Image::new(self.imgs.bar_content) + .w_h(216.0 * energy_percentage / 100.0, 14.0) + .color(Some(STAMINA_COLOR)) + .top_left_with_margins_on(state.ids.stamina_alignment, 4.0, 0.0) + .set(state.ids.stamina_filling, ui); + Rectangle::fill_with([216.0, 14.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.hp_alignment, 4.0, 13.0) + .set(state.ids.hp_txt_alignment, ui); + Rectangle::fill_with([216.0, 14.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.stamina_alignment, 4.0, 13.0) + .set(state.ids.stamina_txt_alignment, ui); + // Bar Text + // Values + 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 + ); + let mut energy_txt = format!("{}", energy_percentage as u32); + if self.stats.is_dead { + hp_txt = self.localized_strings.get("hud.group.dead").to_string(); + energy_txt = self.localized_strings.get("hud.group.dead").to_string(); + }; + Text::new(&hp_txt) + .middle_of(state.ids.hp_txt_alignment) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.hp_txt_bg, ui); + Text::new(&hp_txt) + .bottom_left_with_margins_on(state.ids.hp_txt_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.hp_txt, ui); + Text::new(&energy_txt) + .middle_of(state.ids.stamina_txt_alignment) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.stamina_txt_bg, ui); + Text::new(&energy_txt) + .bottom_left_with_margins_on(state.ids.stamina_txt_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.stamina_txt, ui); } - // M1 Slot - Image::new(self.imgs.skillbar_slot_big_bg) - .w_h(38.0 * scale, 38.0 * scale) - .color( - match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Bow(_) => Some(BG_COLOR_2), - ToolKind::Staff(_) => Some(BG_COLOR_2), - _ => Some(BG_COLOR_2), - }, - _ => Some(BG_COLOR_2), - }, - ) - .middle_of(state.ids.m1_slot) - .set(state.ids.m1_slot_bg, ui); - Button::image( - match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Sword(_) => self.imgs.twohsword_m1, - ToolKind::Dagger(_) => self.imgs.onehdagger_m1, - ToolKind::Shield(_) => self.imgs.onehshield_m1, - ToolKind::Hammer(_) => self.imgs.twohhammer_m1, - ToolKind::Axe(_) => self.imgs.twohaxe_m1, - ToolKind::Bow(_) => self.imgs.bow_m1, - ToolKind::Sceptre(_) => self.imgs.heal_0, - ToolKind::Staff(_) => self.imgs.fireball, - ToolKind::Debug(kind) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m1, - _ => self.imgs.nothing, - }, - _ => self.imgs.nothing, - }, - _ => self.imgs.nothing, - }, - ) // Insert Icon here - .w_h(32.0 * scale, 32.0 * scale) - .middle_of(state.ids.m1_slot_bg) - .set(state.ids.m1_content, ui); - // M2 Slot - match self.character_state { - CharacterState::BasicMelee { .. } => { - let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; - if self.controller.secondary.is_pressed() { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .right_from(state.ids.m1_slot, 0.0) - .set(state.ids.m2_slot, ui); - Image::new(self.imgs.skillbar_slot_big_act) - .w_h(40.0 * scale, 40.0 * scale) - .middle_of(state.ids.m2_slot) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse))) - .floating(true) - .set(state.ids.m2_slot_act, ui); - } else { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .right_from(state.ids.m1_slot, 0.0) - .set(state.ids.m2_slot, ui); - } - }, - _ => { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .right_from(state.ids.m1_slot, 0.0) - .set(state.ids.m2_slot, ui); - }, + //Percentages + 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 { + hp_txt = self.localized_strings.get("hud.group.dead").to_string(); + energy_txt = self.localized_strings.get("hud.group.dead").to_string(); + }; + Text::new(&hp_txt) + .middle_of(state.ids.hp_txt_alignment) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.hp_txt_bg, ui); + Text::new(&hp_txt) + .bottom_left_with_margins_on(state.ids.hp_txt_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.hp_txt, ui); + Text::new(&energy_txt) + .middle_of(state.ids.stamina_txt_alignment) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.stamina_txt_bg, ui); + Text::new(&energy_txt) + .bottom_left_with_margins_on(state.ids.stamina_txt_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.stamina_txt, ui); } - - let active_tool_kind = match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { - Some(ItemKind::Tool(Tool { kind, .. })) => Some(kind), - _ => None, - }; - - let second_tool_kind = match self.loadout.second_item.as_ref().map(|i| i.item.kind()) { - Some(ItemKind::Tool(Tool { kind, .. })) => Some(kind), - _ => None, - }; - - let tool_kind = match ( - active_tool_kind.map(|tk| tk.hands()), - second_tool_kind.map(|tk| tk.hands()), - ) { - (Some(Hands::TwoHand), _) => active_tool_kind, - (_, Some(Hands::OneHand)) => second_tool_kind, - (_, _) => None, - }; - - Image::new(self.imgs.skillbar_slot_big_bg) - .w_h(38.0 * scale, 38.0 * scale) - .color(match tool_kind { - Some(ToolKind::Bow(_)) => Some(BG_COLOR_2), - Some(ToolKind::Staff(_)) => Some(BG_COLOR_2), - _ => Some(BG_COLOR_2), - }) - .middle_of(state.ids.m2_slot) - .set(state.ids.m2_slot_bg, ui); - Button::image(match tool_kind { - Some(ToolKind::Sword(_)) => self.imgs.twohsword_m2, - Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2, - Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2, - Some(ToolKind::Hammer(_)) => self.imgs.hammergolf, - Some(ToolKind::Axe(_)) => self.imgs.axespin, - Some(ToolKind::Bow(_)) => self.imgs.bow_m2, - Some(ToolKind::Sceptre(_)) => self.imgs.heal_bomb, - Some(ToolKind::Staff(_)) => self.imgs.flamethrower, - Some(ToolKind::Debug(kind)) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m2, - _ => self.imgs.nothing, - }, - _ => self.imgs.nothing, - }) - .w_h(32.0 * scale, 32.0 * scale) - .middle_of(state.ids.m2_slot_bg) - .image_color(match tool_kind { - Some(ToolKind::Sword(_)) => { - if self.energy.current() as f64 >= 200.0 { - Color::Rgba(1.0, 1.0, 1.0, 1.0) - } else { - Color::Rgba(0.3, 0.3, 0.3, 0.8) - } - }, - Some(ToolKind::Sceptre(_)) => { - if self.energy.current() as f64 >= 400.0 { - Color::Rgba(1.0, 1.0, 1.0, 1.0) - } else { - Color::Rgba(0.3, 0.3, 0.3, 0.8) - } - }, - Some(ToolKind::Axe(_)) => { - if self.energy.current() as f64 >= 100.0 { - Color::Rgba(1.0, 1.0, 1.0, 1.0) - } else { - Color::Rgba(0.3, 0.3, 0.3, 0.8) - } - }, - _ => Color::Rgba(1.0, 1.0, 1.0, 1.0), - }) - .set(state.ids.m2_content, ui); // Slots let content_source = (self.hotbar, self.inventory, self.loadout, self.energy); // TODO: avoid this let image_source = (self.item_imgs, self.imgs); - let mut slot_maker = SlotMaker { // TODO: is a separate image needed for the frame? - empty_slot: self.imgs.skillbar_slot, - filled_slot: self.imgs.skillbar_slot, - selected_slot: self.imgs.skillbar_slot_act, + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, background_color: None, content_size: ContentSize { width_height_ratio: 1.0, @@ -792,14 +523,6 @@ impl<'a> Widget for Skillbar<'a> { .map(|i| i.item.kind()) .and_then(|kind| match kind { ItemKind::Tool(Tool { kind, .. }) => match kind { - ToolKind::Hammer(_) => Some(( - "Smash of Doom", - "\nAn AOE attack with knockback. \nLeaps to position of \ - cursor.", - )), - ToolKind::Axe(_) => { - Some(("Spin Leap", "\nA slashing running spin leap.")) - }, ToolKind::Staff(_) => Some(( "Firebomb", "\nWhirls a big fireball into the air. \nExplodes the ground \ @@ -809,10 +532,6 @@ impl<'a> Widget for Skillbar<'a> { "Whirlwind", "\nMove forward while spinning with \n your sword.", )), - ToolKind::Bow(_) => Some(( - "Burst", - "\nLaunches a burst of arrows at the top \nof a running leap.", - )), ToolKind::Debug(kind) => match kind.as_ref() { "Boost" => Some(( "Possessing Arrow", @@ -827,71 +546,209 @@ impl<'a> Widget for Skillbar<'a> { }), }) }; - - //Slot 5 - let slot = slot_maker - .fabricate(hotbar::Slot::Five, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) - .bottom_left_with_margins_on(state.ids.m1_slot, 0.0, -20.0 * scale); - if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) { - slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) - .set(state.ids.slot5, ui); - } else { - slot.set(state.ids.slot5, ui); - } - // Slot 4 - let slot = slot_maker - .fabricate(hotbar::Slot::Four, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) - .left_from(state.ids.slot5, 0.0); - if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) { - slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) - .set(state.ids.slot4, ui); - } else { - slot.set(state.ids.slot4, ui); - } - // Slot 3 - let slot = slot_maker - .fabricate(hotbar::Slot::Three, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) - .left_from(state.ids.slot4, 0.0); - if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) { - slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) - .set(state.ids.slot3, ui); - } else { - slot.set(state.ids.slot3, ui); - } - // Slot 2 - let slot = slot_maker - .fabricate(hotbar::Slot::Two, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) - .left_from(state.ids.slot3, 0.0); - if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) { - slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) - .set(state.ids.slot2, ui); - } else { - slot.set(state.ids.slot2, ui); - } + // Slot 1-5 // Slot 1 - slot_maker.empty_slot = self.imgs.skillbar_slot_l; - slot_maker.selected_slot = self.imgs.skillbar_slot_l_act; + slot_maker.empty_slot = self.imgs.inv_slot; + slot_maker.selected_slot = self.imgs.inv_slot; let slot = slot_maker - .fabricate(hotbar::Slot::One, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot_l) - .left_from(state.ids.slot2, 0.0); + .fabricate(hotbar::Slot::One, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .bottom_left_with_margins_on(state.ids.frame, 15.0, 22.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::One) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) .set(state.ids.slot1, ui); } else { slot.set(state.ids.slot1, ui); } - // Slot 6 - slot_maker.empty_slot = self.imgs.skillbar_slot; - slot_maker.selected_slot = self.imgs.skillbar_slot_act; + // Slot 2 let slot = slot_maker - .fabricate(hotbar::Slot::Six, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) - .bottom_right_with_margins_on(state.ids.m2_slot, 0.0, -20.0 * scale); + .fabricate(hotbar::Slot::Two, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .right_from(state.ids.slot1, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) + .set(state.ids.slot2, ui); + } else { + slot.set(state.ids.slot2, ui); + } + // Slot 3 + let slot = slot_maker + .fabricate(hotbar::Slot::Three, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .right_from(state.ids.slot2, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) + .set(state.ids.slot3, ui); + } else { + slot.set(state.ids.slot3, ui); + } + // Slot 4 + let slot = slot_maker + .fabricate(hotbar::Slot::Four, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .right_from(state.ids.slot3, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) + .set(state.ids.slot4, ui); + } else { + slot.set(state.ids.slot4, ui); + } + // Slot 5 + let slot = slot_maker + .fabricate(hotbar::Slot::Five, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .right_from(state.ids.slot4, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) + .set(state.ids.slot5, ui); + } else { + slot.set(state.ids.slot5, ui); + } + // Slot M1 + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .color( + match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { + Some(ItemKind::Tool(Tool { kind, .. })) => match kind { + ToolKind::Bow(_) => Some(BG_COLOR_2), + ToolKind::Staff(_) => Some(BG_COLOR_2), + _ => Some(BG_COLOR_2), + }, + _ => Some(BG_COLOR_2), + }, + ) + .right_from(state.ids.slot5, 0.0) + .set(state.ids.m1_slot_bg, ui); + Button::image( + match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { + Some(ItemKind::Tool(Tool { kind, .. })) => match kind { + ToolKind::Sword(_) => self.imgs.twohsword_m1, + ToolKind::Dagger(_) => self.imgs.onehdagger_m1, + ToolKind::Shield(_) => self.imgs.onehshield_m1, + ToolKind::Hammer(_) => self.imgs.twohhammer_m1, + ToolKind::Axe(_) => self.imgs.twohaxe_m1, + ToolKind::Bow(_) => self.imgs.bow_m1, + ToolKind::Sceptre(_) => self.imgs.heal_0, + ToolKind::Staff(_) => self.imgs.fireball, + ToolKind::Debug(kind) => match kind.as_ref() { + "Boost" => self.imgs.flyingrod_m1, + _ => self.imgs.nothing, + }, + _ => self.imgs.nothing, + }, + _ => self.imgs.nothing, + }, + ) // Insert Icon here + .w_h(36.0, 36.0) + .middle_of(state.ids.m1_slot_bg) + .set(state.ids.m1_content, ui); + // Slot M2 + match self.character_state { + CharacterState::BasicMelee { .. } => { + let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; + if self.controller.secondary.is_pressed() { + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .right_from(state.ids.m1_slot_bg, 0.0) + .set(state.ids.m2_slot, ui); + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .middle_of(state.ids.m2_slot) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse))) + .set(state.ids.m2_slot_act, ui); + } else { + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .right_from(state.ids.m1_slot_bg, 0.0) + .set(state.ids.m2_slot, ui); + } + }, + _ => { + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .right_from(state.ids.m1_slot_bg, 0.0) + .set(state.ids.m2_slot, ui); + }, + } + + let active_tool_kind = match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { + Some(ItemKind::Tool(Tool { kind, .. })) => Some(kind), + _ => None, + }; + + let second_tool_kind = match self.loadout.second_item.as_ref().map(|i| i.item.kind()) { + Some(ItemKind::Tool(Tool { kind, .. })) => Some(kind), + _ => None, + }; + + let tool_kind = match ( + active_tool_kind.map(|tk| tk.hands()), + second_tool_kind.map(|tk| tk.hands()), + ) { + (Some(Hands::TwoHand), _) => active_tool_kind, + (_, Some(Hands::OneHand)) => second_tool_kind, + (_, _) => None, + }; + + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .color(match tool_kind { + Some(ToolKind::Bow(_)) => Some(BG_COLOR_2), + Some(ToolKind::Staff(_)) => Some(BG_COLOR_2), + _ => Some(BG_COLOR_2), + }) + .middle_of(state.ids.m2_slot) + .set(state.ids.m2_slot_bg, ui); + Button::image(match tool_kind { + Some(ToolKind::Sword(_)) => self.imgs.twohsword_m2, + Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2, + Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2, + Some(ToolKind::Hammer(_)) => self.imgs.hammergolf, + Some(ToolKind::Axe(_)) => self.imgs.axespin, + Some(ToolKind::Bow(_)) => self.imgs.bow_m2, + Some(ToolKind::Sceptre(_)) => self.imgs.heal_bomb, + Some(ToolKind::Staff(_)) => self.imgs.flamethrower, + Some(ToolKind::Debug(kind)) => match kind.as_ref() { + "Boost" => self.imgs.flyingrod_m2, + _ => self.imgs.nothing, + }, + _ => self.imgs.nothing, + }) + .w_h(36.0, 36.0) + .middle_of(state.ids.m2_slot_bg) + .image_color(match tool_kind { + Some(ToolKind::Sword(_)) => { + if self.energy.current() as f64 >= 200.0 { + Color::Rgba(1.0, 1.0, 1.0, 1.0) + } else { + Color::Rgba(0.3, 0.3, 0.3, 0.8) + } + }, + Some(ToolKind::Sceptre(_)) => { + if self.energy.current() as f64 >= 400.0 { + Color::Rgba(1.0, 1.0, 1.0, 1.0) + } else { + Color::Rgba(0.3, 0.3, 0.3, 0.8) + } + }, + Some(ToolKind::Axe(_)) => { + if self.energy.current() as f64 >= 100.0 { + Color::Rgba(1.0, 1.0, 1.0, 1.0) + } else { + Color::Rgba(0.3, 0.3, 0.3, 0.8) + } + }, + _ => Color::Rgba(1.0, 1.0, 1.0, 1.0), + }) + .set(state.ids.m2_content, ui); + // Slot 6-10 + // Slot 6 + slot_maker.empty_slot = self.imgs.inv_slot; + slot_maker.selected_slot = self.imgs.inv_slot; + let slot = slot_maker + .fabricate(hotbar::Slot::Six, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) + .right_from(state.ids.m2_slot_bg, 0.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Six) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) .set(state.ids.slot6, ui); @@ -900,8 +757,8 @@ impl<'a> Widget for Skillbar<'a> { } // Slot 7 let slot = slot_maker - .fabricate(hotbar::Slot::Seven, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) + .fabricate(hotbar::Slot::Seven, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) .right_from(state.ids.slot6, 0.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Seven) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) @@ -911,8 +768,8 @@ impl<'a> Widget for Skillbar<'a> { } // Slot 8 let slot = slot_maker - .fabricate(hotbar::Slot::Eight, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) + .fabricate(hotbar::Slot::Eight, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) .right_from(state.ids.slot7, 0.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Eight) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) @@ -922,8 +779,8 @@ impl<'a> Widget for Skillbar<'a> { } // Slot 9 let slot = slot_maker - .fabricate(hotbar::Slot::Nine, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot) + .fabricate(hotbar::Slot::Nine, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) .right_from(state.ids.slot8, 0.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Nine) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) @@ -932,11 +789,11 @@ impl<'a> Widget for Skillbar<'a> { slot.set(state.ids.slot9, ui); } // Quickslot - slot_maker.empty_slot = self.imgs.skillbar_slot_r; - slot_maker.selected_slot = self.imgs.skillbar_slot_r_act; + slot_maker.empty_slot = self.imgs.inv_slot; + slot_maker.selected_slot = self.imgs.inv_slot; let slot = slot_maker - .fabricate(hotbar::Slot::Ten, [20.0 * scale as f32; 2]) - .filled_slot(self.imgs.skillbar_slot_r) + .fabricate(hotbar::Slot::Ten, [40.0; 2]) + .filled_slot(self.imgs.inv_slot) .right_from(state.ids.slot9, 0.0); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Ten) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) @@ -1176,114 +1033,27 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.slot10_text, ui); } }; + // Frame + Image::new(self.imgs.skillbar_frame) + .w_h(524.0, 80.0) + .color(Some(UI_MAIN)) + .middle_of(state.ids.bg) + .floating(true) + .set(state.ids.frame, ui); + // M1 and M2 icons + Image::new(self.imgs.m1_ico) + .w_h(16.0, 18.0) + .mid_bottom_with_margin_on(state.ids.m1_content, tweak!(-9.0)) + .set(state.ids.m1_ico, ui); + Image::new(self.imgs.m2_ico) + .w_h(16.0, 18.0) + .mid_bottom_with_margin_on(state.ids.m2_content, tweak!(-9.0)) + .set(state.ids.m2_ico, ui); - // Lifebar - Image::new(self.imgs.healthbar_bg) - .w_h(100.0 * scale, 20.0 * scale) - .top_left_with_margins_on(state.ids.m1_slot, 0.0, -100.0 * scale) - .set(state.ids.healthbar_bg, ui); - let health_col = match hp_percentage as u8 { - 0..=20 => crit_hp_color, - 21..=40 => LOW_HP_COLOR, - _ => HP_COLOR, - }; - Image::new(self.imgs.bar_content) - .w_h(97.0 * scale * hp_percentage / 100.0, 16.0 * scale) - .color(Some(health_col)) - .top_right_with_margins_on(state.ids.healthbar_bg, 2.0 * scale, 1.0 * scale) - .set(state.ids.healthbar_filling, ui); - // Energybar - Image::new(self.imgs.energybar_bg) - .w_h(100.0 * scale, 20.0 * scale) - .top_right_with_margins_on(state.ids.m2_slot, 0.0, -100.0 * scale) - .set(state.ids.energybar_bg, ui); - Image::new(self.imgs.bar_content) - .w_h(97.0 * scale * energy_percentage / 100.0, 16.0 * scale) - .top_left_with_margins_on(state.ids.energybar_bg, 2.0 * scale, 1.0 * scale) - .color(Some(match self.current_resource { - ResourceType::Mana => MANA_COLOR, - /*ResourceType::Focus => FOCUS_COLOR, - *ResourceType::Rage => RAGE_COLOR, */ - })) - .set(state.ids.energybar_filling, ui); - // Bar Text - // Values - if let BarNumbers::Values = bar_values { - let mut hp_text = 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 - ); - let mut energy_text = format!( - "{}/{}", - self.energy.current() as u32 / 10, /* TODO Fix regeneration with smaller energy - * numbers instead of dividing by 10 here */ - self.energy.maximum() as u32 / 10 - ); - if self.stats.is_dead { - hp_text = self.localized_strings.get("hud.group.dead").to_string(); - energy_text = self.localized_strings.get("hud.group.dead").to_string(); - }; - Text::new(&hp_text) - .mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.health_text_bg, ui); - Text::new(&hp_text) - .bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.health_text, ui); - Text::new(&energy_text) - .mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.energy_text_bg, ui); - Text::new(&energy_text) - .bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.energy_text, ui); - } - //Percentages - if let BarNumbers::Percent = bar_values { - let hp_text = format!("{}%", hp_percentage as u32); - Text::new(&hp_text) - .mid_top_with_margin_on(state.ids.healthbar_bg, 6.0 * scale) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.health_text_bg, ui); - Text::new(&hp_text) - .bottom_left_with_margins_on(state.ids.health_text_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.health_text, ui); - let energy_text = format!("{}%", energy_percentage as u32); - Text::new(&energy_text) - .mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.energy_text_bg, ui); - Text::new(&energy_text) - .bottom_left_with_margins_on(state.ids.energy_text_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.energy_text, ui); - } + // Buffs + // Add debuff slots above the health bar + // Add buff slots above the mana bar + + // Debuffs } - - // Buffs - // Add debuff slots above the health bar - // Add buff slots above the mana bar - - // Debuffs } From bf3e41a3ad5e1a89d716b36ded675ae569335e27 Mon Sep 17 00:00:00 2001 From: Samuel Keiffer <samuelkeiffer@gmail.com> Date: Sun, 11 Oct 2020 16:39:52 -0500 Subject: [PATCH 11/25] Addressed comments. --- common/src/comp/buff.rs | 5 --- common/src/sys/buff.rs | 34 +++++++------------- common/src/sys/combat.rs | 4 +-- server/src/events/entity_manipulation.rs | 41 +++++++++--------------- 4 files changed, 30 insertions(+), 54 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 4193771b28..c2b9073368 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -126,11 +126,6 @@ pub enum BuffSource { /// and undone on removal of the buff (by the specs system). /// Example could be decreasing max health, which, if repeated each tick, /// would be probably an undesired effect). -/// -/// TODO: Make this net/sync-friendly. Events could help there -/// (probably replacing `changes`). Also, the "buff ticking" is really -/// not needed to be synced often, only in case that a buff begins/ends -/// (as the buff ECS system will probably run on a client too). #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Buffs { /// Active de/buffs. diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 203fe30e0e..da1b9eee4d 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -7,11 +7,6 @@ use crate::{ use specs::{Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; -/// This system modifies entity stats, changing them using buffs -/// Currently, the system is VERY, VERY CRUDE and SYNC UN-FRIENDLY. -/// It does not use events and uses `Vec`s stored in component. -/// -/// TODO: Make this production-quality system/design pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -29,11 +24,10 @@ impl<'a> System<'a> for Sys { let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = (Vec::<usize>::new(), Vec::<usize>::new()); // Tick all de/buffs on a Buffs component. - for i in 0..buff_comp.active_buffs.len() { + for (i, active_buff) in buff_comp.active_buffs.iter_mut().enumerate() { // First, tick the buff and subtract delta from it // and return how much "real" time the buff took (for tick independence). - // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. - let buff_delta = if let Some(remaining_time) = &mut buff_comp.active_buffs[i].time { + let buff_delta = if let Some(remaining_time) = &mut active_buff.time { let pre_tick = remaining_time.as_secs_f32(); let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); let post_tick = if let Some(dur) = new_duration { @@ -49,21 +43,16 @@ impl<'a> System<'a> for Sys { pre_tick - post_tick } else { // The buff is indefinite, and it takes full tick (delta). - // TODO: Delta for indefinite buffs might be shorter since they can get removed - // *during a tick* and this treats it as it always happens on a *tick end*. dt.0 }; - let buff_owner = - if let BuffSource::Character { by: owner } = buff_comp.active_buffs[i].source { - Some(owner) - } else { - None - }; + let buff_owner = if let BuffSource::Character { by: owner } = active_buff.source { + Some(owner) + } else { + None + }; // Now, execute the buff, based on it's delta - for effect in &mut buff_comp.active_buffs[i].effects { - #[allow(clippy::single_match)] - // Remove clippy when more effects are added here + for effect in &mut active_buff.effects { match effect { // Only add an effect here if it is continuous or it is not immediate BuffEffect::HealthChangeOverTime { rate, accumulated } => { @@ -85,15 +74,16 @@ impl<'a> System<'a> for Sys { *accumulated = 0.0; }; }, - _ => {}, + BuffEffect::NameChange { .. } => {}, }; } } - for i in 0..buff_comp.inactive_buffs.len() { + + for (i, inactive_buff) in buff_comp.inactive_buffs.iter_mut().enumerate() { // First, tick the buff and subtract delta from it // and return how much "real" time the buff took (for tick independence). // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. - if let Some(remaining_time) = &mut buff_comp.inactive_buffs[i].time { + if let Some(remaining_time) = &mut inactive_buff.time { let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); if new_duration.is_some() { // The buff still continues. diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index be54208631..a0b24b1db7 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -156,8 +156,8 @@ impl<'a> System<'a> for Sys { uid: *uid_b, buff_change: buff::BuffChange::Add(buff::Buff::new( buff::BuffId::Bleeding { - strength: -damage.healthchange, - duration: Some(Duration::from_millis(10000)), + strength: attack.base_damage as f32, + duration: Some(Duration::from_secs(10)), }, vec![buff::BuffCategoryId::Physical], buff::BuffSource::Character { by: *uid }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d61cf9f6cf..1e2ed26a2a 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -747,33 +747,29 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) }, BuffChange::RemoveById(id) => { let some_predicate = |current_id: &buff::BuffId| *current_id == id; - for i in 0..buffs.active_buffs.len() { - if some_predicate(&mut buffs.active_buffs[i].id) { + for (i, buff) in buffs.active_buffs.iter().enumerate() { + if some_predicate(&buff.id) { active_buff_indices_for_removal.push(i); } } - for i in 0..buffs.inactive_buffs.len() { - if some_predicate(&mut buffs.inactive_buffs[i].id) { + for (i, buff) in buffs.inactive_buffs.iter().enumerate() { + if some_predicate(&buff.id) { inactive_buff_indices_for_removal.push(i); } } }, BuffChange::RemoveByCategory(all_required, any_required) => { - for i in 0..buffs.active_buffs.len() { + for (i, buff) in buffs.active_buffs.iter().enumerate() { let mut required_met = true; for required in &all_required { - if !buffs.active_buffs[i] - .cat_ids - .iter() - .any(|cat| cat == required) - { + if !buff.cat_ids.iter().any(|cat| cat == required) { required_met = false; break; } } let mut any_met = any_required.is_empty(); for any in &any_required { - if !buffs.active_buffs[i].cat_ids.iter().any(|cat| cat == any) { + if !buff.cat_ids.iter().any(|cat| cat == any) { any_met = true; break; } @@ -782,21 +778,17 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) active_buff_indices_for_removal.push(i); } } - for i in 0..buffs.inactive_buffs.len() { + for (i, buff) in buffs.inactive_buffs.iter().enumerate() { let mut required_met = true; for required in &all_required { - if !buffs.inactive_buffs[i] - .cat_ids - .iter() - .any(|cat| cat == required) - { + if !buff.cat_ids.iter().any(|cat| cat == required) { required_met = false; break; } } let mut any_met = any_required.is_empty(); for any in &any_required { - if !buffs.inactive_buffs[i].cat_ids.iter().any(|cat| cat == any) { + if !buff.cat_ids.iter().any(|cat| cat == any) { any_met = true; break; } @@ -833,16 +825,15 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } let mut new_active_buff = None::<buff::Buff>; let mut replacement_buff_index = 0; - for i in 0..buffs.inactive_buffs.len() { - let inactive_buff = buffs.inactive_buffs[i].clone(); + for (i, inactive_buff) in buffs.inactive_buffs.iter().enumerate() { if discriminant(&buff_id) == discriminant(&inactive_buff.id) { if let Some(ref buff) = new_active_buff { if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) { - new_active_buff = Some(inactive_buff); + new_active_buff = Some(inactive_buff.clone()); replacement_buff_index = i; } } else { - new_active_buff = Some(inactive_buff); + new_active_buff = Some(inactive_buff.clone()); replacement_buff_index = i; } } @@ -894,17 +885,17 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { for effect in &buff.effects { - #[allow(clippy::single_match)] // Remove clippy when there are more buff effects here + use buff::BuffEffect; match effect { // Only add an effect here if it is immediate and is not continuous - buff::BuffEffect::NameChange { prefix } => { + BuffEffect::NameChange { prefix } => { if let Some(ref mut stats) = stats { let mut pref = String::from(prefix); pref.push_str(&stats.name); stats.name = pref; } }, - _ => {}, + BuffEffect::HealthChangeOverTime { .. } => {}, } } } From 6495684dc2cf43ae9b16660ebce4366d54b1e0b0 Mon Sep 17 00:00:00 2001 From: Monty Marz <m.marzouq@gmx.de> Date: Mon, 12 Oct 2020 23:45:51 +0200 Subject: [PATCH 12/25] skillbar fixes, buff UI module adjustments, fixes, assets, test buffs widgets --- .../items/npc_armor/back/backpack_0.ron | 12 ++ .../items/npc_armor/back/backpack_blue_0.ron | 12 ++ .../voxygen/element/de_buffs/buff_plus_0.png | Bin 0 -> 1870 bytes .../element/de_buffs/debuff_skull_0.png | Bin 0 -> 1878 bytes assets/voxygen/element/skillbar/bg.png | Bin 2430 -> 2417 bytes assets/voxygen/element/skillbar/frame.png | Bin 3383 -> 3484 bytes assets/voxygen/i18n/es_la.ron | 95 ++++++++++-- assets/voxygen/item_image_manifest.ron | 8 + .../voxygen/voxel/armor/back/backpack-0.vox | Bin 0 -> 3864 bytes .../voxel/armor/back/backpack-grey.vox | Bin 0 -> 3864 bytes .../voxel/humanoid_armor_back_manifest.ron | 8 + voxygen/src/hud/buffs.rs | 125 +++++++++++++++ voxygen/src/hud/img_ids.rs | 6 + voxygen/src/hud/mod.rs | 27 +++- voxygen/src/hud/settings_window.rs | 7 +- voxygen/src/hud/skillbar.rs | 142 +++++++----------- 16 files changed, 336 insertions(+), 106 deletions(-) create mode 100644 assets/common/items/npc_armor/back/backpack_0.ron create mode 100644 assets/common/items/npc_armor/back/backpack_blue_0.ron create mode 100644 assets/voxygen/element/de_buffs/buff_plus_0.png create mode 100644 assets/voxygen/element/de_buffs/debuff_skull_0.png create mode 100644 assets/voxygen/voxel/armor/back/backpack-0.vox create mode 100644 assets/voxygen/voxel/armor/back/backpack-grey.vox create mode 100644 voxygen/src/hud/buffs.rs diff --git a/assets/common/items/npc_armor/back/backpack_0.ron b/assets/common/items/npc_armor/back/backpack_0.ron new file mode 100644 index 0000000000..00a64c0d47 --- /dev/null +++ b/assets/common/items/npc_armor/back/backpack_0.ron @@ -0,0 +1,12 @@ +ItemDef( + name: "Rugged Backpack", + description: "Keeps all your stuff together.", + kind: Armor( + ( + kind: Back("Backpack0"), + stats: ( + protection: Normal(0.0)), + ) + ), + quality: Moderate, +) diff --git a/assets/common/items/npc_armor/back/backpack_blue_0.ron b/assets/common/items/npc_armor/back/backpack_blue_0.ron new file mode 100644 index 0000000000..66446c93f2 --- /dev/null +++ b/assets/common/items/npc_armor/back/backpack_blue_0.ron @@ -0,0 +1,12 @@ +ItemDef( + name: "Rugged Backpack", + description: "Keeps all your stuff together.", + kind: Armor( + ( + kind: Back("BackpackBlue0"), + stats: ( + protection: Normal(0.0)), + ) + ), + quality: Moderate, +) diff --git a/assets/voxygen/element/de_buffs/buff_plus_0.png b/assets/voxygen/element/de_buffs/buff_plus_0.png new file mode 100644 index 0000000000000000000000000000000000000000..7ad6751359af29200d75022be41964ba6b60c5b8 GIT binary patch literal 1870 zcmcIlJ&fE$7<Ggcl5oe55($Kau$(3bcs(Bb?6s9mw7cH0hvK`_W~E#tii~H*yVk8e z#`fLrUV{<=(iIe;pg<HU5P~KJ5(NT@--d#M0z^ZTK0<VK@Ol4oKyqBeYQ6s1H}8A% zzUP_!&Gna;Pd$4|lBDIew%394nesSs9Dd*4e{LC0C)4(|T#_C;T^`4z_uhX-l8(O< zb+?Kw|FTO(Tp>&ZyfTVYKugm3i=&j#ZC=O$??p*X`R(&B6**!x<?5=B{ItpYQF|}r zSN7JsbZ?tFOu2YLK0k7SAm#;;NAV!Z-BC@M@VanaKGqa@0x7m@O1&&7Z~2>YQ)FDW zDoCXWVcB*nx@DRcdP&9zBMq%;I#zYdMW(CU^6aBPwTy*s$GbGE1$Q;2UlggUX~W^L zGBhe8>uI{<I2ytl#wsAxe4G?yq$c^<IfKV@nnh_5i9{|LNg#HLngU9vQ;5?AZIaJy z0>iWsNj1HK$|=o(zJEA0ju+^>XzfCkncfEy=iPD2wGPk4PDXiamnX&9d9k!F3X%84 zL$oda{+mKZ7X{OuY%sBhQH>9H4BR<j^@+aJZDyPlBI^n<m^W&3UQxz+1<U8IMhO$c ze5F)08^AqM@S0L~P1WIxyBND>)iuzI2)hVPp+4k*k%AlzELaqdj{^05cP+^al2E?p z)f5m_i6Z6_q=!{znyMYxh8j{cPy+)wAkMHH>R?8QKG6l^JwbO$C(7~D*fK$ZW8tQ2 zV5XZ@h*5|rQ%%#<)v6O1Dh(_PS=2^>8P3@@vIyQBGC0Uus$#(CgxJO*wN(<>T(un& z0S|UmVxv%}251VgBHypu6a$g@f`Pk+A~WiSB8v&+CyGgrYiZIe(+un|i*}Sl>&CP9 z;|hOmb~T9PN$6Zc%dFQFS~i(8W%f9FfO|*NIi(%;IglRG{3$FKVKF2bulK;|{|uM* zF!4FrJzV<H8vj2^pV><LB<XQjI5nl5j8-l&)23+u?AQH%&ofhpG?(4XXEroQZ(+!V zd6=rCiXqJyg6&wU6+oCs7@8_IOnVhE#!SbU)*VFu9^GiwZH&A|({Spox~^B7b<}KG zUKL+z)b$oRFmj@ODnUD3>jZgVVVzGLR_pR|u}#3uVx!>+n3Tab5}E$<umuHcUcFm= zincd4deYrLyWcEb|F+mOKfYNHzg|9Lp1ytJiN_zgb?m1l@5Aq{Cx7L?y!y%~H{ST} z(VyS!o@@^8+&=g2@1MQ(LD9ZEx;2njbpQ3IJGG_#{U2|v#2;Pzs`DxAmp^R(VSI6K R{6e|ot+m#@JB{l%{{jZzNsRyi literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/de_buffs/debuff_skull_0.png b/assets/voxygen/element/de_buffs/debuff_skull_0.png new file mode 100644 index 0000000000000000000000000000000000000000..1e16dcf7ce9781358e86aed8c9f6dedeaa6cbb7f GIT binary patch literal 1878 zcmcIlPi)&%9QF_tI#y{F2vtl-#ybE4$9^yV6I;tl5@)0cw^bKu-HE=u_nf$m{XE-k z(g2692dEGiE=UXsjl(1kuyNsnv<Z5d&_Ln@w@Gl}wlgPqPMoa3R(c_d<3IV{_xs-W z`~2SC^5SbV$Dche2*S)l!&%|yr?caUDgOJx-g7hjG#xcACW7$fQ`vD$`1q4KL74i` zZ?2}R?i)5{K>-ogqJ@4CakL<uneRskZ_re1(Y7C!rF-Apl0=`Br3<>Nx>1dG{Kg=r z=Ld^TJlMb%k><~eXZkiL2xyALe$WjQyI+=uyf(ki9xIYKgrpl~sge~GSKVc?#$qZO z1y#nX3PsZ@07KIZ^%W7SP*qf20Z;~pt!g$f#rz}jYBBNb73W-Di{F){PMSuxqV#&b zLa$h0aa#eFWhp9DAe1>mPPW4o_2n=*J!Wudf@42QeHMxtBWkhDv@CI@qbUT@gf>ia zo48?0A4Li%sM(apK-WDS8Uzz`lGe9)m0a%wiIe7bM3ohqu+13L`W6k-)8k@Mhovm( zu!m@y{QWnD1WyV^n{jt&55WrU(tvX(91Di}BD)q-l(M+VSa;m0<#9z3f&vs@yx@m~ z^^%t|Mfm{gpp=%StZOphU)Y4u)^uA3FRRd2)e+R?IUp!SM*}nBd)r5Wx~{ztCMgOr zU2w`07gg|mVw)Z?EU$!R16xSOo(5&qGN5dk*i(y|QNqw1>f+-ahBq@OvhkzX5{5a) z#7)}+plA|=WX-^)tmzuah%DJeoDC3NCmtTNEyX^6b5QpnYo>~DM$00m2X!Quyq3qw zG$LDuWpI7e({&ANCa@&&e%%`Dve;#WyIYh*f^Cn*0pj`b1JtHU6t=T8b9O$9hM(}( zZRhXDd3q_o>iXg^bT-0S*2@yknoNn5KlUHs-qCc9XnP&XNe^lM2$nD}?V*@f+T7{? z443jS@d?^GT>8-(|36C4ZN(iFwkcmY6)BsHk}WZ#rYQgH*ZqEvGgIMd&bpc8HdIl2 zV#wKXm`bPwVUcILRpb#eTAC^&&(maF)J$C^glJZARJZH@dvvv4F`?>IYelP4uK-Z0 zRn%JDa7yr8wF2tufssS)BMHjkT8GF33+s5|e6`LlC))(SnQSyP<R-;@8|l4s=LX+` z))$;gvownvOG|Cx*FT%zomkuVzQ4Wv*zTRnr#kyTxU<(j-MM%7)(g|W=_fB9`)cmm zyEmskd+YHl&);6(gC{?F|I+7IukY;6uQb4k>pQ<sUzyu|@5jL}($t&er@ODd{l{SK i^;7H5+-QFBh|u}@(XX%W&wiWjd<*qO=gaEa<-Y*#eNw9c literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/skillbar/bg.png b/assets/voxygen/element/skillbar/bg.png index cd8fbaf0c4dfde7c093c1efba6a7049c8ea5871c..eda76288f43bd050546cca5e38dcac5afc19e092 100644 GIT binary patch delta 439 zcmew-^igQTF(xjf5Ca1%LsKgg<H;A86p^{VnUqvgEzQl6Op;P`&CDzebdxMi40IEd z4GeWHEe*^}lPnBPQW6a}J2IbUB1+|C12%Ca8#V{Bl`_^B?+AFsz`)q=>EaktaqI1^ zgPhEUJPeMa3;*5rV$XB_`E#;^o%+p2K_6zO?1wv6tYTnDi)3QpFaRL~(FbQ@4t)N$ zxI4nUh&kaI8;1eM28P4DjN4=c61Fq2<u|&sGwB>Ccpw(R29mJp=Vgg#a0EhD2^I-~ z1cwI4Fa#6IdJx`VYbB7t*v7p5z+z@bu>=c=gnoXO8@vtwq!kV*9AM%xXO&=hk<Q6b zpmZSK&Q&tG$MD(DpD)%jNCR2oEMU3G2RM=@pW{&V|Kr?X{F#;E%WcMUJ`4#SE^Ah- z+Q7pwBREv?0D}S;ITSbioyx}GT-LzY5IK#3fr$r%Oe&enXFYy3V}X?>0}yz+`njxg HN@xNAsi=xm delta 461 zcmew;^iOERF(xj<5F;ZiQ&TGg^T`*O6p^{VnUqw_jZKY`6H^m)%?*r`b&XR^(sYxO z&C+yDjEoG^49(IKlaf+5J2IbUB1+|C12%Ca8#V{Bl`_`1J>L}0z`!`i)5S5Q;?~={ z8`m;9^0)?yKYaGGa}M9OUd5zlRYi&0+9mT!R31%C%RBV{NR&21LmVF?1B(O*t>7w1 zi<5QeJTF^)Z;ClX)xCxjK8$RPVhJ<&HXLST{3hmbIa=X>!T}~8XT~xK5PderOdtWM z1f(clAi<%55zItoHOe!+xzxzOZNT$KnQuBDOGHDW!2vyajt%AvukJH|HArx8kauNk zr~^7hjY&qZpWE2j`1bzw_qaC9oOzR%iHG4zF~{Wh9LAH+ad^jsGMX71Z~iNs03<&$ ztl!GekoLU&ObsK$*K-W%&lnlL2qZ8-5YRmj?3v$0XEI#)tDC`)@Zx;~BhW$+`c}o9 X^Z3<M8+Nlf3_#%N>gTe~DWM4f$aj+s diff --git a/assets/voxygen/element/skillbar/frame.png b/assets/voxygen/element/skillbar/frame.png index bd380548af95113394d8e1486036a314e22fdb44..8af8fe295e7bdc1f0ebee24bc1e7d6a3305061ca 100644 GIT binary patch delta 1642 zcma)5X;f237@aJHEn?z=Pb?rvt8B+d*didZgSZet!3}sp5fs4%mxz$?P=bn-MG9_( z04^YlRD~exD*+W1uxSuQs0*S=ARrYQO^Wtu|M<h6(>Z7EoH=v9`_0_>KB2*d1vR&c zI*fHT4o>#=P7KG>4~ZsUZ`DLoGaDvsXB)+Iv}SCzV_R=$+A*w|kxU0`c&ojQLsaB; zTib1pH{410iT{gv8m7$qD#MLH$^in86D2?a&}dlc<-U&7uNd4MV(JW0SQJ#RJ&>sr z(eYuXpy34+VDXbA?Q$+_EWJ7Zq%QY(cF%y^jw;$Qv#6-3oB%lWfxO1hR_-haxFIp~ zA)f$gk}JtsI=%e0Es!_eA)*8tlrlu-2eBTHXk)vD%JlZ%Mvg{@>hSc=ULt(*LICQH z&*CtIHb)Sjo^)%=b={Jkee{f2aR_TUQ;UjydbxI4(qKtxVZ{qoZ5<J1+rXo@x=5$s zzxzyDo0<mNjScLb<uqE`P<mopPwiYSst+V6#onq^PO67x%iH!OkH*d*lGYs=AH>xz zd#5#5vTLMt9+`p?ob;%&mv{70lR_Oj<c84jK=Tv(jciwIORg>o^UR?@aT>&<7%<F) z+YJvXuxwompMQBb5}?#`Zx+5XvJB0@FXfJ;+<pV+d3a<d(EPIs_?mc}Xw-kluLOCe zp;j=Fy*cWPQJH-k?OKGEeS7T=%QD`<kEs~2TIz60<dOLfdZ|n-*Fy<peh2nkT*rDN zu3PogDpnz0(py?3=pL@ppdbg3vcr09L2eWC0a0|-JIk_=R&!L`Ynk=enYT~7CWS;2 z*prQAhZl=$=n{Iwd-Fl}U(yG*b~xLo3|=u4cZVV1(V^!iOi9uJvoeOKOM}LAr~u<? zI%-ov3I>4I=Vc-M-vICw3t^o0ZE#`0E-F0@3ZOycZ-W~G0PcCgeQ$&Z0LnJqMn(zX z^G|#Q%uPuB-1o{BTq6d8N?JHl2HdW((+k8nw$VP3o~^=<_q!JD_CO8jpg$8n#{CfC zYoJdOO+{N~>dc0Kpa`R>WVO?(FqA!{C6e9CBZHeoxqj+l>r(BRy423N6=}zKbWJ>K z|BtX&^3)P|m-LxZmpY-u6&oIhJaK^Yax-v8k|}(#Q(808vw>skJXuU92;vYwwTvTk ztHlt1A}HMFUy_K_e^9CL1`rHkw5eWEHEJTfYqRWN5CT$Ke4*>UK0T94r3SyI$o4yO zjtLChd{K7!GhRy~2RZJ67Z=dfqROgIT*9)?heOj=gBtXZE%}$ts)bR74*_rq8OxoK z5Ik!_(K+7p8Vw%1o!V(QPd{LrFJxx{MkDvm@T?y1zHr5Eo<F~{_lMw19kVACvBsS3 z8!ny`3WYgFJA;Z;iIs}^J0&f0wPGsngD)?xH9V1bs?%_oYvcHRPNUAFEy(+pYn+K~ zmLj!Uy{1W8x=0=z@vgtH*hkUWb>L#($f3Kc5Doso`b(?Ol04GM(As-p!xkG|CK8Uk zAeXP)AT@(m7kXy#^6;>Vepgr5_{8c!^&f^%6IZihyfLC{t;xmdePhcWgbgyLTR4)0 zn+zUf0rY58`5@EkX84*-iHoK@lI0_Ux=ziKzHydfWXhS9th;E&6K=yCsBN{oY>BLc zWc0E)!>sqBCzOE}-|-pWn{i4d>8q~`MBb{(1Co>b9Opr~yvg5l_`QwB87P}=KP<J9 zKj3zYBkvT34~%^=qTKrD+%I$<&c`25;lJ9%I)89QywmgInsa~7G5*N%GaqciZh2ca zPupA#NUn-K!Z)bBrjj@=(C5E;*by9G(4Nozw0Pq&`mwX?bD!j-#dHrvcX`ToX+DjV zTycGB@tG_fue(<tk)@W3{X|<@jg=!E87r%#lcKvG@y((~%_RV18~RVj4J_sDuCU6q zqsCsxc9@$zAfug8ws-?c7sF-+b=`RN1*J|#UvcKU`5y^d+#0%J2@$w-%ejY#$D<Vb h(DsmgA_&ljJ_2Kl0o%uCTao}iR(h}Xs`fxq{{h+h$gKbX delta 1540 zcma)5dpML?7(c_zn3yybn!<=EOw7kj#{H79vMIHyZKvp))gV#q>LG@i9T|C&TWZ^c zROo3`lP;<WeYvfbD79Ox&}T`K7&A=BetNdH{cq17=RD_k-plWI-uFFiSh-BrprLKI z#opeT#d5ZTj<so+etDhJFf$L??PSjibqKN9&0_Lw_-vMwjgx&ys12LV=Z7*Ow!J<7 z`)gjB%^Ls9@)$&1@<oPgn~4W-LRKk33;<!PAIED;e8+It=5IP7J<mt0&195qi1nn! z8zF9A!M1UqoXAzrDsl_6nC-c`3zZ_<ibxX)&oQ!Bp<uEBkjhW)uRQg~0x=HYYYUO+ zQG#)nFRWQ5%u+7}QPqu$5I`dK926HDXzxhEy{qYbpH_D*YXL1tXk(<kLo=BJiC_wO zIaQ@YECx}l`q~G#{7HG5V6@rjSO}q|)(Zl&b$(7W2eZ->BFJcwF`YubhRFWn)8N$J z)<e@{V?P)O!>^yd5V-vOL;&h%5?AgCZRd$H#l1?U@@3jrL;8}@szy5!F<ey@w~{K# zchFZ=q#>ogLJC=obJmpz6eta$1f-IP9|32JoT5hmGCcR_if->5t&9+Y*bDOXrITY` z?lUHrY`S_M%mHB&ymj0cTE*kRC08A{EvMo&-v~@3T=VO<7OWkf8H<m`X!yU)Gtg}d z9r1Lw$hMXGmuYb?L3J}ad&n`7nfffDd{zvDezsx5_V5@F$z)ZqVHyk&$dF8n(utx) zxy1d#mr7X?XiWK;7W5K9Q|3s;GV*LUO_Kp43vWlo4AL{t;lbmrs={|Q`6u@iG{HM_ zB-^9-amB*x3r(i;c6nV*eVkZNUn6P>@asD!hrx+}9xEem_5KX~Mxh42HV6SAG7epd z9V8;~gut9OFjo*TCkX?wm(P$01^}OeZOC#eW{i2v{n$T{EEFoAnTJvMug||>F!M7g z>{zhzW2YQ{A|!qG{S>a}xwzN$UU$)Y2;9p=DE%*so8iNnu@oF!qc54%5ipQwTH9#6 z>-tX^i!mEp!gHWDWZA^pK^WH=y!WZHpC1mz5o(Xp$!R%(i-2s_DqRp<&0oX20sk62 zMO(*>!Q{zCgxcOGr3`VeM4cpu4-4ZcxHCtZttaM7xVJH?ub9+`DZQK8SbK0{25Y_W zqNEhlqB8XNpF)wIJUnpivr6wrqE$+z1$9mr=|`n(50vNN6k1uJM+R*Z+TNol9xYbP zBq7<C2mIM$ODt*IX6Qjn=eegTrkoU52t=z$lIe@-w_(eI;9Ha1ll3_rG2gjZ*i~<v z6cYg)RO0L4UyytFO4w|2g_dr~68dHL7(PZP74ag7HavLCvux=~owC~C(-f_|-n27H z!4V`i8BSEhDpV>5PL++#(xOqN{Y_u$Idrd}fvwh2*E8O)A5e0&b|#&DEc94ikZb8i z7+NUijeq#yo{`{Y!WT4DPCTRWGBU4>F6|zJE#0=>G0?lAd%^wb#qm68q@r$O=(x+e z=hLJGuTV6Es;&{Gbxy}0_{~&DEIlZAJ$ORhEv-z@by;x)k@Y16U=@*HV(ASGa-s7~ z!i^n%tuJ~^ZG#RuTNNm<`2(_U{_$^-?3uGgS6Hj7-(OuuX3DJ?Z+(Jp6$V10z(rUW zWcLrdQY*r|x?+{+26p^~H>Iw1`5s%W>q~u6rh7ikco)j7CYG6v1!}8bq$Uo`j8v?n zlNQU(kI;2`Pd82!rWK|je3ynWfvuF~jwbduTcdYr1Ft#~zpj09*JMEXy7qhoJJz~< zb)L$_zpE7ow);g@A61NXH(T$e&#%ojaD3o%pF`T|<`4)0PY)#9t>X@reB%;c-B|iC z$ltvyqN$yZ<~A9RXo7M_24gzTC!gS*oLP?}h#Kchc4gh2XO=HFe*&=O=d+P>c`Ypd E8wv-J3IG5A diff --git a/assets/voxygen/i18n/es_la.ron b/assets/voxygen/i18n/es_la.ron index 866257d619..8b13d7e628 100644 --- a/assets/voxygen/i18n/es_la.ron +++ b/assets/voxygen/i18n/es_la.ron @@ -65,6 +65,7 @@ VoxygenLocalization( "common.create": "Crear", "common.okay": "Ok", "common.accept": "Aceptar", + "common.decline": "Rechazar", "common.disclaimer": "Cuidado", "common.cancel": "Cancelar", "common.none": "Ninguno", @@ -73,6 +74,13 @@ VoxygenLocalization( "common.you": "Tu", "common.automatic": "Automatico", "common.random": "Aleatorio", + // Settings Window title + "common.interface_settings": "Ajustes de Interfaz", + "common.gameplay_settings": "Ajustes de Jugabilidad", + "common.controls_settings": "Ajustes de Controles", + "common.video_settings": "Ajustes de Graficos", + "common.sound_settings": "Ajustes de Sonido", + "common.language_settings": "Ajustes de Idiomas", // Message when connection to the server is lost "common.connection_lost": r#"Conexión perdida! @@ -89,9 +97,11 @@ El cliente está actualizado?"#, "common.weapons.axe": "Hacha", "common.weapons.sword": "Espada", - "common.weapons.staff": "Báculo", + "common.weapons.staff": "Vara Mágica", "common.weapons.bow": "Arco", "common.weapons.hammer": "Martillo", + "common.weapons.sceptre": "Cetro curativo", + "common.rand_appearance": "Nombre y Apariencia Aleatoria", /// End Common section @@ -141,6 +151,9 @@ https://account.veloren.net."#, "main.login.invalid_character": "El personaje seleccionado no es válido", "main.login.client_crashed": "El cliente crasheó", "main.login.not_on_whitelist": "No estas en la lista. Contacta al Dueño del Servidor si quieres unirte.", + "main.login.banned": "Usted ha sido baneado por la siguiente razón", + "main.login.kicked": "Te han echado por la siguiente razón", + /// End Main screen section @@ -153,6 +166,7 @@ https://account.veloren.net."#, "hud.waypoint_saved": "Marcador Guardado", "hud.press_key_to_show_keybindings_fmt": "Presiona {key} para mostrar los controles del teclado", + "hud.press_key_to_toggle_lantern_fmt": "[{key}] Encender Linterna", "hud.press_key_to_show_debug_info_fmt": "Presiona {key} para mostrar información de depuración", "hud.press_key_to_toggle_keybindings_fmt": "Presiona {key} para alternar los controles del teclado", "hud.press_key_to_toggle_debug_info_fmt": "Presiona {key} para alternar la información de depuración", @@ -160,6 +174,21 @@ https://account.veloren.net."#, // Chat outputs "hud.chat.online_msg": "[{name}] se ha conectado.", "hud.chat.offline_msg": "[{name}] se ha desconectado.", + + "hud.chat.default_death_msg": "[{name}] Murió", + "hud.chat.environmental_kill_msg": "[{name}] Murió en {environment}", + "hud.chat.fall_kill_msg": "[{name}] Murió por el daño de la caÃda", + "hud.chat.suicide_msg": "[{name}] Murió por heridas autoinfligidas", + + "hud.chat.pvp_melee_kill_msg": "[{attacker}] Derroto a [{victim}]", + "hud.chat.pvp_ranged_kill_msg": "[{attacker}] Le Disparo a [{victim}]", + "hud.chat.pvp_explosion_kill_msg": "[{attacker}] Hizo explotar a [{victim}]", + "hud.chat.pvp_energy_kill_msg": "[{attacker}] usó magia para matar [{victim}]", + + "hud.chat.npc_melee_kill_msg": "{attacker} Mató a [{victim}]", + "hud.chat.npc_ranged_kill_msg": "{attacker} Le Disparo a [{victim}]", + "hud.chat.npc_explosion_kill_msg": "{attacker} Hizo explotar a [{victim}]", + "hud.chat.loot_msg": "Recogiste [{item}]", "hud.chat.loot_fail": "Tu inventario está lleno!", "hud.chat.goodbye": "Adiós!", @@ -197,7 +226,7 @@ Deshazte de ellos haciendo click en ellos y luego arrastralos fuera de la bolsa. Las noches pueden volverse bastante oscuras en Veloren. -Enciende tu farol escribiendo /lantern en el chat o presionando la G. +Enciende tu Linterna escribiendo /lantern en el chat o presionando la G. Quieres liberar tu cursor para cerrar esta ventana? Presiona TAB! @@ -232,6 +261,7 @@ objetos infundidos con magia?"#, "hud.bag.chest": "Torso", "hud.bag.hands": "Manos", "hud.bag.lantern": "Linterna", + "hud.bag.glider": "Planeador", "hud.bag.belt": "Cinturón", "hud.bag.ring": "Anillo", "hud.bag.back": "Espalda", @@ -282,8 +312,8 @@ objetos infundidos con magia?"#, "hud.settings.invert_scroll_zoom": "Invertir Desplazamiento de Zoom", "hud.settings.invert_mouse_y_axis": "Invertir eje Y del Ratón", "hud.settings.enable_mouse_smoothing": "Suavizado de la Cámara", - "hud.settings.free_look_behavior": "Comportamiento de vista libre", - "hud.settings.auto_walk_behavior": "Comportamiento al caminar automaticamente", + "hud.settings.free_look_behavior": "Modo de vista libre", + "hud.settings.auto_walk_behavior": "Modo de caminata automática", "hud.settings.stop_auto_walk_on_input": "Frenar caminata automática", "hud.settings.view_distance": "Distancia de Visión", @@ -292,22 +322,43 @@ objetos infundidos con magia?"#, "hud.settings.maximum_fps": "FPS Máximos", "hud.settings.fov": "Campo de Visión (grados)", "hud.settings.gamma": "Gama", + "hud.settings.ambiance": "Brillo del Ambiente", "hud.settings.antialiasing_mode": "Modo Anti-Aliasing", "hud.settings.cloud_rendering_mode": "Modo de Renderizado de Nubes", - "hud.settings.fluid_rendering_mode": "Modo de Renderizado de Fluidos", - "hud.settings.fluid_rendering_mode.cheap": "Barato", - "hud.settings.fluid_rendering_mode.shiny": "Brillante", - "hud.settings.cloud_rendering_mode.regular": "Regular", + "hud.settings.fluid_rendering_mode": "Modo de Renderizado del Agua", + "hud.settings.fluid_rendering_mode.cheap": "Bajo", + "hud.settings.fluid_rendering_mode.shiny": "Alto", + "hud.settings.cloud_rendering_mode.regular": "Normal", "hud.settings.fullscreen": "Pantalla Completa", - "hud.settings.save_window_size": "Guardar tamaño de la ventana", - + "hud.settings.fullscreen_mode": "Modo de Pantalla Completa", + "hud.settings.fullscreen_mode.exclusive": "Completo", + "hud.settings.fullscreen_mode.borderless": "Con Bordes", + "hud.settings.particles": "Particulas", + "hud.settings.resolution": "Resolución", + "hud.settings.bit_depth": "Profundidad de Bits", + "hud.settings.refresh_rate": "Taza de Refresco", + "hud.settings.save_window_size": " Guardar tamaño de ventana", + "hud.settings.shadow_rendering_mode": "Renderizado de Sombras", + "hud.settings.shadow_rendering_mode.none": "Ninguno", + "hud.settings.shadow_rendering_mode.cheap": "Bajo", + "hud.settings.shadow_rendering_mode.map": "Alto", + "hud.settings.lighting_rendering_mode": "Renderizado de la luz de la Linterna", + "hud.settings.lighting_rendering_mode.ashikhmin": "Tipo A", + "hud.settings.lighting_rendering_mode.blinnphong": "Tipo B", + "hud.settings.lighting_rendering_mode.lambertian": "Tipo L", + "hud.settings.shadow_rendering_mode": "Renderizado de Sombras", + "hud.settings.shadow_rendering_mode.none": "Ninguno", + "hud.settings.shadow_rendering_mode.cheap": "Bajo", + "hud.settings.shadow_rendering_mode.map": "Alto", + "hud.settings.shadow_rendering_mode.map.resolution": "Resolución", + "hud.settings.lod_detail": "Detalle de LoD", "hud.settings.music_volume": "Volumen de Musica", "hud.settings.sound_effect_volume": "Volumen de Efectos de Sonido", "hud.settings.audio_device": "Dispositivo de Audio", "hud.settings.awaitingkey": "Presiona una tecla...", "hud.settings.unbound": "Ninguno", - "hud.settings.reset_keybinds": "Reestablecer a los valores predeterminados", + "hud.settings.reset_keybinds": "Reestablecer Controles", "hud.social": "Lista de jugadores", "hud.social.online": "En LÃnea", @@ -315,12 +366,29 @@ objetos infundidos con magia?"#, "hud.social.not_yet_available": "Aún no esta disponible", "hud.social.faction": "Facción", "hud.social.play_online_fmt": "{nb_player} jugador(es) en lÃnea", + "hud.social.name": "Nombre", + "hud.social.level": "Nivel", + "hud.social.zone": "Zona", + "hud.social.account": "Cuenta", "hud.crafting": "Crafteo", "hud.crafting.recipes": "Recetas", "hud.crafting.ingredients": "Ingredientes:", "hud.crafting.craft": "Fabricar", "hud.crafting.tool_cata": "Requisitos:", + + "hud.group": "Grupo", + "hud.group.invite_to_join": "{name} Te invito a su Grupo!", + "hud.group.invite": "Invitar", + "hud.group.kick": "Echar", + "hud.group.assign_leader": "Asignar Lider", + "hud.group.leave": "Salir del Grupo", + "hud.group.dead" : "Muerto", + "hud.group.out_of_range": "Fuera de Alcance", + "hud.group.add_friend": "Agregar a Amigos", + "hud.group.link_group": "Conectar Grupos", + "hud.group.in_menu": "Eligiendo Personaje", + "hud.group.members": "Miembros del Grupo", "hud.spell": "Hechizos", @@ -381,6 +449,9 @@ objetos infundidos con magia?"#, "gameinput.freelook": "Vista Libre", "gameinput.autowalk": "Caminata Automática", "gameinput.dance": "Bailar", + "gameinput.select": "Seleccione la Entidad", + "gameinput.acceptgroupinvite": "Aceptar invitación al grupo", + "gameinput.declinegroupinvite": "Rechazar invitación al grupo", "gameinput.crafting": "Craftear", "gameinput.sneak": "Agacharse", "gameinput.swimdown": "Sumergirse", @@ -423,7 +494,7 @@ objetos infundidos con magia?"#, Estado FÃsico -Fuerza de Voluntad +ValentÃa Protección "#, diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index fb4b51f230..06677412cc 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -1099,6 +1099,14 @@ "voxel.armor.back.short-2", (0.0, -2.0, 0.0), (-90.0, 180.0, 0.0), 1.0, ), + Armor(Back("Backpack0")): VoxTrans( + "voxel.armor.back.backpack-0", + (0.0, 0.0, 0.0), (-90.0, 0.0, 0.0), 1.0, + ), + Armor(Back("BackpackBlue0")): VoxTrans( + "voxel.armor.back.backpack-0", + (0.0, 0.0, 0.0), (-90.0, 0.0, 0.0), 1.0, + ), // Rings Armor(Ring("Ring0")): Png( "element.icons.ring-0", diff --git a/assets/voxygen/voxel/armor/back/backpack-0.vox b/assets/voxygen/voxel/armor/back/backpack-0.vox new file mode 100644 index 0000000000000000000000000000000000000000..32e778562dac288a091b2649e0491897508d5872 GIT binary patch literal 3864 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 literal 0 HcmV?d00001 diff --git a/assets/voxygen/voxel/armor/back/backpack-grey.vox b/assets/voxygen/voxel/armor/back/backpack-grey.vox new file mode 100644 index 0000000000000000000000000000000000000000..60e4568abad3688b4f984986a02ed2e3e9048164 GIT binary patch literal 3864 zcmbu>Z>W}c9mnzC_51Vv{kzX{dHlcBIhP(CGyhC8&5qNP9+z7uOSUt!7?XA|X-LR+ zI1IMa1}c{KeQw->%vp#;%r-d*u?J>DY#?KpL=Yk)dftPG=*hSD-J%B}_MjVHT;J>Z zet*a7^Skc*!o5#Cx%iYZX7|?N$2l1P31jvQj}EnZCw%TfLht;^r$&cw779MomRV!k z0fGqT+8M4fJAGri0fLBxj6%;@)Ap7=1Q7`tg`RWt9eoHQ5;6)s=jnU;5JV(o6nf4y zZEu=xfS9Hek*G5YJ=-+gW7G8r5JV(o6jWxcZMqH~0fLBxjDia5n1e@vAR-~7pu&3Q z;1PT?C7ydjqRw0^sBoca7dYVvXKdFaK#V1iC66VKC6Dde#*&8_1PCG$G72g))>`r~ zhX6rDLPkM_b<DvdKoF6TQBYw$bMW3y1&)G<gv_<VvBCw`zzIjVXxjzOIC431IdVC2 zIdVC2IdVC2IdU<B06|1TMnPr9T1PJC5Fm(1$SA029DfrI9sz=ggp7iU78=hy96SO9 z5eXRu6)o664J#aBqia_<<H_mC>B;HI>B;HI>B;HI>B-3q0t68W83mP@#xe^Bj{reL zLPkMF3yotQ4juu5h=h!SiWVBrJRH3DJX7Bh>WD<0xmKtvT4)0E!M7_~y;|tOMy|n` zK<>~95EFJrB<hSp&o+VF%pgDzk&sbPnQ1JuaPSBaL?mPsRJ72z(6NqrcmxO{5;6)Z zTFf?{dCxq&59H>4sUs3~=31eyXmQ>I?k5Ct2Xa#jJ=n-KI3pczgm<IxZj>F-2)TuK zqqGY>+X%UZ+(K?4w~$-NE#ww*3%P~dLT({9vj`AGBxDp+v|yZc8pj$qcmxO{5;6)Z zTIgeq@vQT#gM;_N9teBDIwb1MwL)FdLSJu9U`-JALD&asp${9m25Vx+naI01P6Y_( z5s5mZ(5sx=CUzY>0t68W83h%MjqC@r@CXn@BxDp+v|wChKUf0?j{reLLPkMF3w^9H zo^_seaPXe{hfqf(>Wo5N(L!HuO<+wR6I#7m=)*>?!J5Q(BTadeco!tz1&RGg>__5V zka!oQsmhFP68n+Z59T0<NXRItXl!CXn1x4xAR-~7prQriQoC`ifrCeYAR-~7prVC7 z))>z^&pJ4GpV*JYey|RSI-^imw9waE6Ic@x`;piWYM~Dsxdv-8`;pm?%zk9v5t-jg z=C_i0M`ZRRvmcrL$m~aEKbV6cA|a!o!dm9w5g>?2$SA029J6rn2oOXhWE51i(0JzI z;1M8*NXRItXrT$rhfMBFZfc>1HHEx|yoJ1l=U;gKh38**{)N1SyoJ2XHHEy)K@gFU zQBaw&wvd-O1PCG$G72iJV-6kxf{28Sf(q-IgZG8!TX?=@Dk34H(5tY4`wQeifCO?N zQD+oXX1uB7tmLfZtmLfZtUSv)<r{jCh8_}iMxkeIZ9B^h0>m<dM4eGknX!&J#~cF0 zF^5E*QBaw&o;lAP0>m?iM4eGknfc0&N4NI=Q``F}pNWYH{itJf=jp5ZK>y$Mul@ac z<;w0}OWzv)<z3AfoUNPoJ)%S3-l79%HmVs4Y7Q1PXFsXt>TI2{+vuD_Mpq6k(}&(R znhqN6n>0G}q|w<wH@a?j)m?{X=;+Hc_1L9P>GlbuBVD7<Trm3LhpZi$uM5YP=+cQ5 zx_)|47aZ=_B`4<VigSx}^|fU>XP?ofFB^U6l+n$Xjhe6W?~f-n-Fwtr?AN2q?$z_3 znWZ;Bzd&`@{kn<B!}vY^jP46YFH9M|a^C3s?{Ge7^z4w)TfccmZ*jk)oWHQw=*eff ze#Gdu0i*jrYxKx=qyM;eT1VE+*Ri1`IzF;oPaRmJ6JOh)r=HuUoda7{ub<VG56srh zYZmEaUs$dO_HNLFyNphL-RPO`8om4jqw5#W*WndQb>I4j_3-wMx_8BF9b30RPwaS5 zr}wYY(Jyb+gWr5yC!T*=kH7RS9ska#o_zJ7PM?2GZ@hO}YpptW=DoUj@nT&)ute8C z{AvB%ngJc!yjq|5;zm9Bz5DdSn@jW$@2}8XAFR|L@o!F_GkX19qksCH(ebh6`o`p1 zz53QR{mUQs=;7lt^u*8l^_>sr=`CG!*Zgm=FC!x(IyN?@<KyFc?AWpY<k#PxQN4Zj z|IWX5?OJVbIHRjppVduAU(tD!^Yq$JjNW{jukN{XIxsMxM~)oPFYUajW^!C-9iF9^ zUjO(RzBHfj&ccP-ezmJxHg)vb<FD!5{d4q#Up%OnM(1gVJ^Szd@6Gl8)>C_~>*42A zUwr+%PX2USzkl&P{o$`~=p)Y`)h(}_(Z_y#S@*nsLk9;3^&@V@B<SDN-7tFlS0CTs uZN6XstN-txpx*KL?zw;c2JhIT)4P7RUT^Vzj~>#w8^56oR$aK$tobJm*x8@} literal 0 HcmV?d00001 diff --git a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron index 8ab7638b62..baea9952be 100644 --- a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -23,6 +23,14 @@ "Short2": ( vox_spec: ("armor.back.short-2", (-5.0, -1.0, -11.0)), color: None + ), + "Backpack0": ( + vox_spec: ("armor.back.backpack-0", (-7.0, -5.0, -10.0)), + color: None + ), + "BackpackBlue0": ( + vox_spec: ("armor.back.backpack-grey", (-7.0, -5.0, -10.0)), + color: Some((76, 72, 178)) ), }, )) diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs new file mode 100644 index 0000000000..875c63d779 --- /dev/null +++ b/voxygen/src/hud/buffs.rs @@ -0,0 +1,125 @@ +use super::{ + img_ids::{Imgs, ImgsRot}, + TEXT_COLOR, +}; +use crate::{ + i18n::VoxygenLocalization, + ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, + GlobalState, +}; +use client::Client; +use common::comp::Stats; +use conrod_core::{ + color, + widget::{self, Button, Image, Rectangle, Text}, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; +use inline_tweak::*; +widget_ids! { + struct Ids { + align, + buffs_align, + debuffs_align, + buff_test, + debuff_test, + } +} +#[derive(WidgetCommon)] +pub struct Buffs<'a> { + client: &'a Client, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + #[conrod(common_builder)] + common: widget::CommonBuilder, + global_state: &'a GlobalState, + rot_imgs: &'a ImgsRot, + tooltip_manager: &'a mut TooltipManager, + localized_strings: &'a std::sync::Arc<VoxygenLocalization>, + stats: &'a Stats, +} + +impl<'a> Buffs<'a> { + #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 + pub fn new( + client: &'a Client, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + global_state: &'a GlobalState, + rot_imgs: &'a ImgsRot, + tooltip_manager: &'a mut TooltipManager, + localized_strings: &'a std::sync::Arc<VoxygenLocalization>, + stats: &'a Stats, + ) -> Self { + Self { + client, + imgs, + fonts, + common: widget::CommonBuilder::default(), + global_state, + rot_imgs, + tooltip_manager, + localized_strings, + stats, + } + } +} + +pub struct State { + ids: Ids, +} + +impl<'a> Widget for Buffs<'a> { + type Event = (); + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + #[allow(clippy::unused_unit)] // TODO: Pending review in #587 + fn style(&self) -> Self::Style { () } + + fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { + let widget::UpdateArgs { state, ui, .. } = args; + let localized_strings = self.localized_strings; + let buffs_tooltip = Tooltip::new({ + // Edge images [t, b, r, l] + // Corner images [tr, tl, br, bl] + let edge = &self.rot_imgs.tt_side; + let corner = &self.rot_imgs.tt_corner; + ImageFrame::new( + [edge.cw180, edge.none, edge.cw270, edge.cw90], + [corner.none, corner.cw270, corner.cw90, corner.cw180], + Color::Rgba(0.08, 0.07, 0.04, 1.0), + 5.0, + ) + }) + .title_font_size(self.fonts.cyri.scale(15)) + .parent(ui.window) + .desc_font_size(self.fonts.cyri.scale(12)) + .font_id(self.fonts.cyri.conrod_id) + .desc_text_color(TEXT_COLOR); + // Alignment + Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) + .mid_bottom_with_margin_on(ui.window, tweak!(92.0)) + .set(state.ids.align, ui); + Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) + .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) + .set(state.ids.debuffs_align, ui); + Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) + .bottom_right_with_margins_on(state.ids.align, 0.0, 0.0) + .set(state.ids.buffs_align, ui); + // Test Widgets + Image::new(self.imgs.debuff_skull_0) + .w_h(20.0, 20.0) + .bottom_right_with_margins_on(state.ids.debuffs_align, 0.0, 1.0) + .set(state.ids.debuff_test, ui); + Image::new(self.imgs.buff_plus_0) + .w_h(20.0, 20.0) + .bottom_left_with_margins_on(state.ids.buffs_align, 0.0, 1.0) + .set(state.ids.buff_test, ui); + } +} diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index a955e2685f..3aced27a57 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -349,6 +349,12 @@ image_ids! { chat_tell: "voxygen.element.icons.chat.tell", chat_world: "voxygen.element.icons.chat.world", + // Buffs + buff_plus_0: "voxygen.element.de_buffs.buff_plus_0", + + // Debuffs + debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0", + <BlankGraphic> nothing: (), } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 409c13bc2f..5b12014c1e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1,4 +1,5 @@ mod bag; +mod buffs; mod buttons; mod chat; mod crafting; @@ -24,6 +25,7 @@ pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; pub use settings_window::ScaleChange; use bag::Bag; +use buffs::Buffs; use buttons::Buttons; use chat::Chat; use chrono::NaiveTime; @@ -239,6 +241,7 @@ widget_ids! { spell, skillbar, buttons, + buffs, esc_menu, small_window, social_window, @@ -1754,6 +1757,24 @@ impl Hud { } } + // Buffs and Debuffs + if let Some(player_stats) = stats.get(client.entity()) { + match Buffs::new( + client, + &self.imgs, + &self.fonts, + global_state, + &self.rot_imgs, + tooltip_manager, + &self.voxygen_i18n, + &player_stats, + ) + .set(self.ids.buffs, ui_widgets) + { + _ => {}, + } + } + // Popup (waypoint saved and similar notifications) Popup::new( &self.voxygen_i18n, @@ -1848,9 +1869,9 @@ impl Hud { &stats, &loadout, &energy, - &character_state, + //&character_state, self.pulse, - &controller, + //&controller, &inventory, &self.hotbar, tooltip_manager, @@ -2031,7 +2052,7 @@ impl Hud { }, settings_window::Event::CrosshairType(crosshair_type) => { events.push(Event::CrosshairType(crosshair_type)); - }, + }, settings_window::Event::ToggleBarNumbers(bar_numbers) => { events.push(Event::ToggleBarNumbers(bar_numbers)); }, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index e6c0af70b5..9bf18b9473 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -1,5 +1,6 @@ use super::{ - img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MENU_BG, STAMINA_COLOR, + img_ids::Imgs, BarNumbers, CrosshairType, PressBehavior, ShortcutNumbers, Show, + CRITICAL_HP_COLOR, ERROR_COLOR, HP_COLOR, LOW_HP_COLOR, MENU_BG, STAMINA_COLOR, TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ @@ -257,7 +258,7 @@ pub struct State { pub enum Event { ToggleHelp, ToggleDebug, - ToggleTips(bool), + ToggleTips(bool), ToggleBarNumbers(BarNumbers), ToggleShortcutNumbers(ShortcutNumbers), ChangeTab(SettingsTab), @@ -793,7 +794,7 @@ impl<'a> Widget for SettingsWindow<'a> { .font_size(self.fonts.cyri.scale(18)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) - .set(state.ids.hotbar_title, ui); + .set(state.ids.hotbar_title, ui); // Show Shortcut Numbers if Button::image(match self.global_state.settings.gameplay.shortcut_numbers { ShortcutNumbers::On => self.imgs.checkbox_checked, diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index f27bb47257..53712c067c 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -2,8 +2,8 @@ use super::{ hotbar, img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, - slots, BarNumbers, ShortcutNumbers, Show, BLACK, CRITICAL_HP_COLOR, HP_COLOR, - LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, + slots, BarNumbers, ShortcutNumbers, Show, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR, + STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ i18n::VoxygenLocalization, @@ -20,7 +20,7 @@ use common::comp::{ tool::{Tool, ToolKind}, Hands, ItemKind, }, - CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, + Energy, Inventory, Loadout, Stats, }; use conrod_core::{ color, @@ -87,7 +87,6 @@ widget_ids! { slot1, slot1_text, slot1_text_bg, - //slot1_act, slot2, slot2_text, slot2_text_bg, @@ -128,8 +127,8 @@ pub struct Skillbar<'a> { stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, - character_state: &'a CharacterState, - controller: &'a ControllerInputs, + // character_state: &'a CharacterState, + // controller: &'a ControllerInputs, inventory: &'a Inventory, hotbar: &'a hotbar::State, tooltip_manager: &'a mut TooltipManager, @@ -137,7 +136,7 @@ pub struct Skillbar<'a> { localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, #[conrod(common_builder)] - common: widget::CommonBuilder, + common: widget::CommonBuilder, show: &'a Show, } @@ -152,9 +151,9 @@ impl<'a> Skillbar<'a> { stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, - character_state: &'a CharacterState, + // character_state: &'a CharacterState, pulse: f32, - controller: &'a ControllerInputs, + // controller: &'a ControllerInputs, inventory: &'a Inventory, hotbar: &'a hotbar::State, tooltip_manager: &'a mut TooltipManager, @@ -170,11 +169,11 @@ impl<'a> Skillbar<'a> { rot_imgs, stats, loadout, - energy, + energy, common: widget::CommonBuilder::default(), - character_state, + // character_state, pulse, - controller, + // controller, inventory, hotbar, tooltip_manager, @@ -186,8 +185,8 @@ impl<'a> Skillbar<'a> { } pub struct State { - ids: Ids, - last_level: u32, + ids: Ids, + last_level: u32, last_update_level: Instant, } @@ -198,8 +197,8 @@ impl<'a> Widget for Skillbar<'a> { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { State { - ids: Ids::new(id_gen), - last_level: 1, + ids: Ids::new(id_gen), + last_level: 1, last_update_level: Instant::now(), } } @@ -210,7 +209,11 @@ impl<'a> Widget for Skillbar<'a> { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - let level = if self.stats.level.level() > 999 {"A".to_string()} else {(self.stats.level.level()).to_string()}; + let level = if self.stats.level.level() > 999 { + "A".to_string() + } else { + (self.stats.level.level()).to_string() + }; let exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64); @@ -221,12 +224,11 @@ impl<'a> Widget for Skillbar<'a> { if self.stats.is_dead { hp_percentage = 0.0; energy_percentage = 0.0; - }; + }; let bar_values = self.global_state.settings.gameplay.bar_numbers; let shortcuts = self.global_state.settings.gameplay.shortcut_numbers; - const BG_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 0.99); 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); @@ -348,7 +350,7 @@ impl<'a> Widget for Skillbar<'a> { 11..=99 => tweak!(13), 100..=999 => tweak!(10), _ => tweak!(14), - }; + }; Text::new(&level) .mid_top_with_margin_on(state.ids.bg, 3.0) .font_size(self.fonts.cyri.scale(lvl_size)) @@ -357,13 +359,13 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.level, ui); // Exp-Bar Rectangle::fill_with([476.0, 8.0], color::TRANSPARENT) - .mid_bottom_with_margin_on(state.ids.bg, 4.0) - .set(state.ids.exp_alignment, ui); + .mid_bottom_with_margin_on(state.ids.bg, 4.0) + .set(state.ids.exp_alignment, ui); Image::new(self.imgs.bar_content) - .w_h(476.0 * exp_percentage, 8.0) - .color(Some(XP_COLOR)) - .bottom_left_with_margins_on(state.ids.exp_alignment, 0.0, 0.0) - .set(state.ids.exp_filling, ui); + .w_h(476.0 * exp_percentage, 8.0) + .color(Some(XP_COLOR)) + .bottom_left_with_margins_on(state.ids.exp_alignment, 0.0, 0.0) + .set(state.ids.exp_filling, ui); // Health and Stamina bar // Alignment Rectangle::fill_with([240.0, 17.0], color::TRANSPARENT) @@ -388,11 +390,11 @@ impl<'a> Widget for Skillbar<'a> { .color(Some(STAMINA_COLOR)) .top_left_with_margins_on(state.ids.stamina_alignment, 4.0, 0.0) .set(state.ids.stamina_filling, ui); - Rectangle::fill_with([216.0, 14.0], color::TRANSPARENT) - .top_left_with_margins_on(state.ids.hp_alignment, 4.0, 13.0) + Rectangle::fill_with([219.0, 14.0], color::TRANSPARENT) + .top_left_with_margins_on(state.ids.hp_alignment, 4.0, 20.0) .set(state.ids.hp_txt_alignment, ui); - Rectangle::fill_with([216.0, 14.0], color::TRANSPARENT) - .top_right_with_margins_on(state.ids.stamina_alignment, 4.0, 13.0) + Rectangle::fill_with([219.0, 14.0], color::TRANSPARENT) + .top_right_with_margins_on(state.ids.stamina_alignment, 4.0, 20.0) .set(state.ids.stamina_txt_alignment, ui); // Bar Text // Values @@ -410,25 +412,25 @@ impl<'a> Widget for Skillbar<'a> { }; Text::new(&hp_txt) .middle_of(state.ids.hp_txt_alignment) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.hp_txt_bg, ui); Text::new(&hp_txt) .bottom_left_with_margins_on(state.ids.hp_txt_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.hp_txt, ui); Text::new(&energy_txt) .middle_of(state.ids.stamina_txt_alignment) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.stamina_txt_bg, ui); Text::new(&energy_txt) .bottom_left_with_margins_on(state.ids.stamina_txt_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.stamina_txt, ui); @@ -443,25 +445,25 @@ impl<'a> Widget for Skillbar<'a> { }; Text::new(&hp_txt) .middle_of(state.ids.hp_txt_alignment) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.hp_txt_bg, ui); Text::new(&hp_txt) .bottom_left_with_margins_on(state.ids.hp_txt_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.hp_txt, ui); Text::new(&energy_txt) .middle_of(state.ids.stamina_txt_alignment) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.stamina_txt_bg, ui); Text::new(&energy_txt) .bottom_left_with_margins_on(state.ids.stamina_txt_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(14)) + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.stamina_txt, ui); @@ -607,18 +609,8 @@ impl<'a> Widget for Skillbar<'a> { // Slot M1 Image::new(self.imgs.inv_slot) .w_h(40.0, 40.0) - .color( - match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Bow(_) => Some(BG_COLOR_2), - ToolKind::Staff(_) => Some(BG_COLOR_2), - _ => Some(BG_COLOR_2), - }, - _ => Some(BG_COLOR_2), - }, - ) .right_from(state.ids.slot5, 0.0) - .set(state.ids.m1_slot_bg, ui); + .set(state.ids.m1_slot_bg, ui); Button::image( match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { Some(ItemKind::Tool(Tool { kind, .. })) => match kind { @@ -642,34 +634,11 @@ impl<'a> Widget for Skillbar<'a> { .w_h(36.0, 36.0) .middle_of(state.ids.m1_slot_bg) .set(state.ids.m1_content, ui); - // Slot M2 - match self.character_state { - CharacterState::BasicMelee { .. } => { - let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; - if self.controller.secondary.is_pressed() { - Image::new(self.imgs.inv_slot) - .w_h(40.0, 40.0) - .right_from(state.ids.m1_slot_bg, 0.0) - .set(state.ids.m2_slot, ui); - Image::new(self.imgs.inv_slot) - .w_h(40.0, 40.0) - .middle_of(state.ids.m2_slot) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse))) - .set(state.ids.m2_slot_act, ui); - } else { - Image::new(self.imgs.inv_slot) - .w_h(40.0, 40.0) - .right_from(state.ids.m1_slot_bg, 0.0) - .set(state.ids.m2_slot, ui); - } - }, - _ => { - Image::new(self.imgs.inv_slot) - .w_h(40.0, 40.0) - .right_from(state.ids.m1_slot_bg, 0.0) - .set(state.ids.m2_slot, ui); - }, - } + // Slot M2 + Image::new(self.imgs.inv_slot) + .w_h(40.0, 40.0) + .right_from(state.ids.m1_slot_bg, 0.0) + .set(state.ids.m2_slot, ui); let active_tool_kind = match self.loadout.active_item.as_ref().map(|i| i.item.kind()) { Some(ItemKind::Tool(Tool { kind, .. })) => Some(kind), @@ -692,11 +661,6 @@ impl<'a> Widget for Skillbar<'a> { Image::new(self.imgs.inv_slot) .w_h(40.0, 40.0) - .color(match tool_kind { - Some(ToolKind::Bow(_)) => Some(BG_COLOR_2), - Some(ToolKind::Staff(_)) => Some(BG_COLOR_2), - _ => Some(BG_COLOR_2), - }) .middle_of(state.ids.m2_slot) .set(state.ids.m2_slot_bg, ui); Button::image(match tool_kind { @@ -717,6 +681,7 @@ impl<'a> Widget for Skillbar<'a> { .w_h(36.0, 36.0) .middle_of(state.ids.m2_slot_bg) .image_color(match tool_kind { + // TODO Automate this to grey out unavailable M2 skills Some(ToolKind::Sword(_)) => { if self.energy.current() as f64 >= 200.0 { Color::Rgba(1.0, 1.0, 1.0, 1.0) @@ -899,7 +864,7 @@ impl<'a> Widget for Skillbar<'a> { .color(TEXT_COLOR) .set(state.ids.slot5_text, ui); } - if let Some(m1) = &self + /*if let Some(m1) = &self .global_state .settings .controls @@ -936,7 +901,7 @@ impl<'a> Widget for Skillbar<'a> { .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(state.ids.m2_text, ui); - } + }*/ if let Some(slot6) = &self .global_state .settings @@ -1036,18 +1001,19 @@ impl<'a> Widget for Skillbar<'a> { // Frame Image::new(self.imgs.skillbar_frame) .w_h(524.0, 80.0) - .color(Some(UI_MAIN)) + .color(Some(UI_HIGHLIGHT_0)) .middle_of(state.ids.bg) .floating(true) .set(state.ids.frame, ui); // M1 and M2 icons + // TODO Don't show this if key bindings are changed Image::new(self.imgs.m1_ico) - .w_h(16.0, 18.0) - .mid_bottom_with_margin_on(state.ids.m1_content, tweak!(-9.0)) + .w_h(16.0, 18.0) + .mid_bottom_with_margin_on(state.ids.m1_content, tweak!(-11.0)) .set(state.ids.m1_ico, ui); Image::new(self.imgs.m2_ico) .w_h(16.0, 18.0) - .mid_bottom_with_margin_on(state.ids.m2_content, tweak!(-9.0)) + .mid_bottom_with_margin_on(state.ids.m2_content, tweak!(-11.0)) .set(state.ids.m2_ico, ui); // Buffs From e83e219d4b4b232d1de7d93b7d36e10a453bfcbd Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Mon, 12 Oct 2020 19:48:25 -0500 Subject: [PATCH 13/25] Buffs now get removed on death by default. Buffs are now required to have either the 'Buff' or 'Debuff' tag. RemoveByCategory function now has support for blacklisting certain categories. Support for UI stuffs. --- common/src/comp/buff.rs | 17 ++++++++++--- common/src/sys/buff.rs | 22 +++++++++++++--- common/src/sys/combat.rs | 2 +- server/src/events/entity_manipulation.rs | 15 +++++++++-- voxygen/src/hud/buffs.rs | 32 ++++++++++++++++++------ voxygen/src/hud/mod.rs | 9 ++++--- 6 files changed, 77 insertions(+), 20 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index c2b9073368..ca1d690002 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -41,6 +41,7 @@ pub enum BuffCategoryId { Divine, Debuff, Buff, + PersistOnDeath, } /// Data indicating and configuring behaviour of a de/buff. @@ -93,9 +94,12 @@ pub enum BuffChange { RemoveByIndex(Vec<usize>, Vec<usize>), /// Removes buffs of these categories (first vec is of categories of which /// all are required, second vec is of categories of which at least one is - /// required) Note that this functionality is currently untested and - /// should be tested when doing so is possible - RemoveByCategory(Vec<BuffCategoryId>, Vec<BuffCategoryId>), + /// required, third vec is of categories that will not be removed) + RemoveByCategory { + required: Vec<BuffCategoryId>, + optional: Vec<BuffCategoryId>, + blacklisted: Vec<BuffCategoryId>, + }, } /// Source of the de/buff @@ -173,6 +177,13 @@ impl Buff { duration, ), }; + assert_eq!( + cat_ids + .iter() + .any(|cat| *cat == BuffCategoryId::Buff || *cat == BuffCategoryId::Debuff), + true, + "Buff must have either buff or debuff category." + ); Buff { id, cat_ids, diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index da1b9eee4d..e509d5114b 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,5 +1,8 @@ use crate::{ - comp::{BuffChange, BuffEffect, BuffSource, Buffs, HealthChange, HealthSource}, + comp::{ + BuffCategoryId, BuffChange, BuffEffect, BuffSource, Buffs, HealthChange, HealthSource, + Stats, + }, event::{EventBus, ServerEvent}, state::DeltaTime, sync::Uid, @@ -14,12 +17,13 @@ impl<'a> System<'a> for Sys { Read<'a, DeltaTime>, Read<'a, EventBus<ServerEvent>>, ReadStorage<'a, Uid>, + ReadStorage<'a, Stats>, WriteStorage<'a, Buffs>, ); - fn run(&mut self, (dt, server_bus, uids, mut buffs): Self::SystemData) { + fn run(&mut self, (dt, server_bus, uids, stats, mut buffs): Self::SystemData) { let mut server_emitter = server_bus.emitter(); - for (uid, mut buffs) in (&uids, &mut buffs.restrict_mut()).join() { + for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() { let buff_comp = buffs.get_mut_unchecked(); let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = (Vec::<usize>::new(), Vec::<usize>::new()); @@ -95,6 +99,7 @@ impl<'a> System<'a> for Sys { }; } } + server_emitter.emit(ServerEvent::Buff { uid: *uid, buff_change: BuffChange::RemoveByIndex( @@ -102,6 +107,17 @@ impl<'a> System<'a> for Sys { inactive_buff_indices_for_removal, ), }); + + if stat.is_dead { + server_emitter.emit(ServerEvent::Buff { + uid: *uid, + buff_change: BuffChange::RemoveByCategory { + required: vec![], + optional: vec![], + blacklisted: vec![BuffCategoryId::PersistOnDeath], + }, + }); + } } } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index a0b24b1db7..6e46d94d6d 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -159,7 +159,7 @@ impl<'a> System<'a> for Sys { strength: attack.base_damage as f32, duration: Some(Duration::from_secs(10)), }, - vec![buff::BuffCategoryId::Physical], + vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], buff::BuffSource::Character { by: *uid }, )), }); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 1e2ed26a2a..d0ba6ee4d6 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -758,7 +758,11 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } }, - BuffChange::RemoveByCategory(all_required, any_required) => { + BuffChange::RemoveByCategory { + required: all_required, + optional: any_required, + blacklisted: none_required, + } => { for (i, buff) in buffs.active_buffs.iter().enumerate() { let mut required_met = true; for required in &all_required { @@ -774,7 +778,14 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) break; } } - if required_met && any_met { + let mut none_met = true; + for none in &none_required { + if buff.cat_ids.iter().any(|cat| cat == none) { + none_met = false; + break; + } + } + if required_met && any_met && none_met { active_buff_indices_for_removal.push(i); } } diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 875c63d779..ad32915443 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -8,7 +8,7 @@ use crate::{ GlobalState, }; use client::Client; -use common::comp::Stats; +use common::comp::{self, Buffs}; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Text}, @@ -24,8 +24,15 @@ widget_ids! { debuff_test, } } + +pub struct BuffInfo { + id: comp::BuffId, + is_buff: bool, + dur: f32, +} + #[derive(WidgetCommon)] -pub struct Buffs<'a> { +pub struct BuffsBar<'a> { client: &'a Client, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, @@ -35,10 +42,10 @@ pub struct Buffs<'a> { rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, - stats: &'a Stats, + buffs: &'a Buffs, } -impl<'a> Buffs<'a> { +impl<'a> BuffsBar<'a> { #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 pub fn new( client: &'a Client, @@ -48,7 +55,7 @@ impl<'a> Buffs<'a> { rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, - stats: &'a Stats, + buffs: &'a Buffs, ) -> Self { Self { client, @@ -59,7 +66,7 @@ impl<'a> Buffs<'a> { rot_imgs, tooltip_manager, localized_strings, - stats, + buffs, } } } @@ -68,7 +75,7 @@ pub struct State { ids: Ids, } -impl<'a> Widget for Buffs<'a> { +impl<'a> Widget for BuffsBar<'a> { type Event = (); type State = State; type Style = (); @@ -123,3 +130,14 @@ impl<'a> Widget for Buffs<'a> { .set(state.ids.buff_test, ui); } } + +fn get_buff_info(buff: comp::Buff) -> BuffInfo { + BuffInfo { + id: buff.id, + is_buff: buff + .cat_ids + .iter() + .any(|cat| *cat == comp::BuffCategoryId::Buff), + dur: buff.time.map(|dur| dur.as_secs_f32()).unwrap_or(100.0), + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 5b12014c1e..bd3d0575c4 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -25,7 +25,7 @@ pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState}; pub use settings_window::ScaleChange; use bag::Bag; -use buffs::Buffs; +use buffs::BuffsBar; use buttons::Buttons; use chat::Chat; use chrono::NaiveTime; @@ -1733,6 +1733,7 @@ impl Hud { // Bag button and nearby icons let ecs = client.state().ecs(); let stats = ecs.read_storage::<comp::Stats>(); + let buffs = ecs.read_storage::<comp::Buffs>(); if let Some(player_stats) = stats.get(client.entity()) { match Buttons::new( client, @@ -1758,8 +1759,8 @@ impl Hud { } // Buffs and Debuffs - if let Some(player_stats) = stats.get(client.entity()) { - match Buffs::new( + if let Some(player_buffs) = buffs.get(client.entity()) { + match BuffsBar::new( client, &self.imgs, &self.fonts, @@ -1767,7 +1768,7 @@ impl Hud { &self.rot_imgs, tooltip_manager, &self.voxygen_i18n, - &player_stats, + &player_buffs, ) .set(self.ids.buffs, ui_widgets) { From ac17c57975a932b07aee1ba0425c94a3996a5cb2 Mon Sep 17 00:00:00 2001 From: Monty Marz <m.marzouq@gmx.de> Date: Fri, 16 Oct 2020 08:08:45 +0200 Subject: [PATCH 14/25] Initial implementation of buffs UI player buffs animation more testing debuffs sorting and display limit fix overhead buffs fix WIP buff removal function fmt Update buffs.rs Now with compiling: WIP group UI buffs positioning Update group.rs Update group.rs Small optimizations. Fixed positioning of buffs in group panel. Broke buff tooltips in group panel. buff frame visuals added setting for displaying buffs at minimap --- assets/common/items/food/apple_stick.ron | 2 +- .../element/animation/buff_frame/1.png | Bin 0 -> 2149 bytes .../element/animation/buff_frame/2.png | Bin 0 -> 2131 bytes .../element/animation/buff_frame/3.png | Bin 0 -> 2145 bytes .../element/animation/buff_frame/4.png | Bin 0 -> 2128 bytes .../element/animation/buff_frame/5.png | Bin 0 -> 2145 bytes .../element/animation/buff_frame/6.png | Bin 0 -> 2117 bytes .../element/animation/buff_frame/7.png | Bin 0 -> 2147 bytes .../element/animation/buff_frame/8.png | Bin 0 -> 2123 bytes .../{ => icons}/de_buffs/buff_plus_0.png | Bin .../element/icons/de_buffs/debuff_bleed_0.png | Bin 0 -> 228 bytes .../{ => icons}/de_buffs/debuff_skull_0.png | Bin assets/voxygen/i18n/en.ron | 18 +- client/src/lib.rs | 7 + common/src/comp/buff.rs | 14 +- common/src/comp/controller.rs | 7 +- common/src/sys/agent.rs | 2 +- common/src/sys/buff.rs | 2 +- common/src/sys/combat.rs | 21 +- common/src/sys/controller.rs | 10 +- server/src/events/entity_manipulation.rs | 23 +- voxygen/src/hud/buffs.rs | 298 +++++++++++++++--- voxygen/src/hud/group.rs | 244 +++++++++++--- voxygen/src/hud/img_ids.rs | 17 +- voxygen/src/hud/minimap.rs | 2 +- voxygen/src/hud/mod.rs | 109 +++++-- voxygen/src/hud/overhead.rs | 111 ++++++- voxygen/src/hud/settings_window.rs | 76 ++++- voxygen/src/session.rs | 8 + voxygen/src/settings.rs | 4 +- 30 files changed, 797 insertions(+), 178 deletions(-) create mode 100644 assets/voxygen/element/animation/buff_frame/1.png create mode 100644 assets/voxygen/element/animation/buff_frame/2.png create mode 100644 assets/voxygen/element/animation/buff_frame/3.png create mode 100644 assets/voxygen/element/animation/buff_frame/4.png create mode 100644 assets/voxygen/element/animation/buff_frame/5.png create mode 100644 assets/voxygen/element/animation/buff_frame/6.png create mode 100644 assets/voxygen/element/animation/buff_frame/7.png create mode 100644 assets/voxygen/element/animation/buff_frame/8.png rename assets/voxygen/element/{ => icons}/de_buffs/buff_plus_0.png (100%) create mode 100644 assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png rename assets/voxygen/element/{ => icons}/de_buffs/debuff_skull_0.png (100%) diff --git a/assets/common/items/food/apple_stick.ron b/assets/common/items/food/apple_stick.ron index 5bf9454978..7d5071e5f7 100644 --- a/assets/common/items/food/apple_stick.ron +++ b/assets/common/items/food/apple_stick.ron @@ -1,6 +1,6 @@ ItemDef( name: "Apple Stick", - description: "Restores 20 Health", + description: "Restores 25 Health", kind: Consumable( kind: "AppleStick", effect: Health(( diff --git a/assets/voxygen/element/animation/buff_frame/1.png b/assets/voxygen/element/animation/buff_frame/1.png new file mode 100644 index 0000000000000000000000000000000000000000..e05166cd464cb514d8e738eb98b153565f554ece GIT binary patch literal 2149 zcmcIm&2QX96gL6^Db1mP1VSJzS4e!#dOY^}OW9Vt-fgp}8=`cprUxX(Gvi$=yB=dZ z*-gZ$98eC369*6njy)kEai9W;T)0qiL;L~UD^3WGJnvW1l7y%dD}RpP{NC@q_j@zD zzrJ>L`P@tA6h&EXuQoT}{(N~p{UlsJ*?(mjZcFLvtz1!_d9FMkQ@;4}MMZh)*SNb` zY<8~sOb#oQ%TQFt!xXF)W##fXrEE(SYAAYfQq%tU{zpxXc}=@n?ch!th<?1fmx=3p zYhAXt#XPQEzND^<eE=AWf~w=;Aj$o4O`G8Q@LUe-nmRElwrX0vbf|82*402}LbWRx zF^q}ocooC8EE~VA5=^j;tGYpuVf)zf4M&~*H1L-3$lqvQnfZdZn$|Ch)YtXVXjB=Q z6`A#P!}B~H6P*wQ7AW6M3OYtfesPY_6gkV{w1{P*mWVWz+eJ+Srqd9H=>j&%XK8|j z>0_GeMg^B4%?&%9(@lrNg>_!EcEHPw@3FvncQ+OKhREf1#zbpJB*n$Kv$QV@nfK)h z+!mi7V943RVY;0SCh6f!7XvW_>>O;33BS}2GC>QOb)_843$;G?s1l<>)Yosu374b% zjgrx9f@so0)U>i{$bf@%3Gpr0H}PAT_!v)3JD>wj3;K|;)5X}g;IJMrmOPGj&tlr? z`0XSwXu?FhS<`@2C62kzLMm`I6o^JNM7C==h}xcwutz-0BA#uV?t~Z2Z%VdZW}=)w zl`WSHFc#VL9h<OfHKfR{Rxzpy3nMQyO%%EU^Qg+h(DLSJ?`AP{4jmjtEm?6u^djPr zhyj<-5y<fX#|1)&Iyf?zNvRPLO+EA*$brl{l0$aQqvP!$SkGh>58y+-akZhgTY+sr zP>56vP36pwWHzKAw0KB+LQj)kDSMy+;jYFx)OdH+NY}*&v!_9<PSojBRyIXVW2G7d z*Ji`<F=;+qKvURJUjXPSDw&$)GAc$i6ZIaH<UduYpF}>VJEv2p*oa(iS;z?;6GhAl zQD|Zh<ZId<^9YA(%tQZQsL#^M`ZVbYSYdVT(Stdg+>fopzZ>vT>TrB8dbzMp6{bHd zzC)GIJEaaiTuL*a)wDr-i|SrFzA;V04a<e~J;ID5%d!k;sL(_#v~6rN2ZvU)GGQ@@ z53%O$=u($JwFwj+mIgb7cBD~cA!b@m6?4ul&pdf{qg8hZZZ-nbtGDWg;RbaawCtu! zt~BaK3m=&|VK5CxKRxb=<&mukNKb}fzbhXX8!fynwrY`pdNSCEe}C`pK5XJnyIJqL z_wN5<Tu{{W2M2%YFFgKJ<Kt^D2fuy#`8P|w8|UAwzjo(|&%S!Ld;6XBrJoP}{QIL< cKfLh6z5Lrd-`&5<%Rd6`)>`xH#{0Mb0qC)xLI3~& literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/2.png b/assets/voxygen/element/animation/buff_frame/2.png new file mode 100644 index 0000000000000000000000000000000000000000..54c5183ec1047502eb0f01b8a3f245e3ec3711fd GIT binary patch literal 2131 zcmcIl&u`pB6gC2twvYpYLsg1oIYI)#tY^lK?Xj|n_E(dY*bt>FHQX4_jCZZ<dW`L4 zH#s74C{j6cK-?;E<iw2wC;kKO{0Uq*a4XOID``p6suHW+wLO0Gz3;vEy?KxJ_jcB9 z-@R=Z##(o`eE`?@s^85U@c;PH2WxP-mF<2}7{*)gR=?MbAAY=J7;pZO^bgC!-e;lE z;~JMbQnl$g18c+B+@5A!9I4Wb)F4Ug)?dH=W|@hsTMsvSxR<rmFxfrJ)q}IWzBoG) zOj_Gp=H@g6fUzpMIUSGEBAnK(8Ey#o)v|4wGn4YDZZ#^0=3#H&Z0TH?UJWCGF*SWw zBcAJe_*0W&ifz1M6N-ozVmBndx%gP%Ethe4(7wO$1y6NrSe996+mp$pHgRe?AJ~L3 zW@BnoiogODr)kNjC@t2P2yIn}Jju#Lr)Gu7BYj-fEnqqiVVtdC(_)b(NSHn4nN4cA z3TbKB>s@a;9<Qv6vU37n7JM%SF8ZgLvJX_Dk8`0qCn_!1m(H@GE_E@~S8!XseukkG zD~H)}KANRR3R{iT7_bYlB{P0m*vb_zb>7!{v@F#A(xXX9jhY`lOj4;Q#m5z+#Rk>p zrK($1)ewO%?Nb`MLFnL5Fby%DoAy8ll9&8BW517a=#mio&lqc&#HTM~+Utegv?zHh zRJUEXfK)9>Wau-dIP*A)h(}QryB^{g`$)vf5sU=P_cms{V1HYS<0=!?{<&<W7J#wJ zW+)?v`#vWKGv*^nTptC(aS(SE1vKIYiY?JT&lBhzKDvlnv66tuV(Qaa_=rcoLOydb z3K(UG`#2`T;he<OGSB_C^hoDDEg`$k#r1BhwV&%a8NrL<V5ezzJ1vhe&qK6EEK`aw z*7=x&(2_A9C_76BmF$5Agu9y*P~+1@BRx=GE$&8%Ia6oIMb#8_OH^u5(poGhm!$dS z0-D24h6+HhQOVq_&~Z88xoQlcBrjB<eHHnFpIlF!V-g3_b&((W4vK{vp~%4u<m-4$ zFe;%M%h3N9>Wj3BAx{SiPFUM|^<Z93?w8i#zYX{*b+|kjyE<6s3bUUV-?_?{ozj3F zuB2HkYTD$3Rdu&6Z%l_mGX`+J$5=?@x-Nl+iX0>&&%>VZapcCEGZv%d9BbK*0S_ot z8_oxZdB}^P9eEtPNI0&)fu)o#bFSXq>@<9e+s&548l46qL92mV9j_hG`^^UF;0rrv z4Cdk3*T+4xyfB)8^lS;{U3I$}wD7PR)hY$`<S>ZWpL~A{hWLlwcB3CW7XLhby8Sx2 xb93vRZ+6!1ZDj5DcYpc)$&K&s2KUz9Fq(h=Nq;(f``c>lcRPFSpPOHP{V%)uk=OtL literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/3.png b/assets/voxygen/element/animation/buff_frame/3.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a196aeac76d813c343c7d5fdad22599b9939b6 GIT binary patch literal 2145 zcmcIm&5Ptj6wi9lb!9|RK@=foFMd_0QmIZ-VS1PT+MPx-4zmqAf`XO$=nm7Jib-a= zXAXjkh%3n6MG#NxNzm&GdUuch1AFr#;=#L>{+ii!W?U9*I-N@8)$je@d%suJH@7yg zu3dQPf?*hIy^YSczCT}G=O5R<U*3FWP2Zl(Hr^`?<EiJW>zwh`*Do5z6Tc<>opL97 zCuC||qg=(JHXUc$+A!8HPczDPMQO%jkfaUk&pSU`X2KiR^?C%OtSyGg#$hh59d7p7 z;VvWGx_rr8pN1M>EJ|ul$D_0erwwa{8|ra2ZCmEdq}**-&B~#<6K$Dol?&6WLBJqH zrcY{)hp`9WG7&`3hIQLP!0|$eL&rB4A4_}7xeT{ER~EkXQ^OjTWft1@WHPBu+?vV< zwnGT9A+iwy+5!{@X-TIbEiNt*I-+2Cl9h={%?gpmYOicqn&~`*akhd@i$$7r!t5!{ zY^MgRkd}r~bhhbuys|FJ?!NZ2;Cmu)(Lcz9y)6p0mow4b7ioEM=`0(nQWZmW3b)nk z`xtVza+vMqqgi@5v&Be^HFlwGof*F@Z0CZOD(@>bS{7<+>Cr?^4VkZ9Pg1TX#p@NL z#RAcxrD#}H)qtbFsE<&HgV2R<LKH$cH;uFoI4$V|#(p2d5IZ5l_Zcgm$b*M5jiRuZ z79~xY=ye*FCRIxk9tI*{$oE}9ge!rIu>&ZUK41=UkssG748)9=Uf)q{ugXNVelA<C zn8sLTGZbE(N*7B|j|m2(jtL+!WIzh!)dNUe83aqTck@Jd4jmmwtypo5NF?%+WImv= zFMv-l1OY(=P#;Q%xs*B*S>}=7wi>BCQe0=(JwD%Sx3_X7laYQ=Y+r4ey>8oci01)R zb1ajyP^x@Pwa}6=9SA#12bJtK4IS=AQs^2VEE?&W_-HX2CFV?>A!SulG%QxBL2zp^ zot%*7hYM&9I~fWMdWK5oW`&aFgyy0-&?Wg#722nfFX;Z+)G2giz%d4XtYsn@jzR1~ zqUGy)gb~DbHI||OFVq)lWkZ?{gx+Co>(PUGIJuu#hkrNVqtxN#VC-sRoh!_KP<%%! zUv^4U_i!c6Vo}o;9jvN*>Ey<A5qEH)_jd^y2e@kUfW$6fvFAaL`7p+Eea2#x9APcn zF`xm`)keDT!~<Te+mT9%0duimhn#av+|yULx=kO!POI&bX1D1$LAwdtU9S_ME3Kx} zg~wLT7|g@5&yIU$dF*J?q-Rt5ysL(*gH}JRj%tx=_2l{>zWoior4Mnp*J<{HU;nuK z>N&%F#`xjyTW^1I`>`8^EeG<aUk-k}_u0!IKmEeDR{zs4ww`^X)&1`Lz4t%=;P>dx TUw57APe8A`+4;Wp;f;R)lA)Wl literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/4.png b/assets/voxygen/element/animation/buff_frame/4.png new file mode 100644 index 0000000000000000000000000000000000000000..beceaf397b15dcbb314adb9cb14199a28acdaff2 GIT binary patch literal 2128 zcmcIl&2HpG5H_r|EQ@kj;IKj<S>6i>dfeUa@t-n@=5Ldcm=LlfZMXpK?)Hq88Mm>W z%p^x74twAQfER!RFTerbfXhm}0avcvmh${1yCfk3F`5~-+tpuvRrQtY$^PEX#@)B> z8iuja+wB~{_08&c=QjL*{p6huxV)O}eo`35Yj0G)myB<|yKfk;JWU3N<zfHBQ0PgG zOC71&Y?6VsVQg;CGA@o(X+~<8qz&tjpMJK?L^iC)^*-)rZ8b`E&vNzXY;PdWjzl1> z?JaY276QOTmE4?7#%U4G8rB>)gmtxTTjt!PJZf0Y%AtAK-#6PjSEgITNMKA&FQ^fh zF&BSeQcSUp>o%c?xFKdC@yzAN0&lsD!-LMlr7yT^SfjGcLff8Br?sh5)A`UQK@ixO z+LR)&K*ecV@)=5t2P=e*Dny=SWujBFLgbM?E*lmwU4$^n*05=@OcNx`p7G2kHC%<X zGVJ%SH=Rt@)<xMp0WV9wmjV}q(@fb1s?f)|P~8)imJe3WvXL%zG16CXTR(n|p%iO} z*>OIer$-7~jnxFO3$P_~ep%Sg6)$x@(0aTo)c(q&NlA^G?><gasi(zz6{F<=)#0UT zSXI>!fiE3U8Ztk0@cWpC7%xowpaaQE{(`YLz&K<ibn4F;YnjBSH!<z^!(LjHJQb?f zX;?t2mLw9YDPt00A34MckS`cP5u*z6x-UIdXCjK}oEMz$XmMO+qB_5jt<(ZA*4YeY zzyy<mqdN9EVv@xuU=Bk>O3FOSROAFJw2$%xI){%hqE@UVAO<n@Xe>O$BTpeOU>NxU z4G{NmOoYQZsaT!+ZR@el`&vSFos0Xuc6&e9aWaMn#lcR?>~-5N30xP^8nH|%!dT}M z4nj*Ne5mX!9agdj8W8SoQb3JQmyPsDeX(4P6LYT4kc+A*8kVTkprp0jPA*CF%>r7$ zPDctruTjautk7{e<+*AOp(OvPLi;N61wXl-I>#jTC1c2oJO{;sMJRG`0P=O*Km=4m zHCCbjFVvT56(gPw6}(|>>pui@Be`E%hrc)Azp2CJ!PwP{b)hi(Me&`heAOvU=;2D5 z#j>U?K3rFK>+;5Qs3gpX_j`<mM2tb3`$6O&5xFjQg@+>+Z_ZhalXI+9JNn$GP;Cl@ z4_xF%(2hKg84?cj>R3w20_W<}t!~q!xYKGoL9^Q=#BVooyX$s*`mog`U3}r>oWUX- z`}(-&mKR17ke+YBysMV$K?^tQQLR!?PY#3lvrl(^gCTD9I?aK9PyG4p+4e1R|IXIE puXZ-xu4hkw`S=zrzQ6sl@$2K)e@E^Q-&Qlf*WK&<*!uj-zW^Duk`Vv^ literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/5.png b/assets/voxygen/element/animation/buff_frame/5.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb8dc04c1fc75e14513180a84ad33102b03bb97 GIT binary patch literal 2145 zcmcIm&2QX96kntYXdyxZ4yY26<p_xaV~@v<zm#pYUv0LqVU;dwdSg5vyC%CHV>{VR z4oKtzJ#gfLKtkfe33^0A2oBufz=1pb0|>!^D?IO4(vpOz607xkJbv?gzxUqn&FtOo z=8ct0moHT+m6i5Ja|`azme=Et!tV!npI?F7#dPCNUa36sOnE(0`RL>4DwW56j(gk1 zcIUN_sgXgsii9y4rC?pDtY4d?l<kN@i$p(8s`~F=f2(UTuj;qgI=GWI#30@{$i%IK z%^o}0VFA~#UDehnApnd-LAA+fnB?K4s!wr47?;zgu1!sfovL0d9ctU1uGUbQ&|Cu} zhB48+z_47$aq%k}!33Ll&9n%z+z>mV<!SSe4&E{@!>#7^xi5IC>Vu+4L(?3O$Hv$; zRMt1GAP7uMOhOP?pnN|m=maJC>H?uDa+bwu5vxQi5ox4$i>eMxXCaKzC2W$<(*y}K zCp0xJ1D7E!3_G2(O-G}pbzZdgz{{NPiNJYpKNaSd$klGfL~BnZ#p=RYI#7km2kI1V z%h&fY<ZS6M-OYy6^l)a1p%?*n4z|{mUm7+tK?{}jlo~Dy)m?bhh-DD%#oKYh)i}RW zGMX<CO<IVmURDiR@FhJ$LdOqn{4ypX#xv6n=z!CLK49$iFb*9nbiDhF6_4fqc}zQ< zu$|-uO_*pmt2&S};+Tihv!!q`L7uyYktB|TDCa)nw&0jh=5ooWykLD(vE4Eg<@%Xy zxnh8^%w{Nv6dr}}D35%^)&MQ=e2F5T*{&}G&vT;%+Ur>iokNGmQA<`F5Ccg(BAJJ1 z<O$>j4n}@J0z^G5EoM_{Nup~<ej942vX0`AUHkZayV2-oO2$KYk#F6oYwcFUwF1{g z#ISUYvrwvRL_uiri1vk<CjC<OKm)?vh;yj%{=AWHiMQvYVXRHn8B$g@MO9~|8U&<i zHXWak=JN$KTR$EM06jw`GqYUDVoWnp>qANYQ-$Vf<a4@rHg$?E>2s*07kM_4%!yEB z;{fDqy8#ObhiWWB|6i!j)5-=k=?mClP5t46IiK85ti!(>@L}q3axiAOvCb4`J}AB; zl`lG_20dI#GoRPAPW#L1UOl-nZNe?bhy7h*#*yPV7Bp03BNn+XcA1AGN3Ks<4C5oL zMLYV`Cs1tyg%4chM$nE_N(V99@zyZs+zIT{SJzuLkKks#VF$HV&9eMP4L4eD(<j&K zHLHb>t(-ELg=3x__tf&((FCNYQ*hpu!{tE>56h!kB%q!Q4&v@N@4pI%xZQ5ndj1c; z{b;RJ@RQ%&`}4h>3!nY8`pe}jU-tjH^UkmD{(X3}d-0{(3!h*3@YAPzhd1wizy8Tv We?0YuVSZKq323)An_tx5Jp2bGhMsHy literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/6.png b/assets/voxygen/element/animation/buff_frame/6.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc7af2a3634879d63b1994a62bf93076a8bb3f2 GIT binary patch literal 2117 zcmcIlOK%)S5Z(wWfgmG6IG`Y4G`WDFce;Ce=Ou5feI;HgYb<+3%q>0LJ-dT<r-zxb zcWrSYw;T{BE{GeKoVal0zz^ULaOB33@DHfjmmO>EL?Bi>ub%3!zN-3a>dEfT{f(Qq zZ&oUmjqY}P53X;Q$E(-j{LPd1HsJDFy8U@xsl0xxJg!x~|KXiV<;HJu|DZVNJqUyz zSGm-os!qo#SXU~WTho+_Lsb}|8pKK6{OgxrO(T|d^I@%rdudAz<L%Q-?Vs-S#p$8& zrMY$2*qjCcFjfUOrsGkP2h+Ma!wulRT((SOW>Os1%|_|aIOy#fEuAUDsbVBBriSZR ziNlzKKQSn#*upi7P(+*nvw*n9;%9=lOh&<8``*GAJk`x%QKW%oO(v7-#IEXWU=iQ< zEle#+5m=!7Bq{h5CHb8tLR;k`i_;?3iBTf*P#+a_6PV6J7^f@PBwwTn5@t<#YLO}~ zLs}a4dRLo{$1Cf+=p2KW1>d#6dH*C;)}G4sQ6^O9SS7`srL%OX3!M-3CEQl8pJOP+ z%3*qxjb`bQ!crqO2J9Sc$&6nbv@*pDo%OXIEeo}~^k`60rN)O3<3#F7{!z(ju|c(Y zq3UK?HALV~`;-RE3vB!`rUAxt(;nzR@`7J5cKa9yj6P?nWgMNn(5}}Dx=CK}M5u1N zZUUWZ9Ls<+5-P?##B1C}j4_Nt3M#-@P|x;k!YQ9Ig8gkRj>;UA`{#m{S^&l>j{$dF zM~TQqOoS9s$~X$Ssv*ogZriTpoU0|;r&$a=!$;>)OI8vP{fN3W5-#GQtB~shE6=As z;x3MeusJ6YHH|aBEj`j%PfN(FeSW>$YVBq^ibwDw-@D&5x}BCoe8)kwN=!qFAkx{G zgT&%7A1EtL2BqME286pE=TO~~MGNh#FBf;C*qG@u;G%4Yx+zK>C}}R1<27l1xq#-d zlc568EA%lp%XL&tc%~WyD9H=;XI(}<=f_u5=a@vEWDL2XYokc85QR4OLAbW#3!g$! zA(j6l^#!&V@?@ak+iIErA()qv``TbGwBEld!um+8@_Ra$mvvEjXA)nwNdp?V)MdUX zXp;|CrM<hpD{U$X^Wd`{VIj*NC(uoyjYQ};*by!cS+qH0F^bQymc8h4k3y*_RNZ%w z6GAWYC}K$1%&lQ5CG+jecQ-o?m*RG_W&4dzgAlLPz^#tc_UOH4gLLq@oihgWaICB2 zo>`uoNI-hF1Y@qeUCmc`SWRe^fOImLzxLw?AHeK3y6r~ads93z-v7#c>)YGj7k@ls n&u)G9_tUR`y#B`4E7$(~?A@n7@Be<I9Q57JPWz|kqsRXM7H*E_ literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/7.png b/assets/voxygen/element/animation/buff_frame/7.png new file mode 100644 index 0000000000000000000000000000000000000000..80dcb85b4480bbeb1445e726c4b16aa125a816a8 GIT binary patch literal 2147 zcmcImJ&fE$6gG<ZOL9^qC?X(|<upJTduHtQpKcTOFS(W2%jvRGE>w(X#=F+-dW`M6 z-Ma#$bSaP`N<>3LiKu8OD4>Iq3PeqX=qM4-`*)Xccbp=zTK|pTeD8bjeQ(B(wl?pq zU3~4LrfF;4jrKNNU#*UpUV!r_kFKr3<>hSSL7{1vUa5}fw9h`js%bC&ob-3fo!)yP zS7Vb26-jeC&cIsJ)^ANS#&>0@M{<y)b>q)(zcchi)Q$VK9`0o=IZQSVb9wJ@v(FEA zc_55iH}&;t2moVQGJQH8rA0Wc8#CMx?yF(T&}Sy)Zrx~94)vYhmflji)Lj!Jjxp7} zz$C8YxcD8NVu~$Xvj|1R4Y3mvPhb2D@Ro}>+-~1q_=2apF)Yh0w5-WwVoq#R<pYZZ zL11BOQHsC<6$fd_rYJ40FA>_Z;CYgjiAwbfkwt2+tQ){|9>O?V!KTF`O^`5a$})?X zxC&`$*z28bIv%gAi?Xv1UKV^$1TOjqnY6ZLq4shvJNq&%uP>ctLshC`s7~Rwdi?}L z!B-Bmy?iuFkKmRZ$uVFTU`uBFvapp)R;s+O)M#0#t))ku5|iq0-cM4YCdFG7qs0W- zW~HnfRn-uIKkZW*I(}&58<>U|&rN%v1Hnr6l(E;xICN-8$P>m&B=Ny{Onbesn-(QY zx$L&<29Pq7M1(OD1allAKe8FZw&SBfcm%m|;6yCK0pqn9FPPs}e6PwxHGeK!p*Ucy zvKiV@t;T&f202QG7<L@wdkiC=dJ<E+CT-#^(eCC6bPgLGN3B>1Kn!B)(U^OPMV>@n z-~dEG1H?QW6K*p`Vru9|ep_m!@}3fqUHkZWx7FIpRh*3AMX`OSsdqaqmjtehs7VZ6 z@Gw^Sn1RreF&juLO9z$gfd+)TkrYtlgGD3VlOHbbMu|RCXUKTf6m^4FYLLQM3@0a~ z`FsJ*VJAZgpl7ILZdRzcoUmLr22hg!RH1bm`GW1AO`Ty9`vNNIMV^gf?nEfEaRBnQ z-GB#FKsA=3|1Z=RY2`zf4kWCwmhtStoKNm2*5Tg`_$+leIT)*2Smz3}o)+Jc%9owe zfF7=-SuASWWP??8Z=TYaR1n99^*zR1AjfeCG*o0G9=R@dxrZYsUZ1fTB}Z7xcJ!G~ zq1qG*AGpYkpdDEpJBZtkSHnUGC$LYS-Rv|xirdYW9W*)(Li|<(w>oayr?;C8(!s}O z&KS(YvCfWrW_fID0@AY~*zc;_)kX^stF2n5pq?ByV)g!SHf-XxZoAR<fBfU8FW?j4 z^7mi={c&nuedF5B&W+C7`KOP6^}qT3^&d3y?&T{NW|zkgpTG2xd*QFUUuM5t8C0JE N-OgtFtL6s}{{dj6nVA3p literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/animation/buff_frame/8.png b/assets/voxygen/element/animation/buff_frame/8.png new file mode 100644 index 0000000000000000000000000000000000000000..e158dfbb97481c752c8650eb57725ef225fa8632 GIT binary patch literal 2123 zcmcIlPjBNy6gQUz7F9xm16pY%%X<OA#53dnR?}7DWVew>sk)K2+&Z2aC*CF=W4le- z8%P}XuzUc-4GD4Lju7I?1&PCcfc*sU6?o3Sc9*nNAfhC;$8Ub`_ul)xc~AHE9&X*f zb6eB2t=?|u0Iu)Wzc+5d|Ffrex8U+-zWZsZX>Yw#|6bF+{^mVRd;NJjIIIr)ABS8` z8ce8IHfEC?tTk=>{w!zwSXO#0hiTR{{`~0|Lr+E1c<l6XKab=n-90blqw~E1KR@Py zFz)Z@+p`b=CbDAsY%<QuaMm>DxFOuv%ch~vO{(Lj(W)Klhy8s$Qiasr21XoXs(V3$ zxVG)$k93MDHnC$8iijIxJ0zaI{21V^5J`B@d9d^aPfcS~Re5Nd)9JJ^wHm4znj{DU z6H}8?1Qw_~%PKZQS$S`T(2*rC(!5Glrq_rpRwq@{0H%u&CixmRE0<}4gqbszo1}s3 zkXDBM{`IDl$=bTCx~Je}$#)}gIXKIuc_2%5QgGQlm05Lf<t!hmN|hsZ1-JF<mlz7Z zc9@?O<9T`nH|1DP0J{WRGUu0vQ6X8Sih)w&RiXA*9(76@RDb_*nh7;6Kd2ckH^>gF zWYegth6sG=fYQ+RLkoY1X^8Q{v=2HEtYWVi+XLc<4wTq`$ykXrIlGBzzaRFpvSJyR zy-w2rQjIhfA!96#11mtT#1wgsjS+KViENw3o<*=NF`x5-{T;<m>P*!87qS(K1I9X= zp~oo~fx}QjEf@KYEfKSXL?m&T@5(?ro?M}QQl!v1Y<wBDW+ebINT^2>?jaU?5_tiz z@&g(m=HY~Ji!qW=L%;AFsj({hN<en4%j>-;+Ama+j^Ral@UX4-x{*r)*G05J3|;Ur zQN@IT(9#JTN;A)fwd{cggu9!TP~)>@BR!IzE$_yuK38YRc-<6DgV$=1!dNb+8`6BU zfEKXRkp$3dRI)HDRZ>k^AzMQz$v>*lyo!9uPOqoVFiCu2+sFe!poH5oiY**~d@VQN z0Tob<Rp|c<^<`T5h-E_wC#-4whhT0b_l<S<djtNPI&2QctPj?O!pv92ccJoCr?jAl zYiX9tnzq?+UEQ6{jcHLqY#+||1akq+Z4+py*g`yZUF>oX$9A$kXE9DMuvYEpGoM1W zDHJ|%ksH^CM3UHuTejz5A%q=RSMP3jTOP%oc4P&uZi^5<YT>Bsc6|Dv-6CClY3H25 zA{_JjxaXFaMiY>pFTuR4Z`Xqs9@e8;W}uz|2Jv$z{TYV%-Cn0P@SpLcqa#0(+fQD6 m|M|DKzPjT-dGV|Lw)V$&JHP)p{qnbZ-1oYBogdmqU;G6pyp#X{ literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/de_buffs/buff_plus_0.png b/assets/voxygen/element/icons/de_buffs/buff_plus_0.png similarity index 100% rename from assets/voxygen/element/de_buffs/buff_plus_0.png rename to assets/voxygen/element/icons/de_buffs/buff_plus_0.png diff --git a/assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png b/assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png new file mode 100644 index 0000000000000000000000000000000000000000..4638eeb618e80a1d374b6721ade2c2aae045da8b GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE<rYEKu(5DWk0l!Sx?4}bsv{EIYM zjnmJiq_9Y)Z7J1Wn%4Mr{^xIL4bnS){H^Zd=@k`Gxp&~K|GWm>&r>HJ%+$Qd*2L2+ z*>c9syfT63$TOvwbt<P?LrewYD-tIke(hA#c2J2qmiI4@$`h-YlN`&rJ2|aqNTqC5 zk?uGvu!x6+)k-h?(P>kb%Zi3^?&s=Qcw2UgL}xavouF{aA?TDb53}dR9&g<p4NJ>J a85ll?e%<@(hn5=9*$kepelF{r5}E);U{dw~ literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/de_buffs/debuff_skull_0.png b/assets/voxygen/element/icons/de_buffs/debuff_skull_0.png similarity index 100% rename from assets/voxygen/element/de_buffs/debuff_skull_0.png rename to assets/voxygen/element/icons/de_buffs/debuff_skull_0.png diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 55355503b8..c85ffe8d27 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -291,6 +291,8 @@ magically infused items?"#, "hud.settings.transparency": "Transparency", "hud.settings.hotbar": "Hotbar", "hud.settings.toggle_shortcuts": "Toggle Shortcuts", + "hud.settings.buffs_skillbar": "Buffs at Skillbar", + "hud.settings.buffs_mmap": "Buffs at Minimap", "hud.settings.toggle_bar_experience": "Toggle Experience Bar", "hud.settings.scrolling_combat_text": "Scrolling Combat Text", "hud.settings.single_damage_number": "Single Damage Numbers", @@ -343,9 +345,9 @@ magically infused items?"#, "hud.settings.refresh_rate": "Refresh Rate", "hud.settings.save_window_size": "Save window size", "hud.settings.lighting_rendering_mode": "Lighting Rendering Mode", - "hud.settings.lighting_rendering_mode.ashikhmin": "Type A", - "hud.settings.lighting_rendering_mode.blinnphong": "Type B", - "hud.settings.lighting_rendering_mode.lambertian": "Type L", + "hud.settings.lighting_rendering_mode.ashikhmin": "Type A - High ", + "hud.settings.lighting_rendering_mode.blinnphong": "Type B - Medium", + "hud.settings.lighting_rendering_mode.lambertian": "Type L - Cheap", "hud.settings.shadow_rendering_mode": "Shadow Rendering Mode", "hud.settings.shadow_rendering_mode.none": "None", "hud.settings.shadow_rendering_mode.cheap": "Cheap", @@ -509,6 +511,16 @@ Protection "esc_menu.quit_game": "Quit Game", /// End Escape Menu Section + /// Buffs and Debuffs + "buff.remove": "Click to remove", + "buff.title.missing": "Missing Title", + "buff.desc.missing": "Missing Description", + // Buffs + "buff.title.heal_test": "Heal Test", + "buff.desc.heal_test": "This is a test buff to test healing.", + // Debuffs + "debuff.title.bleed_test": "Bleed Test", + "debuff.desc.bleed_test": "This is a test debuff to test bleeding.", }, diff --git a/client/src/lib.rs b/client/src/lib.rs index ad47263340..9342123523 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -37,6 +37,7 @@ use common::{ terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize}, vol::RectVolSize, }; +use comp::BuffId; use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; @@ -631,6 +632,12 @@ impl Client { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); } + pub fn remove_buff(&mut self, buff_id: BuffId) { + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff( + buff_id, + ))); + } + pub fn max_group_size(&self) -> u32 { self.max_group_size } pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> { diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index ca1d690002..828cf39d96 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -151,16 +151,10 @@ impl Buff { pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { let (effects, time) = match id { BuffId::Bleeding { strength, duration } => ( - vec![ - BuffEffect::HealthChangeOverTime { - rate: -strength, - accumulated: 0.0, - }, - // This effect is for testing purposes - BuffEffect::NameChange { - prefix: String::from("Injured "), - }, - ], + vec![BuffEffect::HealthChangeOverTime { + rate: -strength, + accumulated: 0.0, + }], duration, ), BuffId::Regeneration { strength, duration } => ( diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index b9c7e1cb4c..64cdf93505 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -1,4 +1,8 @@ -use crate::{comp::inventory::slot::Slot, sync::Uid, util::Dir}; +use crate::{ + comp::{inventory::slot::Slot, BuffId}, + sync::Uid, + util::Dir, +}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -37,6 +41,7 @@ pub enum ControlEvent { Unmount, InventoryManip(InventoryManip), GroupManip(GroupManip), + RemoveBuff(BuffId), Respawn, } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 06d39cb1c8..63f263a765 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -684,7 +684,7 @@ impl<'a> System<'a> for Sys { for (_invite, /*alignment,*/ agent, controller) in (&invites, /*&alignments,*/ &mut agents, &mut controllers).join() { - let accept = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics + let accept = true; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics if accept { // Clear agent comp *agent = Agent::default(); diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index e509d5114b..a959ef3729 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -62,7 +62,7 @@ impl<'a> System<'a> for Sys { BuffEffect::HealthChangeOverTime { rate, accumulated } => { *accumulated += *rate * buff_delta; // Apply only 0.5 or higher damage - if accumulated.abs() > 5.0 { + if accumulated.abs() > 50.0 { let cause = if *accumulated > 0.0 { HealthSource::Healing { by: buff_owner } } else { diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 6e46d94d6d..7eb6cd53a7 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -157,12 +157,31 @@ impl<'a> System<'a> for Sys { buff_change: buff::BuffChange::Add(buff::Buff::new( buff::BuffId::Bleeding { strength: attack.base_damage as f32, - duration: Some(Duration::from_secs(10)), + duration: Some(Duration::from_secs(30)), }, vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], buff::BuffSource::Character { by: *uid }, )), }); + server_emitter.emit(ServerEvent::Buff { + uid: *uid_b, + buff_change: buff::BuffChange::Add(buff::Buff::new( + buff::BuffId::Regeneration { + strength: 100.0, + duration: Some(Duration::from_secs(60)), + }, + vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Buff], + buff::BuffSource::Character { by: *uid }, + )), + }); + server_emitter.emit(ServerEvent::Buff { + uid: *uid_b, + buff_change: buff::BuffChange::Add(buff::Buff::new( + buff::BuffId::Cursed { duration: None }, + vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], + buff::BuffSource::Character { by: *uid }, + )), + }); attack.hit_count += 1; } if attack.knockback != 0.0 && damage.healthchange != 0.0 { diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 380b176adf..03fbf46c6e 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ slot::{EquipSlot, Slot}, - CharacterState, ControlEvent, Controller, InventoryManip, + BuffChange, CharacterState, ControlEvent, Controller, InventoryManip, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, @@ -51,7 +51,7 @@ impl<'a> System<'a> for Sys { span!(_guard, "run", "controller::Sys::run"); let mut server_emitter = server_bus.emitter(); - for (entity, _uid, controller, character_state) in + for (entity, uid, controller, character_state) in (&entities, &uids, &mut controllers, &mut character_states).join() { let mut inputs = &mut controller.inputs; @@ -83,6 +83,12 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Mount(entity, mountee_entity)); } }, + ControlEvent::RemoveBuff(buff_id) => { + server_emitter.emit(ServerEvent::Buff { + uid: *uid, + buff_change: BuffChange::RemoveById(buff_id), + }); + }, ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), ControlEvent::EnableLantern => { server_emitter.emit(ServerEvent::EnableLantern(entity)) diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d0ba6ee4d6..8d7e1b106e 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -715,6 +715,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) add_buff_effects(new_buff.clone(), stats.get_mut(entity)); buffs.active_buffs.push(new_buff); } else { + let mut duplicate_existed = false; for i in 0..buffs.active_buffs.len() { let active_buff = &buffs.active_buffs[i]; // Checks if new buff has the same id as an already active buff. If it @@ -724,6 +725,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) // inactive buffs and add new buff to active // buffs. if discriminant(&active_buff.id) == discriminant(&new_buff.id) { + duplicate_existed = true; if determine_replace_active_buff( active_buff.clone(), new_buff.clone(), @@ -731,14 +733,21 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) active_buff_indices_for_removal.push(i); add_buff_effects(new_buff.clone(), stats.get_mut(entity)); buffs.active_buffs.push(new_buff.clone()); - } else { - buffs.inactive_buffs.push(new_buff.clone()); + } else if let Some(active_dur) = active_buff.time { + if let Some(new_dur) = new_buff.time { + if new_dur > active_dur { + buffs.inactive_buffs.push(new_buff.clone()); + } + } else { + buffs.inactive_buffs.push(new_buff.clone()); + } } - } else { - add_buff_effects(new_buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(new_buff.clone()); } } + if !duplicate_existed { + add_buff_effects(new_buff.clone(), stats.get_mut(entity)); + buffs.active_buffs.push(new_buff.clone()); + } } }, BuffChange::RemoveByIndex(active_indices, inactive_indices) => { @@ -871,7 +880,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) duration: _, } = active_buff.id { - new_strength > active_strength + new_strength >= active_strength } else { false } @@ -885,7 +894,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) duration: _, } = active_buff.id { - new_strength > active_strength + new_strength >= active_strength } else { false } diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index ad32915443..15a6552399 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -1,20 +1,23 @@ use super::{ img_ids::{Imgs, ImgsRot}, - TEXT_COLOR, + BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR, }; use crate::{ + hud::{get_buff_info, BuffPosition}, i18n::VoxygenLocalization, ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, GlobalState, }; -use client::Client; -use common::comp::{self, Buffs}; + +use crate::hud::BuffInfo; +use common::comp::{BuffId, Buffs}; use conrod_core::{ color, - widget::{self, Button, Image, Rectangle, Text}, - widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, + widget::{self, Button, Image, Rectangle}, + widget_ids, Color, Positionable, Sizeable, Widget, WidgetCommon, }; use inline_tweak::*; +use std::time::Duration; widget_ids! { struct Ids { align, @@ -22,51 +25,49 @@ widget_ids! { debuffs_align, buff_test, debuff_test, + buffs[], + buff_timers[], + debuffs[], + debuff_timers[], } } -pub struct BuffInfo { - id: comp::BuffId, - is_buff: bool, - dur: f32, -} - #[derive(WidgetCommon)] pub struct BuffsBar<'a> { - client: &'a Client, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, - global_state: &'a GlobalState, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, buffs: &'a Buffs, + pulse: f32, + global_state: &'a GlobalState, } impl<'a> BuffsBar<'a> { #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 pub fn new( - client: &'a Client, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, - global_state: &'a GlobalState, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, buffs: &'a Buffs, + pulse: f32, + global_state: &'a GlobalState, ) -> Self { Self { - client, imgs, fonts, common: widget::CommonBuilder::default(), - global_state, rot_imgs, tooltip_manager, localized_strings, buffs, + pulse, + global_state, } } } @@ -75,8 +76,12 @@ pub struct State { ids: Ids, } +pub enum Event { + RemoveBuff(BuffId), +} + impl<'a> Widget for BuffsBar<'a> { - type Event = (); + type Event = Vec<Event>; type State = State; type Style = (); @@ -91,7 +96,11 @@ impl<'a> Widget for BuffsBar<'a> { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; + let mut event = Vec::new(); let localized_strings = self.localized_strings; + let buffs = self.buffs; + let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer + let buff_position = self.global_state.settings.gameplay.buff_position; let buffs_tooltip = Tooltip::new({ // Edge images [t, b, r, l] // Corner images [tr, tl, br, bl] @@ -109,35 +118,230 @@ impl<'a> Widget for BuffsBar<'a> { .desc_font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .desc_text_color(TEXT_COLOR); - // Alignment - Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) - .mid_bottom_with_margin_on(ui.window, tweak!(92.0)) - .set(state.ids.align, ui); - Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) - .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) - .set(state.ids.debuffs_align, ui); - Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) - .bottom_right_with_margins_on(state.ids.align, 0.0, 0.0) - .set(state.ids.buffs_align, ui); - // Test Widgets - Image::new(self.imgs.debuff_skull_0) - .w_h(20.0, 20.0) - .bottom_right_with_margins_on(state.ids.debuffs_align, 0.0, 1.0) - .set(state.ids.debuff_test, ui); - Image::new(self.imgs.buff_plus_0) - .w_h(20.0, 20.0) - .bottom_left_with_margins_on(state.ids.buffs_align, 0.0, 1.0) - .set(state.ids.buff_test, ui); - } -} + if let BuffPosition::Bar = buff_position { + // Alignment + Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) + .mid_bottom_with_margin_on(ui.window, tweak!(92.0)) + .set(state.ids.align, ui); + Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) + .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) + .set(state.ids.debuffs_align, ui); + Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) + .bottom_right_with_margins_on(state.ids.align, 0.0, 0.0) + .set(state.ids.buffs_align, ui); -fn get_buff_info(buff: comp::Buff) -> BuffInfo { - BuffInfo { - id: buff.id, - is_buff: buff - .cat_ids - .iter() - .any(|cat| *cat == comp::BuffCategoryId::Buff), - dur: buff.time.map(|dur| dur.as_secs_f32()).unwrap_or(100.0), + // Buffs and Debuffs + // Create two vecs to display buffs and debuffs separately + let mut buffs_vec = Vec::<BuffInfo>::new(); + let mut debuffs_vec = Vec::<BuffInfo>::new(); + for buff in buffs.active_buffs.clone() { + let info = get_buff_info(buff); + if info.is_buff { + buffs_vec.push(info); + } else { + debuffs_vec.push(info); + } + } + if state.ids.buffs.len() < buffs_vec.len() { + state.update(|state| { + state + .ids + .buffs + .resize(buffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.debuffs.len() < debuffs_vec.len() { + state.update(|state| { + state + .ids + .debuffs + .resize(debuffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.buff_timers.len() < buffs_vec.len() { + state.update(|state| { + state + .ids + .buff_timers + .resize(buffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.debuff_timers.len() < debuffs_vec.len() { + state.update(|state| { + state + .ids + .debuff_timers + .resize(debuffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); + let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); + // Create Buff Widgets + for (i, buff) in buffs_vec.iter().enumerate() { + if i < 22 { + // Limit displayed buffs + let max_duration = match buff.id { + BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), + _ => 10.0, + }; + let current_duration = buff.dur; + let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed + let buff_img = match buff.id { + BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + _ => self.imgs.missing_icon, + }; + let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); + // Sort buffs into rows of 11 slots + let x = i % 11; + let y = i / 11; + let buff_widget = buff_widget.bottom_left_with_margins_on( + state.ids.buffs_align, + 0.0 + y as f64 * (21.0), + 0.0 + x as f64 * (21.0), + ); + buff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(state.ids.buffs[i], ui); + // Create Buff tooltip + let title = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.title.heal_test") + }, + _ => *&localized_strings.get("buff.title.missing"), + }; + let remaining_time = if current_duration == 10e6 as f32 { + "Permanent".to_string() + } else { + format!("Remaining: {:.0}s", current_duration) + }; + let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); + let desc_txt = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.desc.heal_test") + }, + _ => *&localized_strings.get("buff.desc.missing"), + }; + let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove); + // Timer overlay + if Button::image(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, //3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(20.0, 20.0) + .middle_of(state.ids.buffs[i]) + .with_tooltip( + self.tooltip_manager, + title, + &desc, + &buffs_tooltip, + BUFF_COLOR, + ) + .set(state.ids.buff_timers[i], ui) + .was_clicked() + { + event.push(Event::RemoveBuff(buff.id)); + }; + }; + } + // Create Debuff Widgets + for (i, debuff) in debuffs_vec.iter().enumerate() { + if i < 22 { + // Limit displayed buffs + + let max_duration = match debuff.id { + BuffId::Bleeding { duration, .. } => { + duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() + }, + BuffId::Cursed { duration, .. } => { + duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() + }, + + _ => 10.0, + }; + let current_duration = debuff.dur; + let duration_percentage = current_duration / max_duration * 1000.0; // Percentage to determine which frame of the timer overlay is displayed + let debuff_img = match debuff.id { + BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + _ => self.imgs.missing_icon, + }; + let debuff_widget = Image::new(debuff_img).w_h(20.0, 20.0); + // Sort buffs into rows of 11 slots + let x = i % 11; + let y = i / 11; + let debuff_widget = debuff_widget.bottom_right_with_margins_on( + state.ids.debuffs_align, + 0.0 + y as f64 * (21.0), + 0.0 + x as f64 * (21.0), + ); + + debuff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(state.ids.debuffs[i], ui); + // Create Debuff tooltip + let title = match debuff.id { + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.title.bleed_test") + }, + _ => *&localized_strings.get("buff.title.missing"), + }; + let remaining_time = if current_duration == 10e6 as f32 { + "Permanent".to_string() + } else { + format!("Remaining: {:.0}s", current_duration) + }; + let desc_txt = match debuff.id { + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.desc.bleed_test") + }, + _ => *&localized_strings.get("debuff.desc.missing"), + }; + let desc = format!("{}\n\n{}", desc_txt, remaining_time); + Image::new(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, //3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(20.0, 20.0) + .middle_of(state.ids.debuffs[i]) + .with_tooltip( + self.tooltip_manager, + title, + &desc, + &buffs_tooltip, + DEBUFF_COLOR, + ) + .set(state.ids.debuff_timers[i], ui); + }; + } + } + if let BuffPosition::Map = buff_position { + // Alignment + Rectangle::fill_with([tweak!(300.0), tweak!(280.0)], color::RED) + .top_right_with_margins_on(ui.window, tweak!(5.0), tweak!(270.0)) + .set(state.ids.align, ui); + } + event } } diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 26b853cd08..06503bc9d8 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -1,15 +1,20 @@ use super::{ - img_ids::Imgs, Show, BLACK, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, LOW_HP_COLOR, - STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, + img_ids::{Imgs, ImgsRot}, + Show, BLACK, BUFF_COLOR, DEBUFF_COLOR, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, + LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ - i18n::VoxygenLocalization, settings::Settings, ui::fonts::ConrodVoxygenFonts, - window::GameInput, GlobalState, + hud::{get_buff_info, BuffInfo}, + i18n::VoxygenLocalization, + settings::Settings, + ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, + window::GameInput, + GlobalState, }; use client::{self, Client}; use common::{ - comp::{group::Role, Stats}, + comp::{group::Role, BuffId, Buffs, Stats}, sync::{Uid, WorldSyncExt}, }; use conrod_core::{ @@ -18,8 +23,8 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Scrollbar, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; +use inline_tweak::*; use specs::{saveload::MarkerAllocator, WorldExt}; - widget_ids! { pub struct Ids { group_button, @@ -44,6 +49,8 @@ widget_ids! { member_panels_txt[], member_health[], member_stam[], + buffs[], + buff_timers[], dead_txt[], health_txt[], timeout_bg, @@ -63,10 +70,13 @@ pub struct Group<'a> { client: &'a Client, settings: &'a Settings, imgs: &'a Imgs, + rot_imgs: &'a ImgsRot, fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, global_state: &'a GlobalState, + buffs: &'a Buffs, + tooltip_manager: &'a mut TooltipManager, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -79,20 +89,26 @@ impl<'a> Group<'a> { client: &'a Client, settings: &'a Settings, imgs: &'a Imgs, + rot_imgs: &'a ImgsRot, fonts: &'a ConrodVoxygenFonts, localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, global_state: &'a GlobalState, + buffs: &'a Buffs, + tooltip_manager: &'a mut TooltipManager, ) -> Self { Self { show, client, settings, imgs, + rot_imgs, fonts, localized_strings, pulse, global_state, + buffs, + tooltip_manager, common: widget::CommonBuilder::default(), } } @@ -127,8 +143,27 @@ impl<'a> Widget for Group<'a> { #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - let mut events = Vec::new(); + let localized_strings = self.localized_strings; + //let buffs = self.buffs; + let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer + let buffs_tooltip = Tooltip::new({ + // Edge images [t, b, r, l] + // Corner images [tr, tl, br, bl] + let edge = &self.rot_imgs.tt_side; + let corner = &self.rot_imgs.tt_corner; + ImageFrame::new( + [edge.cw180, edge.none, edge.cw270, edge.cw90], + [corner.none, corner.cw270, corner.cw90, corner.cw180], + Color::Rgba(0.08, 0.07, 0.04, 1.0), + 5.0, + ) + }) + .title_font_size(self.fonts.cyri.scale(15)) + .parent(ui.window) + .desc_font_size(self.fonts.cyri.scale(12)) + .font_id(self.fonts.cyri.conrod_id) + .desc_text_color(TEXT_COLOR); // Don't show pets let group_members = self @@ -293,6 +328,7 @@ impl<'a> Widget for Group<'a> { let client_state = self.client.state(); let stats = client_state.ecs().read_storage::<common::comp::Stats>(); 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 .ecs() .read_resource::<common::sync::UidAllocator>(); @@ -302,6 +338,8 @@ impl<'a> Widget for Group<'a> { let entity = uid_allocator.retrieve_entity_internal(uid.into()); let stats = entity.and_then(|entity| stats.get(entity)); let energy = entity.and_then(|entity| energy.get(entity)); + let buffs = entity.and_then(|entity| buffs.get(entity)); + 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; @@ -317,7 +355,7 @@ impl<'a> Widget for Group<'a> { .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], 40.0) + .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); @@ -386,19 +424,19 @@ impl<'a> Widget for Group<'a> { .set(state.ids.member_panels_frame[i], ui); // Panel Text Text::new(&char_name) - .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) - .font_size(20) - .font_id(self.fonts.cyri.conrod_id) - .color(BLACK) - .w(300.0) // limit name length display - .set(state.ids.member_panels_txt_bg[i], ui); + .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) + .font_size(20) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .w(300.0) // limit name length display + .set(state.ids.member_panels_txt_bg[i], ui); Text::new(&char_name) - .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0) - .font_size(20) - .font_id(self.fonts.cyri.conrod_id) - .color(if is_leader { ERROR_COLOR } else { GROUP_COLOR }) - .w(300.0) // limit name length display - .set(state.ids.member_panels_txt[i], ui); + .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0) + .font_size(20) + .font_id(self.fonts.cyri.conrod_id) + .color(if is_leader { ERROR_COLOR } else { GROUP_COLOR }) + .w(300.0) // limit name length display + .set(state.ids.member_panels_txt[i], ui); if let Some(energy) = energy { let stam_perc = energy.current() as f64 / energy.maximum() as f64; // Stamina @@ -408,44 +446,146 @@ impl<'a> Widget for Group<'a> { .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0) .set(state.ids.member_stam[i], ui); } - } else { - // Values N.A. - if let Some(stats) = stats { + if let Some(buffs) = buffs { + let mut buffs_vec = Vec::<BuffInfo>::new(); + for buff in buffs.active_buffs.clone() { + let info = get_buff_info(buff); + buffs_vec.push(info); + } + state.update(|state| { + state.ids.buffs.resize( + state.ids.buffs.len() + buffs_vec.len(), + &mut ui.widget_id_generator(), + ) + }); + state.update(|state| { + state.ids.buff_timers.resize( + state.ids.buff_timers.len() + buffs_vec.len(), + &mut ui.widget_id_generator(), + ) + }); + // Create Buff Widgets + for (x, buff) in buffs_vec.iter().enumerate() { + if x < 11 { + // Limit displayed buffs + let max_duration = match buff.id { + BuffId::Regeneration { duration, .. } => { + duration.unwrap().as_secs_f32() + }, + _ => 10.0, + }; + let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); + let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); + let current_duration = buff.dur; + let duration_percentage = + (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed + let buff_img = match buff.id { + BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + }; + let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); + let buff_widget = if x == 0 { + buff_widget.bottom_left_with_margins_on( + state.ids.member_panels_frame[i], + -21.0, + 1.0, + ) + } else { + buff_widget.right_from(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x - 1/*x - 1*/], 1.0) + }; + buff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui); + // Create Buff tooltip + let title = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.title.heal_test") + }, + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.title.bleed_test") + }, + _ => *&localized_strings.get("buff.title.missing"), + }; + let remaining_time = if current_duration == 10e6 as f32 { + "Permanent".to_string() + } else { + format!("Remaining: {:.0}s", current_duration) + }; + let desc_txt = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.desc.heal_test") + }, + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.desc.bleed_test") + }, + _ => *&localized_strings.get("buff.desc.missing"), + }; + let desc = format!("{}\n\n{}", desc_txt, remaining_time); + Image::new(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, // 3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(20.0, 20.0) + .middle_of(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/]) + .with_tooltip( + self.tooltip_manager, + title, + &desc, + &buffs_tooltip, + if buff.is_buff {BUFF_COLOR} else {DEBUFF_COLOR}, + ) + .set(state.ids.buff_timers[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui); + }; + } + } else { + // Values N.A. Text::new(&stats.name.to_string()) .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(GROUP_COLOR) .set(state.ids.member_panels_txt[i], ui); - }; - let offset = if self.global_state.settings.gameplay.toggle_debug { - 210.0 - } else { - 110.0 - }; - 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], 40.0) - }; - back.w_h(152.0, 36.0) - .color(Some(TEXT_COLOR)) - .set(state.ids.member_panels_bg[i], ui); - // Panel Frame - Image::new(self.imgs.member_frame) - .w_h(152.0, 36.0) - .middle_of(state.ids.member_panels_bg[i]) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.member_panels_frame[i], ui); - // Panel Text - Text::new(&self.localized_strings.get("hud.group.out_of_range")) - .mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0) - .font_size(16) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.dead_txt[i], ui); + let offset = if self.global_state.settings.gameplay.toggle_debug { + 210.0 + } else { + 110.0 + }; + 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], 40.0) + }; + back.w_h(152.0, 36.0) + .color(Some(TEXT_COLOR)) + .set(state.ids.member_panels_bg[i], ui); + // Panel Frame + Image::new(self.imgs.member_frame) + .w_h(152.0, 36.0) + .middle_of(state.ids.member_panels_bg[i]) + .color(Some(UI_HIGHLIGHT_0)) + .set(state.ids.member_panels_frame[i], ui); + // Panel Text + Text::new(&self.localized_strings.get("hud.group.out_of_range")) + .mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0) + .font_size(16) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.dead_txt[i], ui); + } } } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 3aced27a57..fbb48033c0 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -272,6 +272,7 @@ image_ids! { hammerleap: "voxygen.element.icons.skill_hammerleap", skill_axe_leap_slash: "voxygen.element.icons.skill_axe_leap_slash", skill_bow_jump_burst: "voxygen.element.icons.skill_bow_jump_burst", + missing_icon: "voxygen.element.icons.missing_icon_grey", // Buttons button: "voxygen.element.buttons.button", @@ -350,10 +351,22 @@ image_ids! { chat_world: "voxygen.element.icons.chat.world", // Buffs - buff_plus_0: "voxygen.element.de_buffs.buff_plus_0", + buff_plus_0: "voxygen.element.icons.de_buffs.buff_plus_0", // Debuffs - debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0", + debuff_skull_0: "voxygen.element.icons.de_buffs.debuff_skull_0", + debuff_bleed_0: "voxygen.element.icons.de_buffs.debuff_bleed_0", + + // Animation Frames + // Buff Frame + buff_0: "voxygen.element.animation.buff_frame.1", + buff_1: "voxygen.element.animation.buff_frame.2", + buff_2: "voxygen.element.animation.buff_frame.3", + buff_3: "voxygen.element.animation.buff_frame.4", + buff_4: "voxygen.element.animation.buff_frame.5", + buff_5: "voxygen.element.animation.buff_frame.6", + buff_6: "voxygen.element.animation.buff_frame.7", + buff_7: "voxygen.element.animation.buff_frame.8", <BlankGraphic> nothing: (), diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 1bdeef1395..ca993285cb 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -105,7 +105,7 @@ impl<'a> Widget for MiniMap<'a> { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; let zoom = state.zoom; - const SCALE: f64 = 1.5; + const SCALE: f64 = 1.5; // TODO Make this a setting if self.show.mini_map { Image::new(self.imgs.mmap_frame) .w_h(174.0 * SCALE, 190.0 * SCALE) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index bd3d0575c4..1576fb3ff1 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -60,7 +60,10 @@ use client::Client; use common::{ assets::Asset, comp, - comp::item::{ItemDesc, Quality}, + comp::{ + item::{ItemDesc, Quality}, + BuffId, + }, span, sync::Uid, terrain::TerrainChunk, @@ -97,6 +100,8 @@ const STAMINA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9); //const TRANSPARENT: Color = Color::Rgba(0.0, 0.0, 0.0, 0.0); //const FOCUS_COLOR: Color = Color::Rgba(1.0, 0.56, 0.04, 1.0); //const RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0); +const BUFF_COLOR: Color = Color::Rgba(0.06, 0.69, 0.12, 1.0); +const DEBUFF_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0); // Item Quality Colors const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors @@ -267,6 +272,13 @@ widget_ids! { } } +#[derive(Clone, Copy)] +pub struct BuffInfo { + id: comp::BuffId, + is_buff: bool, + dur: f32, +} + pub struct DebugInfo { pub tps: f64, pub frame_time: Duration, @@ -318,6 +330,7 @@ pub enum Event { ChatTransp(f32), ChatCharName(bool), CrosshairType(CrosshairType), + BuffPosition(BuffPosition), ToggleXpBar(XpBar), Intro(Intro), ToggleBarNumbers(BarNumbers), @@ -351,6 +364,7 @@ pub enum Event { KickMember(common::sync::Uid), LeaveGroup, AssignLeader(common::sync::Uid), + RemoveBuff(BuffId), } // TODO: Are these the possible layouts we want? @@ -391,6 +405,13 @@ pub enum ShortcutNumbers { On, Off, } + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum BuffPosition { + Bar, + Map, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum PressBehavior { Toggle = 0, @@ -725,6 +746,7 @@ impl Hud { let ecs = client.state().ecs(); let pos = ecs.read_storage::<comp::Pos>(); let stats = ecs.read_storage::<comp::Stats>(); + let buffs = ecs.read_storage::<comp::Buffs>(); let energy = ecs.read_storage::<comp::Energy>(); let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>(); let uids = ecs.read_storage::<common::sync::Uid>(); @@ -1123,11 +1145,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, stats, buffs, height_offset, hpfl, in_group) in ( &entities, &pos, interpolated.maybe(), &stats, + &buffs, energy.maybe(), scales.maybe(), &bodies, @@ -1141,7 +1164,7 @@ impl Hud { entity != me && !stats.is_dead }) .filter_map( - |(entity, pos, interpolated, stats, energy, scale, body, hpfl, uid)| { + |(entity, pos, interpolated, stats, 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); @@ -1171,6 +1194,7 @@ impl Hud { let info = display_overhead_info.then(|| overhead::Info { name: &stats.name, stats, + buffs, energy, }); let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { @@ -1185,6 +1209,7 @@ impl Hud { info, bubble, stats, + buffs, body.height() * scale.map_or(1.0, |s| s.0) + 0.5, hpfl, in_group, @@ -1760,22 +1785,48 @@ impl Hud { // Buffs and Debuffs if let Some(player_buffs) = buffs.get(client.entity()) { - match BuffsBar::new( - client, + for event in BuffsBar::new( &self.imgs, &self.fonts, - global_state, &self.rot_imgs, tooltip_manager, &self.voxygen_i18n, &player_buffs, + self.pulse, + &global_state, ) .set(self.ids.buffs, ui_widgets) { - _ => {}, + match event { + buffs::Event::RemoveBuff(buff_id) => events.push(Event::RemoveBuff(buff_id)), + } + } + } + // Group Window + let buffs = buffs.get(client.entity()).unwrap(); + for event in Group::new( + &mut self.show, + client, + &global_state.settings, + &self.imgs, + &self.rot_imgs, + &self.fonts, + &self.voxygen_i18n, + self.pulse, + &global_state, + &buffs, + tooltip_manager, + ) + .set(self.ids.group_window, ui_widgets) + { + match event { + group::Event::Accept => events.push(Event::AcceptInvite), + group::Event::Decline => events.push(Event::DeclineInvite), + group::Event::Kick(uid) => events.push(Event::KickMember(uid)), + group::Event::LeaveGroup => events.push(Event::LeaveGroup), + group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)), } } - // Popup (waypoint saved and similar notifications) Popup::new( &self.voxygen_i18n, @@ -1850,8 +1901,8 @@ impl Hud { Some(stats), Some(loadout), Some(energy), - Some(character_state), - Some(controller), + Some(_character_state), + Some(_controller), Some(inventory), ) = ( stats.get(entity), @@ -2018,6 +2069,9 @@ impl Hud { settings_window::Event::ToggleZoomInvert(zoom_inverted) => { events.push(Event::ToggleZoomInvert(zoom_inverted)); }, + settings_window::Event::BuffPosition(buff_position) => { + events.push(Event::BuffPosition(buff_position)); + }, settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => { events.push(Event::ToggleMouseYInvert(mouse_y_inverted)); }, @@ -2142,27 +2196,6 @@ impl Hud { } } } - // Group Window - for event in Group::new( - &mut self.show, - client, - &global_state.settings, - &self.imgs, - &self.fonts, - &self.voxygen_i18n, - self.pulse, - &global_state, - ) - .set(self.ids.group_window, ui_widgets) - { - match event { - group::Event::Accept => events.push(Event::AcceptInvite), - group::Event::Decline => events.push(Event::DeclineInvite), - group::Event::Kick(uid) => events.push(Event::KickMember(uid)), - group::Event::LeaveGroup => events.push(Event::LeaveGroup), - group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)), - } - } // Spellbook if self.show.spell { @@ -2694,3 +2727,17 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color { Quality::Debug => QUALITY_DEBUG, } } +// Get info about applied buffs +fn get_buff_info(buff: comp::Buff) -> BuffInfo { + BuffInfo { + id: buff.id, + is_buff: buff + .cat_ids + .iter() + .any(|cat| *cat == comp::BuffCategoryId::Buff), + dur: buff + .time + .map(|dur| dur.as_secs_f32()) + .unwrap_or(10e6 as f32), + } +} diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index e24e4eb853..8ee435ea1f 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -3,16 +3,19 @@ use super::{ REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR, }; use crate::{ + hud::{get_buff_info, BuffInfo}, i18n::VoxygenLocalization, settings::GameplaySettings, ui::{fonts::ConrodVoxygenFonts, Ingameable}, }; -use common::comp::{Energy, SpeechBubble, SpeechBubbleType, Stats}; +use common::comp::{BuffId, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats}; use conrod_core::{ + color, position::Align, widget::{self, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; +use inline_tweak::*; const MAX_BUBBLE_WIDTH: f64 = 250.0; widget_ids! { @@ -44,13 +47,24 @@ widget_ids! { health_txt, mana_bar, health_bar_fg, + + // Buffs + buffs_align, + buffs[], + buff_timers[], } } +/*pub struct BuffInfo { + id: comp::BuffId, + dur: f32, +}*/ + #[derive(Clone, Copy)] pub struct Info<'a> { pub name: &'a str, pub stats: &'a Stats, + pub buffs: &'a Buffs, pub energy: Option<&'a Energy>, } @@ -119,17 +133,21 @@ impl<'a> Ingameable for Overhead<'a> { // - 1 for HP text // - If there's mana // - 1 Rect::new for mana - // + // If there are Buffs + // - 1 Alignment Rectangle + // - 10 + 10 Buffs and Timer Overlays // If there's a speech bubble // - 2 Text::new for speech bubble // - 1 Image::new for icon // - 10 Image::new for speech bubble (9-slice + tail) self.info.map_or(0, |info| { - 2 + if show_healthbar(info.stats) { - 5 + if info.energy.is_some() { 1 } else { 0 } - } else { - 0 - } + 2 + 1 + + info.buffs.active_buffs.len().min(10) * 2 + + if show_healthbar(info.stats) { + 5 + if info.energy.is_some() { 1 } else { 0 } + } else { + 0 + } }) + if self.bubble.is_some() { 13 } else { 0 } } } @@ -155,6 +173,7 @@ impl<'a> Widget for Overhead<'a> { if let Some(Info { name, stats, + buffs, energy, }) = self.info { @@ -172,6 +191,11 @@ impl<'a> Widget for Overhead<'a> { } else { MANA_BAR_Y + 32.0 }; + let mut buffs_vec = Vec::<BuffInfo>::new(); + for buff in buffs.active_buffs.clone() { + let info = get_buff_info(buff); + buffs_vec.push(info); + } let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 }; // Show K for numbers above 10^3 and truncate them // Show M for numbers above 10^6 and truncate them @@ -185,6 +209,79 @@ impl<'a> Widget for Overhead<'a> { 1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)), _ => format!("{:.0}M", (health_max as f64 / 1.0e6).max(1.0)), }; + // Buffs + // Alignment + Rectangle::fill_with([tweak!(168.0), tweak!(100.0)], color::TRANSPARENT) + .x_y(-1.0, name_y + tweak!(60.0)) + .parent(id) + .set(state.ids.buffs_align, ui); + if state.ids.buffs.len() < buffs_vec.len() { + state.update(|state| { + state + .ids + .buffs + .resize(buffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + if state.ids.buff_timers.len() < buffs_vec.len() { + state.update(|state| { + state + .ids + .buff_timers + .resize(buffs_vec.len(), &mut ui.widget_id_generator()) + }); + }; + let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; //Animation timer + let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); + let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); + // Create Buff Widgets + for (i, buff) in buffs_vec.iter().enumerate() { + if i < 11 && self.bubble.is_none() { + // Limit displayed buffs + let max_duration = match buff.id { + BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), + _ => 10.0, + }; + let current_duration = buff.dur; + let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed + let buff_img = match buff.id { + BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + }; + let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); + // Sort buffs into rows of 5 slots + let x = i % 5; + let y = i / 5; + let buff_widget = buff_widget.bottom_left_with_margins_on( + state.ids.buffs_align, + 0.0 + y as f64 * (21.0), + 0.0 + x as f64 * (21.0), + ); + buff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(state.ids.buffs[i], ui); + + Image::new(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, //3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(20.0, 20.0) + .middle_of(state.ids.buffs[i]) + .set(state.ids.buff_timers[i], ui); + }; + } // Name Text::new(name) .font_id(self.fonts.cyri.conrod_id) diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 9bf18b9473..b2975c7d52 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -4,6 +4,7 @@ use super::{ TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ + hud::BuffPosition, i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode}, ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, @@ -159,6 +160,7 @@ widget_ids! { sfx_volume_text, audio_device_list, audio_device_text, + // hotbar_title, bar_numbers_title, show_bar_numbers_none_button, @@ -167,18 +169,20 @@ widget_ids! { show_bar_numbers_values_text, show_bar_numbers_percentage_button, show_bar_numbers_percentage_text, + // show_shortcuts_button, show_shortcuts_text, - show_xpbar_button, - show_xpbar_text, - show_bars_button, - show_bars_text, - placeholder, + buff_pos_bar_button, + buff_pos_bar_text, + buff_pos_map_button, + buff_pos_map_text, + // chat_transp_title, chat_transp_text, chat_transp_slider, chat_char_name_text, chat_char_name_button, + // sct_title, sct_show_text, sct_show_radio, @@ -195,6 +199,7 @@ widget_ids! { sct_num_dur_text, sct_num_dur_slider, sct_num_dur_value, + // speech_bubble_text, speech_bubble_dark_mode_text, speech_bubble_dark_mode_button, @@ -261,6 +266,7 @@ pub enum Event { ToggleTips(bool), ToggleBarNumbers(BarNumbers), ToggleShortcutNumbers(ShortcutNumbers), + BuffPosition(BuffPosition), ChangeTab(SettingsTab), Close, AdjustMousePan(u32), @@ -829,11 +835,61 @@ impl<'a> Widget for SettingsWindow<'a> { .graphics_for(state.ids.show_shortcuts_button) .color(TEXT_COLOR) .set(state.ids.show_shortcuts_text, ui); - - Rectangle::fill_with([60.0 * 4.0, 1.0 * 4.0], color::TRANSPARENT) - .down_from(state.ids.show_shortcuts_text, 30.0) - .set(state.ids.placeholder, ui); - + // Buff Position + // Buffs above skills + if Button::image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Bar => self.imgs.checkbox_checked, + BuffPosition::Map => self.imgs.checkbox, + }) + .w_h(18.0, 18.0) + .hover_image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Bar => self.imgs.checkbox_checked_mo, + BuffPosition::Map => self.imgs.checkbox_mo, + }) + .press_image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Bar => self.imgs.checkbox_checked, + BuffPosition::Map => self.imgs.checkbox_press, + }) + .down_from(state.ids.show_shortcuts_button, 8.0) + .set(state.ids.buff_pos_bar_button, ui) + .was_clicked() + { + events.push(Event::BuffPosition(BuffPosition::Bar)) + } + Text::new(&self.localized_strings.get("hud.settings.buffs_skillbar")) + .right_from(state.ids.buff_pos_bar_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_shortcuts_button) + .color(TEXT_COLOR) + .set(state.ids.buff_pos_bar_text, ui); + // Buffs left from minimap + if Button::image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Map => self.imgs.checkbox_checked, + BuffPosition::Bar => self.imgs.checkbox, + }) + .w_h(18.0, 18.0) + .hover_image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Map => self.imgs.checkbox_checked_mo, + BuffPosition::Bar => self.imgs.checkbox_mo, + }) + .press_image(match self.global_state.settings.gameplay.buff_position { + BuffPosition::Map => self.imgs.checkbox_checked, + BuffPosition::Bar => self.imgs.checkbox_press, + }) + .down_from(state.ids.buff_pos_bar_button, 8.0) + .set(state.ids.buff_pos_map_button, ui) + .was_clicked() + { + events.push(Event::BuffPosition(BuffPosition::Map)) + } + Text::new(&self.localized_strings.get("hud.settings.buffs_mmap")) + .right_from(state.ids.buff_pos_map_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.show_shortcuts_button) + .color(TEXT_COLOR) + .set(state.ids.buff_pos_map_text, ui); // Content Right Side /*Scrolling Combat text diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 9d752aeed3..d0b3d3a8c9 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -894,6 +894,10 @@ impl PlayState for SessionState { global_state.settings.gameplay.shortcut_numbers = shortcut_numbers; global_state.settings.save_to_file_warn(); }, + HudEvent::BuffPosition(buff_position) => { + global_state.settings.gameplay.buff_position = buff_position; + global_state.settings.save_to_file_warn(); + }, HudEvent::UiScale(scale_change) => { global_state.settings.gameplay.ui_scale = self.hud.scale_change(scale_change); @@ -921,6 +925,10 @@ impl PlayState for SessionState { global_state.settings.graphics.max_fps = fps; global_state.settings.save_to_file_warn(); }, + HudEvent::RemoveBuff(buff_id) => { + let mut client = self.client.borrow_mut(); + client.remove_buff(buff_id); + }, HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x), HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b), HudEvent::DropSlot(x) => { diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index be4eed60db..8b4396a094 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -1,5 +1,5 @@ use crate::{ - hud::{BarNumbers, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar}, + hud::{BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar}, i18n, render::RenderMode, ui::ScaleMode, @@ -507,6 +507,7 @@ pub struct GameplaySettings { pub intro_show: Intro, pub xp_bar: XpBar, pub shortcut_numbers: ShortcutNumbers, + pub buff_position: BuffPosition, pub bar_numbers: BarNumbers, pub ui_scale: ScaleMode, pub free_look_behavior: PressBehavior, @@ -537,6 +538,7 @@ impl Default for GameplaySettings { intro_show: Intro::Show, xp_bar: XpBar::Always, shortcut_numbers: ShortcutNumbers::On, + buff_position: BuffPosition::Bar, bar_numbers: BarNumbers::Values, ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()), free_look_behavior: PressBehavior::Toggle, From 2ce14e9e8a4ce5d3cf5180a0836ff48028dca29d Mon Sep 17 00:00:00 2001 From: Imbris <imbrisf@gmail.com> Date: Sat, 17 Oct 2020 18:41:59 -0400 Subject: [PATCH 15/25] Fix group tooltips, make ui buff code more efficient, avoid crashing on characters button press --- voxygen/src/hud/buffs.rs | 119 ++++++++++++++++--------------- voxygen/src/hud/group.rs | 70 +++++++++--------- voxygen/src/hud/mod.rs | 4 +- voxygen/src/hud/overhead.rs | 137 ++++++++++++++++++------------------ 4 files changed, 170 insertions(+), 160 deletions(-) diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 15a6552399..65688a4252 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -9,7 +9,6 @@ use crate::{ GlobalState, }; -use crate::hud::BuffInfo; use common::comp::{BuffId, Buffs}; use conrod_core::{ color, @@ -131,55 +130,51 @@ impl<'a> Widget for BuffsBar<'a> { .set(state.ids.buffs_align, ui); // Buffs and Debuffs - // Create two vecs to display buffs and debuffs separately - let mut buffs_vec = Vec::<BuffInfo>::new(); - let mut debuffs_vec = Vec::<BuffInfo>::new(); - for buff in buffs.active_buffs.clone() { - let info = get_buff_info(buff); - if info.is_buff { - buffs_vec.push(info); - } else { - debuffs_vec.push(info); - } - } - if state.ids.buffs.len() < buffs_vec.len() { - state.update(|state| { - state - .ids - .buffs - .resize(buffs_vec.len(), &mut ui.widget_id_generator()) - }); + let (buff_count, debuff_count) = buffs.active_buffs.iter().map(get_buff_info).fold( + (0, 0), + |(buff_count, debuff_count), info| { + if info.is_buff { + (buff_count + 1, debuff_count) + } else { + (buff_count, debuff_count + 1) + } + }, + ); + // Limit displayed buffs + let buff_count = buff_count.min(22); + let debuff_count = debuff_count.min(22); + + let gen = &mut ui.widget_id_generator(); + if state.ids.buffs.len() < buff_count { + state.update(|state| state.ids.buffs.resize(buff_count, gen)); }; - if state.ids.debuffs.len() < debuffs_vec.len() { - state.update(|state| { - state - .ids - .debuffs - .resize(debuffs_vec.len(), &mut ui.widget_id_generator()) - }); + if state.ids.debuffs.len() < debuff_count { + state.update(|state| state.ids.debuffs.resize(debuff_count, gen)); }; - if state.ids.buff_timers.len() < buffs_vec.len() { - state.update(|state| { - state - .ids - .buff_timers - .resize(buffs_vec.len(), &mut ui.widget_id_generator()) - }); + if state.ids.buff_timers.len() < buff_count { + state.update(|state| state.ids.buff_timers.resize(buff_count, gen)); }; - if state.ids.debuff_timers.len() < debuffs_vec.len() { - state.update(|state| { - state - .ids - .debuff_timers - .resize(debuffs_vec.len(), &mut ui.widget_id_generator()) - }); + if state.ids.debuff_timers.len() < debuff_count { + state.update(|state| state.ids.debuff_timers.resize(debuff_count, gen)); }; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); // Create Buff Widgets - for (i, buff) in buffs_vec.iter().enumerate() { - if i < 22 { - // Limit displayed buffs + state + .ids + .buffs + .iter() + .copied() + .zip(state.ids.buff_timers.iter().copied()) + .zip( + buffs + .active_buffs + .iter() + .map(get_buff_info) + .filter(|info| info.is_buff), + ) + .enumerate() + .for_each(|(i, ((id, timer_id), buff))| { let max_duration = match buff.id { BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), _ => 10.0, @@ -205,7 +200,7 @@ impl<'a> Widget for BuffsBar<'a> { } else { Some(norm_col) }) - .set(state.ids.buffs[i], ui); + .set(id, ui); // Create Buff tooltip let title = match buff.id { BuffId::Regeneration { .. } => { @@ -239,7 +234,7 @@ impl<'a> Widget for BuffsBar<'a> { _ => self.imgs.nothing, }) .w_h(20.0, 20.0) - .middle_of(state.ids.buffs[i]) + .middle_of(id) .with_tooltip( self.tooltip_manager, title, @@ -247,18 +242,28 @@ impl<'a> Widget for BuffsBar<'a> { &buffs_tooltip, BUFF_COLOR, ) - .set(state.ids.buff_timers[i], ui) + .set(timer_id, ui) .was_clicked() { event.push(Event::RemoveBuff(buff.id)); }; - }; - } + }); // Create Debuff Widgets - for (i, debuff) in debuffs_vec.iter().enumerate() { - if i < 22 { - // Limit displayed buffs - + state + .ids + .debuffs + .iter() + .copied() + .zip(state.ids.debuff_timers.iter().copied()) + .zip( + buffs + .active_buffs + .iter() + .map(get_buff_info) + .filter(|info| !info.is_buff), + ) + .enumerate() + .for_each(|(i, ((id, timer_id), debuff))| { let max_duration = match debuff.id { BuffId::Bleeding { duration, .. } => { duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() @@ -292,7 +297,7 @@ impl<'a> Widget for BuffsBar<'a> { } else { Some(norm_col) }) - .set(state.ids.debuffs[i], ui); + .set(id, ui); // Create Debuff tooltip let title = match debuff.id { BuffId::Bleeding { .. } => { @@ -324,7 +329,7 @@ impl<'a> Widget for BuffsBar<'a> { _ => self.imgs.nothing, }) .w_h(20.0, 20.0) - .middle_of(state.ids.debuffs[i]) + .middle_of(id) .with_tooltip( self.tooltip_manager, title, @@ -332,10 +337,10 @@ impl<'a> Widget for BuffsBar<'a> { &buffs_tooltip, DEBUFF_COLOR, ) - .set(state.ids.debuff_timers[i], ui); - }; - } + .set(timer_id, ui); + }); } + if let BuffPosition::Map = buff_position { // Alignment Rectangle::fill_with([tweak!(300.0), tweak!(280.0)], color::RED) diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 06503bc9d8..4c1385593c 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ - hud::{get_buff_info, BuffInfo}, + hud::get_buff_info, i18n::VoxygenLocalization, settings::Settings, ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, @@ -75,7 +75,6 @@ pub struct Group<'a> { localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, global_state: &'a GlobalState, - buffs: &'a Buffs, tooltip_manager: &'a mut TooltipManager, #[conrod(common_builder)] @@ -94,7 +93,6 @@ impl<'a> Group<'a> { localized_strings: &'a std::sync::Arc<VoxygenLocalization>, pulse: f32, global_state: &'a GlobalState, - buffs: &'a Buffs, tooltip_manager: &'a mut TooltipManager, ) -> Self { Self { @@ -107,7 +105,6 @@ impl<'a> Group<'a> { localized_strings, pulse, global_state, - buffs, tooltip_manager, common: widget::CommonBuilder::default(), } @@ -145,7 +142,6 @@ impl<'a> Widget for Group<'a> { let widget::UpdateArgs { state, ui, .. } = args; let mut events = Vec::new(); let localized_strings = self.localized_strings; - //let buffs = self.buffs; let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer let buffs_tooltip = Tooltip::new({ // Edge images [t, b, r, l] @@ -333,6 +329,8 @@ impl<'a> Widget for Group<'a> { .ecs() .read_resource::<common::sync::UidAllocator>(); + // Keep track of the total number of widget ids we are using for buffs + let mut total_buff_count = 0; for (i, &uid) in group_members.iter().copied().enumerate() { self.show.group = true; let entity = uid_allocator.retrieve_entity_internal(uid.into()); @@ -447,27 +445,29 @@ impl<'a> Widget for Group<'a> { .set(state.ids.member_stam[i], ui); } if let Some(buffs) = buffs { - let mut buffs_vec = Vec::<BuffInfo>::new(); - for buff in buffs.active_buffs.clone() { - let info = get_buff_info(buff); - buffs_vec.push(info); + // Limit displayed buffs to 11 + let buff_count = buffs.active_buffs.len().min(11); + total_buff_count += buff_count; + let gen = &mut ui.widget_id_generator(); + if state.ids.buffs.len() < total_buff_count { + state.update(|state| state.ids.buffs.resize(total_buff_count, gen)); + } + if state.ids.buff_timers.len() < total_buff_count { + state.update(|state| { + state.ids.buff_timers.resize(total_buff_count, gen) + }); } - state.update(|state| { - state.ids.buffs.resize( - state.ids.buffs.len() + buffs_vec.len(), - &mut ui.widget_id_generator(), - ) - }); - state.update(|state| { - state.ids.buff_timers.resize( - state.ids.buff_timers.len() + buffs_vec.len(), - &mut ui.widget_id_generator(), - ) - }); // Create Buff Widgets - for (x, buff) in buffs_vec.iter().enumerate() { - if x < 11 { - // Limit displayed buffs + let mut prev_id = None; + state + .ids + .buffs + .iter() + .copied() + .zip(state.ids.buff_timers.iter().copied()) + .skip(total_buff_count - buff_count) + .zip(buffs.active_buffs.iter().map(get_buff_info)) + .for_each(|((id, timer_id), buff)| { let max_duration = match buff.id { BuffId::Regeneration { duration, .. } => { duration.unwrap().as_secs_f32() @@ -485,22 +485,23 @@ impl<'a> Widget for Group<'a> { BuffId::Cursed { .. } => self.imgs.debuff_skull_0, }; let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); - let buff_widget = if x == 0 { + let buff_widget = if let Some(id) = prev_id { + buff_widget.right_from(id, 1.0) + } else { buff_widget.bottom_left_with_margins_on( state.ids.member_panels_frame[i], -21.0, 1.0, ) - } else { - buff_widget.right_from(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x - 1/*x - 1*/], 1.0) }; + prev_id = Some(id); buff_widget .color(if current_duration < 10.0 { Some(pulsating_col) } else { Some(norm_col) }) - .set(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui); + .set(id, ui); // Create Buff tooltip let title = match buff.id { BuffId::Regeneration { .. } => { @@ -538,17 +539,20 @@ impl<'a> Widget for Group<'a> { _ => self.imgs.nothing, }) .w_h(20.0, 20.0) - .middle_of(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/]) + .middle_of(id) .with_tooltip( self.tooltip_manager, title, &desc, &buffs_tooltip, - if buff.is_buff {BUFF_COLOR} else {DEBUFF_COLOR}, + if buff.is_buff { + BUFF_COLOR + } else { + DEBUFF_COLOR + }, ) - .set(state.ids.buff_timers[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui); - }; - } + .set(timer_id, ui); + }); } else { // Values N.A. Text::new(&stats.name.to_string()) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 1576fb3ff1..ec48a034b3 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1803,7 +1803,6 @@ impl Hud { } } // Group Window - let buffs = buffs.get(client.entity()).unwrap(); for event in Group::new( &mut self.show, client, @@ -1814,7 +1813,6 @@ impl Hud { &self.voxygen_i18n, self.pulse, &global_state, - &buffs, tooltip_manager, ) .set(self.ids.group_window, ui_widgets) @@ -2728,7 +2726,7 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color { } } // Get info about applied buffs -fn get_buff_info(buff: comp::Buff) -> BuffInfo { +fn get_buff_info(buff: &comp::Buff) -> BuffInfo { BuffInfo { id: buff.id, is_buff: buff diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 8ee435ea1f..da8861dcc8 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -3,7 +3,7 @@ use super::{ REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR, }; use crate::{ - hud::{get_buff_info, BuffInfo}, + hud::get_buff_info, i18n::VoxygenLocalization, settings::GameplaySettings, ui::{fonts::ConrodVoxygenFonts, Ingameable}, @@ -135,14 +135,18 @@ impl<'a> Ingameable for Overhead<'a> { // - 1 Rect::new for mana // If there are Buffs // - 1 Alignment Rectangle - // - 10 + 10 Buffs and Timer Overlays + // - 10 + 10 Buffs and Timer Overlays (only if there is no speech bubble) // If there's a speech bubble // - 2 Text::new for speech bubble // - 1 Image::new for icon // - 10 Image::new for speech bubble (9-slice + tail) self.info.map_or(0, |info| { 2 + 1 - + info.buffs.active_buffs.len().min(10) * 2 + + if self.bubble.is_none() { + info.buffs.active_buffs.len().min(10) * 2 + } else { + 0 + } + if show_healthbar(info.stats) { 5 + if info.energy.is_some() { 1 } else { 0 } } else { @@ -191,11 +195,6 @@ impl<'a> Widget for Overhead<'a> { } else { MANA_BAR_Y + 32.0 }; - let mut buffs_vec = Vec::<BuffInfo>::new(); - for buff in buffs.active_buffs.clone() { - let info = get_buff_info(buff); - buffs_vec.push(info); - } let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 }; // Show K for numbers above 10^3 and truncate them // Show M for numbers above 10^6 and truncate them @@ -211,76 +210,80 @@ impl<'a> Widget for Overhead<'a> { }; // Buffs // Alignment + let buff_count = buffs.active_buffs.len().min(11); Rectangle::fill_with([tweak!(168.0), tweak!(100.0)], color::TRANSPARENT) .x_y(-1.0, name_y + tweak!(60.0)) .parent(id) .set(state.ids.buffs_align, ui); - if state.ids.buffs.len() < buffs_vec.len() { - state.update(|state| { - state - .ids - .buffs - .resize(buffs_vec.len(), &mut ui.widget_id_generator()) - }); + + let gen = &mut ui.widget_id_generator(); + if state.ids.buffs.len() < buff_count { + state.update(|state| state.ids.buffs.resize(buff_count, gen)); }; - if state.ids.buff_timers.len() < buffs_vec.len() { - state.update(|state| { - state - .ids - .buff_timers - .resize(buffs_vec.len(), &mut ui.widget_id_generator()) - }); + if state.ids.buff_timers.len() < buff_count { + state.update(|state| state.ids.buff_timers.resize(buff_count, gen)); }; + let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; //Animation timer let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); // Create Buff Widgets - for (i, buff) in buffs_vec.iter().enumerate() { - if i < 11 && self.bubble.is_none() { - // Limit displayed buffs - let max_duration = match buff.id { - BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), - _ => 10.0, - }; - let current_duration = buff.dur; - let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.id { - BuffId::Regeneration { .. } => self.imgs.buff_plus_0, - BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffId::Cursed { .. } => self.imgs.debuff_skull_0, - }; - let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); - // Sort buffs into rows of 5 slots - let x = i % 5; - let y = i / 5; - let buff_widget = buff_widget.bottom_left_with_margins_on( - state.ids.buffs_align, - 0.0 + y as f64 * (21.0), - 0.0 + x as f64 * (21.0), - ); - buff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) - .set(state.ids.buffs[i], ui); + if self.bubble.is_none() { + state + .ids + .buffs + .iter() + .copied() + .zip(state.ids.buff_timers.iter().copied()) + .zip(buffs.active_buffs.iter().map(get_buff_info)) + .enumerate() + .for_each(|(i, ((id, timer_id), buff))| { + // Limit displayed buffs + let max_duration = match buff.id { + BuffId::Regeneration { duration, .. } => { + duration.unwrap().as_secs_f32() + }, + _ => 10.0, + }; + let current_duration = buff.dur; + let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed + let buff_img = match buff.id { + BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + }; + let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); + // Sort buffs into rows of 5 slots + let x = i % 5; + let y = i / 5; + let buff_widget = buff_widget.bottom_left_with_margins_on( + state.ids.buffs_align, + 0.0 + y as f64 * (21.0), + 0.0 + x as f64 * (21.0), + ); + buff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(id, ui); - Image::new(match duration_percentage as u64 { - 875..=1000 => self.imgs.nothing, // 8/8 - 750..=874 => self.imgs.buff_0, // 7/8 - 625..=749 => self.imgs.buff_1, // 6/8 - 500..=624 => self.imgs.buff_2, // 5/8 - 375..=499 => self.imgs.buff_3, // 4/8 - 250..=374 => self.imgs.buff_4, //3/8 - 125..=249 => self.imgs.buff_5, // 2/8 - 0..=124 => self.imgs.buff_6, // 1/8 - _ => self.imgs.nothing, - }) - .w_h(20.0, 20.0) - .middle_of(state.ids.buffs[i]) - .set(state.ids.buff_timers[i], ui); - }; + Image::new(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, //3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(20.0, 20.0) + .middle_of(id) + .set(timer_id, ui); + }); } // Name Text::new(name) From d258fc5bda7b7beeeb2d5154abf513d9fa701e3c Mon Sep 17 00:00:00 2001 From: Monty Marz <m.marzouq@gmx.de> Date: Sun, 18 Oct 2020 04:09:33 +0200 Subject: [PATCH 16/25] player buffs at minimap visuals Update settings.rs buff timers --- assets/voxygen/i18n/en.ron | 2 +- voxygen/src/hud/buffs.rs | 141 +++++++++++++++++++++++++++++++++++-- voxygen/src/hud/group.rs | 28 +++----- voxygen/src/settings.rs | 2 +- 4 files changed, 148 insertions(+), 25 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index c85ffe8d27..5239007e82 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -183,7 +183,7 @@ https://account.veloren.net."#, "hud.chat.pvp_melee_kill_msg": "[{attacker}] defeated [{victim}]", "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}] used magic to kill [{victim}]", + "hud.chat.pvp_energy_kill_msg": "[{attacker}] killed [{victim}] with magic", "hud.chat.pvp_buff_kill_msg": "[{attacker}] killed [{victim}]", "hud.chat.npc_melee_kill_msg": "{attacker} killed [{victim}]", diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 65688a4252..3ee3540193 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -12,8 +12,8 @@ use crate::{ use common::comp::{BuffId, Buffs}; use conrod_core::{ color, - widget::{self, Button, Image, Rectangle}, - widget_ids, Color, Positionable, Sizeable, Widget, WidgetCommon, + widget::{self, Button, Text, Image, Rectangle}, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use inline_tweak::*; use std::time::Duration; @@ -28,6 +28,7 @@ widget_ids! { buff_timers[], debuffs[], debuff_timers[], + buff_txts[], } } @@ -99,6 +100,8 @@ impl<'a> Widget for BuffsBar<'a> { let localized_strings = self.localized_strings; let buffs = self.buffs; let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer + let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); + let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); let buff_position = self.global_state.settings.gameplay.buff_position; let buffs_tooltip = Tooltip::new({ // Edge images [t, b, r, l] @@ -157,8 +160,7 @@ impl<'a> Widget for BuffsBar<'a> { if state.ids.debuff_timers.len() < debuff_count { state.update(|state| state.ids.debuff_timers.resize(debuff_count, gen)); }; - let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); - let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); + // Create Buff Widgets state .ids @@ -343,9 +345,136 @@ impl<'a> Widget for BuffsBar<'a> { if let BuffPosition::Map = buff_position { // Alignment - Rectangle::fill_with([tweak!(300.0), tweak!(280.0)], color::RED) - .top_right_with_margins_on(ui.window, tweak!(5.0), tweak!(270.0)) + Rectangle::fill_with([210.0, 210.0], color::TRANSPARENT) + .top_right_with_margins_on(ui.window, 5.0, tweak!(270.0)) .set(state.ids.align, ui); + + // Buffs and Debuffs + let buff_count = buffs.active_buffs.len().min(11); + // Limit displayed buffs + let buff_count = buff_count.min(20); + + let gen = &mut ui.widget_id_generator(); + if state.ids.buffs.len() < buff_count { + state.update(|state| state.ids.buffs.resize(buff_count, gen)); + }; + if state.ids.buff_timers.len() < buff_count { + state.update(|state| state.ids.buff_timers.resize(buff_count, gen)); + }; + if state.ids.buff_txts.len() < buff_count { + state.update(|state| state.ids.buff_txts.resize(buff_count, gen)); + }; + + // Create Buff Widgets + state + .ids + .buffs + .iter() + .copied() + .zip(state.ids.buff_timers.iter().copied()) + .zip(state.ids.buff_txts.iter().copied()) + .zip(buffs.active_buffs.iter().map(get_buff_info)) + .enumerate() + .for_each(|(i, (((id, timer_id),txt_id), buff))| { + let max_duration = match buff.id { + BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), + BuffId::Bleeding { duration, .. } => duration.unwrap().as_secs_f32(), + _ => 10e6, + }; + let current_duration = buff.dur; + // Percentage to determine which frame of the timer overlay is displayed + let duration_percentage = (current_duration / max_duration * 1000.0) as u32; + let buff_img = match buff.id { + BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + }; + let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); + // Sort buffs into rows of 6 slots + let x = i % 6; + let y = i / 6; + let buff_widget = buff_widget.top_right_with_margins_on( + state.ids.align, + 0.0 + y as f64 * (54.0), + 0.0 + x as f64 * (42.0), + ); + buff_widget + .color(if current_duration < 10.0 { + Some(pulsating_col) + } else { + Some(norm_col) + }) + .set(id, ui); + // Create Buff tooltip + let title = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.title.heal_test") + }, + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.title.bleed_test") + }, + _ => *&localized_strings.get("buff.title.missing"), + }; + let remaining_time = if current_duration == 10e6 as f32 { + "".to_string() + } else { + format!("{:.0}s", current_duration) + }; + let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); + let desc_txt = match buff.id { + BuffId::Regeneration { .. } => { + *&localized_strings.get("buff.desc.heal_test") + }, + BuffId::Bleeding { .. } => { + *&localized_strings.get("debuff.desc.bleed_test") + }, + _ => *&localized_strings.get("buff.desc.missing"), + }; + let desc = if buff.is_buff { + format!("{}\n\n{}", desc_txt, click_to_remove) + } else { + format!("{}", desc_txt) + }; + // Timer overlay + if Button::image(match duration_percentage as u64 { + 875..=1000 => self.imgs.nothing, // 8/8 + 750..=874 => self.imgs.buff_0, // 7/8 + 625..=749 => self.imgs.buff_1, // 6/8 + 500..=624 => self.imgs.buff_2, // 5/8 + 375..=499 => self.imgs.buff_3, // 4/8 + 250..=374 => self.imgs.buff_4, //3/8 + 125..=249 => self.imgs.buff_5, // 2/8 + 0..=124 => self.imgs.buff_6, // 1/8 + _ => self.imgs.nothing, + }) + .w_h(40.0, 40.0) + .middle_of(id) + .with_tooltip( + self.tooltip_manager, + title, + &desc, + &buffs_tooltip, + if buff.is_buff { + BUFF_COLOR + } else { + DEBUFF_COLOR + }, + ) + .set(timer_id, ui) + .was_clicked() + { + if buff.is_buff { + event.push(Event::RemoveBuff(buff.id)); + } + } + Text::new(&remaining_time) + .down_from(timer_id, tweak!(1.0)) + .font_size(self.fonts.cyri.scale(tweak!(10))) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(timer_id) + .color(TEXT_COLOR) + .set(txt_id, ui); + }); } event } diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 4c1385593c..7cf1cee901 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -14,7 +14,7 @@ use crate::{ }; use client::{self, Client}; use common::{ - comp::{group::Role, BuffId, Buffs, Stats}, + comp::{group::Role, BuffId, Stats}, sync::{Uid, WorldSyncExt}, }; use conrod_core::{ @@ -328,7 +328,11 @@ impl<'a> Widget for Group<'a> { let uid_allocator = client_state .ecs() .read_resource::<common::sync::UidAllocator>(); - + let offset = if self.global_state.settings.gameplay.toggle_debug { + tweak!(320.0) + } else { + 110.0 + }; // Keep track of the total number of widget ids we are using for buffs let mut total_buff_count = 0; for (i, &uid) in group_members.iter().copied().enumerate() { @@ -342,12 +346,7 @@ impl<'a> Widget for Group<'a> { 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 offset = if self.global_state.settings.gameplay.toggle_debug { - 290.0 - } else { - 110.0 - }; + // 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) @@ -484,13 +483,13 @@ impl<'a> Widget for Group<'a> { BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, BuffId::Cursed { .. } => self.imgs.debuff_skull_0, }; - let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); + let buff_widget = Image::new(buff_img).w_h(15.0, 15.0); let buff_widget = if let Some(id) = prev_id { buff_widget.right_from(id, 1.0) } else { buff_widget.bottom_left_with_margins_on( state.ids.member_panels_frame[i], - -21.0, + -16.0, 1.0, ) }; @@ -538,7 +537,7 @@ impl<'a> Widget for Group<'a> { 0..=124 => self.imgs.buff_6, // 1/8 _ => self.imgs.nothing, }) - .w_h(20.0, 20.0) + .w_h(15.0, 15.0) .middle_of(id) .with_tooltip( self.tooltip_manager, @@ -560,12 +559,7 @@ impl<'a> Widget for Group<'a> { .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(GROUP_COLOR) - .set(state.ids.member_panels_txt[i], ui); - let offset = if self.global_state.settings.gameplay.toggle_debug { - 210.0 - } else { - 110.0 - }; + .set(state.ids.member_panels_txt[i], ui); let back = if i == 0 { Image::new(self.imgs.member_bg) .top_left_with_margins_on(ui.window, offset, 20.0) diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 8b4396a094..1f65e800de 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -538,7 +538,7 @@ impl Default for GameplaySettings { intro_show: Intro::Show, xp_bar: XpBar::Always, shortcut_numbers: ShortcutNumbers::On, - buff_position: BuffPosition::Bar, + buff_position: BuffPosition::Map, bar_numbers: BarNumbers::Values, ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()), free_look_behavior: PressBehavior::Toggle, From 6619ac8c0f0169b9ede135d653c90bd42dccd4f9 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sun, 18 Oct 2020 17:39:13 -0500 Subject: [PATCH 17/25] Cleaned up logic used to handle buff addition. Old active buffs now get deleted if they had a smaller duration and weaker strength. --- server/src/events/entity_manipulation.rs | 37 +++++++++++++++++++----- voxygen/src/hud/buffs.rs | 24 +++++++-------- voxygen/src/hud/group.rs | 12 ++++---- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 8d7e1b106e..3fccd6d37a 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -717,7 +717,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } else { let mut duplicate_existed = false; for i in 0..buffs.active_buffs.len() { - let active_buff = &buffs.active_buffs[i]; + let active_buff = &buffs.active_buffs[i].clone(); // Checks if new buff has the same id as an already active buff. If it // doesn't, new buff added to active buffs. If it does, compares the new // buff and the active buff, and decides to either add new buff to @@ -726,6 +726,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) // buffs. if discriminant(&active_buff.id) == discriminant(&new_buff.id) { duplicate_existed = true; + // Determines if active buff is weaker than newer buff if determine_replace_active_buff( active_buff.clone(), new_buff.clone(), @@ -733,6 +734,15 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) active_buff_indices_for_removal.push(i); add_buff_effects(new_buff.clone(), stats.get_mut(entity)); buffs.active_buffs.push(new_buff.clone()); + // Sees if weaker active has longer duration than new buff + #[allow(clippy::blocks_in_if_conditions)] + if active_buff.time.map_or(true, |act_dur| { + new_buff.time.map_or(false, |new_dur| act_dur > new_dur) + }) { + buffs.inactive_buffs.push(active_buff.clone()); + } + // Sees if weaker new buff has longer duration + // than active buff } else if let Some(active_dur) = active_buff.time { if let Some(new_dur) = new_buff.time { if new_dur > active_dur { @@ -742,11 +752,12 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) buffs.inactive_buffs.push(new_buff.clone()); } } + break; } } if !duplicate_existed { add_buff_effects(new_buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(new_buff.clone()); + buffs.active_buffs.push(new_buff); } } }, @@ -873,33 +884,45 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) match new_buff.id { BuffId::Bleeding { strength: new_strength, - duration: _, + duration: new_duration, } => { if let BuffId::Bleeding { strength: active_strength, duration: _, } = active_buff.id { - new_strength >= active_strength + new_strength > active_strength + || (new_strength >= active_strength + && new_duration.map_or(true, |new_dur| { + active_buff.time.map_or(false, |act_dur| new_dur > act_dur) + })) } else { false } }, BuffId::Regeneration { strength: new_strength, - duration: _, + duration: new_duration, } => { if let BuffId::Regeneration { strength: active_strength, duration: _, } = active_buff.id { - new_strength >= active_strength + new_strength > active_strength + || (new_strength >= active_strength + && new_duration.map_or(true, |new_dur| { + active_buff.time.map_or(false, |act_dur| new_dur > act_dur) + })) } else { false } }, - BuffId::Cursed { duration: _ } => false, + BuffId::Cursed { + duration: new_duration, + } => new_duration.map_or(true, |new_dur| { + active_buff.time.map_or(false, |act_dur| new_dur > act_dur) + }), } } diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 3ee3540193..04d30f6809 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -12,7 +12,7 @@ use crate::{ use common::comp::{BuffId, Buffs}; use conrod_core::{ color, - widget::{self, Button, Text, Image, Rectangle}, + widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use inline_tweak::*; @@ -375,15 +375,15 @@ impl<'a> Widget for BuffsBar<'a> { .zip(state.ids.buff_txts.iter().copied()) .zip(buffs.active_buffs.iter().map(get_buff_info)) .enumerate() - .for_each(|(i, (((id, timer_id),txt_id), buff))| { + .for_each(|(i, (((id, timer_id), txt_id), buff))| { let max_duration = match buff.id { BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), BuffId::Bleeding { duration, .. } => duration.unwrap().as_secs_f32(), - _ => 10e6, + _ => 10e6, }; let current_duration = buff.dur; // Percentage to determine which frame of the timer overlay is displayed - let duration_percentage = (current_duration / max_duration * 1000.0) as u32; + let duration_percentage = (current_duration / max_duration * 1000.0) as u32; let buff_img = match buff.id { BuffId::Regeneration { .. } => self.imgs.buff_plus_0, BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, @@ -467,14 +467,14 @@ impl<'a> Widget for BuffsBar<'a> { event.push(Event::RemoveBuff(buff.id)); } } - Text::new(&remaining_time) - .down_from(timer_id, tweak!(1.0)) - .font_size(self.fonts.cyri.scale(tweak!(10))) - .font_id(self.fonts.cyri.conrod_id) - .graphics_for(timer_id) - .color(TEXT_COLOR) - .set(txt_id, ui); - }); + Text::new(&remaining_time) + .down_from(timer_id, tweak!(1.0)) + .font_size(self.fonts.cyri.scale(tweak!(10))) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(timer_id) + .color(TEXT_COLOR) + .set(txt_id, ui); + }); } event } diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 7cf1cee901..00b1fc7f72 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -329,10 +329,10 @@ impl<'a> Widget for Group<'a> { .ecs() .read_resource::<common::sync::UidAllocator>(); let offset = if self.global_state.settings.gameplay.toggle_debug { - tweak!(320.0) - } else { - 110.0 - }; + tweak!(320.0) + } else { + 110.0 + }; // Keep track of the total number of widget ids we are using for buffs let mut total_buff_count = 0; for (i, &uid) in group_members.iter().copied().enumerate() { @@ -346,7 +346,7 @@ impl<'a> Widget for Group<'a> { 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 + // 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) @@ -559,7 +559,7 @@ impl<'a> Widget for Group<'a> { .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(GROUP_COLOR) - .set(state.ids.member_panels_txt[i], ui); + .set(state.ids.member_panels_txt[i], ui); let back = if i == 0 { Image::new(self.imgs.member_bg) .top_left_with_margins_on(ui.window, offset, 20.0) From c013ae8b5cd84c16e10c38f07f2aa4fc62689f96 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sun, 18 Oct 2020 20:52:07 -0500 Subject: [PATCH 18/25] Handled health change over time buff effect better. --- common/src/sys/buff.rs | 8 ++++++-- common/src/sys/combat.rs | 2 +- voxygen/src/ecs/sys/floater.rs | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index a959ef3729..96b4feede3 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -61,8 +61,12 @@ impl<'a> System<'a> for Sys { // Only add an effect here if it is continuous or it is not immediate BuffEffect::HealthChangeOverTime { rate, accumulated } => { *accumulated += *rate * buff_delta; - // Apply only 0.5 or higher damage - if accumulated.abs() > 50.0 { + // Apply damage only once a second (with a minimum of 1 damage), or when a buff is removed + if accumulated.abs() > rate.abs().max(10.0) + || active_buff_indices_for_removal + .iter() + .any(|index| *index == i) + { let cause = if *accumulated > 0.0 { HealthSource::Healing { by: buff_owner } } else { diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 7eb6cd53a7..13b8901013 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -167,7 +167,7 @@ impl<'a> System<'a> for Sys { uid: *uid_b, buff_change: buff::BuffChange::Add(buff::Buff::new( buff::BuffId::Regeneration { - strength: 100.0, + strength: 1.0, duration: Some(Duration::from_secs(60)), }, vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Buff], diff --git a/voxygen/src/ecs/sys/floater.rs b/voxygen/src/ecs/sys/floater.rs index 6964010d9f..ad9c8c1de7 100644 --- a/voxygen/src/ecs/sys/floater.rs +++ b/voxygen/src/ecs/sys/floater.rs @@ -76,6 +76,7 @@ impl<'a> System<'a> for Sys { | 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) } => { let by_me = my_uid.map_or(false, |&uid| by == uid); // If the attack was by me also reset this timer From f95a0411d8567a2921fb1023cc60cbd330ce2e41 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sun, 18 Oct 2020 22:00:35 -0500 Subject: [PATCH 19/25] Cleaned up UI code. Removed stuff added for testing. Added 10% for melee attacks to inflict a bleeding debuff. Renamed BuffId to BuffKind. Fixed memory leak. Set event emission to false when timer is decremented. --- CHANGELOG.md | 1 + Cargo.lock | 2 - client/src/lib.rs | 9 +- common/src/comp/buff.rs | 112 +++++++------ common/src/comp/controller.rs | 4 +- common/src/comp/mod.rs | 2 +- common/src/sys/agent.rs | 2 +- common/src/sys/buff.rs | 95 ++++++------ common/src/sys/combat.rs | 47 ++---- common/src/sys/controller.rs | 2 +- server/src/events/entity_manipulation.rs | 61 +++++--- voxygen/Cargo.toml | 1 - voxygen/src/anim/Cargo.toml | 1 - voxygen/src/hud/buffs.rs | 190 ++++++++++++----------- voxygen/src/hud/group.rs | 74 +++++---- voxygen/src/hud/mod.rs | 17 +- voxygen/src/hud/overhead.rs | 49 +++--- voxygen/src/hud/settings_window.rs | 7 +- voxygen/src/hud/skillbar.rs | 11 +- voxygen/src/menu/main/ui.rs | 1 - 20 files changed, 351 insertions(+), 337 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c3498d68..734b6c53a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Gave the axe a third attack - A new secondary charged melee attack for the hammer - Added Dutch translations +- Buff system ### Changed diff --git a/Cargo.lock b/Cargo.lock index fb8f8f1e2e..3eb2a911bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4957,7 +4957,6 @@ dependencies = [ "guillotiere", "hashbrown 0.7.2", "image", - "inline_tweak", "itertools", "native-dialog", "num 0.2.1", @@ -4989,7 +4988,6 @@ name = "veloren-voxygen-anim" version = "0.7.0" dependencies = [ "find_folder", - "inline_tweak", "lazy_static", "libloading 0.6.3", "notify", diff --git a/client/src/lib.rs b/client/src/lib.rs index 9342123523..7261e220bc 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -37,7 +37,7 @@ use common::{ terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize}, vol::RectVolSize, }; -use comp::BuffId; +use comp::BuffKind; use futures_executor::block_on; use futures_timer::Delay; use futures_util::{select, FutureExt}; @@ -632,7 +632,7 @@ impl Client { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); } - pub fn remove_buff(&mut self, buff_id: BuffId) { + pub fn remove_buff(&mut self, buff_id: BuffKind) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff( buff_id, ))); @@ -971,6 +971,11 @@ impl Client { // 4) Tick the client's LocalState self.state.tick(dt, add_foreign_systems, true); + // TODO: avoid emitting these in the first place + self.state + .ecs() + .fetch::<EventBus<common::event::ServerEvent>>() + .recv_all(); // 5) Terrain let pos = self diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 828cf39d96..c5dabe6c48 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -4,17 +4,12 @@ use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; use std::time::Duration; -/// De/buff ID. -/// 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 -/// different strength, but they could use the same `BuffId`, -/// making it harder to recognize which is which. -/// -/// Also, this should be dehardcoded eventually. +/// De/buff Kind. +/// This is used to determine what effects a buff will have, as well as +/// determine the strength and duration of the buff effects using the internal +/// values #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] -pub enum BuffId { +pub enum BuffKind { /// Restores health/time for some period Regeneration { strength: f32, @@ -31,22 +26,21 @@ pub enum BuffId { } /// De/buff category ID. -/// Similar to `BuffId`, but to mark a category (for more generic usage, like +/// Similar to `BuffKind`, but to mark a category (for more generic usage, like /// positive/negative buffs). #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffCategoryId { + // Buff and debuff get added in builder function based off of the buff kind + Debuff, + Buff, Natural, Physical, Magical, Divine, - Debuff, - Buff, PersistOnDeath, } /// Data indicating and configuring behaviour of a de/buff. -/// -/// NOTE: Contents of this enum are WIP/Placeholder #[derive(Clone, Debug, Serialize, Deserialize)] pub enum BuffEffect { /// Periodically damages or heals entity @@ -58,23 +52,16 @@ pub enum BuffEffect { /// Actual de/buff. /// Buff can timeout after some time if `time` is Some. If `time` is None, /// Buff will last indefinitely, until removed manually (by some action, like -/// uncursing). The `time` field might be moved into the `Buffs` component -/// (so that `Buff` does not own this information). +/// uncursing). /// -/// Buff has an id and data, which can be independent on each other. -/// This makes it hard to create buff stacking "helpers", as the system -/// does not assume that the same id is always the same behaviour (data). -/// Therefore id=behaviour relationship has to be enforced elsewhere (if -/// desired). +/// Buff has a kind, which is used to determine the effects in a builder +/// function. /// /// To provide more classification info when needed, /// buff can be in one or more buff category. -/// -/// `data` is separate, to make this system more flexible -/// (at the cost of the fact that id=behaviour relationship might not apply). #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Buff { - pub id: BuffId, + pub kind: BuffKind, pub cat_ids: Vec<BuffCategoryId>, pub time: Option<Duration>, pub effects: Vec<BuffEffect>, @@ -88,10 +75,12 @@ pub enum BuffChange { /// Adds this buff. Add(Buff), /// Removes all buffs with this ID. - RemoveById(BuffId), + RemoveByKind(BuffKind), + /// Removes all buffs with this ID, but not debuffs. + RemoveFromClient(BuffKind), /// Removes buffs of these indices (first vec is for active buffs, second is - /// for inactive buffs) - RemoveByIndex(Vec<usize>, Vec<usize>), + /// for inactive buffs), should only be called when buffs expire + RemoveExpiredByIndex(Vec<usize>, Vec<usize>), /// Removes buffs of these categories (first vec is of categories of which /// all are required, second vec is of categories of which at least one is /// required, third vec is of categories that will not be removed) @@ -139,37 +128,40 @@ pub struct Buffs { pub inactive_buffs: Vec<Buff>, } -impl Buffs { - /// This is a primitive check if a specific buff is present and active. - /// (for purposes like blocking usage of abilities or something like this). - pub fn has_buff_id(&self, id: &BuffId) -> bool { - self.active_buffs.iter().any(|buff| buff.id == *id) - } -} - impl Buff { - pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { - let (effects, time) = match id { - BuffId::Bleeding { strength, duration } => ( - vec![BuffEffect::HealthChangeOverTime { - rate: -strength, - accumulated: 0.0, - }], - duration, - ), - BuffId::Regeneration { strength, duration } => ( - vec![BuffEffect::HealthChangeOverTime { - rate: strength, - accumulated: 0.0, - }], - duration, - ), - BuffId::Cursed { duration } => ( - vec![BuffEffect::NameChange { - prefix: String::from("Cursed "), - }], - duration, - ), + /// Builder function for buffs + pub fn new(kind: BuffKind, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { + let mut cat_ids = cat_ids; + let (effects, time) = match kind { + BuffKind::Bleeding { strength, duration } => { + cat_ids.push(BuffCategoryId::Debuff); + ( + vec![BuffEffect::HealthChangeOverTime { + rate: -strength, + accumulated: 0.0, + }], + duration, + ) + }, + BuffKind::Regeneration { strength, duration } => { + cat_ids.push(BuffCategoryId::Buff); + ( + vec![BuffEffect::HealthChangeOverTime { + rate: strength, + accumulated: 0.0, + }], + duration, + ) + }, + BuffKind::Cursed { duration } => { + cat_ids.push(BuffCategoryId::Debuff); + ( + vec![BuffEffect::NameChange { + prefix: String::from("Cursed "), + }], + duration, + ) + }, }; assert_eq!( cat_ids @@ -179,7 +171,7 @@ impl Buff { "Buff must have either buff or debuff category." ); Buff { - id, + kind, cat_ids, time, effects, diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 64cdf93505..5c7f3558c8 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{inventory::slot::Slot, BuffId}, + comp::{inventory::slot::Slot, BuffKind}, sync::Uid, util::Dir, }; @@ -41,7 +41,7 @@ pub enum ControlEvent { Unmount, InventoryManip(InventoryManip), GroupManip(GroupManip), - RemoveBuff(BuffId), + RemoveBuff(BuffKind), Respawn, } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 2098e44dc0..4357952d37 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -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, BuffEffect, BuffId, BuffSource, Buffs}; +pub use buff::{Buff, BuffCategoryId, BuffChange, BuffEffect, BuffKind, BuffSource, Buffs}; pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use chat::{ ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 63f263a765..06d39cb1c8 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -684,7 +684,7 @@ impl<'a> System<'a> for Sys { for (_invite, /*alignment,*/ agent, controller) in (&invites, /*&alignments,*/ &mut agents, &mut controllers).join() { - let accept = true; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics + let accept = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics if accept { // Clear agent comp *agent = Agent::default(); diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 96b4feede3..8d50ce15fb 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -23,33 +23,62 @@ impl<'a> System<'a> for Sys { fn run(&mut self, (dt, server_bus, uids, stats, mut buffs): Self::SystemData) { let mut server_emitter = server_bus.emitter(); - for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() { - let buff_comp = buffs.get_mut_unchecked(); + // Set to false to avoid spamming server + buffs.set_event_emission(false); + for (buff_comp, uid) in (&mut buffs, &uids).join() { let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = (Vec::<usize>::new(), Vec::<usize>::new()); - // Tick all de/buffs on a Buffs component. for (i, active_buff) in buff_comp.active_buffs.iter_mut().enumerate() { - // First, tick the buff and subtract delta from it - // and return how much "real" time the buff took (for tick independence). - let buff_delta = if let Some(remaining_time) = &mut active_buff.time { - let pre_tick = remaining_time.as_secs_f32(); + // Tick the buff and subtract delta from it + if let Some(remaining_time) = &mut active_buff.time { let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); - let post_tick = if let Some(dur) = new_duration { + if new_duration.is_some() { // The buff still continues. *remaining_time -= Duration::from_secs_f32(dt.0); - dur.as_secs_f32() } else { // The buff has expired. // Remove it. active_buff_indices_for_removal.push(i); - 0.0 + active_buff.time = Some(Duration::default()); }; - pre_tick - post_tick - } else { - // The buff is indefinite, and it takes full tick (delta). - dt.0 - }; + } + } + for (i, inactive_buff) in buff_comp.inactive_buffs.iter_mut().enumerate() { + // Tick the buff and subtract delta from it + if let Some(remaining_time) = &mut inactive_buff.time { + let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); + if new_duration.is_some() { + // The buff still continues. + *remaining_time -= Duration::from_secs_f32(dt.0); + } else { + // The buff has expired. + // Remove it. + inactive_buff_indices_for_removal.push(i); + inactive_buff.time = Some(Duration::default()); + }; + } + } + + if !active_buff_indices_for_removal.is_empty() + || !inactive_buff_indices_for_removal.is_empty() + { + server_emitter.emit(ServerEvent::Buff { + uid: *uid, + buff_change: BuffChange::RemoveExpiredByIndex( + active_buff_indices_for_removal, + inactive_buff_indices_for_removal, + ), + }); + } + } + // Set back to true after timer decrement + buffs.set_event_emission(true); + for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() { + let buff_comp = buffs.get_mut_unchecked(); + // Tick all de/buffs on a Buffs component. + for active_buff in buff_comp.active_buffs.iter_mut() { + // Get buff owner let buff_owner = if let BuffSource::Character { by: owner } = active_buff.source { Some(owner) } else { @@ -60,12 +89,13 @@ impl<'a> System<'a> for Sys { match effect { // Only add an effect here if it is continuous or it is not immediate BuffEffect::HealthChangeOverTime { rate, accumulated } => { - *accumulated += *rate * buff_delta; - // Apply damage only once a second (with a minimum of 1 damage), or when a buff is removed + *accumulated += *rate * dt.0; + // Apply damage only once a second (with a minimum of 1 damage), or when + // a buff is removed if accumulated.abs() > rate.abs().max(10.0) - || active_buff_indices_for_removal - .iter() - .any(|index| *index == i) + || active_buff + .time + .map_or(false, |dur| dur == Duration::default()) { let cause = if *accumulated > 0.0 { HealthSource::Healing { by: buff_owner } @@ -87,31 +117,6 @@ impl<'a> System<'a> for Sys { } } - for (i, inactive_buff) in buff_comp.inactive_buffs.iter_mut().enumerate() { - // First, tick the buff and subtract delta from it - // and return how much "real" time the buff took (for tick independence). - // TODO: handle delta for "indefinite" buffs, i.e. time since they got removed. - if let Some(remaining_time) = &mut inactive_buff.time { - let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); - if new_duration.is_some() { - // The buff still continues. - *remaining_time -= Duration::from_secs_f32(dt.0); - } else { - // The buff has expired. - // Remove it. - inactive_buff_indices_for_removal.push(i); - }; - } - } - - server_emitter.emit(ServerEvent::Buff { - uid: *uid, - buff_change: BuffChange::RemoveByIndex( - active_buff_indices_for_removal, - inactive_buff_indices_for_removal, - ), - }); - if stat.is_dead { server_emitter.emit(ServerEvent::Buff { uid: *uid, diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 13b8901013..95665bf038 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -9,6 +9,7 @@ use crate::{ sync::Uid, util::Dir, }; +use rand::{thread_rng, Rng}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use std::time::Duration; use vek::*; @@ -151,37 +152,21 @@ 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_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffId::Bleeding { - strength: attack.base_damage as f32, - duration: Some(Duration::from_secs(30)), - }, - vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], - buff::BuffSource::Character { by: *uid }, - )), - }); - server_emitter.emit(ServerEvent::Buff { - uid: *uid_b, - buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffId::Regeneration { - strength: 1.0, - duration: Some(Duration::from_secs(60)), - }, - vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Buff], - buff::BuffSource::Character { by: *uid }, - )), - }); - server_emitter.emit(ServerEvent::Buff { - uid: *uid_b, - buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffId::Cursed { duration: None }, - vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], - buff::BuffSource::Character { by: *uid }, - )), - }); + // Apply bleeding buff on melee hits with 10% chance + // TODO: Don't have buff uniformly applied on all melee attacks + if thread_rng().gen::<f32>() < 0.1 { + server_emitter.emit(ServerEvent::Buff { + uid: *uid_b, + buff_change: buff::BuffChange::Add(buff::Buff::new( + buff::BuffKind::Bleeding { + strength: attack.base_damage as f32 / 10.0, + duration: Some(Duration::from_secs(10)), + }, + vec![buff::BuffCategoryId::Physical], + buff::BuffSource::Character { by: *uid }, + )), + }); + } attack.hit_count += 1; } if attack.knockback != 0.0 && damage.healthchange != 0.0 { diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 03fbf46c6e..68d40b6e41 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -86,7 +86,7 @@ impl<'a> System<'a> for Sys { ControlEvent::RemoveBuff(buff_id) => { server_emitter.emit(ServerEvent::Buff { uid: *uid, - buff_change: BuffChange::RemoveById(buff_id), + buff_change: BuffChange::RemoveFromClient(buff_id), }); }, ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 3fccd6d37a..9bca72ff09 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -724,7 +724,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) // inactive buffs, or move active buff to // inactive buffs and add new buff to active // buffs. - if discriminant(&active_buff.id) == discriminant(&new_buff.id) { + if discriminant(&active_buff.kind) == discriminant(&new_buff.kind) { duplicate_existed = true; // Determines if active buff is weaker than newer buff if determine_replace_active_buff( @@ -761,19 +761,40 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } }, - BuffChange::RemoveByIndex(active_indices, inactive_indices) => { + BuffChange::RemoveExpiredByIndex(active_indices, inactive_indices) => { active_buff_indices_for_removal = active_indices; inactive_buff_indices_for_removal = inactive_indices; }, - BuffChange::RemoveById(id) => { - let some_predicate = |current_id: &buff::BuffId| *current_id == id; + BuffChange::RemoveByKind(kind) => { for (i, buff) in buffs.active_buffs.iter().enumerate() { - if some_predicate(&buff.id) { + if discriminant(&kind) == discriminant(&buff.kind) { active_buff_indices_for_removal.push(i); } } for (i, buff) in buffs.inactive_buffs.iter().enumerate() { - if some_predicate(&buff.id) { + if discriminant(&kind) == discriminant(&buff.kind) { + inactive_buff_indices_for_removal.push(i); + } + } + }, + BuffChange::RemoveFromClient(kind) => { + for (i, buff) in buffs.active_buffs.iter().enumerate() { + if discriminant(&kind) == discriminant(&buff.kind) + && buff + .cat_ids + .iter() + .any(|cat| *cat == buff::BuffCategoryId::Buff) + { + active_buff_indices_for_removal.push(i); + } + } + for (i, buff) in buffs.inactive_buffs.iter().enumerate() { + if discriminant(&kind) == discriminant(&buff.kind) + && buff + .cat_ids + .iter() + .any(|cat| *cat == buff::BuffCategoryId::Buff) + { inactive_buff_indices_for_removal.push(i); } } @@ -830,11 +851,11 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } }, } - let mut removed_active_buff_ids = Vec::new(); + let mut removed_active_buff_kinds = Vec::new(); while !active_buff_indices_for_removal.is_empty() { if let Some(i) = active_buff_indices_for_removal.pop() { let buff = buffs.active_buffs.remove(i); - removed_active_buff_ids.push(buff.id); + removed_active_buff_kinds.push(buff.kind); remove_buff_effects(buff, stats.get_mut(entity)); } } @@ -845,19 +866,19 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } // Checks after buffs are removed so that it doesn't grab incorrect // index - for buff_id in removed_active_buff_ids { + for buff_kind in removed_active_buff_kinds { // Checks to verify that there are no active buffs with the same id if buffs .active_buffs .iter() - .any(|buff| discriminant(&buff.id) == discriminant(&buff_id)) + .any(|buff| discriminant(&buff.kind) == discriminant(&buff_kind)) { continue; } let mut new_active_buff = None::<buff::Buff>; let mut replacement_buff_index = 0; for (i, inactive_buff) in buffs.inactive_buffs.iter().enumerate() { - if discriminant(&buff_id) == discriminant(&inactive_buff.id) { + if discriminant(&buff_kind) == discriminant(&inactive_buff.kind) { if let Some(ref buff) = new_active_buff { if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) { new_active_buff = Some(inactive_buff.clone()); @@ -880,16 +901,16 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool { - use buff::BuffId; - match new_buff.id { - BuffId::Bleeding { + use buff::BuffKind; + match new_buff.kind { + BuffKind::Bleeding { strength: new_strength, duration: new_duration, } => { - if let BuffId::Bleeding { + if let BuffKind::Bleeding { strength: active_strength, duration: _, - } = active_buff.id + } = active_buff.kind { new_strength > active_strength || (new_strength >= active_strength @@ -900,14 +921,14 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) false } }, - BuffId::Regeneration { + BuffKind::Regeneration { strength: new_strength, duration: new_duration, } => { - if let BuffId::Regeneration { + if let BuffKind::Regeneration { strength: active_strength, duration: _, - } = active_buff.id + } = active_buff.kind { new_strength > active_strength || (new_strength >= active_strength @@ -918,7 +939,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) false } }, - BuffId::Cursed { + BuffKind::Cursed { duration: new_duration, } => new_duration.map_or(true, |new_dur| { active_buff.time.map_or(false, |act_dur| new_dur > act_dur) diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 439574c43f..03d70d004b 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -74,7 +74,6 @@ treeculler = "0.1.0" uvth = "3.1.1" # vec_map = { version = "0.8.2" } const-tweaker = {version = "0.3.1", optional = true} -inline_tweak = "1.0.2" itertools = "0.9.0" # Logging diff --git a/voxygen/src/anim/Cargo.toml b/voxygen/src/anim/Cargo.toml index 86545be0f6..7fae1fbe67 100644 --- a/voxygen/src/anim/Cargo.toml +++ b/voxygen/src/anim/Cargo.toml @@ -20,7 +20,6 @@ default = ["be-dyn-lib", "simd"] [dependencies] common = {package = "veloren-common", path = "../../../common"} find_folder = {version = "0.3.0", optional = true} -inline_tweak = "1.0.2" lazy_static = {version = "1.4.0", optional = true} libloading = {version = "0.6.2", optional = true} notify = {version = "5.0.0-pre.2", optional = true} diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 04d30f6809..8572ce50d8 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -9,14 +9,13 @@ use crate::{ GlobalState, }; -use common::comp::{BuffId, Buffs}; +use common::comp::{BuffKind, Buffs}; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; -use std::time::Duration; + widget_ids! { struct Ids { align, @@ -77,7 +76,7 @@ pub struct State { } pub enum Event { - RemoveBuff(BuffId), + RemoveBuff(BuffKind), } impl<'a> Widget for BuffsBar<'a> { @@ -123,7 +122,7 @@ impl<'a> Widget for BuffsBar<'a> { if let BuffPosition::Bar = buff_position { // Alignment Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) - .mid_bottom_with_margin_on(ui.window, tweak!(92.0)) + .mid_bottom_with_margin_on(ui.window, 92.0) .set(state.ids.align, ui); Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) @@ -177,14 +176,18 @@ impl<'a> Widget for BuffsBar<'a> { ) .enumerate() .for_each(|(i, ((id, timer_id), buff))| { - let max_duration = match buff.id { - BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), - _ => 10.0, + let max_duration = match buff.kind { + BuffKind::Bleeding { duration, .. } => duration, + BuffKind::Regeneration { duration, .. } => duration, + BuffKind::Cursed { duration } => duration, }; let current_duration = buff.dur; - let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.id { - BuffId::Regeneration { .. } => self.imgs.buff_plus_0, + let duration_percentage = current_duration.map_or(1000.0, |cur| { + max_duration + .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 + let buff_img = match buff.kind { + BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, _ => self.imgs.missing_icon, }; let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); @@ -197,30 +200,32 @@ impl<'a> Widget for BuffsBar<'a> { 0.0 + x as f64 * (21.0), ); buff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) + .color( + if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) { + Some(pulsating_col) + } else { + Some(norm_col) + }, + ) .set(id, ui); // Create Buff tooltip - let title = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.title.heal_test") + let title = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.title.heal_test") }, - _ => *&localized_strings.get("buff.title.missing"), + _ => localized_strings.get("buff.title.missing"), }; - let remaining_time = if current_duration == 10e6 as f32 { + let remaining_time = if current_duration.is_none() { "Permanent".to_string() } else { - format!("Remaining: {:.0}s", current_duration) + format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32()) }; let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); - let desc_txt = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.desc.heal_test") + let desc_txt = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.desc.heal_test") }, - _ => *&localized_strings.get("buff.desc.missing"), + _ => localized_strings.get("buff.desc.missing"), }; let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove); // Timer overlay @@ -247,7 +252,7 @@ impl<'a> Widget for BuffsBar<'a> { .set(timer_id, ui) .was_clicked() { - event.push(Event::RemoveBuff(buff.id)); + event.push(Event::RemoveBuff(buff.kind)); }; }); // Create Debuff Widgets @@ -266,21 +271,19 @@ impl<'a> Widget for BuffsBar<'a> { ) .enumerate() .for_each(|(i, ((id, timer_id), debuff))| { - let max_duration = match debuff.id { - BuffId::Bleeding { duration, .. } => { - duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() - }, - BuffId::Cursed { duration, .. } => { - duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() - }, - - _ => 10.0, + let max_duration = match debuff.kind { + BuffKind::Bleeding { duration, .. } => duration, + BuffKind::Regeneration { duration, .. } => duration, + BuffKind::Cursed { duration } => duration, }; let current_duration = debuff.dur; - let duration_percentage = current_duration / max_duration * 1000.0; // Percentage to determine which frame of the timer overlay is displayed - let debuff_img = match debuff.id { - BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + let duration_percentage = current_duration.map_or(1000.0, |cur| { + max_duration + .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 + let debuff_img = match debuff.kind { + BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, _ => self.imgs.missing_icon, }; let debuff_widget = Image::new(debuff_img).w_h(20.0, 20.0); @@ -294,29 +297,31 @@ impl<'a> Widget for BuffsBar<'a> { ); debuff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) + .color( + if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) { + Some(pulsating_col) + } else { + Some(norm_col) + }, + ) .set(id, ui); // Create Debuff tooltip - let title = match debuff.id { - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.title.bleed_test") + let title = match debuff.kind { + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.title.bleed_test") }, - _ => *&localized_strings.get("buff.title.missing"), + _ => localized_strings.get("buff.title.missing"), }; - let remaining_time = if current_duration == 10e6 as f32 { + let remaining_time = if current_duration.is_none() { "Permanent".to_string() } else { - format!("Remaining: {:.0}s", current_duration) + format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32()) }; - let desc_txt = match debuff.id { - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.desc.bleed_test") + let desc_txt = match debuff.kind { + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.desc.bleed_test") }, - _ => *&localized_strings.get("debuff.desc.missing"), + _ => localized_strings.get("debuff.desc.missing"), }; let desc = format!("{}\n\n{}", desc_txt, remaining_time); Image::new(match duration_percentage as u64 { @@ -346,7 +351,7 @@ impl<'a> Widget for BuffsBar<'a> { if let BuffPosition::Map = buff_position { // Alignment Rectangle::fill_with([210.0, 210.0], color::TRANSPARENT) - .top_right_with_margins_on(ui.window, 5.0, tweak!(270.0)) + .top_right_with_margins_on(ui.window, 5.0, 270.0) .set(state.ids.align, ui); // Buffs and Debuffs @@ -376,18 +381,21 @@ impl<'a> Widget for BuffsBar<'a> { .zip(buffs.active_buffs.iter().map(get_buff_info)) .enumerate() .for_each(|(i, (((id, timer_id), txt_id), buff))| { - let max_duration = match buff.id { - BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), - BuffId::Bleeding { duration, .. } => duration.unwrap().as_secs_f32(), - _ => 10e6, + let max_duration = match buff.kind { + BuffKind::Bleeding { duration, .. } => duration, + BuffKind::Regeneration { duration, .. } => duration, + BuffKind::Cursed { duration } => duration, }; let current_duration = buff.dur; // Percentage to determine which frame of the timer overlay is displayed - let duration_percentage = (current_duration / max_duration * 1000.0) as u32; - let buff_img = match buff.id { - BuffId::Regeneration { .. } => self.imgs.buff_plus_0, - BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + let duration_percentage = current_duration.map_or(1000.0, |cur| { + max_duration + .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0) + }) as u32; + let buff_img = match buff.kind { + BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, + BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, }; let buff_widget = Image::new(buff_img).w_h(40.0, 40.0); // Sort buffs into rows of 6 slots @@ -399,41 +407,43 @@ impl<'a> Widget for BuffsBar<'a> { 0.0 + x as f64 * (42.0), ); buff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) + .color( + if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) { + Some(pulsating_col) + } else { + Some(norm_col) + }, + ) .set(id, ui); // Create Buff tooltip - let title = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.title.heal_test") + let title = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.title.heal_test") }, - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.title.bleed_test") + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.title.bleed_test") }, - _ => *&localized_strings.get("buff.title.missing"), + _ => localized_strings.get("buff.title.missing"), }; - let remaining_time = if current_duration == 10e6 as f32 { + let remaining_time = if current_duration.is_none() { "".to_string() } else { - format!("{:.0}s", current_duration) + format!("{:.0}s", current_duration.unwrap().as_secs_f32()) }; let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove")); - let desc_txt = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.desc.heal_test") + let desc_txt = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.desc.heal_test") }, - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.desc.bleed_test") + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.desc.bleed_test") }, - _ => *&localized_strings.get("buff.desc.missing"), + _ => localized_strings.get("buff.desc.missing"), }; let desc = if buff.is_buff { format!("{}\n\n{}", desc_txt, click_to_remove) } else { - format!("{}", desc_txt) + desc_txt.to_string() }; // Timer overlay if Button::image(match duration_percentage as u64 { @@ -442,7 +452,7 @@ impl<'a> Widget for BuffsBar<'a> { 625..=749 => self.imgs.buff_1, // 6/8 500..=624 => self.imgs.buff_2, // 5/8 375..=499 => self.imgs.buff_3, // 4/8 - 250..=374 => self.imgs.buff_4, //3/8 + 250..=374 => self.imgs.buff_4, // 3/8 125..=249 => self.imgs.buff_5, // 2/8 0..=124 => self.imgs.buff_6, // 1/8 _ => self.imgs.nothing, @@ -463,13 +473,11 @@ impl<'a> Widget for BuffsBar<'a> { .set(timer_id, ui) .was_clicked() { - if buff.is_buff { - event.push(Event::RemoveBuff(buff.id)); - } + event.push(Event::RemoveBuff(buff.kind)); } Text::new(&remaining_time) - .down_from(timer_id, tweak!(1.0)) - .font_size(self.fonts.cyri.scale(tweak!(10))) + .down_from(timer_id, 1.0) + .font_size(self.fonts.cyri.scale(10)) .font_id(self.fonts.cyri.conrod_id) .graphics_for(timer_id) .color(TEXT_COLOR) diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 00b1fc7f72..ab3239e099 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -14,7 +14,7 @@ use crate::{ }; use client::{self, Client}; use common::{ - comp::{group::Role, BuffId, Stats}, + comp::{group::Role, BuffKind, Stats}, sync::{Uid, WorldSyncExt}, }; use conrod_core::{ @@ -23,7 +23,6 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Scrollbar, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; use specs::{saveload::MarkerAllocator, WorldExt}; widget_ids! { pub struct Ids { @@ -329,7 +328,7 @@ impl<'a> Widget for Group<'a> { .ecs() .read_resource::<common::sync::UidAllocator>(); let offset = if self.global_state.settings.gameplay.toggle_debug { - tweak!(320.0) + 320.0 } else { 110.0 }; @@ -467,21 +466,23 @@ impl<'a> Widget for Group<'a> { .skip(total_buff_count - buff_count) .zip(buffs.active_buffs.iter().map(get_buff_info)) .for_each(|((id, timer_id), buff)| { - let max_duration = match buff.id { - BuffId::Regeneration { duration, .. } => { - duration.unwrap().as_secs_f32() - }, - _ => 10.0, + let max_duration = match buff.kind { + BuffKind::Bleeding { duration, .. } => duration, + BuffKind::Regeneration { duration, .. } => duration, + BuffKind::Cursed { duration } => duration, }; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); let current_duration = buff.dur; - let duration_percentage = - (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.id { - BuffId::Regeneration { .. } => self.imgs.buff_plus_0, - BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + let duration_percentage = current_duration.map_or(1000.0, |cur| { + max_duration.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 + let buff_img = match buff.kind { + BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, + BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, }; let buff_widget = Image::new(buff_img).w_h(15.0, 15.0); let buff_widget = if let Some(id) = prev_id { @@ -495,35 +496,42 @@ impl<'a> Widget for Group<'a> { }; prev_id = Some(id); buff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) + .color( + if current_duration + .map_or(false, |cur| cur.as_secs_f32() < 10.0) + { + Some(pulsating_col) + } else { + Some(norm_col) + }, + ) .set(id, ui); // Create Buff tooltip - let title = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.title.heal_test") + let title = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.title.heal_test") }, - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.title.bleed_test") + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.title.bleed_test") }, - _ => *&localized_strings.get("buff.title.missing"), + _ => localized_strings.get("buff.title.missing"), }; - let remaining_time = if current_duration == 10e6 as f32 { + let remaining_time = if current_duration.is_none() { "Permanent".to_string() } else { - format!("Remaining: {:.0}s", current_duration) + format!( + "Remaining: {:.0}s", + current_duration.unwrap().as_secs_f32() + ) }; - let desc_txt = match buff.id { - BuffId::Regeneration { .. } => { - *&localized_strings.get("buff.desc.heal_test") + let desc_txt = match buff.kind { + BuffKind::Regeneration { .. } => { + localized_strings.get("buff.desc.heal_test") }, - BuffId::Bleeding { .. } => { - *&localized_strings.get("debuff.desc.bleed_test") + BuffKind::Bleeding { .. } => { + localized_strings.get("debuff.desc.bleed_test") }, - _ => *&localized_strings.get("buff.desc.missing"), + _ => localized_strings.get("buff.desc.missing"), }; let desc = format!("{}\n\n{}", desc_txt, remaining_time); Image::new(match duration_percentage as u64 { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index ec48a034b3..34201c5021 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -62,7 +62,7 @@ use common::{ comp, comp::{ item::{ItemDesc, Quality}, - BuffId, + BuffKind, }, span, sync::Uid, @@ -274,9 +274,9 @@ widget_ids! { #[derive(Clone, Copy)] pub struct BuffInfo { - id: comp::BuffId, + kind: comp::BuffKind, is_buff: bool, - dur: f32, + dur: Option<Duration>, } pub struct DebugInfo { @@ -364,7 +364,7 @@ pub enum Event { KickMember(common::sync::Uid), LeaveGroup, AssignLeader(common::sync::Uid), - RemoveBuff(BuffId), + RemoveBuff(BuffKind), } // TODO: Are these the possible layouts we want? @@ -1145,7 +1145,7 @@ impl Hud { let speech_bubbles = &self.speech_bubbles; // Render overhead name tags and health bars - for (pos, info, bubble, stats, buffs, height_offset, hpfl, in_group) in ( + for (pos, info, bubble, stats, _, height_offset, hpfl, in_group) in ( &entities, &pos, interpolated.maybe(), @@ -2728,14 +2728,11 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color { // Get info about applied buffs fn get_buff_info(buff: &comp::Buff) -> BuffInfo { BuffInfo { - id: buff.id, + kind: buff.kind, is_buff: buff .cat_ids .iter() .any(|cat| *cat == comp::BuffCategoryId::Buff), - dur: buff - .time - .map(|dur| dur.as_secs_f32()) - .unwrap_or(10e6 as f32), + dur: buff.time, } } diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index da8861dcc8..f50437d4f0 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -8,14 +8,13 @@ use crate::{ settings::GameplaySettings, ui::{fonts::ConrodVoxygenFonts, Ingameable}, }; -use common::comp::{BuffId, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats}; +use common::comp::{BuffKind, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats}; use conrod_core::{ color, position::Align, widget::{self, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; const MAX_BUBBLE_WIDTH: f64 = 250.0; widget_ids! { @@ -55,11 +54,6 @@ widget_ids! { } } -/*pub struct BuffInfo { - id: comp::BuffId, - dur: f32, -}*/ - #[derive(Clone, Copy)] pub struct Info<'a> { pub name: &'a str, @@ -211,8 +205,8 @@ impl<'a> Widget for Overhead<'a> { // Buffs // Alignment let buff_count = buffs.active_buffs.len().min(11); - Rectangle::fill_with([tweak!(168.0), tweak!(100.0)], color::TRANSPARENT) - .x_y(-1.0, name_y + tweak!(60.0)) + Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT) + .x_y(-1.0, name_y + 60.0) .parent(id) .set(state.ids.buffs_align, ui); @@ -239,18 +233,21 @@ impl<'a> Widget for Overhead<'a> { .enumerate() .for_each(|(i, ((id, timer_id), buff))| { // Limit displayed buffs - let max_duration = match buff.id { - BuffId::Regeneration { duration, .. } => { - duration.unwrap().as_secs_f32() - }, - _ => 10.0, + let max_duration = match buff.kind { + BuffKind::Bleeding { duration, .. } => duration, + BuffKind::Regeneration { duration, .. } => duration, + BuffKind::Cursed { duration } => duration, }; let current_duration = buff.dur; - let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed - let buff_img = match buff.id { - BuffId::Regeneration { .. } => self.imgs.buff_plus_0, - BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, - BuffId::Cursed { .. } => self.imgs.debuff_skull_0, + let duration_percentage = current_duration.map_or(1000.0, |cur| { + max_duration.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 + let buff_img = match buff.kind { + BuffKind::Regeneration { .. } => self.imgs.buff_plus_0, + BuffKind::Bleeding { .. } => self.imgs.debuff_bleed_0, + BuffKind::Cursed { .. } => self.imgs.debuff_skull_0, }; let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); // Sort buffs into rows of 5 slots @@ -262,11 +259,13 @@ impl<'a> Widget for Overhead<'a> { 0.0 + x as f64 * (21.0), ); buff_widget - .color(if current_duration < 10.0 { - Some(pulsating_col) - } else { - Some(norm_col) - }) + .color( + if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) { + Some(pulsating_col) + } else { + Some(norm_col) + }, + ) .set(id, ui); Image::new(match duration_percentage as u64 { @@ -275,7 +274,7 @@ impl<'a> Widget for Overhead<'a> { 625..=749 => self.imgs.buff_1, // 6/8 500..=624 => self.imgs.buff_2, // 5/8 375..=499 => self.imgs.buff_3, // 4/8 - 250..=374 => self.imgs.buff_4, //3/8 + 250..=374 => self.imgs.buff_4, // 3/8 125..=249 => self.imgs.buff_5, // 2/8 0..=124 => self.imgs.buff_6, // 1/8 _ => self.imgs.nothing, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index b2975c7d52..59f5a6f14b 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -20,7 +20,6 @@ use conrod_core::{ }; use core::convert::TryFrom; -use inline_tweak::*; use itertools::Itertools; use std::iter::once; use winit::monitor::VideoMode; @@ -2709,8 +2708,8 @@ impl<'a> Widget for SettingsWindow<'a> { }); }; for (i, language) in language_list.iter().enumerate() { - let button_w = tweak!(400.0); - let button_h = tweak!(50.0); + let button_w = 400.0; + let button_h = 50.0; let button = Button::image(if selected_language == &language.language_identifier { self.imgs.selection } else { @@ -2727,7 +2726,7 @@ impl<'a> Widget for SettingsWindow<'a> { .hover_image(self.imgs.selection_hover) .press_image(self.imgs.selection_press) .label_color(TEXT_COLOR) - .label_font_size(self.fonts.cyri.scale(tweak!(22))) + .label_font_size(self.fonts.cyri.scale(22)) .label_font_id(self.fonts.cyri.conrod_id) .label_y(conrod_core::position::Relative::Scalar(2.0)) .set(state.ids.language_list[i], ui) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 53712c067c..d73ff01b66 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -27,7 +27,6 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; use std::time::{Duration, Instant}; use vek::*; @@ -347,9 +346,9 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.bg, ui); // Level let lvl_size = match self.stats.level.level() { - 11..=99 => tweak!(13), - 100..=999 => tweak!(10), - _ => tweak!(14), + 11..=99 => 13, + 100..=999 => 10, + _ => 14, }; Text::new(&level) .mid_top_with_margin_on(state.ids.bg, 3.0) @@ -1009,11 +1008,11 @@ impl<'a> Widget for Skillbar<'a> { // TODO Don't show this if key bindings are changed Image::new(self.imgs.m1_ico) .w_h(16.0, 18.0) - .mid_bottom_with_margin_on(state.ids.m1_content, tweak!(-11.0)) + .mid_bottom_with_margin_on(state.ids.m1_content, -11.0) .set(state.ids.m1_ico, ui); Image::new(self.imgs.m2_ico) .w_h(16.0, 18.0) - .mid_bottom_with_margin_on(state.ids.m2_content, tweak!(-11.0)) + .mid_bottom_with_margin_on(state.ids.m2_content, -11.0) .set(state.ids.m2_ico, ui); // Buffs diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 81355d8996..8ce9fb4fcb 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -18,7 +18,6 @@ use conrod_core::{ widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; use image::DynamicImage; -//use inline_tweak::*; use rand::{seq::SliceRandom, thread_rng, Rng}; use std::time::Duration; From 02f5f0facd40d9bf3bb2859d485ec3c3e0664909 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sat, 24 Oct 2020 15:12:37 -0500 Subject: [PATCH 20/25] Transitioned buff storage from a vec to a hashmap. Addressed other comments. Only continuous buff effects are handled right now. --- Cargo.lock | 11 ++ common/src/comp/buff.rs | 220 +++++++++++++++-------- common/src/comp/mod.rs | 4 +- common/src/sys/buff.rs | 134 ++++++-------- common/src/sys/combat.rs | 5 +- common/src/sys/controller.rs | 2 +- server/src/events/entity_manipulation.rs | 219 ++-------------------- voxygen/Cargo.toml | 1 + voxygen/src/anim/Cargo.toml | 1 + voxygen/src/hud/buffs.rs | 33 +--- voxygen/src/hud/group.rs | 10 +- voxygen/src/hud/mod.rs | 7 +- voxygen/src/hud/overhead.rs | 12 +- voxygen/src/hud/skillbar.rs | 6 - 14 files changed, 257 insertions(+), 408 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3eb2a911bd..156f052930 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,6 +2037,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "inline_tweak" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b" +dependencies = [ + "lazy_static", +] + [[package]] name = "inotify" version = "0.8.3" @@ -4957,6 +4966,7 @@ dependencies = [ "guillotiere", "hashbrown 0.7.2", "image", + "inline_tweak", "itertools", "native-dialog", "num 0.2.1", @@ -4988,6 +4998,7 @@ name = "veloren-voxygen-anim" version = "0.7.0" dependencies = [ "find_folder", + "inline_tweak", "lazy_static", "libloading 0.6.3", "notify", diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index c5dabe6c48..9f661199b1 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -2,37 +2,46 @@ use crate::sync::Uid; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; -use std::time::Duration; +use std::{collections::HashMap, time::Duration}; /// De/buff Kind. /// This is used to determine what effects a buff will have, as well as /// determine the strength and duration of the buff effects using the internal /// values -#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum BuffKind { /// Restores health/time for some period - Regeneration { - strength: f32, - duration: Option<Duration>, - }, + Regeneration, /// Lowers health over time for some duration - Bleeding { - strength: f32, - duration: Option<Duration>, - }, + Bleeding, /// Prefixes an entity's name with "Cursed" /// Currently placeholder buff to show other stuff is possible - Cursed { duration: Option<Duration> }, + Cursed, +} + +impl BuffKind { + // Checks if buff is buff or debuff + pub fn is_buff(self) -> bool { + match self { + BuffKind::Regeneration { .. } => true, + BuffKind::Bleeding { .. } => false, + BuffKind::Cursed { .. } => false, + } + } +} + +// Struct used to store data relevant to a buff +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct BuffData { + pub strength: f32, + pub duration: Option<Duration>, } /// De/buff category ID. /// Similar to `BuffKind`, but to mark a category (for more generic usage, like /// positive/negative buffs). #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] -pub enum BuffCategoryId { - // Buff and debuff get added in builder function based off of the buff kind - Debuff, - Buff, +pub enum BuffCategory { Natural, Physical, Magical, @@ -62,7 +71,8 @@ pub enum BuffEffect { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Buff { pub kind: BuffKind, - pub cat_ids: Vec<BuffCategoryId>, + pub data: BuffData, + pub cat_ids: Vec<BuffCategory>, pub time: Option<Duration>, pub effects: Vec<BuffEffect>, pub source: BuffSource, @@ -70,27 +80,68 @@ pub struct Buff { /// Information about whether buff addition or removal was requested. /// This to implement "on_add" and "on_remove" hooks for constant buffs. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub enum BuffChange { /// Adds this buff. Add(Buff), /// Removes all buffs with this ID. RemoveByKind(BuffKind), /// Removes all buffs with this ID, but not debuffs. - RemoveFromClient(BuffKind), + RemoveFromController(BuffKind), /// Removes buffs of these indices (first vec is for active buffs, second is /// for inactive buffs), should only be called when buffs expire - RemoveExpiredByIndex(Vec<usize>, Vec<usize>), + RemoveById(Vec<BuffId>), /// Removes buffs of these categories (first vec is of categories of which /// all are required, second vec is of categories of which at least one is /// required, third vec is of categories that will not be removed) RemoveByCategory { - required: Vec<BuffCategoryId>, - optional: Vec<BuffCategoryId>, - blacklisted: Vec<BuffCategoryId>, + all_required: Vec<BuffCategory>, + any_required: Vec<BuffCategory>, + none_required: Vec<BuffCategory>, }, } +impl Buff { + /// Builder function for buffs + pub fn new( + kind: BuffKind, + data: BuffData, + cat_ids: Vec<BuffCategory>, + source: BuffSource, + ) -> Self { + let (effects, time) = match kind { + BuffKind::Bleeding => ( + vec![BuffEffect::HealthChangeOverTime { + rate: -data.strength, + accumulated: 0.0, + }], + data.duration, + ), + BuffKind::Regeneration => ( + vec![BuffEffect::HealthChangeOverTime { + rate: data.strength, + accumulated: 0.0, + }], + data.duration, + ), + BuffKind::Cursed => ( + vec![BuffEffect::NameChange { + prefix: String::from("Cursed "), + }], + data.duration, + ), + }; + Buff { + kind, + data, + cat_ids, + time, + effects, + source, + } + } +} + /// Source of the de/buff #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffSource { @@ -121,65 +172,84 @@ pub enum BuffSource { /// would be probably an undesired effect). #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Buffs { - /// Active de/buffs. - pub active_buffs: Vec<Buff>, - /// Inactive de/buffs (used so that only 1 buff of a particular type is - /// active at any time) - pub inactive_buffs: Vec<Buff>, + id_counter: u64, + pub kinds: HashMap<BuffKind, Vec<BuffId>>, + pub buffs: HashMap<BuffId, Buff>, } -impl Buff { - /// Builder function for buffs - pub fn new(kind: BuffKind, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { - let mut cat_ids = cat_ids; - let (effects, time) = match kind { - BuffKind::Bleeding { strength, duration } => { - cat_ids.push(BuffCategoryId::Debuff); - ( - vec![BuffEffect::HealthChangeOverTime { - rate: -strength, - accumulated: 0.0, - }], - duration, - ) - }, - BuffKind::Regeneration { strength, duration } => { - cat_ids.push(BuffCategoryId::Buff); - ( - vec![BuffEffect::HealthChangeOverTime { - rate: strength, - accumulated: 0.0, - }], - duration, - ) - }, - BuffKind::Cursed { duration } => { - cat_ids.push(BuffCategoryId::Debuff); - ( - vec![BuffEffect::NameChange { - prefix: String::from("Cursed "), - }], - duration, - ) - }, - }; - assert_eq!( - cat_ids - .iter() - .any(|cat| *cat == BuffCategoryId::Buff || *cat == BuffCategoryId::Debuff), - true, - "Buff must have either buff or debuff category." - ); - Buff { - kind, - cat_ids, - time, - effects, - source, +impl Buffs { + fn sort_kind(&mut self, kind: BuffKind) { + if let Some(buff_order) = self.kinds.get_mut(&kind) { + if buff_order.len() == 0 { + self.kinds.remove(&kind); + } else { + let buffs = &self.buffs; + buff_order.sort_by(|a, b| { + buffs[&b] + .data + .strength + .partial_cmp(&buffs[&a].data.strength) + .unwrap() + }); + } } } + + pub fn remove_kind(&mut self, kind: BuffKind) { + if let Some(buff_ids) = self.kinds.get_mut(&kind) { + for id in buff_ids { + self.buffs.remove(id); + } + self.kinds.remove(&kind); + } + self.sort_kind(kind); + } + + pub fn force_insert(&mut self, id: BuffId, buff: Buff) -> BuffId { + let kind = buff.kind; + self.kinds.entry(kind).or_default().push(id); + self.buffs.insert(id, buff); + self.sort_kind(kind); + id + } + + pub fn insert(&mut self, buff: Buff) -> BuffId { + self.id_counter += 1; + self.force_insert(self.id_counter, buff) + } + + // Iterate through buffs of a given kind in effect order (most powerful first) + pub fn iter_kind(&self, kind: BuffKind) -> impl Iterator<Item = (BuffId, &Buff)> + '_ { + self.kinds + .get(&kind) + .map(|ids| ids.iter()) + .unwrap_or((&[]).iter()) + .map(move |id| (*id, &self.buffs[id])) + } + + // Iterates through all active buffs (the most powerful buff of each kind) + pub fn iter_active(&self) -> impl Iterator<Item = &Buff> + '_ { + self.kinds + .values() + .map(move |ids| self.buffs.get(&ids[0])) + .filter(|buff| buff.is_some()) + .map(|buff| buff.unwrap()) + } + + // Gets most powerful buff of a given kind + // pub fn get_active_kind(&self, kind: BuffKind) -> Buff + + pub fn remove(&mut self, buff_id: BuffId) { + let kind = self.buffs.remove(&buff_id).unwrap().kind; + self.kinds + .get_mut(&kind) + .map(|ids| ids.retain(|id| *id != buff_id)); + self.sort_kind(kind); + } } +pub type BuffId = u64; + impl Component for Buffs { type Storage = FlaggedStorage<Self, IdvStorage<Self>>; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 4357952d37..73a62193a1 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -32,7 +32,9 @@ 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, BuffEffect, BuffKind, BuffSource, Buffs}; +pub use buff::{ + Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, +}; pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use chat::{ ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 8d50ce15fb..b5243c0334 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - BuffCategoryId, BuffChange, BuffEffect, BuffSource, Buffs, HealthChange, HealthSource, - Stats, + BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange, + HealthSource, Stats, }, event::{EventBus, ServerEvent}, state::DeltaTime, @@ -25,108 +25,84 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server buffs.set_event_emission(false); - for (buff_comp, uid) in (&mut buffs, &uids).join() { - let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = - (Vec::<usize>::new(), Vec::<usize>::new()); - for (i, active_buff) in buff_comp.active_buffs.iter_mut().enumerate() { + for (buff_comp, uid, stat) in (&mut buffs, &uids, &stats).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 - if let Some(remaining_time) = &mut active_buff.time { - let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); - if new_duration.is_some() { + if let Some(remaining_time) = &mut buff.time { + if let Some(new_duration) = + remaining_time.checked_sub(Duration::from_secs_f32(dt.0)) + { // The buff still continues. - *remaining_time -= Duration::from_secs_f32(dt.0); + *remaining_time = new_duration; } else { // The buff has expired. // Remove it. - active_buff_indices_for_removal.push(i); - active_buff.time = Some(Duration::default()); - }; + expired_buffs.push(*id); + } } } - for (i, inactive_buff) in buff_comp.inactive_buffs.iter_mut().enumerate() { - // Tick the buff and subtract delta from it - if let Some(remaining_time) = &mut inactive_buff.time { - let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0)); - if new_duration.is_some() { - // The buff still continues. - *remaining_time -= Duration::from_secs_f32(dt.0); + for buff_ids in buff_comp.kinds.values() { + if let Some(buff) = buff_comp.buffs.get_mut(&buff_ids[0]) { + // Get buff owner + let buff_owner = if let BuffSource::Character { by: owner } = buff.source { + Some(owner) } else { - // The buff has expired. - // Remove it. - inactive_buff_indices_for_removal.push(i); - inactive_buff.time = Some(Duration::default()); + None }; + // Now, execute the buff, based on it's delta + for effect in &mut buff.effects { + match effect { + // Only add an effect here if it is continuous or it is not immediate + BuffEffect::HealthChangeOverTime { rate, accumulated } => { + *accumulated += *rate * dt.0; + // Apply damage only once a second (with a minimum of 1 damage), or + // when a buff is removed + if accumulated.abs() > rate.abs().max(10.0) + || buff.time.map_or(false, |dur| dur == Duration::default()) + { + let cause = if *accumulated > 0.0 { + HealthSource::Healing { by: buff_owner } + } else { + HealthSource::Buff { owner: buff_owner } + }; + server_emitter.emit(ServerEvent::Damage { + uid: *uid, + change: HealthChange { + amount: *accumulated as i32, + cause, + }, + }); + *accumulated = 0.0; + }; + }, + BuffEffect::NameChange { .. } => {}, + }; + } } } - if !active_buff_indices_for_removal.is_empty() - || !inactive_buff_indices_for_removal.is_empty() - { + // Remove buffs that expire + if !expired_buffs.is_empty() { server_emitter.emit(ServerEvent::Buff { uid: *uid, - buff_change: BuffChange::RemoveExpiredByIndex( - active_buff_indices_for_removal, - inactive_buff_indices_for_removal, - ), + buff_change: BuffChange::RemoveById(expired_buffs), }); } - } - // Set back to true after timer decrement - buffs.set_event_emission(true); - for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() { - let buff_comp = buffs.get_mut_unchecked(); - // Tick all de/buffs on a Buffs component. - for active_buff in buff_comp.active_buffs.iter_mut() { - // Get buff owner - let buff_owner = if let BuffSource::Character { by: owner } = active_buff.source { - Some(owner) - } else { - None - }; - // Now, execute the buff, based on it's delta - for effect in &mut active_buff.effects { - match effect { - // Only add an effect here if it is continuous or it is not immediate - BuffEffect::HealthChangeOverTime { rate, accumulated } => { - *accumulated += *rate * dt.0; - // Apply damage only once a second (with a minimum of 1 damage), or when - // a buff is removed - if accumulated.abs() > rate.abs().max(10.0) - || active_buff - .time - .map_or(false, |dur| dur == Duration::default()) - { - let cause = if *accumulated > 0.0 { - HealthSource::Healing { by: buff_owner } - } else { - HealthSource::Buff { owner: buff_owner } - }; - server_emitter.emit(ServerEvent::Damage { - uid: *uid, - change: HealthChange { - amount: *accumulated as i32, - cause, - }, - }); - *accumulated = 0.0; - }; - }, - BuffEffect::NameChange { .. } => {}, - }; - } - } + // Remove stats that don't persist on death if stat.is_dead { server_emitter.emit(ServerEvent::Buff { uid: *uid, buff_change: BuffChange::RemoveByCategory { - required: vec![], - optional: vec![], - blacklisted: vec![BuffCategoryId::PersistOnDeath], + all_required: vec![], + any_required: vec![], + none_required: vec![BuffCategory::PersistOnDeath], }, }); } } + buffs.set_event_emission(true); } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 95665bf038..ef07258227 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -158,11 +158,12 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Buff { uid: *uid_b, buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffKind::Bleeding { + buff::BuffKind::Bleeding, + buff::BuffData { strength: attack.base_damage as f32 / 10.0, duration: Some(Duration::from_secs(10)), }, - vec![buff::BuffCategoryId::Physical], + vec![buff::BuffCategory::Physical], buff::BuffSource::Character { by: *uid }, )), }); diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index 68d40b6e41..df9a404d36 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -86,7 +86,7 @@ impl<'a> System<'a> for Sys { ControlEvent::RemoveBuff(buff_id) => { server_emitter.emit(ServerEvent::Buff { uid: *uid, - buff_change: BuffChange::RemoveFromClient(buff_id), + buff_change: BuffChange::RemoveFromController(buff_id), }); }, ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 9bca72ff09..4e4c2a1ad4 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -24,7 +24,6 @@ use common::{ use comp::item::Reagent; use rand::prelude::*; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; -use std::mem::discriminant; use tracing::error; use vek::Vec3; @@ -706,105 +705,31 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) 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>(); - let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) = - (Vec::new(), Vec::new()); use buff::BuffChange; match buff_change { BuffChange::Add(new_buff) => { - if buffs.active_buffs.is_empty() { - add_buff_effects(new_buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(new_buff); - } else { - let mut duplicate_existed = false; - for i in 0..buffs.active_buffs.len() { - let active_buff = &buffs.active_buffs[i].clone(); - // Checks if new buff has the same id as an already active buff. If it - // doesn't, new buff added to active buffs. If it does, compares the new - // buff and the active buff, and decides to either add new buff to - // inactive buffs, or move active buff to - // inactive buffs and add new buff to active - // buffs. - if discriminant(&active_buff.kind) == discriminant(&new_buff.kind) { - duplicate_existed = true; - // Determines if active buff is weaker than newer buff - if determine_replace_active_buff( - active_buff.clone(), - new_buff.clone(), - ) { - active_buff_indices_for_removal.push(i); - add_buff_effects(new_buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(new_buff.clone()); - // Sees if weaker active has longer duration than new buff - #[allow(clippy::blocks_in_if_conditions)] - if active_buff.time.map_or(true, |act_dur| { - new_buff.time.map_or(false, |new_dur| act_dur > new_dur) - }) { - buffs.inactive_buffs.push(active_buff.clone()); - } - // Sees if weaker new buff has longer duration - // than active buff - } else if let Some(active_dur) = active_buff.time { - if let Some(new_dur) = new_buff.time { - if new_dur > active_dur { - buffs.inactive_buffs.push(new_buff.clone()); - } - } else { - buffs.inactive_buffs.push(new_buff.clone()); - } - } - break; - } - } - if !duplicate_existed { - add_buff_effects(new_buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(new_buff); - } - } + buffs.insert(new_buff); }, - BuffChange::RemoveExpiredByIndex(active_indices, inactive_indices) => { - active_buff_indices_for_removal = active_indices; - inactive_buff_indices_for_removal = inactive_indices; + BuffChange::RemoveById(ids) => { + for id in ids { + buffs.remove(id); + } }, BuffChange::RemoveByKind(kind) => { - for (i, buff) in buffs.active_buffs.iter().enumerate() { - if discriminant(&kind) == discriminant(&buff.kind) { - active_buff_indices_for_removal.push(i); - } - } - for (i, buff) in buffs.inactive_buffs.iter().enumerate() { - if discriminant(&kind) == discriminant(&buff.kind) { - inactive_buff_indices_for_removal.push(i); - } - } + buffs.remove_kind(kind); }, - BuffChange::RemoveFromClient(kind) => { - for (i, buff) in buffs.active_buffs.iter().enumerate() { - if discriminant(&kind) == discriminant(&buff.kind) - && buff - .cat_ids - .iter() - .any(|cat| *cat == buff::BuffCategoryId::Buff) - { - active_buff_indices_for_removal.push(i); - } - } - for (i, buff) in buffs.inactive_buffs.iter().enumerate() { - if discriminant(&kind) == discriminant(&buff.kind) - && buff - .cat_ids - .iter() - .any(|cat| *cat == buff::BuffCategoryId::Buff) - { - inactive_buff_indices_for_removal.push(i); - } + BuffChange::RemoveFromController(kind) => { + if kind.is_buff() { + buffs.remove_kind(kind); } }, BuffChange::RemoveByCategory { - required: all_required, - optional: any_required, - blacklisted: none_required, + all_required, + any_required, + none_required, } => { - for (i, buff) in buffs.active_buffs.iter().enumerate() { + let mut ids_to_remove = Vec::new(); + for (id, buff) in buffs.buffs.iter() { let mut required_met = true; for required in &all_required { if !buff.cat_ids.iter().any(|cat| cat == required) { @@ -814,7 +739,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } let mut any_met = any_required.is_empty(); for any in &any_required { - if !buff.cat_ids.iter().any(|cat| cat == any) { + if buff.cat_ids.iter().any(|cat| cat == any) { any_met = true; break; } @@ -827,126 +752,18 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } if required_met && any_met && none_met { - active_buff_indices_for_removal.push(i); + ids_to_remove.push(*id); } } - for (i, buff) in buffs.inactive_buffs.iter().enumerate() { - let mut required_met = true; - for required in &all_required { - if !buff.cat_ids.iter().any(|cat| cat == required) { - required_met = false; - break; - } - } - let mut any_met = any_required.is_empty(); - for any in &any_required { - if !buff.cat_ids.iter().any(|cat| cat == any) { - any_met = true; - break; - } - } - if required_met && any_met { - inactive_buff_indices_for_removal.push(i); - } + for id in ids_to_remove { + buffs.remove(id); } }, } - let mut removed_active_buff_kinds = Vec::new(); - while !active_buff_indices_for_removal.is_empty() { - if let Some(i) = active_buff_indices_for_removal.pop() { - let buff = buffs.active_buffs.remove(i); - removed_active_buff_kinds.push(buff.kind); - remove_buff_effects(buff, stats.get_mut(entity)); - } - } - while !inactive_buff_indices_for_removal.is_empty() { - if let Some(i) = inactive_buff_indices_for_removal.pop() { - buffs.inactive_buffs.remove(i); - } - } - // Checks after buffs are removed so that it doesn't grab incorrect - // index - for buff_kind in removed_active_buff_kinds { - // Checks to verify that there are no active buffs with the same id - if buffs - .active_buffs - .iter() - .any(|buff| discriminant(&buff.kind) == discriminant(&buff_kind)) - { - continue; - } - let mut new_active_buff = None::<buff::Buff>; - let mut replacement_buff_index = 0; - for (i, inactive_buff) in buffs.inactive_buffs.iter().enumerate() { - if discriminant(&buff_kind) == discriminant(&inactive_buff.kind) { - if let Some(ref buff) = new_active_buff { - if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) { - new_active_buff = Some(inactive_buff.clone()); - replacement_buff_index = i; - } - } else { - new_active_buff = Some(inactive_buff.clone()); - replacement_buff_index = i; - } - } - } - if new_active_buff.is_some() { - let buff = buffs.inactive_buffs.remove(replacement_buff_index); - add_buff_effects(buff.clone(), stats.get_mut(entity)); - buffs.active_buffs.push(buff.clone()); - } - } } } } -fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool { - use buff::BuffKind; - match new_buff.kind { - BuffKind::Bleeding { - strength: new_strength, - duration: new_duration, - } => { - if let BuffKind::Bleeding { - strength: active_strength, - duration: _, - } = active_buff.kind - { - new_strength > active_strength - || (new_strength >= active_strength - && new_duration.map_or(true, |new_dur| { - active_buff.time.map_or(false, |act_dur| new_dur > act_dur) - })) - } else { - false - } - }, - BuffKind::Regeneration { - strength: new_strength, - duration: new_duration, - } => { - if let BuffKind::Regeneration { - strength: active_strength, - duration: _, - } = active_buff.kind - { - new_strength > active_strength - || (new_strength >= active_strength - && new_duration.map_or(true, |new_dur| { - active_buff.time.map_or(false, |act_dur| new_dur > act_dur) - })) - } else { - false - } - }, - BuffKind::Cursed { - duration: new_duration, - } => new_duration.map_or(true, |new_dur| { - active_buff.time.map_or(false, |act_dur| new_dur > act_dur) - }), - } -} - fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { for effect in &buff.effects { use buff::BuffEffect; diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 03d70d004b..439574c43f 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -74,6 +74,7 @@ treeculler = "0.1.0" uvth = "3.1.1" # vec_map = { version = "0.8.2" } const-tweaker = {version = "0.3.1", optional = true} +inline_tweak = "1.0.2" itertools = "0.9.0" # Logging diff --git a/voxygen/src/anim/Cargo.toml b/voxygen/src/anim/Cargo.toml index 7fae1fbe67..86545be0f6 100644 --- a/voxygen/src/anim/Cargo.toml +++ b/voxygen/src/anim/Cargo.toml @@ -20,6 +20,7 @@ default = ["be-dyn-lib", "simd"] [dependencies] common = {package = "veloren-common", path = "../../../common"} find_folder = {version = "0.3.0", optional = true} +inline_tweak = "1.0.2" lazy_static = {version = "1.4.0", optional = true} libloading = {version = "0.6.2", optional = true} notify = {version = "5.0.0-pre.2", optional = true} diff --git a/voxygen/src/hud/buffs.rs b/voxygen/src/hud/buffs.rs index 8572ce50d8..1bd91eabc3 100644 --- a/voxygen/src/hud/buffs.rs +++ b/voxygen/src/hud/buffs.rs @@ -90,8 +90,7 @@ impl<'a> Widget for BuffsBar<'a> { } } - #[allow(clippy::unused_unit)] // TODO: Pending review in #587 - fn style(&self) -> Self::Style { () } + fn style(&self) -> Self::Style {} fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; @@ -132,7 +131,7 @@ impl<'a> Widget for BuffsBar<'a> { .set(state.ids.buffs_align, ui); // Buffs and Debuffs - let (buff_count, debuff_count) = buffs.active_buffs.iter().map(get_buff_info).fold( + let (buff_count, debuff_count) = buffs.iter_active().map(get_buff_info).fold( (0, 0), |(buff_count, debuff_count), info| { if info.is_buff { @@ -169,18 +168,13 @@ impl<'a> Widget for BuffsBar<'a> { .zip(state.ids.buff_timers.iter().copied()) .zip( buffs - .active_buffs - .iter() + .iter_active() .map(get_buff_info) .filter(|info| info.is_buff), ) .enumerate() .for_each(|(i, ((id, timer_id), buff))| { - let max_duration = match buff.kind { - BuffKind::Bleeding { duration, .. } => duration, - BuffKind::Regeneration { duration, .. } => duration, - BuffKind::Cursed { duration } => duration, - }; + let max_duration = buff.data.duration; let current_duration = buff.dur; let duration_percentage = current_duration.map_or(1000.0, |cur| { max_duration @@ -264,18 +258,13 @@ impl<'a> Widget for BuffsBar<'a> { .zip(state.ids.debuff_timers.iter().copied()) .zip( buffs - .active_buffs - .iter() + .iter_active() .map(get_buff_info) .filter(|info| !info.is_buff), ) .enumerate() .for_each(|(i, ((id, timer_id), debuff))| { - let max_duration = match debuff.kind { - BuffKind::Bleeding { duration, .. } => duration, - BuffKind::Regeneration { duration, .. } => duration, - BuffKind::Cursed { duration } => duration, - }; + let max_duration = debuff.data.duration; let current_duration = debuff.dur; let duration_percentage = current_duration.map_or(1000.0, |cur| { max_duration @@ -355,7 +344,7 @@ impl<'a> Widget for BuffsBar<'a> { .set(state.ids.align, ui); // Buffs and Debuffs - let buff_count = buffs.active_buffs.len().min(11); + let buff_count = buffs.kinds.len().min(11); // Limit displayed buffs let buff_count = buff_count.min(20); @@ -378,14 +367,10 @@ impl<'a> Widget for BuffsBar<'a> { .copied() .zip(state.ids.buff_timers.iter().copied()) .zip(state.ids.buff_txts.iter().copied()) - .zip(buffs.active_buffs.iter().map(get_buff_info)) + .zip(buffs.iter_active().map(get_buff_info)) .enumerate() .for_each(|(i, (((id, timer_id), txt_id), buff))| { - let max_duration = match buff.kind { - BuffKind::Bleeding { duration, .. } => duration, - BuffKind::Regeneration { duration, .. } => duration, - BuffKind::Cursed { duration } => duration, - }; + let max_duration = buff.data.duration; let current_duration = buff.dur; // Percentage to determine which frame of the timer overlay is displayed let duration_percentage = current_duration.map_or(1000.0, |cur| { diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index ab3239e099..1bfeaa1cb2 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -444,7 +444,7 @@ impl<'a> Widget for Group<'a> { } if let Some(buffs) = buffs { // Limit displayed buffs to 11 - let buff_count = buffs.active_buffs.len().min(11); + let buff_count = buffs.kinds.len().min(11); total_buff_count += buff_count; let gen = &mut ui.widget_id_generator(); if state.ids.buffs.len() < total_buff_count { @@ -464,13 +464,9 @@ impl<'a> Widget for Group<'a> { .copied() .zip(state.ids.buff_timers.iter().copied()) .skip(total_buff_count - buff_count) - .zip(buffs.active_buffs.iter().map(get_buff_info)) + .zip(buffs.iter_active().map(get_buff_info)) .for_each(|((id, timer_id), buff)| { - let max_duration = match buff.kind { - BuffKind::Bleeding { duration, .. } => duration, - BuffKind::Regeneration { duration, .. } => duration, - BuffKind::Cursed { duration } => duration, - }; + let max_duration = buff.data.duration; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0); let current_duration = buff.dur; diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 34201c5021..907a04857f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -275,6 +275,7 @@ widget_ids! { #[derive(Clone, Copy)] pub struct BuffInfo { kind: comp::BuffKind, + data: comp::BuffData, is_buff: bool, dur: Option<Duration>, } @@ -2729,10 +2730,8 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color { fn get_buff_info(buff: &comp::Buff) -> BuffInfo { BuffInfo { kind: buff.kind, - is_buff: buff - .cat_ids - .iter() - .any(|cat| *cat == comp::BuffCategoryId::Buff), + data: buff.data, + is_buff: buff.kind.is_buff(), dur: buff.time, } } diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index f50437d4f0..7ebfb29cfc 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -137,7 +137,7 @@ impl<'a> Ingameable for Overhead<'a> { self.info.map_or(0, |info| { 2 + 1 + if self.bubble.is_none() { - info.buffs.active_buffs.len().min(10) * 2 + info.buffs.kinds.len().min(10) * 2 } else { 0 } @@ -204,7 +204,7 @@ impl<'a> Widget for Overhead<'a> { }; // Buffs // Alignment - let buff_count = buffs.active_buffs.len().min(11); + let buff_count = buffs.kinds.len().min(11); Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT) .x_y(-1.0, name_y + 60.0) .parent(id) @@ -229,15 +229,11 @@ impl<'a> Widget for Overhead<'a> { .iter() .copied() .zip(state.ids.buff_timers.iter().copied()) - .zip(buffs.active_buffs.iter().map(get_buff_info)) + .zip(buffs.iter_active().map(get_buff_info)) .enumerate() .for_each(|(i, ((id, timer_id), buff))| { // Limit displayed buffs - let max_duration = match buff.kind { - BuffKind::Bleeding { duration, .. } => duration, - BuffKind::Regeneration { duration, .. } => duration, - BuffKind::Cursed { duration } => duration, - }; + let max_duration = buff.data.duration; let current_duration = buff.dur; let duration_percentage = current_duration.map_or(1000.0, |cur| { max_duration.map_or(1000.0, |max| { diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index d73ff01b66..172671622e 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1014,11 +1014,5 @@ impl<'a> Widget for Skillbar<'a> { .w_h(16.0, 18.0) .mid_bottom_with_margin_on(state.ids.m2_content, -11.0) .set(state.ids.m2_ico, ui); - - // Buffs - // Add debuff slots above the health bar - // Add buff slots above the mana bar - - // Debuffs } } From aa95e2ba5b5f2648c19799bb35983404731a8abc Mon Sep 17 00:00:00 2001 From: Adam Whitehurst <Adam.Whitehurst@live.com> Date: Sat, 24 Oct 2020 13:17:49 -0700 Subject: [PATCH 21/25] add comments --- common/src/comp/buff.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 9f661199b1..05abc28362 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -172,8 +172,11 @@ pub enum BuffSource { /// would be probably an undesired effect). #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Buffs { + /// Uid used for synchronization id_counter: u64, + /// Maps Kinds of buff to Id's of currently applied buffs of that kind pub kinds: HashMap<BuffKind, Vec<BuffId>>, + // All currently applied buffs stored by Id pub buffs: HashMap<BuffId, Buff>, } From e83942fecda92ee2928ade985830dcb1a1191e8e Mon Sep 17 00:00:00 2001 From: Adam Whitehurst <Adam.Whitehurst@live.com> Date: Sat, 24 Oct 2020 16:07:38 -0700 Subject: [PATCH 22/25] add maxhealthmodifier oops variable --- common/src/comp/buff.rs | 15 +++++++--- common/src/comp/mod.rs | 1 + common/src/comp/stats.rs | 17 ++++++++++- common/src/sys/buff.rs | 36 ++++++++++++++++++------ common/src/sys/combat.rs | 25 ++++++++++++---- server/src/events/entity_manipulation.rs | 34 ---------------------- 6 files changed, 76 insertions(+), 52 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 05abc28362..71621653c6 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -49,13 +49,19 @@ pub enum BuffCategory { PersistOnDeath, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ModifierKind { + Additive, + Multiplicative, +} + /// Data indicating and configuring behaviour of a de/buff. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum BuffEffect { /// Periodically damages or heals entity HealthChangeOverTime { rate: f32, accumulated: f32 }, - /// Changes name on_add/on_remove - NameChange { prefix: String }, + /// Changes maximum health by a certain amount + MaxHealthModifier { value: f32, kind: ModifierKind }, } /// Actual de/buff. @@ -125,8 +131,9 @@ impl Buff { data.duration, ), BuffKind::Cursed => ( - vec![BuffEffect::NameChange { - prefix: String::from("Cursed "), + vec![BuffEffect::MaxHealthModifier { + value: -100., + kind: ModifierKind::Additive, }], data.duration, ), diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 73a62193a1..329546a54e 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -34,6 +34,7 @@ pub use body::{ }; pub use buff::{ Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, + ModifierKind, }; pub use character_state::{Attacking, CharacterState, StateUpdate}; pub use chat::{ diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 6c9feb5e8a..3be36dd33e 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -8,6 +8,7 @@ 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, @@ -33,6 +34,7 @@ pub enum HealthSource { #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Health { + base_max: u32, current: u32, maximum: u32, pub last_change: (f64, HealthChange), @@ -69,10 +71,18 @@ impl Health { } // This is private because max hp is based on the level - fn set_maximum(&mut self, amount: u32) { + 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 { @@ -149,6 +159,8 @@ impl Stats { // 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); } @@ -180,6 +192,7 @@ impl Stats { health: Health { current: 0, maximum: 0, + base_max: 0, last_change: (0.0, HealthChange { amount: 0, cause: HealthSource::Revive, @@ -199,6 +212,7 @@ impl Stats { }; stats.update_max_hp(body); + stats .health .set_to(stats.health.maximum(), HealthSource::Revive); @@ -214,6 +228,7 @@ impl Stats { health: Health { current: 0, maximum: 0, + base_max: 0, last_change: (0.0, HealthChange { amount: 0, cause: HealthSource::Revive, diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index b5243c0334..2bf3333370 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange, - HealthSource, Stats, + HealthSource, ModifierKind, Stats, }, event::{EventBus, ServerEvent}, state::DeltaTime, @@ -17,15 +17,15 @@ impl<'a> System<'a> for Sys { Read<'a, DeltaTime>, Read<'a, EventBus<ServerEvent>>, ReadStorage<'a, Uid>, - ReadStorage<'a, Stats>, + WriteStorage<'a, Stats>, WriteStorage<'a, Buffs>, ); - fn run(&mut self, (dt, server_bus, uids, stats, mut buffs): Self::SystemData) { + fn run(&mut self, (dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server - buffs.set_event_emission(false); - for (buff_comp, uid, stat) in (&mut buffs, &uids, &stats).join() { + // buffs.set_event_emission(false); + for (buff_comp, uid, stat) in (&mut buffs, &uids, &mut stats).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 @@ -36,6 +36,9 @@ impl<'a> System<'a> for Sys { // The buff still continues. *remaining_time = new_duration; } else { + // checked_sub returns None when remaining time + // went below 0, so set to 0 + *remaining_time = Duration::default(); // The buff has expired. // Remove it. expired_buffs.push(*id); @@ -43,14 +46,20 @@ impl<'a> System<'a> for Sys { } } + // Call to reset stats to base values + stat.health.reset_max(); + + // Iterator over the lists of buffs by kind for buff_ids in buff_comp.kinds.values() { + // Get the strongest of this buff kind if let Some(buff) = buff_comp.buffs.get_mut(&buff_ids[0]) { - // Get buff owner + // Get buff owner? let buff_owner = if let BuffSource::Character { by: owner } = buff.source { Some(owner) } else { None }; + // Now, execute the buff, based on it's delta for effect in &mut buff.effects { match effect { @@ -77,7 +86,18 @@ impl<'a> System<'a> for Sys { *accumulated = 0.0; }; }, - BuffEffect::NameChange { .. } => {}, + BuffEffect::MaxHealthModifier { value, kind } => match kind { + ModifierKind::Multiplicative => { + stat.health.set_maximum( + (stat.health.maximum() as f32 * *value) as u32, + ); + }, + ModifierKind::Additive => { + stat.health.set_maximum( + (stat.health.maximum() as f32 + *value) as u32, + ); + }, + }, }; } } @@ -103,6 +123,6 @@ impl<'a> System<'a> for Sys { }); } } - buffs.set_event_emission(true); + // buffs.set_event_emission(true); } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index ef07258227..5d2ca3f568 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -152,19 +152,34 @@ impl<'a> System<'a> for Sys { cause, }, }); + + use buff::*; + server_emitter.emit(ServerEvent::Buff { + uid: *uid_b, + buff_change: BuffChange::Add(Buff::new( + BuffKind::Cursed, + BuffData { + strength: attack.base_damage as f32 / 10.0, + duration: Some(Duration::from_secs(10)), + }, + vec![BuffCategory::Physical], + BuffSource::Character { by: *uid }, + )), + }); + // Apply bleeding buff on melee hits with 10% chance // TODO: Don't have buff uniformly applied on all melee attacks if thread_rng().gen::<f32>() < 0.1 { server_emitter.emit(ServerEvent::Buff { uid: *uid_b, - buff_change: buff::BuffChange::Add(buff::Buff::new( - buff::BuffKind::Bleeding, - buff::BuffData { + buff_change: BuffChange::Add(Buff::new( + BuffKind::Bleeding, + BuffData { strength: attack.base_damage as f32 / 10.0, duration: Some(Duration::from_secs(10)), }, - vec![buff::BuffCategory::Physical], - buff::BuffSource::Character { by: *uid }, + vec![BuffCategory::Physical], + BuffSource::Character { by: *uid }, )), }); } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 4e4c2a1ad4..986939c602 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -704,7 +704,6 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) 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>(); use buff::BuffChange; match buff_change { BuffChange::Add(new_buff) => { @@ -763,36 +762,3 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) } } } - -fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { - for effect in &buff.effects { - use buff::BuffEffect; - match effect { - // Only add an effect here if it is immediate and is not continuous - BuffEffect::NameChange { prefix } => { - if let Some(ref mut stats) = stats { - let mut pref = String::from(prefix); - pref.push_str(&stats.name); - stats.name = pref; - } - }, - BuffEffect::HealthChangeOverTime { .. } => {}, - } - } -} - -fn remove_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) { - for effect in &buff.effects { - #[allow(clippy::single_match)] // Remove clippy when there are more buff effects here - match effect { - // Only remove an effect here if its effect was not continuously - // applied - buff::BuffEffect::NameChange { prefix } => { - if let Some(ref mut stats) = stats { - stats.name = stats.name.replacen(prefix, "", 1); - } - }, - _ => {}, - } - } -} From 074e44c0e794387db3a53b43bfb5f33aa632a159 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sat, 24 Oct 2020 20:20:03 -0500 Subject: [PATCH 23/25] Addressed more comments. Changed how buffs were sorted so that duration was also taken into account. --- common/src/comp/buff.rs | 46 +++++++--- common/src/event.rs | 2 +- common/src/sys/buff.rs | 16 ++-- common/src/sys/combat.rs | 17 +--- common/src/sys/controller.rs | 4 +- server/src/events/entity_manipulation.rs | 106 +++++++++++------------ server/src/events/mod.rs | 5 +- 7 files changed, 104 insertions(+), 92 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 71621653c6..4e144c2df2 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -2,7 +2,7 @@ use crate::sync::Uid; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; -use std::{collections::HashMap, time::Duration}; +use std::{cmp::Ordering, collections::HashMap, time::Duration}; /// De/buff Kind. /// This is used to determine what effects a buff will have, as well as @@ -132,7 +132,7 @@ impl Buff { ), BuffKind::Cursed => ( vec![BuffEffect::MaxHealthModifier { - value: -100., + value: -100. * data.strength, kind: ModifierKind::Additive, }], data.duration, @@ -149,6 +149,34 @@ impl Buff { } } +impl PartialOrd for Buff { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + if self.data.strength > other.data.strength { + Some(Ordering::Greater) + } else if self.data.strength < other.data.strength { + Some(Ordering::Less) + } else if compare_duration(self.time, other.time) { + Some(Ordering::Greater) + } else if compare_duration(other.time, self.time) { + Some(Ordering::Less) + } else if self == other { + Some(Ordering::Equal) + } else { + None + } + } +} + +fn compare_duration(a: Option<Duration>, b: Option<Duration>) -> bool { + a.map_or(true, |dur_a| b.map_or(false, |dur_b| dur_a > dur_b)) +} + +impl PartialEq for Buff { + fn eq(&self, other: &Self) -> bool { + self.data.strength == other.data.strength || self.time == other.time + } +} + /// Source of the de/buff #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum BuffSource { @@ -190,17 +218,13 @@ pub struct Buffs { impl Buffs { fn sort_kind(&mut self, kind: BuffKind) { if let Some(buff_order) = self.kinds.get_mut(&kind) { - if buff_order.len() == 0 { + if buff_order.is_empty() { self.kinds.remove(&kind); } else { let buffs = &self.buffs; - buff_order.sort_by(|a, b| { - buffs[&b] - .data - .strength - .partial_cmp(&buffs[&a].data.strength) - .unwrap() - }); + // Intentionally sorted in reverse so that the strongest buffs are earlier in + // the vector + buff_order.sort_by(|a, b| buffs[&b].partial_cmp(&buffs[&a]).unwrap()); } } } @@ -233,7 +257,7 @@ impl Buffs { self.kinds .get(&kind) .map(|ids| ids.iter()) - .unwrap_or((&[]).iter()) + .unwrap_or_else(|| (&[]).iter()) .map(move |id| (*id, &self.buffs[id])) } diff --git a/common/src/event.rs b/common/src/event.rs index aedbbdf14f..a51620c961 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -107,7 +107,7 @@ pub enum ServerEvent { /// Send a chat message to the player from an npc or other player Chat(comp::UnresolvedChatMsg), Buff { - uid: Uid, + entity: EcsEntity, buff_change: comp::BuffChange, }, } diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 2bf3333370..9bda128e8a 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -7,13 +7,14 @@ use crate::{ state::DeltaTime, sync::Uid, }; -use specs::{Join, Read, ReadStorage, System, WriteStorage}; +use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] type SystemData = ( + Entities<'a>, Read<'a, DeltaTime>, Read<'a, EventBus<ServerEvent>>, ReadStorage<'a, Uid>, @@ -21,11 +22,11 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Buffs>, ); - fn run(&mut self, (dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { + fn run(&mut self, (entities, dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server - // buffs.set_event_emission(false); - for (buff_comp, uid, stat) in (&mut buffs, &uids, &mut stats).join() { + buffs.set_event_emission(false); + for (entity, buff_comp, uid, stat) in (&entities, &mut buffs, &uids, &mut stats).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,7 +64,6 @@ impl<'a> System<'a> for Sys { // Now, execute the buff, based on it's delta for effect in &mut buff.effects { match effect { - // Only add an effect here if it is continuous or it is not immediate BuffEffect::HealthChangeOverTime { rate, accumulated } => { *accumulated += *rate * dt.0; // Apply damage only once a second (with a minimum of 1 damage), or @@ -106,7 +106,7 @@ impl<'a> System<'a> for Sys { // Remove buffs that expire if !expired_buffs.is_empty() { server_emitter.emit(ServerEvent::Buff { - uid: *uid, + entity, buff_change: BuffChange::RemoveById(expired_buffs), }); } @@ -114,7 +114,7 @@ impl<'a> System<'a> for Sys { // Remove stats that don't persist on death if stat.is_dead { server_emitter.emit(ServerEvent::Buff { - uid: *uid, + entity, buff_change: BuffChange::RemoveByCategory { all_required: vec![], any_required: vec![], @@ -123,6 +123,6 @@ impl<'a> System<'a> for Sys { }); } } - // buffs.set_event_emission(true); + buffs.set_event_emission(true); } } diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 5d2ca3f568..cfa469a910 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -153,25 +153,12 @@ impl<'a> System<'a> for Sys { }, }); - use buff::*; - server_emitter.emit(ServerEvent::Buff { - uid: *uid_b, - buff_change: BuffChange::Add(Buff::new( - BuffKind::Cursed, - BuffData { - strength: attack.base_damage as f32 / 10.0, - duration: Some(Duration::from_secs(10)), - }, - vec![BuffCategory::Physical], - BuffSource::Character { by: *uid }, - )), - }); - // Apply bleeding buff on melee hits with 10% chance // TODO: Don't have buff uniformly applied on all melee attacks if thread_rng().gen::<f32>() < 0.1 { + use buff::*; server_emitter.emit(ServerEvent::Buff { - uid: *uid_b, + entity: b, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, BuffData { diff --git a/common/src/sys/controller.rs b/common/src/sys/controller.rs index df9a404d36..3fe6b8bedb 100644 --- a/common/src/sys/controller.rs +++ b/common/src/sys/controller.rs @@ -51,7 +51,7 @@ impl<'a> System<'a> for Sys { span!(_guard, "run", "controller::Sys::run"); let mut server_emitter = server_bus.emitter(); - for (entity, uid, controller, character_state) in + for (entity, _uid, controller, character_state) in (&entities, &uids, &mut controllers, &mut character_states).join() { let mut inputs = &mut controller.inputs; @@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys { }, ControlEvent::RemoveBuff(buff_id) => { server_emitter.emit(ServerEvent::Buff { - uid: *uid, + entity, buff_change: BuffChange::RemoveFromController(buff_id), }); }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 986939c602..e32e7318b7 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -699,66 +699,64 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) { )); } -pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange) { +pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: 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) { - use buff::BuffChange; - match buff_change { - BuffChange::Add(new_buff) => { - buffs.insert(new_buff); - }, - BuffChange::RemoveById(ids) => { - for id in ids { - buffs.remove(id); - } - }, - BuffChange::RemoveByKind(kind) => { + if let Some(buffs) = buffs_all.get_mut(entity) { + use buff::BuffChange; + match buff_change { + BuffChange::Add(new_buff) => { + buffs.insert(new_buff); + }, + BuffChange::RemoveById(ids) => { + for id in ids { + buffs.remove(id); + } + }, + BuffChange::RemoveByKind(kind) => { + buffs.remove_kind(kind); + }, + BuffChange::RemoveFromController(kind) => { + if kind.is_buff() { buffs.remove_kind(kind); - }, - BuffChange::RemoveFromController(kind) => { - if kind.is_buff() { - buffs.remove_kind(kind); - } - }, - BuffChange::RemoveByCategory { - all_required, - any_required, - none_required, - } => { - let mut ids_to_remove = Vec::new(); - for (id, buff) in buffs.buffs.iter() { - let mut required_met = true; - for required in &all_required { - if !buff.cat_ids.iter().any(|cat| cat == required) { - required_met = false; - break; - } - } - let mut any_met = any_required.is_empty(); - for any in &any_required { - if buff.cat_ids.iter().any(|cat| cat == any) { - any_met = true; - break; - } - } - let mut none_met = true; - for none in &none_required { - if buff.cat_ids.iter().any(|cat| cat == none) { - none_met = false; - break; - } - } - if required_met && any_met && none_met { - ids_to_remove.push(*id); + } + }, + BuffChange::RemoveByCategory { + all_required, + any_required, + none_required, + } => { + let mut ids_to_remove = Vec::new(); + for (id, buff) in buffs.buffs.iter() { + let mut required_met = true; + for required in &all_required { + if !buff.cat_ids.iter().any(|cat| cat == required) { + required_met = false; + break; } } - for id in ids_to_remove { - buffs.remove(id); + let mut any_met = any_required.is_empty(); + for any in &any_required { + if buff.cat_ids.iter().any(|cat| cat == any) { + any_met = true; + break; + } } - }, - } + let mut none_met = true; + for none in &none_required { + if buff.cat_ids.iter().any(|cat| cat == none) { + none_met = false; + break; + } + } + if required_met && any_met && none_met { + ids_to_remove.push(*id); + } + } + for id in ids_to_remove { + buffs.remove(id); + } + }, } } } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 49a133a5a8..651b49a350 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -133,7 +133,10 @@ impl Server { ServerEvent::Chat(msg) => { chat_messages.push(msg); }, - ServerEvent::Buff { uid, buff_change } => handle_buff(self, uid, buff_change), + ServerEvent::Buff { + entity, + buff_change, + } => handle_buff(self, entity, buff_change), } } From e0637f2e31c94dff7873e1e86616d951698d91f8 Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Sun, 25 Oct 2020 13:42:42 -0500 Subject: [PATCH 24/25] Admin armor now provides immunity to debuffs. --- Cargo.lock | 9 --------- common/src/sys/buff.rs | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 156f052930..fb8f8f1e2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,15 +2037,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "inline_tweak" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b" -dependencies = [ - "lazy_static", -] - [[package]] name = "inotify" version = "0.8.3" diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 9bda128e8a..ee3dbcdb93 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange, - HealthSource, ModifierKind, Stats, + HealthSource, Loadout, ModifierKind, Stats, }, event::{EventBus, ServerEvent}, state::DeltaTime, @@ -18,11 +18,15 @@ impl<'a> System<'a> for Sys { Read<'a, DeltaTime>, Read<'a, EventBus<ServerEvent>>, ReadStorage<'a, Uid>, + ReadStorage<'a, Loadout>, WriteStorage<'a, Stats>, WriteStorage<'a, Buffs>, ); - fn run(&mut self, (entities, dt, server_bus, uids, mut stats, mut buffs): Self::SystemData) { + fn run( + &mut self, + (entities, dt, server_bus, uids, loadouts, mut stats, mut buffs): Self::SystemData, + ) { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server buffs.set_event_emission(false); @@ -47,6 +51,17 @@ impl<'a> System<'a> for Sys { } } + if let Some(loadout) = loadouts.get(entity) { + let damage_reduction = loadout.get_damage_reduction(); + if (damage_reduction - 1.0).abs() < f32::EPSILON { + for (id, buff) in buff_comp.buffs.iter() { + if !buff.kind.is_buff() { + expired_buffs.push(*id); + } + } + } + } + // Call to reset stats to base values stat.health.reset_max(); From cafa218850d49dd83dd0f3c510dc5891dbb0ac4a Mon Sep 17 00:00:00 2001 From: Sam <samuelkeiffer@gmail.com> Date: Mon, 26 Oct 2020 20:17:46 -0500 Subject: [PATCH 25/25] Addressed comments. --- common/src/comp/buff.rs | 15 ++++++--------- common/src/comp/stats.rs | 4 +++- common/src/sys/buff.rs | 3 +++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 4e144c2df2..08a489baf3 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -5,16 +5,14 @@ use specs_idvs::IdvStorage; use std::{cmp::Ordering, collections::HashMap, time::Duration}; /// De/buff Kind. -/// This is used to determine what effects a buff will have, as well as -/// determine the strength and duration of the buff effects using the internal -/// values +/// This is used to determine what effects a buff will have #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum BuffKind { /// Restores health/time for some period Regeneration, /// Lowers health over time for some duration Bleeding, - /// Prefixes an entity's name with "Cursed" + /// Lower a creature's max health /// Currently placeholder buff to show other stuff is possible Cursed, } @@ -151,7 +149,9 @@ impl Buff { impl PartialOrd for Buff { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - if self.data.strength > other.data.strength { + if self == other { + Some(Ordering::Equal) + } else if self.data.strength > other.data.strength { Some(Ordering::Greater) } else if self.data.strength < other.data.strength { Some(Ordering::Less) @@ -159,8 +159,6 @@ impl PartialOrd for Buff { Some(Ordering::Greater) } else if compare_duration(other.time, self.time) { Some(Ordering::Less) - } else if self == other { - Some(Ordering::Equal) } else { None } @@ -173,7 +171,7 @@ fn compare_duration(a: Option<Duration>, b: Option<Duration>) -> bool { impl PartialEq for Buff { fn eq(&self, other: &Self) -> bool { - self.data.strength == other.data.strength || self.time == other.time + self.data.strength == other.data.strength && self.time == other.time } } @@ -236,7 +234,6 @@ impl Buffs { } self.kinds.remove(&kind); } - self.sort_kind(kind); } pub fn force_insert(&mut self, id: BuffId, buff: Buff) -> BuffId { diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 3be36dd33e..7289bfbdf7 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -70,7 +70,9 @@ impl Health { self.last_change = (0.0, change); } - // This is private because max hp is based on the level + // 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); diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index ee3dbcdb93..3cff89f8fc 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -30,6 +30,7 @@ impl<'a> System<'a> for Sys { 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() { let mut expired_buffs = Vec::<BuffId>::new(); for (id, buff) in buff_comp.buffs.iter_mut() { @@ -138,6 +139,8 @@ impl<'a> System<'a> for Sys { }); } } + // Turned back to true buffs.set_event_emission(true); + stats.set_event_emission(true); } }