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.

This commit is contained in:
Sam 2020-10-18 22:00:35 -05:00
parent fdf8decb18
commit 337cf6e137
20 changed files with 351 additions and 337 deletions

View File

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Gave the axe a third attack - Gave the axe a third attack
- A new secondary charged melee attack for the hammer - A new secondary charged melee attack for the hammer
- Added Dutch translations - Added Dutch translations
- Buff system
### Changed ### Changed

2
Cargo.lock generated
View File

@ -4957,7 +4957,6 @@ dependencies = [
"guillotiere", "guillotiere",
"hashbrown 0.7.2", "hashbrown 0.7.2",
"image", "image",
"inline_tweak",
"itertools", "itertools",
"native-dialog", "native-dialog",
"num 0.2.1", "num 0.2.1",
@ -4989,7 +4988,6 @@ name = "veloren-voxygen-anim"
version = "0.7.0" version = "0.7.0"
dependencies = [ dependencies = [
"find_folder", "find_folder",
"inline_tweak",
"lazy_static", "lazy_static",
"libloading 0.6.3", "libloading 0.6.3",
"notify", "notify",

View File

@ -37,7 +37,7 @@ use common::{
terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize}, terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
}; };
use comp::BuffId; use comp::BuffKind;
use futures_executor::block_on; use futures_executor::block_on;
use futures_timer::Delay; use futures_timer::Delay;
use futures_util::{select, FutureExt}; use futures_util::{select, FutureExt};
@ -632,7 +632,7 @@ impl Client {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); 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( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff(
buff_id, buff_id,
))); )));
@ -971,6 +971,11 @@ impl Client {
// 4) Tick the client's LocalState // 4) Tick the client's LocalState
self.state.tick(dt, add_foreign_systems, true); 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 // 5) Terrain
let pos = self let pos = self

View File

@ -4,17 +4,12 @@ use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use std::time::Duration; use std::time::Duration;
/// De/buff ID. /// De/buff Kind.
/// ID can be independant of an actual type/config of a `BuffEffect`. /// This is used to determine what effects a buff will have, as well as
/// Therefore, information provided by `BuffId` can be incomplete/incorrect. /// determine the strength and duration of the buff effects using the internal
/// /// values
/// 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, Copy, PartialEq, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
pub enum BuffId { pub enum BuffKind {
/// Restores health/time for some period /// Restores health/time for some period
Regeneration { Regeneration {
strength: f32, strength: f32,
@ -31,22 +26,21 @@ pub enum BuffId {
} }
/// De/buff category ID. /// 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). /// positive/negative buffs).
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum BuffCategoryId { pub enum BuffCategoryId {
// Buff and debuff get added in builder function based off of the buff kind
Debuff,
Buff,
Natural, Natural,
Physical, Physical,
Magical, Magical,
Divine, Divine,
Debuff,
Buff,
PersistOnDeath, PersistOnDeath,
} }
/// Data indicating and configuring behaviour of a de/buff. /// Data indicating and configuring behaviour of a de/buff.
///
/// NOTE: Contents of this enum are WIP/Placeholder
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum BuffEffect { pub enum BuffEffect {
/// Periodically damages or heals entity /// Periodically damages or heals entity
@ -58,23 +52,16 @@ pub enum BuffEffect {
/// Actual de/buff. /// Actual de/buff.
/// Buff can timeout after some time if `time` is Some. If `time` is None, /// 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 /// Buff will last indefinitely, until removed manually (by some action, like
/// uncursing). The `time` field might be moved into the `Buffs` component /// uncursing).
/// (so that `Buff` does not own this information).
/// ///
/// Buff has an id and data, which can be independent on each other. /// Buff has a kind, which is used to determine the effects in a builder
/// This makes it hard to create buff stacking "helpers", as the system /// function.
/// 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, /// To provide more classification info when needed,
/// buff can be in one or more buff category. /// 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Buff { pub struct Buff {
pub id: BuffId, pub kind: BuffKind,
pub cat_ids: Vec<BuffCategoryId>, pub cat_ids: Vec<BuffCategoryId>,
pub time: Option<Duration>, pub time: Option<Duration>,
pub effects: Vec<BuffEffect>, pub effects: Vec<BuffEffect>,
@ -88,10 +75,12 @@ pub enum BuffChange {
/// Adds this buff. /// Adds this buff.
Add(Buff), Add(Buff),
/// Removes all buffs with this ID. /// 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 /// Removes buffs of these indices (first vec is for active buffs, second is
/// for inactive buffs) /// for inactive buffs), should only be called when buffs expire
RemoveByIndex(Vec<usize>, Vec<usize>), RemoveExpiredByIndex(Vec<usize>, Vec<usize>),
/// Removes buffs of these categories (first vec is of categories of which /// 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 /// 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) /// required, third vec is of categories that will not be removed)
@ -139,37 +128,40 @@ pub struct Buffs {
pub inactive_buffs: Vec<Buff>, 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 { impl Buff {
pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { /// Builder function for buffs
let (effects, time) = match id { pub fn new(kind: BuffKind, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self {
BuffId::Bleeding { strength, duration } => ( let mut cat_ids = cat_ids;
let (effects, time) = match kind {
BuffKind::Bleeding { strength, duration } => {
cat_ids.push(BuffCategoryId::Debuff);
(
vec![BuffEffect::HealthChangeOverTime { vec![BuffEffect::HealthChangeOverTime {
rate: -strength, rate: -strength,
accumulated: 0.0, accumulated: 0.0,
}], }],
duration, duration,
), )
BuffId::Regeneration { strength, duration } => ( },
BuffKind::Regeneration { strength, duration } => {
cat_ids.push(BuffCategoryId::Buff);
(
vec![BuffEffect::HealthChangeOverTime { vec![BuffEffect::HealthChangeOverTime {
rate: strength, rate: strength,
accumulated: 0.0, accumulated: 0.0,
}], }],
duration, duration,
), )
BuffId::Cursed { duration } => ( },
BuffKind::Cursed { duration } => {
cat_ids.push(BuffCategoryId::Debuff);
(
vec![BuffEffect::NameChange { vec![BuffEffect::NameChange {
prefix: String::from("Cursed "), prefix: String::from("Cursed "),
}], }],
duration, duration,
), )
},
}; };
assert_eq!( assert_eq!(
cat_ids cat_ids
@ -179,7 +171,7 @@ impl Buff {
"Buff must have either buff or debuff category." "Buff must have either buff or debuff category."
); );
Buff { Buff {
id, kind,
cat_ids, cat_ids,
time, time,
effects, effects,

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{inventory::slot::Slot, BuffId}, comp::{inventory::slot::Slot, BuffKind},
sync::Uid, sync::Uid,
util::Dir, util::Dir,
}; };
@ -41,7 +41,7 @@ pub enum ControlEvent {
Unmount, Unmount,
InventoryManip(InventoryManip), InventoryManip(InventoryManip),
GroupManip(GroupManip), GroupManip(GroupManip),
RemoveBuff(BuffId), RemoveBuff(BuffKind),
Respawn, Respawn,
} }

View File

@ -32,7 +32,7 @@ pub use body::{
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object, biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData, 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 character_state::{Attacking, CharacterState, StateUpdate};
pub use chat::{ pub use chat::{
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,

View File

@ -684,7 +684,7 @@ impl<'a> System<'a> for Sys {
for (_invite, /*alignment,*/ agent, controller) in for (_invite, /*alignment,*/ agent, controller) in
(&invites, /*&alignments,*/ &mut agents, &mut controllers).join() (&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 { if accept {
// Clear agent comp // Clear agent comp
*agent = Agent::default(); *agent = Agent::default();

View File

@ -23,33 +23,62 @@ impl<'a> System<'a> for Sys {
fn run(&mut self, (dt, server_bus, uids, stats, mut buffs): Self::SystemData) { fn run(&mut self, (dt, server_bus, uids, stats, mut buffs): Self::SystemData) {
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() { // Set to false to avoid spamming server
let buff_comp = buffs.get_mut_unchecked(); 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) = let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) =
(Vec::<usize>::new(), Vec::<usize>::new()); (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() { for (i, active_buff) in buff_comp.active_buffs.iter_mut().enumerate() {
// First, tick the buff and subtract delta from it // Tick the buff and subtract delta from it
// and return how much "real" time the buff took (for tick independence). if let Some(remaining_time) = &mut active_buff.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 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. // The buff still continues.
*remaining_time -= Duration::from_secs_f32(dt.0); *remaining_time -= Duration::from_secs_f32(dt.0);
dur.as_secs_f32()
} else { } else {
// The buff has expired. // The buff has expired.
// Remove it. // Remove it.
active_buff_indices_for_removal.push(i); 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 { let buff_owner = if let BuffSource::Character { by: owner } = active_buff.source {
Some(owner) Some(owner)
} else { } else {
@ -60,12 +89,13 @@ impl<'a> System<'a> for Sys {
match effect { match effect {
// Only add an effect here if it is continuous or it is not immediate // Only add an effect here if it is continuous or it is not immediate
BuffEffect::HealthChangeOverTime { rate, accumulated } => { BuffEffect::HealthChangeOverTime { rate, accumulated } => {
*accumulated += *rate * buff_delta; *accumulated += *rate * dt.0;
// Apply damage only once a second (with a minimum of 1 damage), or when a buff is removed // 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) if accumulated.abs() > rate.abs().max(10.0)
|| active_buff_indices_for_removal || active_buff
.iter() .time
.any(|index| *index == i) .map_or(false, |dur| dur == Duration::default())
{ {
let cause = if *accumulated > 0.0 { let cause = if *accumulated > 0.0 {
HealthSource::Healing { by: buff_owner } 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 { if stat.is_dead {
server_emitter.emit(ServerEvent::Buff { server_emitter.emit(ServerEvent::Buff {
uid: *uid, uid: *uid,

View File

@ -9,6 +9,7 @@ use crate::{
sync::Uid, sync::Uid,
util::Dir, util::Dir,
}; };
use rand::{thread_rng, Rng};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
use std::time::Duration; use std::time::Duration;
use vek::*; use vek::*;
@ -151,37 +152,21 @@ impl<'a> System<'a> for Sys {
cause, cause,
}, },
}); });
// Test for server event of buff, remove before merging // 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 { server_emitter.emit(ServerEvent::Buff {
uid: *uid_b, uid: *uid_b,
buff_change: buff::BuffChange::Add(buff::Buff::new( buff_change: buff::BuffChange::Add(buff::Buff::new(
buff::BuffId::Bleeding { buff::BuffKind::Bleeding {
strength: attack.base_damage as f32, strength: attack.base_damage as f32 / 10.0,
duration: Some(Duration::from_secs(30)), duration: Some(Duration::from_secs(10)),
}, },
vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], vec![buff::BuffCategoryId::Physical],
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 }, buff::BuffSource::Character { by: *uid },
)), )),
}); });
}
attack.hit_count += 1; attack.hit_count += 1;
} }
if attack.knockback != 0.0 && damage.healthchange != 0.0 { if attack.knockback != 0.0 && damage.healthchange != 0.0 {

View File

@ -86,7 +86,7 @@ impl<'a> System<'a> for Sys {
ControlEvent::RemoveBuff(buff_id) => { ControlEvent::RemoveBuff(buff_id) => {
server_emitter.emit(ServerEvent::Buff { server_emitter.emit(ServerEvent::Buff {
uid: *uid, uid: *uid,
buff_change: BuffChange::RemoveById(buff_id), buff_change: BuffChange::RemoveFromClient(buff_id),
}); });
}, },
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),

View File

@ -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, or move active buff to
// inactive buffs and add new buff to active // inactive buffs and add new buff to active
// buffs. // buffs.
if discriminant(&active_buff.id) == discriminant(&new_buff.id) { if discriminant(&active_buff.kind) == discriminant(&new_buff.kind) {
duplicate_existed = true; duplicate_existed = true;
// Determines if active buff is weaker than newer buff // Determines if active buff is weaker than newer buff
if determine_replace_active_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; active_buff_indices_for_removal = active_indices;
inactive_buff_indices_for_removal = inactive_indices; inactive_buff_indices_for_removal = inactive_indices;
}, },
BuffChange::RemoveById(id) => { BuffChange::RemoveByKind(kind) => {
let some_predicate = |current_id: &buff::BuffId| *current_id == id;
for (i, buff) in buffs.active_buffs.iter().enumerate() { 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); active_buff_indices_for_removal.push(i);
} }
} }
for (i, buff) in buffs.inactive_buffs.iter().enumerate() { 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); 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() { while !active_buff_indices_for_removal.is_empty() {
if let Some(i) = active_buff_indices_for_removal.pop() { if let Some(i) = active_buff_indices_for_removal.pop() {
let buff = buffs.active_buffs.remove(i); 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)); 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 // Checks after buffs are removed so that it doesn't grab incorrect
// index // 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 // Checks to verify that there are no active buffs with the same id
if buffs if buffs
.active_buffs .active_buffs
.iter() .iter()
.any(|buff| discriminant(&buff.id) == discriminant(&buff_id)) .any(|buff| discriminant(&buff.kind) == discriminant(&buff_kind))
{ {
continue; continue;
} }
let mut new_active_buff = None::<buff::Buff>; let mut new_active_buff = None::<buff::Buff>;
let mut replacement_buff_index = 0; let mut replacement_buff_index = 0;
for (i, inactive_buff) in buffs.inactive_buffs.iter().enumerate() { 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 let Some(ref buff) = new_active_buff {
if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) { if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) {
new_active_buff = Some(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 { fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool {
use buff::BuffId; use buff::BuffKind;
match new_buff.id { match new_buff.kind {
BuffId::Bleeding { BuffKind::Bleeding {
strength: new_strength, strength: new_strength,
duration: new_duration, duration: new_duration,
} => { } => {
if let BuffId::Bleeding { if let BuffKind::Bleeding {
strength: active_strength, strength: active_strength,
duration: _, duration: _,
} = active_buff.id } = active_buff.kind
{ {
new_strength > active_strength new_strength > active_strength
|| (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 false
} }
}, },
BuffId::Regeneration { BuffKind::Regeneration {
strength: new_strength, strength: new_strength,
duration: new_duration, duration: new_duration,
} => { } => {
if let BuffId::Regeneration { if let BuffKind::Regeneration {
strength: active_strength, strength: active_strength,
duration: _, duration: _,
} = active_buff.id } = active_buff.kind
{ {
new_strength > active_strength new_strength > active_strength
|| (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 false
} }
}, },
BuffId::Cursed { BuffKind::Cursed {
duration: new_duration, duration: new_duration,
} => new_duration.map_or(true, |new_dur| { } => new_duration.map_or(true, |new_dur| {
active_buff.time.map_or(false, |act_dur| new_dur > act_dur) active_buff.time.map_or(false, |act_dur| new_dur > act_dur)

View File

@ -74,7 +74,6 @@ treeculler = "0.1.0"
uvth = "3.1.1" uvth = "3.1.1"
# vec_map = { version = "0.8.2" } # vec_map = { version = "0.8.2" }
const-tweaker = {version = "0.3.1", optional = true} const-tweaker = {version = "0.3.1", optional = true}
inline_tweak = "1.0.2"
itertools = "0.9.0" itertools = "0.9.0"
# Logging # Logging

View File

@ -20,7 +20,6 @@ default = ["be-dyn-lib", "simd"]
[dependencies] [dependencies]
common = {package = "veloren-common", path = "../../../common"} common = {package = "veloren-common", path = "../../../common"}
find_folder = {version = "0.3.0", optional = true} find_folder = {version = "0.3.0", optional = true}
inline_tweak = "1.0.2"
lazy_static = {version = "1.4.0", optional = true} lazy_static = {version = "1.4.0", optional = true}
libloading = {version = "0.6.2", optional = true} libloading = {version = "0.6.2", optional = true}
notify = {version = "5.0.0-pre.2", optional = true} notify = {version = "5.0.0-pre.2", optional = true}

View File

@ -9,14 +9,13 @@ use crate::{
GlobalState, GlobalState,
}; };
use common::comp::{BuffId, Buffs}; use common::comp::{BuffKind, Buffs};
use conrod_core::{ use conrod_core::{
color, color,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*;
use std::time::Duration;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
align, align,
@ -77,7 +76,7 @@ pub struct State {
} }
pub enum Event { pub enum Event {
RemoveBuff(BuffId), RemoveBuff(BuffKind),
} }
impl<'a> Widget for BuffsBar<'a> { impl<'a> Widget for BuffsBar<'a> {
@ -123,7 +122,7 @@ impl<'a> Widget for BuffsBar<'a> {
if let BuffPosition::Bar = buff_position { if let BuffPosition::Bar = buff_position {
// Alignment // Alignment
Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) 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); .set(state.ids.align, ui);
Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT)
.bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0)
@ -177,14 +176,18 @@ impl<'a> Widget for BuffsBar<'a> {
) )
.enumerate() .enumerate()
.for_each(|(i, ((id, timer_id), buff))| { .for_each(|(i, ((id, timer_id), buff))| {
let max_duration = match buff.id { let max_duration = match buff.kind {
BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), BuffKind::Bleeding { duration, .. } => duration,
_ => 10.0, BuffKind::Regeneration { duration, .. } => duration,
BuffKind::Cursed { duration } => duration,
}; };
let current_duration = buff.dur; 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 duration_percentage = current_duration.map_or(1000.0, |cur| {
let buff_img = match buff.id { max_duration
BuffId::Regeneration { .. } => self.imgs.buff_plus_0, .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
}) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.kind {
BuffKind::Regeneration { .. } => self.imgs.buff_plus_0,
_ => self.imgs.missing_icon, _ => self.imgs.missing_icon,
}; };
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0); let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
@ -197,30 +200,32 @@ impl<'a> Widget for BuffsBar<'a> {
0.0 + x as f64 * (21.0), 0.0 + x as f64 * (21.0),
); );
buff_widget buff_widget
.color(if current_duration < 10.0 { .color(
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
Some(pulsating_col) Some(pulsating_col)
} else { } else {
Some(norm_col) Some(norm_col)
}) },
)
.set(id, ui); .set(id, ui);
// Create Buff tooltip // Create Buff tooltip
let title = match buff.id { let title = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.title.heal_test") 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() "Permanent".to_string()
} else { } 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 click_to_remove = format!("<{}>", &localized_strings.get("buff.remove"));
let desc_txt = match buff.id { let desc_txt = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.desc.heal_test") 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); let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove);
// Timer overlay // Timer overlay
@ -247,7 +252,7 @@ impl<'a> Widget for BuffsBar<'a> {
.set(timer_id, ui) .set(timer_id, ui)
.was_clicked() .was_clicked()
{ {
event.push(Event::RemoveBuff(buff.id)); event.push(Event::RemoveBuff(buff.kind));
}; };
}); });
// Create Debuff Widgets // Create Debuff Widgets
@ -266,21 +271,19 @@ impl<'a> Widget for BuffsBar<'a> {
) )
.enumerate() .enumerate()
.for_each(|(i, ((id, timer_id), debuff))| { .for_each(|(i, ((id, timer_id), debuff))| {
let max_duration = match debuff.id { let max_duration = match debuff.kind {
BuffId::Bleeding { duration, .. } => { BuffKind::Bleeding { duration, .. } => duration,
duration.unwrap_or(Duration::from_secs(60)).as_secs_f32() BuffKind::Regeneration { duration, .. } => duration,
}, BuffKind::Cursed { duration } => duration,
BuffId::Cursed { duration, .. } => {
duration.unwrap_or(Duration::from_secs(60)).as_secs_f32()
},
_ => 10.0,
}; };
let current_duration = debuff.dur; 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 duration_percentage = current_duration.map_or(1000.0, |cur| {
let debuff_img = match debuff.id { max_duration
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
BuffId::Cursed { .. } => self.imgs.debuff_skull_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, _ => self.imgs.missing_icon,
}; };
let debuff_widget = Image::new(debuff_img).w_h(20.0, 20.0); 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 debuff_widget
.color(if current_duration < 10.0 { .color(
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
Some(pulsating_col) Some(pulsating_col)
} else { } else {
Some(norm_col) Some(norm_col)
}) },
)
.set(id, ui); .set(id, ui);
// Create Debuff tooltip // Create Debuff tooltip
let title = match debuff.id { let title = match debuff.kind {
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.title.bleed_test") 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() "Permanent".to_string()
} else { } else {
format!("Remaining: {:.0}s", current_duration) format!("Remaining: {:.0}s", current_duration.unwrap().as_secs_f32())
}; };
let desc_txt = match debuff.id { let desc_txt = match debuff.kind {
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.desc.bleed_test") 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); let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(match duration_percentage as u64 { Image::new(match duration_percentage as u64 {
@ -346,7 +351,7 @@ impl<'a> Widget for BuffsBar<'a> {
if let BuffPosition::Map = buff_position { if let BuffPosition::Map = buff_position {
// Alignment // Alignment
Rectangle::fill_with([210.0, 210.0], color::TRANSPARENT) 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); .set(state.ids.align, ui);
// Buffs and Debuffs // Buffs and Debuffs
@ -376,18 +381,21 @@ impl<'a> Widget for BuffsBar<'a> {
.zip(buffs.active_buffs.iter().map(get_buff_info)) .zip(buffs.active_buffs.iter().map(get_buff_info))
.enumerate() .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 { let max_duration = match buff.kind {
BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(), BuffKind::Bleeding { duration, .. } => duration,
BuffId::Bleeding { duration, .. } => duration.unwrap().as_secs_f32(), BuffKind::Regeneration { duration, .. } => duration,
_ => 10e6, BuffKind::Cursed { duration } => duration,
}; };
let current_duration = buff.dur; let current_duration = buff.dur;
// Percentage to determine which frame of the timer overlay is displayed // 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.map_or(1000.0, |cur| {
let buff_img = match buff.id { max_duration
BuffId::Regeneration { .. } => self.imgs.buff_plus_0, .map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, }) as u32;
BuffId::Cursed { .. } => self.imgs.debuff_skull_0, 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); let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 6 slots // Sort buffs into rows of 6 slots
@ -399,41 +407,43 @@ impl<'a> Widget for BuffsBar<'a> {
0.0 + x as f64 * (42.0), 0.0 + x as f64 * (42.0),
); );
buff_widget buff_widget
.color(if current_duration < 10.0 { .color(
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
Some(pulsating_col) Some(pulsating_col)
} else { } else {
Some(norm_col) Some(norm_col)
}) },
)
.set(id, ui); .set(id, ui);
// Create Buff tooltip // Create Buff tooltip
let title = match buff.id { let title = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.title.heal_test") localized_strings.get("buff.title.heal_test")
}, },
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.title.bleed_test") 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() "".to_string()
} else { } 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 click_to_remove = format!("<{}>", &localized_strings.get("buff.remove"));
let desc_txt = match buff.id { let desc_txt = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.desc.heal_test") localized_strings.get("buff.desc.heal_test")
}, },
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.desc.bleed_test") 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 { let desc = if buff.is_buff {
format!("{}\n\n{}", desc_txt, click_to_remove) format!("{}\n\n{}", desc_txt, click_to_remove)
} else { } else {
format!("{}", desc_txt) desc_txt.to_string()
}; };
// Timer overlay // Timer overlay
if Button::image(match duration_percentage as u64 { if Button::image(match duration_percentage as u64 {
@ -463,13 +473,11 @@ impl<'a> Widget for BuffsBar<'a> {
.set(timer_id, ui) .set(timer_id, ui)
.was_clicked() .was_clicked()
{ {
if buff.is_buff { event.push(Event::RemoveBuff(buff.kind));
event.push(Event::RemoveBuff(buff.id));
}
} }
Text::new(&remaining_time) Text::new(&remaining_time)
.down_from(timer_id, tweak!(1.0)) .down_from(timer_id, 1.0)
.font_size(self.fonts.cyri.scale(tweak!(10))) .font_size(self.fonts.cyri.scale(10))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.graphics_for(timer_id) .graphics_for(timer_id)
.color(TEXT_COLOR) .color(TEXT_COLOR)

View File

@ -14,7 +14,7 @@ use crate::{
}; };
use client::{self, Client}; use client::{self, Client};
use common::{ use common::{
comp::{group::Role, BuffId, Stats}, comp::{group::Role, BuffKind, Stats},
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
}; };
use conrod_core::{ use conrod_core::{
@ -23,7 +23,6 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Scrollbar, Text}, widget::{self, Button, Image, Rectangle, Scrollbar, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*;
use specs::{saveload::MarkerAllocator, WorldExt}; use specs::{saveload::MarkerAllocator, WorldExt};
widget_ids! { widget_ids! {
pub struct Ids { pub struct Ids {
@ -329,7 +328,7 @@ impl<'a> Widget for Group<'a> {
.ecs() .ecs()
.read_resource::<common::sync::UidAllocator>(); .read_resource::<common::sync::UidAllocator>();
let offset = if self.global_state.settings.gameplay.toggle_debug { let offset = if self.global_state.settings.gameplay.toggle_debug {
tweak!(320.0) 320.0
} else { } else {
110.0 110.0
}; };
@ -467,21 +466,23 @@ impl<'a> Widget for Group<'a> {
.skip(total_buff_count - buff_count) .skip(total_buff_count - buff_count)
.zip(buffs.active_buffs.iter().map(get_buff_info)) .zip(buffs.active_buffs.iter().map(get_buff_info))
.for_each(|((id, timer_id), buff)| { .for_each(|((id, timer_id), buff)| {
let max_duration = match buff.id { let max_duration = match buff.kind {
BuffId::Regeneration { duration, .. } => { BuffKind::Bleeding { duration, .. } => duration,
duration.unwrap().as_secs_f32() BuffKind::Regeneration { duration, .. } => duration,
}, BuffKind::Cursed { duration } => duration,
_ => 10.0,
}; };
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani); 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 norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
let current_duration = buff.dur; let current_duration = buff.dur;
let duration_percentage = let duration_percentage = current_duration.map_or(1000.0, |cur| {
(current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed max_duration.map_or(1000.0, |max| {
let buff_img = match buff.id { cur.as_secs_f32() / max.as_secs_f32() * 1000.0
BuffId::Regeneration { .. } => self.imgs.buff_plus_0, })
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, }) as u32; // Percentage to determine which frame of the timer overlay is displayed
BuffId::Cursed { .. } => self.imgs.debuff_skull_0, 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 = Image::new(buff_img).w_h(15.0, 15.0);
let buff_widget = if let Some(id) = prev_id { let buff_widget = if let Some(id) = prev_id {
@ -495,35 +496,42 @@ impl<'a> Widget for Group<'a> {
}; };
prev_id = Some(id); prev_id = Some(id);
buff_widget buff_widget
.color(if current_duration < 10.0 { .color(
if current_duration
.map_or(false, |cur| cur.as_secs_f32() < 10.0)
{
Some(pulsating_col) Some(pulsating_col)
} else { } else {
Some(norm_col) Some(norm_col)
}) },
)
.set(id, ui); .set(id, ui);
// Create Buff tooltip // Create Buff tooltip
let title = match buff.id { let title = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.title.heal_test") localized_strings.get("buff.title.heal_test")
}, },
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.title.bleed_test") 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() "Permanent".to_string()
} else { } else {
format!("Remaining: {:.0}s", current_duration) format!(
"Remaining: {:.0}s",
current_duration.unwrap().as_secs_f32()
)
}; };
let desc_txt = match buff.id { let desc_txt = match buff.kind {
BuffId::Regeneration { .. } => { BuffKind::Regeneration { .. } => {
*&localized_strings.get("buff.desc.heal_test") localized_strings.get("buff.desc.heal_test")
}, },
BuffId::Bleeding { .. } => { BuffKind::Bleeding { .. } => {
*&localized_strings.get("debuff.desc.bleed_test") 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); let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(match duration_percentage as u64 { Image::new(match duration_percentage as u64 {

View File

@ -62,7 +62,7 @@ use common::{
comp, comp,
comp::{ comp::{
item::{ItemDesc, Quality}, item::{ItemDesc, Quality},
BuffId, BuffKind,
}, },
span, span,
sync::Uid, sync::Uid,
@ -274,9 +274,9 @@ widget_ids! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct BuffInfo { pub struct BuffInfo {
id: comp::BuffId, kind: comp::BuffKind,
is_buff: bool, is_buff: bool,
dur: f32, dur: Option<Duration>,
} }
pub struct DebugInfo { pub struct DebugInfo {
@ -364,7 +364,7 @@ pub enum Event {
KickMember(common::sync::Uid), KickMember(common::sync::Uid),
LeaveGroup, LeaveGroup,
AssignLeader(common::sync::Uid), AssignLeader(common::sync::Uid),
RemoveBuff(BuffId), RemoveBuff(BuffKind),
} }
// TODO: Are these the possible layouts we want? // TODO: Are these the possible layouts we want?
@ -1145,7 +1145,7 @@ impl Hud {
let speech_bubbles = &self.speech_bubbles; let speech_bubbles = &self.speech_bubbles;
// Render overhead name tags and health bars // 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, &entities,
&pos, &pos,
interpolated.maybe(), interpolated.maybe(),
@ -2728,14 +2728,11 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color {
// Get info about applied buffs // Get info about applied buffs
fn get_buff_info(buff: &comp::Buff) -> BuffInfo { fn get_buff_info(buff: &comp::Buff) -> BuffInfo {
BuffInfo { BuffInfo {
id: buff.id, kind: buff.kind,
is_buff: buff is_buff: buff
.cat_ids .cat_ids
.iter() .iter()
.any(|cat| *cat == comp::BuffCategoryId::Buff), .any(|cat| *cat == comp::BuffCategoryId::Buff),
dur: buff dur: buff.time,
.time
.map(|dur| dur.as_secs_f32())
.unwrap_or(10e6 as f32),
} }
} }

View File

@ -8,14 +8,13 @@ use crate::{
settings::GameplaySettings, settings::GameplaySettings,
ui::{fonts::ConrodVoxygenFonts, Ingameable}, 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::{ use conrod_core::{
color, color,
position::Align, position::Align,
widget::{self, Image, Rectangle, Text}, widget::{self, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*;
const MAX_BUBBLE_WIDTH: f64 = 250.0; const MAX_BUBBLE_WIDTH: f64 = 250.0;
widget_ids! { widget_ids! {
@ -55,11 +54,6 @@ widget_ids! {
} }
} }
/*pub struct BuffInfo {
id: comp::BuffId,
dur: f32,
}*/
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Info<'a> { pub struct Info<'a> {
pub name: &'a str, pub name: &'a str,
@ -211,8 +205,8 @@ impl<'a> Widget for Overhead<'a> {
// Buffs // Buffs
// Alignment // Alignment
let buff_count = buffs.active_buffs.len().min(11); let buff_count = buffs.active_buffs.len().min(11);
Rectangle::fill_with([tweak!(168.0), tweak!(100.0)], color::TRANSPARENT) Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
.x_y(-1.0, name_y + tweak!(60.0)) .x_y(-1.0, name_y + 60.0)
.parent(id) .parent(id)
.set(state.ids.buffs_align, ui); .set(state.ids.buffs_align, ui);
@ -239,18 +233,21 @@ impl<'a> Widget for Overhead<'a> {
.enumerate() .enumerate()
.for_each(|(i, ((id, timer_id), buff))| { .for_each(|(i, ((id, timer_id), buff))| {
// Limit displayed buffs // Limit displayed buffs
let max_duration = match buff.id { let max_duration = match buff.kind {
BuffId::Regeneration { duration, .. } => { BuffKind::Bleeding { duration, .. } => duration,
duration.unwrap().as_secs_f32() BuffKind::Regeneration { duration, .. } => duration,
}, BuffKind::Cursed { duration } => duration,
_ => 10.0,
}; };
let current_duration = buff.dur; 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 duration_percentage = current_duration.map_or(1000.0, |cur| {
let buff_img = match buff.id { max_duration.map_or(1000.0, |max| {
BuffId::Regeneration { .. } => self.imgs.buff_plus_0, cur.as_secs_f32() / max.as_secs_f32() * 1000.0
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0, })
BuffId::Cursed { .. } => self.imgs.debuff_skull_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); let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 5 slots // Sort buffs into rows of 5 slots
@ -262,11 +259,13 @@ impl<'a> Widget for Overhead<'a> {
0.0 + x as f64 * (21.0), 0.0 + x as f64 * (21.0),
); );
buff_widget buff_widget
.color(if current_duration < 10.0 { .color(
if current_duration.map_or(false, |cur| cur.as_secs_f32() < 10.0) {
Some(pulsating_col) Some(pulsating_col)
} else { } else {
Some(norm_col) Some(norm_col)
}) },
)
.set(id, ui); .set(id, ui);
Image::new(match duration_percentage as u64 { Image::new(match duration_percentage as u64 {

View File

@ -20,7 +20,6 @@ use conrod_core::{
}; };
use core::convert::TryFrom; use core::convert::TryFrom;
use inline_tweak::*;
use itertools::Itertools; use itertools::Itertools;
use std::iter::once; use std::iter::once;
use winit::monitor::VideoMode; use winit::monitor::VideoMode;
@ -2709,8 +2708,8 @@ impl<'a> Widget for SettingsWindow<'a> {
}); });
}; };
for (i, language) in language_list.iter().enumerate() { for (i, language) in language_list.iter().enumerate() {
let button_w = tweak!(400.0); let button_w = 400.0;
let button_h = tweak!(50.0); let button_h = 50.0;
let button = Button::image(if selected_language == &language.language_identifier { let button = Button::image(if selected_language == &language.language_identifier {
self.imgs.selection self.imgs.selection
} else { } else {
@ -2727,7 +2726,7 @@ impl<'a> Widget for SettingsWindow<'a> {
.hover_image(self.imgs.selection_hover) .hover_image(self.imgs.selection_hover)
.press_image(self.imgs.selection_press) .press_image(self.imgs.selection_press)
.label_color(TEXT_COLOR) .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_font_id(self.fonts.cyri.conrod_id)
.label_y(conrod_core::position::Relative::Scalar(2.0)) .label_y(conrod_core::position::Relative::Scalar(2.0))
.set(state.ids.language_list[i], ui) .set(state.ids.language_list[i], ui)

View File

@ -27,7 +27,6 @@ use conrod_core::{
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use vek::*; use vek::*;
@ -347,9 +346,9 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.bg, ui); .set(state.ids.bg, ui);
// Level // Level
let lvl_size = match self.stats.level.level() { let lvl_size = match self.stats.level.level() {
11..=99 => tweak!(13), 11..=99 => 13,
100..=999 => tweak!(10), 100..=999 => 10,
_ => tweak!(14), _ => 14,
}; };
Text::new(&level) Text::new(&level)
.mid_top_with_margin_on(state.ids.bg, 3.0) .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 // TODO Don't show this if key bindings are changed
Image::new(self.imgs.m1_ico) Image::new(self.imgs.m1_ico)
.w_h(16.0, 18.0) .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); .set(state.ids.m1_ico, ui);
Image::new(self.imgs.m2_ico) Image::new(self.imgs.m2_ico)
.w_h(16.0, 18.0) .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); .set(state.ids.m2_ico, ui);
// Buffs // Buffs

View File

@ -18,7 +18,6 @@ use conrod_core::{
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };
use image::DynamicImage; use image::DynamicImage;
//use inline_tweak::*;
use rand::{seq::SliceRandom, thread_rng, Rng}; use rand::{seq::SliceRandom, thread_rng, Rng};
use std::time::Duration; use std::time::Duration;