Merge branch 'kitswas/poise-indicator' into 'master'

Poise Indicator

See merge request veloren/veloren!3582
This commit is contained in:
Samuel Keiffer 2022-10-24 23:58:12 +00:00
commit 5ad84b7fca
11 changed files with 172 additions and 21 deletions

View File

@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Water will now move according to its apparent flow direction
- Added screen-space reflection and refraction shaders
- Added reflection quality setting
- UI: Added a poise indicator to the player's status bars
### Changed
- Use fluent for translations

BIN
assets/voxygen/element/ui/skillbar/poise_bg.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/ui/skillbar/poise_frame.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -29,6 +29,7 @@ hud-settings-speech_bubble_dark_mode = Speech Bubble Dark Mode
hud-settings-speech_bubble_icon = Speech Bubble Icon
hud-settings-energybar_numbers = Energybar Numbers
hud-settings-always_show_bars = Always show Energybars
hud-settings-enable_poise_bar = Enable Poise bar
hud-settings-experience_numbers = Experience Numbers
hud-settings-accumulate_experience = Accumulate Experience Numbers
hud-settings-values = Values

View File

@ -55,6 +55,8 @@ pub struct Poise {
pub regen_rate: f32,
/// Time that entity was last in a poise state
last_stun_time: Option<Time>,
/// The previous poise state
pub previous_state: PoiseState,
}
/// States to define effects of a poise change
@ -146,11 +148,13 @@ impl Poise {
const MAX_SCALED_POISE: u32 = Self::MAX_POISE as u32 * Self::SCALING_FACTOR_INT;
/// The amount of time after being in a poise state before you can take
/// poise damage again
const POISE_BUFFER_TIME: f64 = 1.0;
pub const POISE_BUFFER_TIME: f64 = 1.0;
/// Used when comparisons to poise are needed outside this module.
// This value is chosen as anything smaller than this is more precise than our
// units of poise.
pub const POISE_EPSILON: f32 = 0.5 / Self::MAX_SCALED_POISE as f32;
/// The thresholds where poise changes to a different state
pub const POISE_THRESHOLDS: [f32; 4] = [50.0, 30.0, 15.0, 5.0];
/// The amount poise is scaled by within this module
const SCALING_FACTOR_FLOAT: f32 = 256.;
const SCALING_FACTOR_INT: u32 = Self::SCALING_FACTOR_FLOAT as u32;
@ -187,6 +191,7 @@ impl Poise {
last_change: Dir::default(),
regen_rate: 0.0,
last_stun_time: None,
previous_state: PoiseState::Normal,
}
}
@ -194,6 +199,9 @@ impl Poise {
match self.last_stun_time {
Some(last_time) if last_time.0 + Poise::POISE_BUFFER_TIME > change.time.0 => {},
_ => {
// if self.previous_state != self.poise_state() {
self.previous_state = self.poise_state();
// };
self.current = (((self.current() + change.amount)
.clamp(0.0, f32::from(Self::MAX_POISE))
* Self::SCALING_FACTOR_FLOAT) as u32)
@ -216,10 +224,10 @@ impl Poise {
/// Defines the poise states based on current poise value
pub fn poise_state(&self) -> PoiseState {
match self.current() {
x if x > 50.0 => PoiseState::Normal,
x if x > 30.0 => PoiseState::Interrupted,
x if x > 15.0 => PoiseState::Stunned,
x if x > 5.0 => PoiseState::Dazed,
x if x > Self::POISE_THRESHOLDS[0] => PoiseState::Normal,
x if x > Self::POISE_THRESHOLDS[1] => PoiseState::Interrupted,
x if x > Self::POISE_THRESHOLDS[2] => PoiseState::Stunned,
x if x > Self::POISE_THRESHOLDS[3] => PoiseState::Dazed,
_ => PoiseState::KnockedDown,
}
}

View File

@ -339,6 +339,9 @@ image_ids! {
decayed_bg: "voxygen.element.ui.skillbar.decayed_bg",
energy_bg: "voxygen.element.ui.skillbar.energy_bg",
energy_frame: "voxygen.element.ui.skillbar.energy_frame",
poise_bg: "voxygen.element.ui.skillbar.poise_bg",
poise_frame: "voxygen.element.ui.skillbar.poise_frame",
poise_tick: "voxygen.element.ui.skillbar.bar_content",
m1_ico: "voxygen.element.ui.generic.icons.m1",
m2_ico: "voxygen.element.ui.generic.icons.m2",
m_scroll_ico: "voxygen.element.ui.generic.icons.m_scroll",

View File

@ -145,6 +145,8 @@ const CRITICAL_HP_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
const STAMINA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9);
const ENEMY_HP_COLOR: Color = Color::Rgba(0.93, 0.1, 0.29, 1.0);
const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0);
const POISE_COLOR: Color = Color::Rgba(0.70, 0.0, 0.60, 1.0);
const POISEBAR_TICK_COLOR: Color = Color::Rgba(0.70, 0.90, 0.0, 1.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 RAGE_COLOR: Color = Color::Rgba(0.5, 0.04, 0.13, 1.0);
@ -2727,10 +2729,18 @@ impl Hud {
});
self.floaters.combo_floater = self.floaters.combo_floater.filter(|f| f.timer > 0_f64);
if let (Some(health), Some(inventory), Some(energy), Some(skillset), Some(body)) = (
if let (
Some(health),
Some(inventory),
Some(energy),
Some(poise),
Some(skillset),
Some(body),
) = (
healths.get(entity),
inventories.get(entity),
energies.get(entity),
poises.get(entity),
skillsets.get(entity),
bodies.get(entity),
) {
@ -2745,6 +2755,7 @@ impl Hud {
health,
inventory,
energy,
poise,
skillset,
active_abilities.get(entity),
body,

View File

@ -66,6 +66,8 @@ widget_ids! {
show_bar_numbers_percentage_text,
always_show_bars_button,
always_show_bars_label,
enable_poise_bar_button,
enable_poise_bar_label,
//
show_shortcuts_button,
show_shortcuts_text,
@ -1153,13 +1155,41 @@ impl<'a> Widget for Interface<'a> {
.color(TEXT_COLOR)
.set(state.ids.always_show_bars_label, ui);
// Enable poise bar
let enable_poise_bar = ToggleButton::new(
self.global_state.settings.interface.enable_poise_bar,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
.w_h(18.0, 18.0)
.down_from(state.ids.always_show_bars_button, 20.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.enable_poise_bar_button, ui);
if enable_poise_bar != self.global_state.settings.interface.enable_poise_bar {
events.push(TogglePoiseBar(enable_poise_bar));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-enable_poise_bar"),
)
.right_from(state.ids.enable_poise_bar_button, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.graphics_for(state.ids.enable_poise_bar_button)
.color(TEXT_COLOR)
.set(state.ids.enable_poise_bar_label, ui);
// Experience Numbers
Text::new(
&self
.localized_strings
.get_msg("hud-settings-experience_numbers"),
)
.down_from(state.ids.always_show_bars_button, 20.0)
.down_from(state.ids.enable_poise_bar_button, 20.0)
.font_size(self.fonts.cyri.scale(18))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)

View File

@ -3,7 +3,8 @@ use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
slots, util, BarNumbers, HudInfo, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR,
LOW_HP_COLOR, QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
LOW_HP_COLOR, POISEBAR_TICK_COLOR, POISE_COLOR, QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR,
UI_HIGHLIGHT_0,
};
use crate::{
game_input::GameInput,
@ -24,7 +25,7 @@ use common::comp::{
self,
ability::AbilityInput,
item::{ItemDesc, MaterialStatManifest},
Ability, ActiveAbilities, Body, Energy, Health, Inventory, SkillSet,
Ability, ActiveAbilities, Body, Energy, Health, Inventory, Poise, PoiseState, SkillSet,
};
use conrod_core::{
color,
@ -57,6 +58,8 @@ widget_ids! {
frame_health,
bg_energy,
frame_energy,
bg_poise,
frame_poise,
m1_ico,
m2_ico,
// Level
@ -79,6 +82,13 @@ widget_ids! {
energy_txt_alignment,
energy_txt_bg,
energy_txt,
// Poise-Bar
poise_alignment,
poise_filling,
poise_ticks[],
poise_txt_alignment,
poise_txt_bg,
poise_txt,
// Combo Counter
combo_align,
combo_bg,
@ -251,6 +261,7 @@ pub struct Skillbar<'a> {
health: &'a Health,
inventory: &'a Inventory,
energy: &'a Energy,
poise: &'a Poise,
skillset: &'a SkillSet,
active_abilities: Option<&'a ActiveAbilities>,
body: &'a Body,
@ -281,6 +292,7 @@ impl<'a> Skillbar<'a> {
health: &'a Health,
inventory: &'a Inventory,
energy: &'a Energy,
poise: &'a Poise,
skillset: &'a SkillSet,
active_abilities: Option<&'a ActiveAbilities>,
body: &'a Body,
@ -306,6 +318,7 @@ impl<'a> Skillbar<'a> {
health,
inventory,
energy,
poise,
skillset,
active_abilities,
body,
@ -365,14 +378,16 @@ impl<'a> Skillbar<'a> {
}
fn show_stat_bars(&self, state: &State, ui: &mut UiCell) {
let (hp_percentage, energy_percentage): (f64, f64) = if self.health.is_dead {
(0.0, 0.0)
let (hp_percentage, energy_percentage, poise_percentage): (f64, f64, f64) =
if self.health.is_dead {
(0.0, 0.0, 0.0)
} else {
let max_hp = f64::from(self.health.base_max().max(self.health.maximum()));
let current_hp = f64::from(self.health.current());
(
current_hp / max_hp * 100.0,
f64::from(self.energy.fraction() * 100.0),
f64::from(self.poise.fraction() * 100.0),
)
};
@ -384,6 +399,9 @@ impl<'a> Skillbar<'a> {
|| (self.health.current() - self.health.maximum()).abs() > Health::HEALTH_EPSILON;
let show_energy = self.global_state.settings.interface.always_show_bars
|| (self.energy.current() - self.energy.maximum()).abs() > Energy::ENERGY_EPSILON;
let show_poise = self.global_state.settings.interface.enable_poise_bar
&& (self.global_state.settings.interface.always_show_bars
|| (self.poise.current() - self.poise.maximum()).abs() > Poise::POISE_EPSILON);
let decayed_health = 1.0 - self.health.maximum() as f64 / self.health.base_max() as f64;
if show_health && !self.health.is_dead || decayed_health > 0.0 {
@ -452,6 +470,46 @@ impl<'a> Skillbar<'a> {
.middle_of(state.ids.bg_energy)
.set(state.ids.frame_energy, ui);
}
if show_poise && !self.health.is_dead {
let offset = 17.0;
let poise_colour = match self.poise.previous_state {
self::PoiseState::KnockedDown => BLACK,
self::PoiseState::Dazed => Color::Rgba(0.25, 0.0, 0.15, 1.0),
self::PoiseState::Stunned => Color::Rgba(0.40, 0.0, 0.30, 1.0),
self::PoiseState::Interrupted => Color::Rgba(0.55, 0.0, 0.45, 1.0),
_ => POISE_COLOR,
};
Image::new(self.imgs.poise_bg)
.w_h(323.0, 14.0)
.mid_top_with_margin_on(state.ids.frame, -offset)
.set(state.ids.bg_poise, ui);
Rectangle::fill_with([319.0, 10.0], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.bg_poise, 2.0, 2.0)
.set(state.ids.poise_alignment, ui);
Image::new(self.imgs.bar_content)
.w_h(319.0 * poise_percentage / 100.0, 10.0)
.color(Some(poise_colour))
.top_left_with_margins_on(state.ids.poise_alignment, 0.0, 0.0)
.set(state.ids.poise_filling, ui);
for i in 0..state.ids.poise_ticks.len() {
Image::new(self.imgs.poise_tick)
.w_h(3.0, 10.0)
.color(Some(POISEBAR_TICK_COLOR))
.top_left_with_margins_on(
state.ids.poise_alignment,
0.0,
319.0f64 * (self::Poise::POISE_THRESHOLDS[i] / self.poise.maximum()) as f64,
)
.set(state.ids.poise_ticks[i], ui);
}
Image::new(self.imgs.poise_frame)
.w_h(323.0, 16.0)
.color(Some(UI_HIGHLIGHT_0))
.middle_of(state.ids.bg_poise)
.set(state.ids.frame_poise, ui);
}
// Bar Text
let bar_text = if self.health.is_dead {
Some((
@ -461,6 +519,9 @@ impl<'a> Skillbar<'a> {
self.localized_strings
.get_msg("hud-group-dead")
.into_owned(),
self.localized_strings
.get_msg("hud-group-dead")
.into_owned(),
))
} else if let BarNumbers::Values = bar_values {
Some((
@ -475,16 +536,18 @@ impl<'a> Skillbar<'a> {
self.energy.current().round() as u32,
self.energy.maximum().round() as u32
),
String::new(), // Don't obscure the tick mark
))
} else if let BarNumbers::Percent = bar_values {
Some((
format!("{}%", hp_percentage as u32),
format!("{}%", energy_percentage as u32),
String::new(), // Don't obscure the tick mark
))
} else {
None
};
if let Some((hp_txt, energy_txt)) = bar_text {
if let Some((hp_txt, energy_txt, poise_txt)) = bar_text {
Text::new(&hp_txt)
.middle_of(state.ids.frame_health)
.font_size(self.fonts.cyri.scale(12))
@ -510,6 +573,19 @@ impl<'a> Skillbar<'a> {
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.energy_txt, ui);
Text::new(&poise_txt)
.middle_of(state.ids.frame_poise)
.font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.poise_txt_bg, ui);
Text::new(&poise_txt)
.bottom_left_with_margins_on(state.ids.poise_txt_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.poise_txt, ui);
}
}
@ -811,13 +887,22 @@ impl<'a> Widget for Skillbar<'a> {
}
// Skillbar
// Poise bar ticks
state.update(|s| {
s.ids.poise_ticks.resize(
self::Poise::POISE_THRESHOLDS.len(),
&mut ui.widget_id_generator(),
)
});
// Alignment and BG
let alignment_size = 40.0 * 12.0 + slot_offset * 11.0;
Rectangle::fill_with([alignment_size, 80.0], color::TRANSPARENT)
.mid_bottom_with_margin_on(ui.window, 10.0)
.set(state.ids.frame, ui);
// Health and Energy bar
// Health, Energy and Poise bars
self.show_stat_bars(state, ui);
// Slots

View File

@ -122,6 +122,7 @@ pub enum Interface {
ToggleXpBar(XpBar),
ToggleBarNumbers(BarNumbers),
ToggleAlwaysShowBars(bool),
TogglePoiseBar(bool),
ToggleShortcutNumbers(ShortcutNumbers),
BuffPosition(BuffPosition),
@ -571,6 +572,9 @@ impl SettingsChange {
Interface::ToggleAlwaysShowBars(always_show_bars) => {
settings.interface.always_show_bars = always_show_bars;
},
Interface::TogglePoiseBar(enable_poise_bar) => {
settings.interface.enable_poise_bar = enable_poise_bar;
},
Interface::ToggleShortcutNumbers(shortcut_numbers) => {
settings.interface.shortcut_numbers = shortcut_numbers;
},

View File

@ -30,6 +30,7 @@ pub struct InterfaceSettings {
pub buff_position: BuffPosition,
pub bar_numbers: BarNumbers,
pub always_show_bars: bool,
pub enable_poise_bar: bool,
pub ui_scale: ScaleMode,
pub map_zoom: f64,
pub map_show_topo_map: bool,
@ -73,6 +74,7 @@ impl Default for InterfaceSettings {
buff_position: BuffPosition::Bar,
bar_numbers: BarNumbers::Values,
always_show_bars: false,
enable_poise_bar: false,
ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()),
map_zoom: 10.0,
map_show_topo_map: true,