Initial implementation of buffs UI

player buffs animation

more testing debuffs

sorting and display limit fix

overhead buffs

fix

WIP buff removal function

fmt

Update buffs.rs

Now with compiling: WIP group UI buffs

positioning

Update group.rs

Update group.rs

Small optimizations.

Fixed positioning of buffs in group panel. Broke buff tooltips in group panel.

buff frame visuals

added setting for displaying buffs at minimap
This commit is contained in:
Monty Marz
2020-10-16 08:08:45 +02:00
committed by Sam
parent 007d3c09ac
commit 8fa398954d
30 changed files with 824 additions and 178 deletions

View File

@ -1,6 +1,6 @@
ItemDef( ItemDef(
name: "Apple Stick", name: "Apple Stick",
description: "Restores 20 Health", description: "Restores 25 Health",
kind: Consumable( kind: Consumable(
kind: "AppleStick", kind: "AppleStick",
effect: Health(( effect: Health((

BIN
assets/voxygen/element/animation/buff_frame/1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/2.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/4.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/6.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/7.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/animation/buff_frame/8.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/de_buffs/debuff_bleed_0.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -291,6 +291,8 @@ magically infused items?"#,
"hud.settings.transparency": "Transparency", "hud.settings.transparency": "Transparency",
"hud.settings.hotbar": "Hotbar", "hud.settings.hotbar": "Hotbar",
"hud.settings.toggle_shortcuts": "Toggle Shortcuts", "hud.settings.toggle_shortcuts": "Toggle Shortcuts",
"hud.settings.buffs_skillbar": "Buffs at Skillbar",
"hud.settings.buffs_mmap": "Buffs at Minimap",
"hud.settings.toggle_bar_experience": "Toggle Experience Bar", "hud.settings.toggle_bar_experience": "Toggle Experience Bar",
"hud.settings.scrolling_combat_text": "Scrolling Combat Text", "hud.settings.scrolling_combat_text": "Scrolling Combat Text",
"hud.settings.single_damage_number": "Single Damage Numbers", "hud.settings.single_damage_number": "Single Damage Numbers",
@ -343,9 +345,9 @@ magically infused items?"#,
"hud.settings.refresh_rate": "Refresh Rate", "hud.settings.refresh_rate": "Refresh Rate",
"hud.settings.save_window_size": "Save window size", "hud.settings.save_window_size": "Save window size",
"hud.settings.lighting_rendering_mode": "Lighting Rendering Mode", "hud.settings.lighting_rendering_mode": "Lighting Rendering Mode",
"hud.settings.lighting_rendering_mode.ashikhmin": "Type A", "hud.settings.lighting_rendering_mode.ashikhmin": "Type A - High ",
"hud.settings.lighting_rendering_mode.blinnphong": "Type B", "hud.settings.lighting_rendering_mode.blinnphong": "Type B - Medium",
"hud.settings.lighting_rendering_mode.lambertian": "Type L", "hud.settings.lighting_rendering_mode.lambertian": "Type L - Cheap",
"hud.settings.shadow_rendering_mode": "Shadow Rendering Mode", "hud.settings.shadow_rendering_mode": "Shadow Rendering Mode",
"hud.settings.shadow_rendering_mode.none": "None", "hud.settings.shadow_rendering_mode.none": "None",
"hud.settings.shadow_rendering_mode.cheap": "Cheap", "hud.settings.shadow_rendering_mode.cheap": "Cheap",
@ -509,6 +511,16 @@ Protection
"esc_menu.quit_game": "Quit Game", "esc_menu.quit_game": "Quit Game",
/// End Escape Menu Section /// End Escape Menu Section
/// Buffs and Debuffs
"buff.remove": "Click to remove",
"buff.title.missing": "Missing Title",
"buff.desc.missing": "Missing Description",
// Buffs
"buff.title.heal_test": "Heal Test",
"buff.desc.heal_test": "This is a test buff to test healing.",
// Debuffs
"debuff.title.bleed_test": "Bleed Test",
"debuff.desc.bleed_test": "This is a test debuff to test bleeding.",
}, },

View File

@ -37,6 +37,7 @@ use common::{
terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize}, terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize},
vol::RectVolSize, vol::RectVolSize,
}; };
use comp::BuffId;
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};
@ -631,6 +632,12 @@ 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) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff(
buff_id,
)));
}
pub fn max_group_size(&self) -> u32 { self.max_group_size } pub fn max_group_size(&self) -> u32 { self.max_group_size }
pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> { pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> {

View File

@ -151,16 +151,10 @@ impl Buff {
pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self { pub fn new(id: BuffId, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self {
let (effects, time) = match id { let (effects, time) = match id {
BuffId::Bleeding { strength, duration } => ( BuffId::Bleeding { strength, duration } => (
vec![ vec![BuffEffect::HealthChangeOverTime {
BuffEffect::HealthChangeOverTime { rate: -strength,
rate: -strength, accumulated: 0.0,
accumulated: 0.0, }],
},
// This effect is for testing purposes
BuffEffect::NameChange {
prefix: String::from("Injured "),
},
],
duration, duration,
), ),
BuffId::Regeneration { strength, duration } => ( BuffId::Regeneration { strength, duration } => (

View File

@ -1,4 +1,8 @@
use crate::{comp::inventory::slot::Slot, sync::Uid, util::Dir}; use crate::{
comp::{inventory::slot::Slot, BuffId},
sync::Uid,
util::Dir,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage}; use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
@ -37,6 +41,7 @@ pub enum ControlEvent {
Unmount, Unmount,
InventoryManip(InventoryManip), InventoryManip(InventoryManip),
GroupManip(GroupManip), GroupManip(GroupManip),
RemoveBuff(BuffId),
Respawn, Respawn,
} }

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 = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics let accept = true; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics
if accept { if accept {
// Clear agent comp // Clear agent comp
*agent = Agent::default(); *agent = Agent::default();

View File

@ -62,7 +62,7 @@ impl<'a> System<'a> for Sys {
BuffEffect::HealthChangeOverTime { rate, accumulated } => { BuffEffect::HealthChangeOverTime { rate, accumulated } => {
*accumulated += *rate * buff_delta; *accumulated += *rate * buff_delta;
// Apply only 0.5 or higher damage // Apply only 0.5 or higher damage
if accumulated.abs() > 5.0 { if accumulated.abs() > 50.0 {
let cause = if *accumulated > 0.0 { let cause = if *accumulated > 0.0 {
HealthSource::Healing { by: buff_owner } HealthSource::Healing { by: buff_owner }
} else { } else {

View File

@ -157,12 +157,31 @@ impl<'a> System<'a> for Sys {
buff_change: buff::BuffChange::Add(buff::Buff::new( buff_change: buff::BuffChange::Add(buff::Buff::new(
buff::BuffId::Bleeding { buff::BuffId::Bleeding {
strength: attack.base_damage as f32, strength: attack.base_damage as f32,
duration: Some(Duration::from_secs(10)), duration: Some(Duration::from_secs(30)),
}, },
vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff], vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff],
buff::BuffSource::Character { by: *uid }, buff::BuffSource::Character { by: *uid },
)), )),
}); });
server_emitter.emit(ServerEvent::Buff {
uid: *uid_b,
buff_change: buff::BuffChange::Add(buff::Buff::new(
buff::BuffId::Regeneration {
strength: 100.0,
duration: Some(Duration::from_secs(60)),
},
vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Buff],
buff::BuffSource::Character { by: *uid },
)),
});
server_emitter.emit(ServerEvent::Buff {
uid: *uid_b,
buff_change: buff::BuffChange::Add(buff::Buff::new(
buff::BuffId::Cursed { duration: None },
vec![buff::BuffCategoryId::Physical, buff::BuffCategoryId::Debuff],
buff::BuffSource::Character { by: *uid },
)),
});
attack.hit_count += 1; attack.hit_count += 1;
} }
if attack.knockback != 0.0 && damage.healthchange != 0.0 { if attack.knockback != 0.0 && damage.healthchange != 0.0 {

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
slot::{EquipSlot, Slot}, slot::{EquipSlot, Slot},
CharacterState, ControlEvent, Controller, InventoryManip, BuffChange, CharacterState, ControlEvent, Controller, InventoryManip,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
@ -51,7 +51,7 @@ impl<'a> System<'a> for Sys {
span!(_guard, "run", "controller::Sys::run"); span!(_guard, "run", "controller::Sys::run");
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
for (entity, _uid, controller, character_state) in for (entity, uid, controller, character_state) in
(&entities, &uids, &mut controllers, &mut character_states).join() (&entities, &uids, &mut controllers, &mut character_states).join()
{ {
let mut inputs = &mut controller.inputs; let mut inputs = &mut controller.inputs;
@ -83,6 +83,12 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity)); server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
} }
}, },
ControlEvent::RemoveBuff(buff_id) => {
server_emitter.emit(ServerEvent::Buff {
uid: *uid,
buff_change: BuffChange::RemoveById(buff_id),
});
},
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)), ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
ControlEvent::EnableLantern => { ControlEvent::EnableLantern => {
server_emitter.emit(ServerEvent::EnableLantern(entity)) server_emitter.emit(ServerEvent::EnableLantern(entity))

View File

@ -715,6 +715,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
add_buff_effects(new_buff.clone(), stats.get_mut(entity)); add_buff_effects(new_buff.clone(), stats.get_mut(entity));
buffs.active_buffs.push(new_buff); buffs.active_buffs.push(new_buff);
} else { } else {
let mut duplicate_existed = false;
for i in 0..buffs.active_buffs.len() { for i in 0..buffs.active_buffs.len() {
let active_buff = &buffs.active_buffs[i]; let active_buff = &buffs.active_buffs[i];
// Checks if new buff has the same id as an already active buff. If it // Checks if new buff has the same id as an already active buff. If it
@ -724,6 +725,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
// inactive buffs and add new buff to active // inactive buffs and add new buff to active
// buffs. // buffs.
if discriminant(&active_buff.id) == discriminant(&new_buff.id) { if discriminant(&active_buff.id) == discriminant(&new_buff.id) {
duplicate_existed = true;
if determine_replace_active_buff( if determine_replace_active_buff(
active_buff.clone(), active_buff.clone(),
new_buff.clone(), new_buff.clone(),
@ -731,14 +733,21 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
active_buff_indices_for_removal.push(i); active_buff_indices_for_removal.push(i);
add_buff_effects(new_buff.clone(), stats.get_mut(entity)); add_buff_effects(new_buff.clone(), stats.get_mut(entity));
buffs.active_buffs.push(new_buff.clone()); buffs.active_buffs.push(new_buff.clone());
} else { } else if let Some(active_dur) = active_buff.time {
buffs.inactive_buffs.push(new_buff.clone()); if let Some(new_dur) = new_buff.time {
if new_dur > active_dur {
buffs.inactive_buffs.push(new_buff.clone());
}
} else {
buffs.inactive_buffs.push(new_buff.clone());
}
} }
} else {
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
buffs.active_buffs.push(new_buff.clone());
} }
} }
if !duplicate_existed {
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
buffs.active_buffs.push(new_buff.clone());
}
} }
}, },
BuffChange::RemoveByIndex(active_indices, inactive_indices) => { BuffChange::RemoveByIndex(active_indices, inactive_indices) => {
@ -871,7 +880,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff)
duration: _, duration: _,
} = active_buff.id } = active_buff.id
{ {
new_strength > active_strength new_strength >= active_strength
} else { } else {
false false
} }
@ -885,7 +894,7 @@ fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff)
duration: _, duration: _,
} = active_buff.id } = active_buff.id
{ {
new_strength > active_strength new_strength >= active_strength
} else { } else {
false false
} }

View File

@ -1,20 +1,23 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
TEXT_COLOR, BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR,
}; };
use crate::{ use crate::{
hud::{get_buff_info, BuffPosition},
i18n::VoxygenLocalization, i18n::VoxygenLocalization,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
GlobalState, GlobalState,
}; };
use client::Client;
use common::comp::{self, Buffs}; use crate::hud::BuffInfo;
use common::comp::{BuffId, Buffs};
use conrod_core::{ use conrod_core::{
color, color,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use inline_tweak::*; use inline_tweak::*;
use std::time::Duration;
widget_ids! { widget_ids! {
struct Ids { struct Ids {
align, align,
@ -22,51 +25,49 @@ widget_ids! {
debuffs_align, debuffs_align,
buff_test, buff_test,
debuff_test, debuff_test,
buffs[],
buff_timers[],
debuffs[],
debuff_timers[],
} }
} }
pub struct BuffInfo {
id: comp::BuffId,
is_buff: bool,
dur: f32,
}
#[derive(WidgetCommon)] #[derive(WidgetCommon)]
pub struct BuffsBar<'a> { pub struct BuffsBar<'a> {
client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts, fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
global_state: &'a GlobalState,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
buffs: &'a Buffs, buffs: &'a Buffs,
pulse: f32,
global_state: &'a GlobalState,
} }
impl<'a> BuffsBar<'a> { impl<'a> BuffsBar<'a> {
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn new( pub fn new(
client: &'a Client,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts, fonts: &'a ConrodVoxygenFonts,
global_state: &'a GlobalState,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
buffs: &'a Buffs, buffs: &'a Buffs,
pulse: f32,
global_state: &'a GlobalState,
) -> Self { ) -> Self {
Self { Self {
client,
imgs, imgs,
fonts, fonts,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
global_state,
rot_imgs, rot_imgs,
tooltip_manager, tooltip_manager,
localized_strings, localized_strings,
buffs, buffs,
pulse,
global_state,
} }
} }
} }
@ -75,8 +76,12 @@ pub struct State {
ids: Ids, ids: Ids,
} }
pub enum Event {
RemoveBuff(BuffId),
}
impl<'a> Widget for BuffsBar<'a> { impl<'a> Widget for BuffsBar<'a> {
type Event = (); type Event = Vec<Event>;
type State = State; type State = State;
type Style = (); type Style = ();
@ -91,7 +96,11 @@ impl<'a> Widget for BuffsBar<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let mut event = Vec::new();
let localized_strings = self.localized_strings; let localized_strings = self.localized_strings;
let buffs = self.buffs;
let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer
let buff_position = self.global_state.settings.gameplay.buff_position;
let buffs_tooltip = Tooltip::new({ let buffs_tooltip = Tooltip::new({
// Edge images [t, b, r, l] // Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl] // Corner images [tr, tl, br, bl]
@ -109,35 +118,230 @@ impl<'a> Widget for BuffsBar<'a> {
.desc_font_size(self.fonts.cyri.scale(12)) .desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR); .desc_text_color(TEXT_COLOR);
// Alignment if let BuffPosition::Bar = buff_position {
Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT) // Alignment
.mid_bottom_with_margin_on(ui.window, tweak!(92.0)) Rectangle::fill_with([484.0, 100.0], color::TRANSPARENT)
.set(state.ids.align, ui); .mid_bottom_with_margin_on(ui.window, tweak!(92.0))
Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) .set(state.ids.align, ui);
.bottom_left_with_margins_on(state.ids.align, 0.0, 0.0) Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT)
.set(state.ids.debuffs_align, ui); .bottom_left_with_margins_on(state.ids.align, 0.0, 0.0)
Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT) .set(state.ids.debuffs_align, ui);
.bottom_right_with_margins_on(state.ids.align, 0.0, 0.0) Rectangle::fill_with([484.0 / 2.0, 90.0], color::TRANSPARENT)
.set(state.ids.buffs_align, ui); .bottom_right_with_margins_on(state.ids.align, 0.0, 0.0)
// Test Widgets .set(state.ids.buffs_align, ui);
Image::new(self.imgs.debuff_skull_0)
.w_h(20.0, 20.0)
.bottom_right_with_margins_on(state.ids.debuffs_align, 0.0, 1.0)
.set(state.ids.debuff_test, ui);
Image::new(self.imgs.buff_plus_0)
.w_h(20.0, 20.0)
.bottom_left_with_margins_on(state.ids.buffs_align, 0.0, 1.0)
.set(state.ids.buff_test, ui);
}
}
fn get_buff_info(buff: comp::Buff) -> BuffInfo { // Buffs and Debuffs
BuffInfo { // Create two vecs to display buffs and debuffs separately
id: buff.id, let mut buffs_vec = Vec::<BuffInfo>::new();
is_buff: buff let mut debuffs_vec = Vec::<BuffInfo>::new();
.cat_ids for buff in buffs.active_buffs.clone() {
.iter() let info = get_buff_info(buff);
.any(|cat| *cat == comp::BuffCategoryId::Buff), if info.is_buff {
dur: buff.time.map(|dur| dur.as_secs_f32()).unwrap_or(100.0), buffs_vec.push(info);
} else {
debuffs_vec.push(info);
}
}
if state.ids.buffs.len() < buffs_vec.len() {
state.update(|state| {
state
.ids
.buffs
.resize(buffs_vec.len(), &mut ui.widget_id_generator())
});
};
if state.ids.debuffs.len() < debuffs_vec.len() {
state.update(|state| {
state
.ids
.debuffs
.resize(debuffs_vec.len(), &mut ui.widget_id_generator())
});
};
if state.ids.buff_timers.len() < buffs_vec.len() {
state.update(|state| {
state
.ids
.buff_timers
.resize(buffs_vec.len(), &mut ui.widget_id_generator())
});
};
if state.ids.debuff_timers.len() < debuffs_vec.len() {
state.update(|state| {
state
.ids
.debuff_timers
.resize(debuffs_vec.len(), &mut ui.widget_id_generator())
});
};
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// Create Buff Widgets
for (i, buff) in buffs_vec.iter().enumerate() {
if i < 22 {
// Limit displayed buffs
let max_duration = match buff.id {
BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(),
_ => 10.0,
};
let current_duration = buff.dur;
let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.id {
BuffId::Regeneration { .. } => self.imgs.buff_plus_0,
_ => self.imgs.missing_icon,
};
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 11 slots
let x = i % 11;
let y = i / 11;
let buff_widget = buff_widget.bottom_left_with_margins_on(
state.ids.buffs_align,
0.0 + y as f64 * (21.0),
0.0 + x as f64 * (21.0),
);
buff_widget
.color(if current_duration < 10.0 {
Some(pulsating_col)
} else {
Some(norm_col)
})
.set(state.ids.buffs[i], ui);
// Create Buff tooltip
let title = match buff.id {
BuffId::Regeneration { .. } => {
*&localized_strings.get("buff.title.heal_test")
},
_ => *&localized_strings.get("buff.title.missing"),
};
let remaining_time = if current_duration == 10e6 as f32 {
"Permanent".to_string()
} else {
format!("Remaining: {:.0}s", current_duration)
};
let click_to_remove = format!("<{}>", &localized_strings.get("buff.remove"));
let desc_txt = match buff.id {
BuffId::Regeneration { .. } => {
*&localized_strings.get("buff.desc.heal_test")
},
_ => *&localized_strings.get("buff.desc.missing"),
};
let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove);
// Timer overlay
if Button::image(match duration_percentage as u64 {
875..=1000 => self.imgs.nothing, // 8/8
750..=874 => self.imgs.buff_0, // 7/8
625..=749 => self.imgs.buff_1, // 6/8
500..=624 => self.imgs.buff_2, // 5/8
375..=499 => self.imgs.buff_3, // 4/8
250..=374 => self.imgs.buff_4, //3/8
125..=249 => self.imgs.buff_5, // 2/8
0..=124 => self.imgs.buff_6, // 1/8
_ => self.imgs.nothing,
})
.w_h(20.0, 20.0)
.middle_of(state.ids.buffs[i])
.with_tooltip(
self.tooltip_manager,
title,
&desc,
&buffs_tooltip,
BUFF_COLOR,
)
.set(state.ids.buff_timers[i], ui)
.was_clicked()
{
event.push(Event::RemoveBuff(buff.id));
};
};
}
// Create Debuff Widgets
for (i, debuff) in debuffs_vec.iter().enumerate() {
if i < 22 {
// Limit displayed buffs
let max_duration = match debuff.id {
BuffId::Bleeding { duration, .. } => {
duration.unwrap_or(Duration::from_secs(60)).as_secs_f32()
},
BuffId::Cursed { duration, .. } => {
duration.unwrap_or(Duration::from_secs(60)).as_secs_f32()
},
_ => 10.0,
};
let current_duration = debuff.dur;
let duration_percentage = current_duration / max_duration * 1000.0; // Percentage to determine which frame of the timer overlay is displayed
let debuff_img = match debuff.id {
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffId::Cursed { .. } => self.imgs.debuff_skull_0,
_ => self.imgs.missing_icon,
};
let debuff_widget = Image::new(debuff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 11 slots
let x = i % 11;
let y = i / 11;
let debuff_widget = debuff_widget.bottom_right_with_margins_on(
state.ids.debuffs_align,
0.0 + y as f64 * (21.0),
0.0 + x as f64 * (21.0),
);
debuff_widget
.color(if current_duration < 10.0 {
Some(pulsating_col)
} else {
Some(norm_col)
})
.set(state.ids.debuffs[i], ui);
// Create Debuff tooltip
let title = match debuff.id {
BuffId::Bleeding { .. } => {
*&localized_strings.get("debuff.title.bleed_test")
},
_ => *&localized_strings.get("buff.title.missing"),
};
let remaining_time = if current_duration == 10e6 as f32 {
"Permanent".to_string()
} else {
format!("Remaining: {:.0}s", current_duration)
};
let desc_txt = match debuff.id {
BuffId::Bleeding { .. } => {
*&localized_strings.get("debuff.desc.bleed_test")
},
_ => *&localized_strings.get("debuff.desc.missing"),
};
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(match duration_percentage as u64 {
875..=1000 => self.imgs.nothing, // 8/8
750..=874 => self.imgs.buff_0, // 7/8
625..=749 => self.imgs.buff_1, // 6/8
500..=624 => self.imgs.buff_2, // 5/8
375..=499 => self.imgs.buff_3, // 4/8
250..=374 => self.imgs.buff_4, //3/8
125..=249 => self.imgs.buff_5, // 2/8
0..=124 => self.imgs.buff_6, // 1/8
_ => self.imgs.nothing,
})
.w_h(20.0, 20.0)
.middle_of(state.ids.debuffs[i])
.with_tooltip(
self.tooltip_manager,
title,
&desc,
&buffs_tooltip,
DEBUFF_COLOR,
)
.set(state.ids.debuff_timers[i], ui);
};
}
}
if let BuffPosition::Map = buff_position {
// Alignment
Rectangle::fill_with([tweak!(300.0), tweak!(280.0)], color::RED)
.top_right_with_margins_on(ui.window, tweak!(5.0), tweak!(270.0))
.set(state.ids.align, ui);
}
event
} }
} }

View File

@ -1,15 +1,20 @@
use super::{ use super::{
img_ids::Imgs, Show, BLACK, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, LOW_HP_COLOR, img_ids::{Imgs, ImgsRot},
STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, Show, BLACK, BUFF_COLOR, DEBUFF_COLOR, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR,
LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN,
}; };
use crate::{ use crate::{
i18n::VoxygenLocalization, settings::Settings, ui::fonts::ConrodVoxygenFonts, hud::{get_buff_info, BuffInfo},
window::GameInput, GlobalState, i18n::VoxygenLocalization,
settings::Settings,
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
window::GameInput,
GlobalState,
}; };
use client::{self, Client}; use client::{self, Client};
use common::{ use common::{
comp::{group::Role, Stats}, comp::{group::Role, BuffId, Buffs, Stats},
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
}; };
use conrod_core::{ use conrod_core::{
@ -18,8 +23,8 @@ 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 {
group_button, group_button,
@ -44,6 +49,8 @@ widget_ids! {
member_panels_txt[], member_panels_txt[],
member_health[], member_health[],
member_stam[], member_stam[],
buffs[],
buff_timers[],
dead_txt[], dead_txt[],
health_txt[], health_txt[],
timeout_bg, timeout_bg,
@ -63,10 +70,13 @@ pub struct Group<'a> {
client: &'a Client, client: &'a Client,
settings: &'a Settings, settings: &'a Settings,
imgs: &'a Imgs, imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a ConrodVoxygenFonts, fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
pulse: f32, pulse: f32,
global_state: &'a GlobalState, global_state: &'a GlobalState,
buffs: &'a Buffs,
tooltip_manager: &'a mut TooltipManager,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
@ -79,20 +89,26 @@ impl<'a> Group<'a> {
client: &'a Client, client: &'a Client,
settings: &'a Settings, settings: &'a Settings,
imgs: &'a Imgs, imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
fonts: &'a ConrodVoxygenFonts, fonts: &'a ConrodVoxygenFonts,
localized_strings: &'a std::sync::Arc<VoxygenLocalization>, localized_strings: &'a std::sync::Arc<VoxygenLocalization>,
pulse: f32, pulse: f32,
global_state: &'a GlobalState, global_state: &'a GlobalState,
buffs: &'a Buffs,
tooltip_manager: &'a mut TooltipManager,
) -> Self { ) -> Self {
Self { Self {
show, show,
client, client,
settings, settings,
imgs, imgs,
rot_imgs,
fonts, fonts,
localized_strings, localized_strings,
pulse, pulse,
global_state, global_state,
buffs,
tooltip_manager,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
} }
} }
@ -127,8 +143,27 @@ impl<'a> Widget for Group<'a> {
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let mut events = Vec::new(); let mut events = Vec::new();
let localized_strings = self.localized_strings;
//let buffs = self.buffs;
let buff_ani = ((self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8) + 0.5; //Animation timer
let buffs_tooltip = Tooltip::new({
// Edge images [t, b, r, l]
// Corner images [tr, tl, br, bl]
let edge = &self.rot_imgs.tt_side;
let corner = &self.rot_imgs.tt_corner;
ImageFrame::new(
[edge.cw180, edge.none, edge.cw270, edge.cw90],
[corner.none, corner.cw270, corner.cw90, corner.cw180],
Color::Rgba(0.08, 0.07, 0.04, 1.0),
5.0,
)
})
.title_font_size(self.fonts.cyri.scale(15))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
// Don't show pets // Don't show pets
let group_members = self let group_members = self
@ -293,6 +328,7 @@ impl<'a> Widget for Group<'a> {
let client_state = self.client.state(); let client_state = self.client.state();
let stats = client_state.ecs().read_storage::<common::comp::Stats>(); let stats = client_state.ecs().read_storage::<common::comp::Stats>();
let energy = client_state.ecs().read_storage::<common::comp::Energy>(); let energy = client_state.ecs().read_storage::<common::comp::Energy>();
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
let uid_allocator = client_state let uid_allocator = client_state
.ecs() .ecs()
.read_resource::<common::sync::UidAllocator>(); .read_resource::<common::sync::UidAllocator>();
@ -302,6 +338,8 @@ impl<'a> Widget for Group<'a> {
let entity = uid_allocator.retrieve_entity_internal(uid.into()); let entity = uid_allocator.retrieve_entity_internal(uid.into());
let stats = entity.and_then(|entity| stats.get(entity)); let stats = entity.and_then(|entity| stats.get(entity));
let energy = entity.and_then(|entity| energy.get(entity)); let energy = entity.and_then(|entity| energy.get(entity));
let buffs = entity.and_then(|entity| buffs.get(entity));
if let Some(stats) = stats { if let Some(stats) = stats {
let char_name = stats.name.to_string(); let char_name = stats.name.to_string();
let health_perc = stats.health.current() as f64 / stats.health.maximum() as f64; let health_perc = stats.health.current() as f64 / stats.health.maximum() as f64;
@ -317,7 +355,7 @@ impl<'a> Widget for Group<'a> {
.top_left_with_margins_on(ui.window, offset, 20.0) .top_left_with_margins_on(ui.window, offset, 20.0)
} else { } else {
Image::new(self.imgs.member_bg) Image::new(self.imgs.member_bg)
.down_from(state.ids.member_panels_bg[i - 1], 40.0) .down_from(state.ids.member_panels_bg[i - 1], 45.0)
}; };
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
@ -386,19 +424,19 @@ impl<'a> Widget for Group<'a> {
.set(state.ids.member_panels_frame[i], ui); .set(state.ids.member_panels_frame[i], ui);
// Panel Text // Panel Text
Text::new(&char_name) Text::new(&char_name)
.top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0)
.font_size(20) .font_size(20)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(BLACK) .color(BLACK)
.w(300.0) // limit name length display .w(300.0) // limit name length display
.set(state.ids.member_panels_txt_bg[i], ui); .set(state.ids.member_panels_txt_bg[i], ui);
Text::new(&char_name) Text::new(&char_name)
.bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0) .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0)
.font_size(20) .font_size(20)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(if is_leader { ERROR_COLOR } else { GROUP_COLOR }) .color(if is_leader { ERROR_COLOR } else { GROUP_COLOR })
.w(300.0) // limit name length display .w(300.0) // limit name length display
.set(state.ids.member_panels_txt[i], ui); .set(state.ids.member_panels_txt[i], ui);
if let Some(energy) = energy { if let Some(energy) = energy {
let stam_perc = energy.current() as f64 / energy.maximum() as f64; let stam_perc = energy.current() as f64 / energy.maximum() as f64;
// Stamina // Stamina
@ -408,44 +446,146 @@ impl<'a> Widget for Group<'a> {
.top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0) .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
.set(state.ids.member_stam[i], ui); .set(state.ids.member_stam[i], ui);
} }
} else { if let Some(buffs) = buffs {
// Values N.A. let mut buffs_vec = Vec::<BuffInfo>::new();
if let Some(stats) = stats { for buff in buffs.active_buffs.clone() {
let info = get_buff_info(buff);
buffs_vec.push(info);
}
state.update(|state| {
state.ids.buffs.resize(
state.ids.buffs.len() + buffs_vec.len(),
&mut ui.widget_id_generator(),
)
});
state.update(|state| {
state.ids.buff_timers.resize(
state.ids.buff_timers.len() + buffs_vec.len(),
&mut ui.widget_id_generator(),
)
});
// Create Buff Widgets
for (x, buff) in buffs_vec.iter().enumerate() {
if x < 11 {
// Limit displayed buffs
let max_duration = match buff.id {
BuffId::Regeneration { duration, .. } => {
duration.unwrap().as_secs_f32()
},
_ => 10.0,
};
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
let current_duration = buff.dur;
let duration_percentage =
(current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.id {
BuffId::Regeneration { .. } => self.imgs.buff_plus_0,
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffId::Cursed { .. } => self.imgs.debuff_skull_0,
};
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
let buff_widget = if x == 0 {
buff_widget.bottom_left_with_margins_on(
state.ids.member_panels_frame[i],
-21.0,
1.0,
)
} else {
buff_widget.right_from(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x - 1/*x - 1*/], 1.0)
};
buff_widget
.color(if current_duration < 10.0 {
Some(pulsating_col)
} else {
Some(norm_col)
})
.set(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui);
// Create Buff tooltip
let title = match buff.id {
BuffId::Regeneration { .. } => {
*&localized_strings.get("buff.title.heal_test")
},
BuffId::Bleeding { .. } => {
*&localized_strings.get("debuff.title.bleed_test")
},
_ => *&localized_strings.get("buff.title.missing"),
};
let remaining_time = if current_duration == 10e6 as f32 {
"Permanent".to_string()
} else {
format!("Remaining: {:.0}s", current_duration)
};
let desc_txt = match buff.id {
BuffId::Regeneration { .. } => {
*&localized_strings.get("buff.desc.heal_test")
},
BuffId::Bleeding { .. } => {
*&localized_strings.get("debuff.desc.bleed_test")
},
_ => *&localized_strings.get("buff.desc.missing"),
};
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(match duration_percentage as u64 {
875..=1000 => self.imgs.nothing, // 8/8
750..=874 => self.imgs.buff_0, // 7/8
625..=749 => self.imgs.buff_1, // 6/8
500..=624 => self.imgs.buff_2, // 5/8
375..=499 => self.imgs.buff_3, // 4/8
250..=374 => self.imgs.buff_4, // 3/8
125..=249 => self.imgs.buff_5, // 2/8
0..=124 => self.imgs.buff_6, // 1/8
_ => self.imgs.nothing,
})
.w_h(20.0, 20.0)
.middle_of(state.ids.buffs[state.ids.buffs.len() - buffs_vec.len() + x/*x*/])
.with_tooltip(
self.tooltip_manager,
title,
&desc,
&buffs_tooltip,
if buff.is_buff {BUFF_COLOR} else {DEBUFF_COLOR},
)
.set(state.ids.buff_timers[state.ids.buffs.len() - buffs_vec.len() + x/*x*/], ui);
};
}
} else {
// Values N.A.
Text::new(&stats.name.to_string()) Text::new(&stats.name.to_string())
.top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0)
.font_size(20) .font_size(20)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(GROUP_COLOR) .color(GROUP_COLOR)
.set(state.ids.member_panels_txt[i], ui); .set(state.ids.member_panels_txt[i], ui);
}; let offset = if self.global_state.settings.gameplay.toggle_debug {
let offset = if self.global_state.settings.gameplay.toggle_debug { 210.0
210.0 } else {
} else { 110.0
110.0 };
}; let back = if i == 0 {
let back = if i == 0 { Image::new(self.imgs.member_bg)
Image::new(self.imgs.member_bg) .top_left_with_margins_on(ui.window, offset, 20.0)
.top_left_with_margins_on(ui.window, offset, 20.0) } else {
} else { Image::new(self.imgs.member_bg)
Image::new(self.imgs.member_bg) .down_from(state.ids.member_panels_bg[i - 1], 40.0)
.down_from(state.ids.member_panels_bg[i - 1], 40.0) };
}; back.w_h(152.0, 36.0)
back.w_h(152.0, 36.0) .color(Some(TEXT_COLOR))
.color(Some(TEXT_COLOR)) .set(state.ids.member_panels_bg[i], ui);
.set(state.ids.member_panels_bg[i], ui); // Panel Frame
// Panel Frame Image::new(self.imgs.member_frame)
Image::new(self.imgs.member_frame) .w_h(152.0, 36.0)
.w_h(152.0, 36.0) .middle_of(state.ids.member_panels_bg[i])
.middle_of(state.ids.member_panels_bg[i]) .color(Some(UI_HIGHLIGHT_0))
.color(Some(UI_HIGHLIGHT_0)) .set(state.ids.member_panels_frame[i], ui);
.set(state.ids.member_panels_frame[i], ui); // Panel Text
// Panel Text Text::new(&self.localized_strings.get("hud.group.out_of_range"))
Text::new(&self.localized_strings.get("hud.group.out_of_range")) .mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0)
.mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0) .font_size(16)
.font_size(16) .font_id(self.fonts.cyri.conrod_id)
.font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR)
.color(TEXT_COLOR) .set(state.ids.dead_txt[i], ui);
.set(state.ids.dead_txt[i], ui); }
} }
} }

View File

@ -272,6 +272,7 @@ image_ids! {
hammerleap: "voxygen.element.icons.skill_hammerleap", hammerleap: "voxygen.element.icons.skill_hammerleap",
skill_axe_leap_slash: "voxygen.element.icons.skill_axe_leap_slash", skill_axe_leap_slash: "voxygen.element.icons.skill_axe_leap_slash",
skill_bow_jump_burst: "voxygen.element.icons.skill_bow_jump_burst", skill_bow_jump_burst: "voxygen.element.icons.skill_bow_jump_burst",
missing_icon: "voxygen.element.icons.missing_icon_grey",
// Buttons // Buttons
button: "voxygen.element.buttons.button", button: "voxygen.element.buttons.button",
@ -350,10 +351,22 @@ image_ids! {
chat_world: "voxygen.element.icons.chat.world", chat_world: "voxygen.element.icons.chat.world",
// Buffs // Buffs
buff_plus_0: "voxygen.element.de_buffs.buff_plus_0", buff_plus_0: "voxygen.element.icons.de_buffs.buff_plus_0",
// Debuffs // Debuffs
debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0", debuff_skull_0: "voxygen.element.icons.de_buffs.debuff_skull_0",
debuff_bleed_0: "voxygen.element.icons.de_buffs.debuff_bleed_0",
// Animation Frames
// Buff Frame
buff_0: "voxygen.element.animation.buff_frame.1",
buff_1: "voxygen.element.animation.buff_frame.2",
buff_2: "voxygen.element.animation.buff_frame.3",
buff_3: "voxygen.element.animation.buff_frame.4",
buff_4: "voxygen.element.animation.buff_frame.5",
buff_5: "voxygen.element.animation.buff_frame.6",
buff_6: "voxygen.element.animation.buff_frame.7",
buff_7: "voxygen.element.animation.buff_frame.8",
<BlankGraphic> <BlankGraphic>
nothing: (), nothing: (),

View File

@ -105,7 +105,7 @@ impl<'a> Widget for MiniMap<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args; let widget::UpdateArgs { state, ui, .. } = args;
let zoom = state.zoom; let zoom = state.zoom;
const SCALE: f64 = 1.5; const SCALE: f64 = 1.5; // TODO Make this a setting
if self.show.mini_map { if self.show.mini_map {
Image::new(self.imgs.mmap_frame) Image::new(self.imgs.mmap_frame)
.w_h(174.0 * SCALE, 190.0 * SCALE) .w_h(174.0 * SCALE, 190.0 * SCALE)

View File

@ -60,7 +60,10 @@ use client::Client;
use common::{ use common::{
assets::Asset, assets::Asset,
comp, comp,
comp::item::{ItemDesc, Quality}, comp::{
item::{ItemDesc, Quality},
BuffId,
},
span, span,
sync::Uid, sync::Uid,
terrain::TerrainChunk, terrain::TerrainChunk,
@ -97,6 +100,8 @@ const STAMINA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9);
//const TRANSPARENT: Color = Color::Rgba(0.0, 0.0, 0.0, 0.0); //const TRANSPARENT: Color = Color::Rgba(0.0, 0.0, 0.0, 0.0);
//const FOCUS_COLOR: Color = Color::Rgba(1.0, 0.56, 0.04, 1.0); //const FOCUS_COLOR: Color = Color::Rgba(1.0, 0.56, 0.04, 1.0);
//const RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0); //const RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0);
const BUFF_COLOR: Color = Color::Rgba(0.06, 0.69, 0.12, 1.0);
const DEBUFF_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
// Item Quality Colors // Item Quality Colors
const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors const QUALITY_LOW: Color = Color::Rgba(0.41, 0.41, 0.41, 1.0); // Grey - Trash, can be sold to vendors
@ -267,6 +272,13 @@ widget_ids! {
} }
} }
#[derive(Clone, Copy)]
pub struct BuffInfo {
id: comp::BuffId,
is_buff: bool,
dur: f32,
}
pub struct DebugInfo { pub struct DebugInfo {
pub tps: f64, pub tps: f64,
pub frame_time: Duration, pub frame_time: Duration,
@ -318,6 +330,7 @@ pub enum Event {
ChatTransp(f32), ChatTransp(f32),
ChatCharName(bool), ChatCharName(bool),
CrosshairType(CrosshairType), CrosshairType(CrosshairType),
BuffPosition(BuffPosition),
ToggleXpBar(XpBar), ToggleXpBar(XpBar),
Intro(Intro), Intro(Intro),
ToggleBarNumbers(BarNumbers), ToggleBarNumbers(BarNumbers),
@ -351,6 +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),
} }
// TODO: Are these the possible layouts we want? // TODO: Are these the possible layouts we want?
@ -391,6 +405,13 @@ pub enum ShortcutNumbers {
On, On,
Off, Off,
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum BuffPosition {
Bar,
Map,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum PressBehavior { pub enum PressBehavior {
Toggle = 0, Toggle = 0,
@ -725,6 +746,7 @@ impl Hud {
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let pos = ecs.read_storage::<comp::Pos>(); let pos = ecs.read_storage::<comp::Pos>();
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
let buffs = ecs.read_storage::<comp::Buffs>();
let energy = ecs.read_storage::<comp::Energy>(); let energy = ecs.read_storage::<comp::Energy>();
let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>(); let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>();
let uids = ecs.read_storage::<common::sync::Uid>(); let uids = ecs.read_storage::<common::sync::Uid>();
@ -1123,11 +1145,12 @@ 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, height_offset, hpfl, in_group) in ( for (pos, info, bubble, stats, buffs, height_offset, hpfl, in_group) in (
&entities, &entities,
&pos, &pos,
interpolated.maybe(), interpolated.maybe(),
&stats, &stats,
&buffs,
energy.maybe(), energy.maybe(),
scales.maybe(), scales.maybe(),
&bodies, &bodies,
@ -1141,7 +1164,7 @@ impl Hud {
entity != me && !stats.is_dead entity != me && !stats.is_dead
}) })
.filter_map( .filter_map(
|(entity, pos, interpolated, stats, energy, scale, body, hpfl, uid)| { |(entity, pos, interpolated, stats, buffs, energy, scale, body, hpfl, uid)| {
// Use interpolated position if available // Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos); let pos = interpolated.map_or(pos.0, |i| i.pos);
let in_group = client.group_members().contains_key(uid); let in_group = client.group_members().contains_key(uid);
@ -1171,6 +1194,7 @@ impl Hud {
let info = display_overhead_info.then(|| overhead::Info { let info = display_overhead_info.then(|| overhead::Info {
name: &stats.name, name: &stats.name,
stats, stats,
buffs,
energy, energy,
}); });
let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) {
@ -1185,6 +1209,7 @@ impl Hud {
info, info,
bubble, bubble,
stats, stats,
buffs,
body.height() * scale.map_or(1.0, |s| s.0) + 0.5, body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
hpfl, hpfl,
in_group, in_group,
@ -1760,22 +1785,48 @@ impl Hud {
// Buffs and Debuffs // Buffs and Debuffs
if let Some(player_buffs) = buffs.get(client.entity()) { if let Some(player_buffs) = buffs.get(client.entity()) {
match BuffsBar::new( for event in BuffsBar::new(
client,
&self.imgs, &self.imgs,
&self.fonts, &self.fonts,
global_state,
&self.rot_imgs, &self.rot_imgs,
tooltip_manager, tooltip_manager,
&self.voxygen_i18n, &self.voxygen_i18n,
&player_buffs, &player_buffs,
self.pulse,
&global_state,
) )
.set(self.ids.buffs, ui_widgets) .set(self.ids.buffs, ui_widgets)
{ {
_ => {}, match event {
buffs::Event::RemoveBuff(buff_id) => events.push(Event::RemoveBuff(buff_id)),
}
}
}
// Group Window
let buffs = buffs.get(client.entity()).unwrap();
for event in Group::new(
&mut self.show,
client,
&global_state.settings,
&self.imgs,
&self.rot_imgs,
&self.fonts,
&self.voxygen_i18n,
self.pulse,
&global_state,
&buffs,
tooltip_manager,
)
.set(self.ids.group_window, ui_widgets)
{
match event {
group::Event::Accept => events.push(Event::AcceptInvite),
group::Event::Decline => events.push(Event::DeclineInvite),
group::Event::Kick(uid) => events.push(Event::KickMember(uid)),
group::Event::LeaveGroup => events.push(Event::LeaveGroup),
group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)),
} }
} }
// Popup (waypoint saved and similar notifications) // Popup (waypoint saved and similar notifications)
Popup::new( Popup::new(
&self.voxygen_i18n, &self.voxygen_i18n,
@ -1850,8 +1901,8 @@ impl Hud {
Some(stats), Some(stats),
Some(loadout), Some(loadout),
Some(energy), Some(energy),
Some(character_state), Some(_character_state),
Some(controller), Some(_controller),
Some(inventory), Some(inventory),
) = ( ) = (
stats.get(entity), stats.get(entity),
@ -2018,6 +2069,9 @@ impl Hud {
settings_window::Event::ToggleZoomInvert(zoom_inverted) => { settings_window::Event::ToggleZoomInvert(zoom_inverted) => {
events.push(Event::ToggleZoomInvert(zoom_inverted)); events.push(Event::ToggleZoomInvert(zoom_inverted));
}, },
settings_window::Event::BuffPosition(buff_position) => {
events.push(Event::BuffPosition(buff_position));
},
settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => { settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => {
events.push(Event::ToggleMouseYInvert(mouse_y_inverted)); events.push(Event::ToggleMouseYInvert(mouse_y_inverted));
}, },
@ -2142,27 +2196,6 @@ impl Hud {
} }
} }
} }
// Group Window
for event in Group::new(
&mut self.show,
client,
&global_state.settings,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
self.pulse,
&global_state,
)
.set(self.ids.group_window, ui_widgets)
{
match event {
group::Event::Accept => events.push(Event::AcceptInvite),
group::Event::Decline => events.push(Event::DeclineInvite),
group::Event::Kick(uid) => events.push(Event::KickMember(uid)),
group::Event::LeaveGroup => events.push(Event::LeaveGroup),
group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)),
}
}
// Spellbook // Spellbook
if self.show.spell { if self.show.spell {
@ -2694,3 +2727,17 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color {
Quality::Debug => QUALITY_DEBUG, Quality::Debug => QUALITY_DEBUG,
} }
} }
// Get info about applied buffs
fn get_buff_info(buff: comp::Buff) -> BuffInfo {
BuffInfo {
id: buff.id,
is_buff: buff
.cat_ids
.iter()
.any(|cat| *cat == comp::BuffCategoryId::Buff),
dur: buff
.time
.map(|dur| dur.as_secs_f32())
.unwrap_or(10e6 as f32),
}
}

View File

@ -3,16 +3,19 @@ use super::{
REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR, REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR,
}; };
use crate::{ use crate::{
hud::{get_buff_info, BuffInfo},
i18n::VoxygenLocalization, i18n::VoxygenLocalization,
settings::GameplaySettings, settings::GameplaySettings,
ui::{fonts::ConrodVoxygenFonts, Ingameable}, ui::{fonts::ConrodVoxygenFonts, Ingameable},
}; };
use common::comp::{Energy, SpeechBubble, SpeechBubbleType, Stats}; use common::comp::{BuffId, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats};
use conrod_core::{ use conrod_core::{
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! {
@ -44,13 +47,24 @@ widget_ids! {
health_txt, health_txt,
mana_bar, mana_bar,
health_bar_fg, health_bar_fg,
// Buffs
buffs_align,
buffs[],
buff_timers[],
} }
} }
/*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,
pub stats: &'a Stats, pub stats: &'a Stats,
pub buffs: &'a Buffs,
pub energy: Option<&'a Energy>, pub energy: Option<&'a Energy>,
} }
@ -119,17 +133,21 @@ impl<'a> Ingameable for Overhead<'a> {
// - 1 for HP text // - 1 for HP text
// - If there's mana // - If there's mana
// - 1 Rect::new for mana // - 1 Rect::new for mana
// // If there are Buffs
// - 1 Alignment Rectangle
// - 10 + 10 Buffs and Timer Overlays
// If there's a speech bubble // If there's a speech bubble
// - 2 Text::new for speech bubble // - 2 Text::new for speech bubble
// - 1 Image::new for icon // - 1 Image::new for icon
// - 10 Image::new for speech bubble (9-slice + tail) // - 10 Image::new for speech bubble (9-slice + tail)
self.info.map_or(0, |info| { self.info.map_or(0, |info| {
2 + if show_healthbar(info.stats) { 2 + 1
5 + if info.energy.is_some() { 1 } else { 0 } + info.buffs.active_buffs.len().min(10) * 2
} else { + if show_healthbar(info.stats) {
0 5 + if info.energy.is_some() { 1 } else { 0 }
} } else {
0
}
}) + if self.bubble.is_some() { 13 } else { 0 } }) + if self.bubble.is_some() { 13 } else { 0 }
} }
} }
@ -155,6 +173,7 @@ impl<'a> Widget for Overhead<'a> {
if let Some(Info { if let Some(Info {
name, name,
stats, stats,
buffs,
energy, energy,
}) = self.info }) = self.info
{ {
@ -172,6 +191,11 @@ impl<'a> Widget for Overhead<'a> {
} else { } else {
MANA_BAR_Y + 32.0 MANA_BAR_Y + 32.0
}; };
let mut buffs_vec = Vec::<BuffInfo>::new();
for buff in buffs.active_buffs.clone() {
let info = get_buff_info(buff);
buffs_vec.push(info);
}
let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 }; let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 };
// Show K for numbers above 10^3 and truncate them // Show K for numbers above 10^3 and truncate them
// Show M for numbers above 10^6 and truncate them // Show M for numbers above 10^6 and truncate them
@ -185,6 +209,79 @@ impl<'a> Widget for Overhead<'a> {
1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)), 1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)),
_ => format!("{:.0}M", (health_max as f64 / 1.0e6).max(1.0)), _ => format!("{:.0}M", (health_max as f64 / 1.0e6).max(1.0)),
}; };
// Buffs
// Alignment
Rectangle::fill_with([tweak!(168.0), tweak!(100.0)], color::TRANSPARENT)
.x_y(-1.0, name_y + tweak!(60.0))
.parent(id)
.set(state.ids.buffs_align, ui);
if state.ids.buffs.len() < buffs_vec.len() {
state.update(|state| {
state
.ids
.buffs
.resize(buffs_vec.len(), &mut ui.widget_id_generator())
});
};
if state.ids.buff_timers.len() < buffs_vec.len() {
state.update(|state| {
state
.ids
.buff_timers
.resize(buffs_vec.len(), &mut ui.widget_id_generator())
});
};
let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; //Animation timer
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// Create Buff Widgets
for (i, buff) in buffs_vec.iter().enumerate() {
if i < 11 && self.bubble.is_none() {
// Limit displayed buffs
let max_duration = match buff.id {
BuffId::Regeneration { duration, .. } => duration.unwrap().as_secs_f32(),
_ => 10.0,
};
let current_duration = buff.dur;
let duration_percentage = (current_duration / max_duration * 1000.0) as u32; // Percentage to determine which frame of the timer overlay is displayed
let buff_img = match buff.id {
BuffId::Regeneration { .. } => self.imgs.buff_plus_0,
BuffId::Bleeding { .. } => self.imgs.debuff_bleed_0,
BuffId::Cursed { .. } => self.imgs.debuff_skull_0,
};
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 5 slots
let x = i % 5;
let y = i / 5;
let buff_widget = buff_widget.bottom_left_with_margins_on(
state.ids.buffs_align,
0.0 + y as f64 * (21.0),
0.0 + x as f64 * (21.0),
);
buff_widget
.color(if current_duration < 10.0 {
Some(pulsating_col)
} else {
Some(norm_col)
})
.set(state.ids.buffs[i], ui);
Image::new(match duration_percentage as u64 {
875..=1000 => self.imgs.nothing, // 8/8
750..=874 => self.imgs.buff_0, // 7/8
625..=749 => self.imgs.buff_1, // 6/8
500..=624 => self.imgs.buff_2, // 5/8
375..=499 => self.imgs.buff_3, // 4/8
250..=374 => self.imgs.buff_4, //3/8
125..=249 => self.imgs.buff_5, // 2/8
0..=124 => self.imgs.buff_6, // 1/8
_ => self.imgs.nothing,
})
.w_h(20.0, 20.0)
.middle_of(state.ids.buffs[i])
.set(state.ids.buff_timers[i], ui);
};
}
// Name // Name
Text::new(name) Text::new(name)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)

View File

@ -4,6 +4,7 @@ use super::{
TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, TEXT_BIND_CONFLICT_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
}; };
use crate::{ use crate::{
hud::BuffPosition,
i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode}, render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode},
ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
@ -159,6 +160,7 @@ widget_ids! {
sfx_volume_text, sfx_volume_text,
audio_device_list, audio_device_list,
audio_device_text, audio_device_text,
//
hotbar_title, hotbar_title,
bar_numbers_title, bar_numbers_title,
show_bar_numbers_none_button, show_bar_numbers_none_button,
@ -167,18 +169,20 @@ widget_ids! {
show_bar_numbers_values_text, show_bar_numbers_values_text,
show_bar_numbers_percentage_button, show_bar_numbers_percentage_button,
show_bar_numbers_percentage_text, show_bar_numbers_percentage_text,
//
show_shortcuts_button, show_shortcuts_button,
show_shortcuts_text, show_shortcuts_text,
show_xpbar_button, buff_pos_bar_button,
show_xpbar_text, buff_pos_bar_text,
show_bars_button, buff_pos_map_button,
show_bars_text, buff_pos_map_text,
placeholder, //
chat_transp_title, chat_transp_title,
chat_transp_text, chat_transp_text,
chat_transp_slider, chat_transp_slider,
chat_char_name_text, chat_char_name_text,
chat_char_name_button, chat_char_name_button,
//
sct_title, sct_title,
sct_show_text, sct_show_text,
sct_show_radio, sct_show_radio,
@ -195,6 +199,7 @@ widget_ids! {
sct_num_dur_text, sct_num_dur_text,
sct_num_dur_slider, sct_num_dur_slider,
sct_num_dur_value, sct_num_dur_value,
//
speech_bubble_text, speech_bubble_text,
speech_bubble_dark_mode_text, speech_bubble_dark_mode_text,
speech_bubble_dark_mode_button, speech_bubble_dark_mode_button,
@ -261,6 +266,7 @@ pub enum Event {
ToggleTips(bool), ToggleTips(bool),
ToggleBarNumbers(BarNumbers), ToggleBarNumbers(BarNumbers),
ToggleShortcutNumbers(ShortcutNumbers), ToggleShortcutNumbers(ShortcutNumbers),
BuffPosition(BuffPosition),
ChangeTab(SettingsTab), ChangeTab(SettingsTab),
Close, Close,
AdjustMousePan(u32), AdjustMousePan(u32),
@ -829,11 +835,61 @@ impl<'a> Widget for SettingsWindow<'a> {
.graphics_for(state.ids.show_shortcuts_button) .graphics_for(state.ids.show_shortcuts_button)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.show_shortcuts_text, ui); .set(state.ids.show_shortcuts_text, ui);
// Buff Position
Rectangle::fill_with([60.0 * 4.0, 1.0 * 4.0], color::TRANSPARENT) // Buffs above skills
.down_from(state.ids.show_shortcuts_text, 30.0) if Button::image(match self.global_state.settings.gameplay.buff_position {
.set(state.ids.placeholder, ui); BuffPosition::Bar => self.imgs.checkbox_checked,
BuffPosition::Map => self.imgs.checkbox,
})
.w_h(18.0, 18.0)
.hover_image(match self.global_state.settings.gameplay.buff_position {
BuffPosition::Bar => self.imgs.checkbox_checked_mo,
BuffPosition::Map => self.imgs.checkbox_mo,
})
.press_image(match self.global_state.settings.gameplay.buff_position {
BuffPosition::Bar => self.imgs.checkbox_checked,
BuffPosition::Map => self.imgs.checkbox_press,
})
.down_from(state.ids.show_shortcuts_button, 8.0)
.set(state.ids.buff_pos_bar_button, ui)
.was_clicked()
{
events.push(Event::BuffPosition(BuffPosition::Bar))
}
Text::new(&self.localized_strings.get("hud.settings.buffs_skillbar"))
.right_from(state.ids.buff_pos_bar_button, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.show_shortcuts_button)
.color(TEXT_COLOR)
.set(state.ids.buff_pos_bar_text, ui);
// Buffs left from minimap
if Button::image(match self.global_state.settings.gameplay.buff_position {
BuffPosition::Map => self.imgs.checkbox_checked,
BuffPosition::Bar => self.imgs.checkbox,
})
.w_h(18.0, 18.0)
.hover_image(match self.global_state.settings.gameplay.buff_position {
BuffPosition::Map => self.imgs.checkbox_checked_mo,
BuffPosition::Bar => self.imgs.checkbox_mo,
})
.press_image(match self.global_state.settings.gameplay.buff_position {
BuffPosition::Map => self.imgs.checkbox_checked,
BuffPosition::Bar => self.imgs.checkbox_press,
})
.down_from(state.ids.buff_pos_bar_button, 8.0)
.set(state.ids.buff_pos_map_button, ui)
.was_clicked()
{
events.push(Event::BuffPosition(BuffPosition::Map))
}
Text::new(&self.localized_strings.get("hud.settings.buffs_mmap"))
.right_from(state.ids.buff_pos_map_button, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.show_shortcuts_button)
.color(TEXT_COLOR)
.set(state.ids.buff_pos_map_text, ui);
// Content Right Side // Content Right Side
/*Scrolling Combat text /*Scrolling Combat text

View File

@ -894,6 +894,10 @@ impl PlayState for SessionState {
global_state.settings.gameplay.shortcut_numbers = shortcut_numbers; global_state.settings.gameplay.shortcut_numbers = shortcut_numbers;
global_state.settings.save_to_file_warn(); global_state.settings.save_to_file_warn();
}, },
HudEvent::BuffPosition(buff_position) => {
global_state.settings.gameplay.buff_position = buff_position;
global_state.settings.save_to_file_warn();
},
HudEvent::UiScale(scale_change) => { HudEvent::UiScale(scale_change) => {
global_state.settings.gameplay.ui_scale = global_state.settings.gameplay.ui_scale =
self.hud.scale_change(scale_change); self.hud.scale_change(scale_change);
@ -921,6 +925,10 @@ impl PlayState for SessionState {
global_state.settings.graphics.max_fps = fps; global_state.settings.graphics.max_fps = fps;
global_state.settings.save_to_file_warn(); global_state.settings.save_to_file_warn();
}, },
HudEvent::RemoveBuff(buff_id) => {
let mut client = self.client.borrow_mut();
client.remove_buff(buff_id);
},
HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x), HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x),
HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b), HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b),
HudEvent::DropSlot(x) => { HudEvent::DropSlot(x) => {

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
hud::{BarNumbers, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar}, hud::{BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar},
i18n, i18n,
render::RenderMode, render::RenderMode,
ui::ScaleMode, ui::ScaleMode,
@ -507,6 +507,7 @@ pub struct GameplaySettings {
pub intro_show: Intro, pub intro_show: Intro,
pub xp_bar: XpBar, pub xp_bar: XpBar,
pub shortcut_numbers: ShortcutNumbers, pub shortcut_numbers: ShortcutNumbers,
pub buff_position: BuffPosition,
pub bar_numbers: BarNumbers, pub bar_numbers: BarNumbers,
pub ui_scale: ScaleMode, pub ui_scale: ScaleMode,
pub free_look_behavior: PressBehavior, pub free_look_behavior: PressBehavior,
@ -537,6 +538,7 @@ impl Default for GameplaySettings {
intro_show: Intro::Show, intro_show: Intro::Show,
xp_bar: XpBar::Always, xp_bar: XpBar::Always,
shortcut_numbers: ShortcutNumbers::On, shortcut_numbers: ShortcutNumbers::On,
buff_position: BuffPosition::Bar,
bar_numbers: BarNumbers::Values, bar_numbers: BarNumbers::Values,
ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()), ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()),
free_look_behavior: PressBehavior::Toggle, free_look_behavior: PressBehavior::Toggle,