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
- A new secondary charged melee attack for the hammer
- Added Dutch translations
- Buff system
### Changed

2
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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 } => (
/// 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,
),
BuffId::Regeneration { strength, duration } => (
)
},
BuffKind::Regeneration { strength, duration } => {
cat_ids.push(BuffCategoryId::Buff);
(
vec![BuffEffect::HealthChangeOverTime {
rate: strength,
accumulated: 0.0,
}],
duration,
),
BuffId::Cursed { 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,

View File

@ -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,
}

View File

@ -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,

View File

@ -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();

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) {
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
};
pre_tick - post_tick
} else {
// The buff is indefinite, and it takes full tick (delta).
dt.0
active_buff.time = Some(Duration::default());
};
}
}
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,

View File

@ -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
// 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::BuffId::Bleeding {
strength: attack.base_damage as f32,
duration: Some(Duration::from_secs(30)),
buff::BuffKind::Bleeding {
strength: attack.base_damage as f32 / 10.0,
duration: Some(Duration::from_secs(10)),
},
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],
vec![buff::BuffCategoryId::Physical],
buff::BuffSource::Character { by: *uid },
)),
});
}
attack.hit_count += 1;
}
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) => {
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)),

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 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)

View File

@ -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

View File

@ -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}

View File

@ -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 {
.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 {
.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 {
.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 {
@ -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)

View File

@ -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 {
.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 {

View File

@ -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,
}
}

View File

@ -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 {
.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 {

View File

@ -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)

View File

@ -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

View File

@ -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;