The currently active stance shows as a buff

This commit is contained in:
Sam 2022-09-10 19:59:36 -04:00
parent d3a52bd63b
commit 191174aa30
4 changed files with 230 additions and 84 deletions

View File

@ -3,13 +3,13 @@ use super::{
BUFF_COLOR, DEBUFF_COLOR, TEXT_COLOR,
};
use crate::{
hud::{self, animation::animation_timer, BuffPosition},
hud::{animation::animation_timer, BuffIcon, BuffIconKind, BuffPosition},
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
GlobalState,
};
use i18n::Localization;
use common::comp::{BuffKind, Buffs, Energy, Health};
use common::comp::{BuffKind, Buffs, CharacterState, Energy, Health, Inventory};
use conrod_core::{
color,
image::Id,
@ -41,10 +41,12 @@ pub struct BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization,
buffs: &'a Buffs,
char_state: &'a CharacterState,
pulse: f32,
global_state: &'a GlobalState,
health: &'a Health,
energy: &'a Energy,
inventory: &'a Inventory,
}
impl<'a> BuffsBar<'a> {
@ -55,10 +57,12 @@ impl<'a> BuffsBar<'a> {
tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization,
buffs: &'a Buffs,
char_state: &'a CharacterState,
pulse: f32,
global_state: &'a GlobalState,
health: &'a Health,
energy: &'a Energy,
inventory: &'a Inventory,
) -> Self {
Self {
imgs,
@ -68,10 +72,12 @@ impl<'a> BuffsBar<'a> {
tooltip_manager,
localized_strings,
buffs,
char_state,
pulse,
global_state,
health,
energy,
inventory,
}
}
}
@ -102,7 +108,6 @@ impl<'a> Widget for BuffsBar<'a> {
let widget::UpdateArgs { state, ui, .. } = args;
let mut event = Vec::new();
let localized_strings = self.localized_strings;
let buffs = self.buffs;
let buff_ani = animation_timer(self.pulse) + 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);
@ -124,6 +129,7 @@ impl<'a> Widget for BuffsBar<'a> {
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
let buff_icons = BuffIcon::icons_vec(self.buffs, self.char_state, Some(self.inventory));
if let BuffPosition::Bar = buff_position {
let decayed_health = 1.0 - self.health.maximum() / self.health.base_max();
let show_health = self.global_state.settings.interface.always_show_bars
@ -150,16 +156,17 @@ impl<'a> Widget for BuffsBar<'a> {
.set(state.ids.buffs_align, ui);
// Buffs and Debuffs
let (buff_count, debuff_count) = buffs.iter_active().map(hud::get_buff_info).fold(
(0, 0),
|(buff_count, debuff_count), info| {
if info.is_buff {
(buff_count + 1, debuff_count)
} else {
(buff_count, debuff_count + 1)
}
},
);
let (buff_count, debuff_count) =
buff_icons
.iter()
.fold((0, 0), |(buff_count, debuff_count), info| {
if info.is_buff {
(buff_count + 1, debuff_count)
} else {
(buff_count, debuff_count + 1)
}
});
// Limit displayed buffs
let buff_count = buff_count.min(12);
let debuff_count = debuff_count.min(12);
@ -185,12 +192,7 @@ impl<'a> Widget for BuffsBar<'a> {
.iter()
.copied()
.zip(state.ids.buff_timers.iter().copied())
.zip(
buffs
.iter_active()
.map(hud::get_buff_info)
.filter(|info| info.is_buff),
)
.zip(buff_icons.iter().filter(|info| info.is_buff))
.collect::<Vec<_>>();
// Sort the buffs by kind
@ -200,13 +202,13 @@ impl<'a> Widget for BuffsBar<'a> {
.iter()
.enumerate()
.for_each(|(i, ((id, timer_id), buff))| {
let max_duration = buff.data.duration;
let max_duration = buff.kind.max_duration();
let current_duration = buff.dur;
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 = hud::get_buff_image(buff.kind, self.imgs);
let buff_img = buff.kind.image(self.imgs);
let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 11 slots
let x = i % 6;
@ -227,9 +229,8 @@ impl<'a> Widget for BuffsBar<'a> {
)
.set(*id, ui);
// Create Buff tooltip
let title = hud::get_buff_title(buff.kind, localized_strings);
let desc_txt = hud::get_buff_desc(buff.kind, buff.data, localized_strings);
let remaining_time = hud::get_buff_time(*buff);
let (title, desc_txt) = buff.kind.title_description(localized_strings);
let remaining_time = buff.get_buff_time();
let click_to_remove =
format!("<{}>", &localized_strings.get_msg("buff-remove"));
let desc = format!("{}\n\n{}\n\n{}", desc_txt, remaining_time, click_to_remove);
@ -247,7 +248,10 @@ impl<'a> Widget for BuffsBar<'a> {
.set(*timer_id, ui)
.was_clicked()
{
event.push(Event::RemoveBuff(buff.kind));
match buff.kind {
BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)),
BuffIconKind::Ability { .. } => todo!(),
}
};
});
@ -258,12 +262,7 @@ impl<'a> Widget for BuffsBar<'a> {
.iter()
.copied()
.zip(state.ids.debuff_timers.iter().copied())
.zip(
buffs
.iter_active()
.map(hud::get_buff_info)
.filter(|info| !info.is_buff),
)
.zip(buff_icons.iter().filter(|info| !info.is_buff))
.collect::<Vec<_>>();
// Sort the debuffs by kind
@ -273,13 +272,13 @@ impl<'a> Widget for BuffsBar<'a> {
.iter()
.enumerate()
.for_each(|(i, ((id, timer_id), debuff))| {
let max_duration = debuff.data.duration;
let max_duration = debuff.kind.max_duration();
let current_duration = debuff.dur;
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 = hud::get_buff_image(debuff.kind, self.imgs);
let debuff_img = debuff.kind.image(self.imgs);
let debuff_widget = Image::new(debuff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 11 slots
let x = i % 6;
@ -300,9 +299,8 @@ impl<'a> Widget for BuffsBar<'a> {
)
.set(*id, ui);
// Create Debuff tooltip
let title = hud::get_buff_title(debuff.kind, localized_strings);
let desc_txt = hud::get_buff_desc(debuff.kind, debuff.data, localized_strings);
let remaining_time = hud::get_buff_time(*debuff);
let (title, desc_txt) = debuff.kind.title_description(localized_strings);
let remaining_time = debuff.get_buff_time();
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(self.get_duration_image(duration_percentage))
.w_h(40.0, 40.0)
@ -325,7 +323,7 @@ impl<'a> Widget for BuffsBar<'a> {
.set(state.ids.align, ui);
// Buffs and Debuffs
let buff_count = buffs.kinds.len().min(11);
let buff_count = buff_icons.len().min(11);
// Limit displayed buffs
let buff_count = buff_count.min(20);
@ -349,7 +347,7 @@ impl<'a> Widget for BuffsBar<'a> {
.copied()
.zip(state.ids.buff_timers.iter().copied())
.zip(state.ids.buff_txts.iter().copied())
.zip(buffs.iter_active().map(hud::get_buff_info))
.zip(buff_icons.iter())
.collect::<Vec<_>>();
// Sort the buffs by kind
@ -359,14 +357,14 @@ impl<'a> Widget for BuffsBar<'a> {
.iter()
.enumerate()
.for_each(|(i, (((id, timer_id), txt_id), buff))| {
let max_duration = buff.data.duration;
let max_duration = buff.kind.max_duration();
let current_duration = buff.dur;
// Percentage to determine which frame of the timer overlay is displayed
let duration_percentage = current_duration.map_or(1000.0, |cur| {
max_duration
.map_or(1000.0, |max| cur.as_secs_f32() / max.as_secs_f32() * 1000.0)
}) as u32;
let buff_img = hud::get_buff_image(buff.kind, self.imgs);
let buff_img = buff.kind.image(self.imgs);
let buff_widget = Image::new(buff_img).w_h(40.0, 40.0);
// Sort buffs into rows of 6 slots
let x = i % 6;
@ -386,9 +384,8 @@ impl<'a> Widget for BuffsBar<'a> {
)
.set(*id, ui);
// Create Buff tooltip
let title = hud::get_buff_title(buff.kind, localized_strings);
let desc_txt = hud::get_buff_desc(buff.kind, buff.data, localized_strings);
let remaining_time = hud::get_buff_time(*buff);
let (title, desc_txt) = buff.kind.title_description(localized_strings);
let remaining_time = buff.get_buff_time();
let click_to_remove =
format!("<{}>", &localized_strings.get_msg("buff-remove"));
let desc = if buff.is_buff {
@ -414,7 +411,10 @@ impl<'a> Widget for BuffsBar<'a> {
.set(*timer_id, ui)
.was_clicked()
{
event.push(Event::RemoveBuff(buff.kind));
match buff.kind {
BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)),
BuffIconKind::Ability { .. } => todo!(),
}
}
Text::new(&remaining_time)
.down_from(*timer_id, 1.0)

View File

@ -8,7 +8,7 @@ use super::{
use crate::{
game_input::GameInput,
hud,
hud::BuffIcon,
settings::Settings,
ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
GlobalState,
@ -355,6 +355,9 @@ impl<'a> Widget for Group<'a> {
let uid_allocator = client_state.ecs().read_resource::<UidAllocator>();
let bodies = client_state.ecs().read_storage::<common::comp::Body>();
let poises = client_state.ecs().read_storage::<common::comp::Poise>();
let char_states = client_state
.ecs()
.read_storage::<common::comp::CharacterState>();
// Keep track of the total number of widget ids we are using for buffs
let mut total_buff_count = 0;
@ -370,6 +373,7 @@ impl<'a> Widget for Group<'a> {
let is_leader = uid == leader;
let body = entity.and_then(|entity| bodies.get(entity));
let poise = entity.and_then(|entity| poises.get(entity));
let char_state = entity.and_then(|entity| char_states.get(entity));
if let (
Some(stats),
@ -379,8 +383,10 @@ impl<'a> Widget for Group<'a> {
Some(energy),
Some(body),
Some(poise),
) = (stats, skill_set, inventory, health, energy, body, poise)
{
Some(char_state),
) = (
stats, skill_set, inventory, health, energy, body, poise, char_state,
) {
let combat_rating = combat::combat_rating(
inventory, health, energy, poise, skill_set, *body, self.msm,
);
@ -499,8 +505,9 @@ impl<'a> Widget for Group<'a> {
.top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
.set(state.ids.member_energy[i], ui);
if let Some(buffs) = buffs {
let buff_icons = BuffIcon::icons_vec(buffs, char_state, Some(inventory));
// Limit displayed buffs to 11
let buff_count = buffs.kinds.len().min(11);
let buff_count = buff_icons.len().min(11);
total_buff_count += buff_count;
let gen = &mut ui.widget_id_generator();
if state.ids.buffs.len() < total_buff_count {
@ -520,9 +527,9 @@ impl<'a> Widget for Group<'a> {
.copied()
.zip(state.ids.buff_timers.iter().copied())
.skip(total_buff_count - buff_count)
.zip(buffs.iter_active().map(hud::get_buff_info))
.zip(buff_icons.iter())
.for_each(|((id, timer_id), buff)| {
let max_duration = buff.data.duration;
let max_duration = buff.kind.max_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;
@ -531,7 +538,7 @@ impl<'a> Widget for Group<'a> {
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 = hud::get_buff_image(buff.kind, self.imgs);
let buff_img = buff.kind.image(self.imgs);
let buff_widget = Image::new(buff_img).w_h(15.0, 15.0);
let buff_widget = if let Some(id) = prev_id {
buff_widget.right_from(id, 1.0)
@ -555,10 +562,9 @@ impl<'a> Widget for Group<'a> {
)
.set(id, ui);
// Create Buff tooltip
let title = hud::get_buff_title(buff.kind, localized_strings);
let desc_txt =
hud::get_buff_desc(buff.kind, buff.data, localized_strings);
let remaining_time = hud::get_buff_time(buff);
let (title, desc_txt) =
buff.kind.title_description(localized_strings);
let remaining_time = buff.get_buff_time();
let desc = format!("{}\n\n{}", desc_txt, remaining_time);
Image::new(match duration_percentage as u64 {
875..=1000 => self.imgs.nothing, // 8/8

View File

@ -120,6 +120,7 @@ use rand::Rng;
use specs::{Entity as EcsEntity, Join, WorldExt};
use std::{
borrow::Cow,
cmp::Ordering,
collections::VecDeque,
sync::Arc,
time::{Duration, Instant},
@ -449,13 +450,154 @@ impl<W: Positionable> Position for W {
}
#[derive(Clone, Copy)]
pub struct BuffInfo {
kind: BuffKind,
data: BuffData,
pub enum BuffIconKind<'a> {
Buff { kind: BuffKind, data: BuffData },
Ability { ability_id: &'a str },
}
impl<'a> BuffIconKind<'a> {
pub fn image(&self, imgs: &Imgs) -> conrod_core::image::Id {
match self {
Self::Buff { kind, .. } => get_buff_image(*kind, imgs),
Self::Ability { ability_id, .. } => util::ability_image(imgs, ability_id),
}
}
pub fn max_duration(&self) -> Option<Duration> {
match self {
Self::Buff { data, .. } => data.duration,
Self::Ability { .. } => None,
}
}
pub fn title_description<'b>(
&self,
localized_strings: &'b Localization,
) -> (Cow<'b, str>, Cow<'b, str>) {
match self {
Self::Buff { kind, data } => (
get_buff_title(*kind, localized_strings),
get_buff_desc(*kind, *data, localized_strings),
),
Self::Ability { ability_id } => {
util::ability_description(ability_id, localized_strings)
},
}
}
}
impl<'a> PartialOrd for BuffIconKind<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(
BuffIconKind::Buff { kind, .. },
BuffIconKind::Buff {
kind: other_kind, ..
},
) => Some(kind.cmp(other_kind)),
(BuffIconKind::Buff { .. }, BuffIconKind::Ability { .. }) => Some(Ordering::Greater),
(BuffIconKind::Ability { .. }, BuffIconKind::Buff { .. }) => Some(Ordering::Less),
(
BuffIconKind::Ability { ability_id },
BuffIconKind::Ability {
ability_id: other_id,
},
) => Some(ability_id.cmp(other_id)),
}
}
}
impl<'a> Ord for BuffIconKind<'a> {
fn cmp(&self, other: &Self) -> Ordering {
// We know this is safe since we can look at the partialord implementation and
// see that every variant is wrapped in Some
self.partial_cmp(other).unwrap()
}
}
impl<'a> PartialEq for BuffIconKind<'a> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
BuffIconKind::Buff { kind, .. },
BuffIconKind::Buff {
kind: other_kind, ..
},
) => kind == other_kind,
(
BuffIconKind::Ability { ability_id },
BuffIconKind::Ability {
ability_id: other_id,
},
) => ability_id == other_id,
_ => false,
}
}
}
impl<'a> Eq for BuffIconKind<'a> {}
#[derive(Clone, Copy)]
pub struct BuffIcon<'a> {
kind: BuffIconKind<'a>,
is_buff: bool,
dur: Option<Duration>,
}
impl<'a> BuffIcon<'a> {
pub fn get_buff_time(&self) -> String {
if let Some(dur) = self.dur {
format!("{:.0}s", dur.as_secs_f32())
} else {
"".to_string()
}
}
pub fn icons_vec(
buffs: &comp::Buffs,
char_state: &comp::CharacterState,
inv: Option<&'a comp::Inventory>,
) -> Vec<Self> {
buffs
.iter_active()
.map(BuffIcon::from_buff)
.chain(BuffIcon::from_char_state(char_state, inv).into_iter())
.collect::<Vec<_>>()
}
fn from_char_state(
char_state: &comp::CharacterState,
inv: Option<&'a comp::Inventory>,
) -> Option<Self> {
let ability_id = || {
char_state
.ability_info()
.and_then(|info| info.ability)
.and_then(|ability| ability.ability_id(inv))
};
use comp::CharacterState::*;
match char_state {
ComboMelee2(data) if data.static_data.is_stance => ability_id().map(|id| BuffIcon {
kind: BuffIconKind::Ability { ability_id: id },
is_buff: true,
dur: None,
}),
_ => None,
}
}
fn from_buff(buff: &comp::Buff) -> Self {
Self {
kind: BuffIconKind::Buff {
kind: buff.kind,
data: buff.data,
},
is_buff: buff.kind.is_buff(),
dur: buff.time,
}
}
}
pub struct ExpFloater {
pub owner: Uid,
pub exp_change: u32,
@ -1290,6 +1432,7 @@ impl Hud {
let poises = ecs.read_storage::<comp::Poise>();
let alignments = ecs.read_storage::<comp::Alignment>();
let is_mount = ecs.read_storage::<Is<Mount>>();
let char_states = ecs.read_storage::<comp::CharacterState>();
// Check if there was a persistence load error of the skillset, and if so
// display a dialog prompt
@ -1980,7 +2123,7 @@ impl Hud {
&uids,
&inventories,
poises.maybe(),
(alignments.maybe(), is_mount.maybe()),
(alignments.maybe(), is_mount.maybe(), &char_states),
)
.join()
.filter(|t| {
@ -2003,7 +2146,7 @@ impl Hud {
uid,
inventory,
poise,
(alignment, is_mount),
(alignment, is_mount, char_state),
)| {
// Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos);
@ -2054,6 +2197,8 @@ impl Hud {
} else {
0.0
},
inventory,
char_state,
});
// Only render bubble if nearby or if its me and setting is on
let bubble = if (dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) && !is_me)
@ -2598,6 +2743,7 @@ impl Hud {
let stats = ecs.read_storage::<comp::Stats>();
let skill_sets = ecs.read_storage::<comp::SkillSet>();
let buffs = ecs.read_storage::<comp::Buffs>();
let char_states = ecs.read_storage::<comp::CharacterState>();
let msm = ecs.read_resource::<MaterialStatManifest>();
if let (Some(player_stats), Some(skill_set)) = (stats.get(entity), skill_sets.get(entity)) {
match Buttons::new(
@ -2881,10 +3027,12 @@ impl Hud {
}
// Buffs
if let (Some(player_buffs), Some(health), Some(energy)) = (
if let (Some(player_buffs), Some(health), Some(energy), Some(char_state), Some(inventory)) = (
buffs.get(info.viewpoint_entity),
healths.get(entity),
energies.get(entity),
char_states.get(entity),
inventories.get(entity),
) {
for event in BuffsBar::new(
&self.imgs,
@ -2893,10 +3041,12 @@ impl Hud {
tooltip_manager,
i18n,
player_buffs,
char_state,
self.pulse,
global_state,
health,
energy,
inventory,
)
.set(self.ids.buffs, ui_widgets)
{
@ -4548,15 +4698,6 @@ pub fn get_quality_col<I: ItemDesc + ?Sized>(item: &I) -> Color {
Quality::Debug => QUALITY_DEBUG,
}
}
// Get info about applied buffs
fn get_buff_info(buff: &comp::Buff) -> BuffInfo {
BuffInfo {
kind: buff.kind,
data: buff.data,
is_buff: buff.kind.is_buff(),
dur: buff.time,
}
}
fn try_hotbar_slot_from_input(input: GameInput) -> Option<hotbar::Slot> {
Some(match input {
@ -4721,14 +4862,6 @@ pub fn get_sprite_desc(sprite: SpriteKind, localized_strings: &Localization) ->
Some(localized_strings.get_msg(i18n_key))
}
pub fn get_buff_time(buff: BuffInfo) -> String {
if let Some(dur) = buff.dur {
format!("{:.0}s", dur.as_secs_f32())
} else {
"".to_string()
}
}
pub fn angle_of_attack_text(
fluid: Option<comp::Fluid>,
velocity: Option<comp::Vel>,

View File

@ -5,11 +5,13 @@ use super::{
};
use crate::{
game_input::GameInput,
hud::{get_buff_image, get_buff_info},
hud::BuffIcon,
settings::{ControlSettings, InterfaceSettings},
ui::{fonts::Fonts, Ingameable},
};
use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
use common::comp::{
Buffs, CharacterState, Energy, Health, Inventory, SpeechBubble, SpeechBubbleType,
};
use conrod_core::{
color,
position::Align,
@ -69,6 +71,8 @@ pub struct Info<'a> {
pub buffs: &'a Buffs,
pub energy: Option<&'a Energy>,
pub combat_rating: f32,
pub inventory: &'a Inventory,
pub char_state: &'a CharacterState,
}
/// Determines whether to show the healthbar
@ -198,6 +202,8 @@ impl<'a> Widget for Overhead<'a> {
buffs,
energy,
combat_rating,
inventory,
char_state,
}) = self.info
{
// Used to set healthbar colours based on hp_percentage
@ -227,7 +233,8 @@ impl<'a> Widget for Overhead<'a> {
};
// Buffs
// Alignment
let buff_count = buffs.kinds.len().min(11);
let buff_icons = BuffIcon::icons_vec(buffs, char_state, Some(inventory));
let buff_count = buff_icons.len().min(11);
Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
.x_y(-1.0, name_y + 60.0)
.parent(id)
@ -252,18 +259,18 @@ impl<'a> Widget for Overhead<'a> {
.iter()
.copied()
.zip(state.ids.buff_timers.iter().copied())
.zip(buffs.iter_active().map(get_buff_info))
.zip(buff_icons.iter())
.enumerate()
.for_each(|(i, ((id, timer_id), buff))| {
// Limit displayed buffs
let max_duration = buff.data.duration;
let max_duration = buff.kind.max_duration();
let current_duration = buff.dur;
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 = get_buff_image(buff.kind, self.imgs);
let buff_img = buff.kind.image(self.imgs);
let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
// Sort buffs into rows of 5 slots
let x = i % 5;