veloren/voxygen/src/hud/overhead.rs

301 lines
10 KiB
Rust
Raw Normal View History

use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR};
use crate::ui::{fonts::ConrodVoxygenFonts, Ingameable};
use common::comp::{Energy, Stats};
use conrod_core::{
position::Align,
widget::{self, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
widget_ids! {
struct Ids {
// Chat bubble
chat_bubble_text,
chat_bubble_text2,
chat_bubble_top_left,
chat_bubble_top,
chat_bubble_top_right,
chat_bubble_left,
chat_bubble_mid,
chat_bubble_right,
chat_bubble_bottom_left,
chat_bubble_bottom,
chat_bubble_bottom_right,
chat_bubble_tail,
// Name
name_bg,
name,
// HP
level,
level_skull,
health_bar,
health_bar_bg,
mana_bar,
health_bar_fg,
}
}
/// ui widget containing everything that goes over a character's head
/// (Speech bubble, Name, Level, HP/energy bars, etc.)
#[derive(WidgetCommon)]
pub struct Overhead<'a> {
name: &'a str,
stats: &'a Stats,
energy: &'a Energy,
own_level: u32,
pulse: f32,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> Overhead<'a> {
pub fn new(
name: &'a str,
stats: &'a Stats,
energy: &'a Energy,
own_level: u32,
pulse: f32,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
) -> Self {
Self {
name,
stats,
energy,
own_level,
pulse,
imgs,
fonts,
common: widget::CommonBuilder::default(),
}
}
}
pub struct State {
ids: Ids,
}
impl<'a> Ingameable for Overhead<'a> {
fn prim_count(&self) -> usize {
// Number of conrod primitives contained in the overhead display. TODO maybe
// this could be done automatically?
// - 2 Text::new for name
// - 2 Text::new for speech bubble
// - 10 Image::new for speech bubble (9-slice + tail)
// - 1 for level: either Text or Image
// - 4 for HP + mana + fg + bg
19
}
}
impl<'a> Widget for Overhead<'a> {
type Event = ();
type State = State;
type Style = ();
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style { () }
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args;
const BARSIZE: f64 = 2.0;
const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5;
const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0;
// Name
Text::new(&self.name)
.font_id(self.fonts.cyri.conrod_id)
.font_size(30)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.x_y(-1.0, MANA_BAR_Y + 48.0)
.set(state.ids.name_bg, ui);
Text::new(&self.name)
.font_id(self.fonts.cyri.conrod_id)
.font_size(30)
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
.x_y(0.0, MANA_BAR_Y + 50.0)
.set(state.ids.name, ui);
// Speech bubble
Text::new("Hello")
.font_id(self.fonts.cyri.conrod_id)
.font_size(15)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.up_from(state.ids.name, 10.0)
.x_align_to(state.ids.name, Align::Middle)
.parent(id)
.set(state.ids.chat_bubble_text, ui);
Image::new(self.imgs.chat_bubble_top_left)
.w_h(10.0, 10.0)
.top_left_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_top_left, ui);
Image::new(self.imgs.chat_bubble_top)
.h(10.0)
.w_of(state.ids.chat_bubble_text)
.mid_top_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_top, ui);
Image::new(self.imgs.chat_bubble_top_right)
.w_h(10.0, 10.0)
.top_right_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_top_right, ui);
Image::new(self.imgs.chat_bubble_left)
.w(10.0)
.h_of(state.ids.chat_bubble_text)
.mid_left_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_left, ui);
Image::new(self.imgs.chat_bubble_mid)
.wh_of(state.ids.chat_bubble_text)
.top_left_of(state.ids.chat_bubble_text)
.parent(id)
.set(state.ids.chat_bubble_mid, ui);
Image::new(self.imgs.chat_bubble_right)
.w(10.0)
.h_of(state.ids.chat_bubble_text)
.mid_right_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_right, ui);
Image::new(self.imgs.chat_bubble_bottom_left)
.w_h(10.0, 10.0)
.bottom_left_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_bottom_left, ui);
Image::new(self.imgs.chat_bubble_bottom)
.h(10.0)
.w_of(state.ids.chat_bubble_text)
.mid_bottom_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_bottom, ui);
Image::new(self.imgs.chat_bubble_bottom_right)
.w_h(10.0, 10.0)
.bottom_right_with_margin_on(state.ids.chat_bubble_text, -10.0)
.parent(id)
.set(state.ids.chat_bubble_bottom_right, ui);
Image::new(self.imgs.chat_bubble_tail)
.w_h(11.0, 16.0)
.mid_bottom_with_margin_on(state.ids.chat_bubble_text, -16.0)
.parent(id)
.set(state.ids.chat_bubble_tail, ui);
// Why is there a second text widget?: The first is to position the 9-slice
// around and the second is to display text. Changing .depth manually
// causes strange problems in unrelated parts of the ui (the debug
// overlay is offset by a npc's screen position) TODO
Text::new("Hello")
.font_id(self.fonts.cyri.conrod_id)
.font_size(15)
.top_left_of(state.ids.chat_bubble_text)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.parent(id)
.set(state.ids.chat_bubble_text2, ui);
let hp_percentage =
self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0;
let energy_percentage = self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0;
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
// Background
Image::new(self.imgs.enemy_health_bg)
.w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
.x_y(0.0, MANA_BAR_Y + 6.5) //-25.5)
.color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8)))
.parent(id)
.set(state.ids.health_bar_bg, ui);
// % HP Filling
Image::new(self.imgs.enemy_bar)
.w_h(73.0 * (hp_percentage / 100.0) * BARSIZE, 6.0 * BARSIZE)
.x_y(
(4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE,
MANA_BAR_Y + 7.5,
)
.color(Some(if hp_percentage <= 25.0 {
crit_hp_color
} else if hp_percentage <= 50.0 {
LOW_HP_COLOR
} else {
HP_COLOR
}))
.parent(id)
.set(state.ids.health_bar, ui);
// % Mana Filling
Rectangle::fill_with(
[
72.0 * (self.energy.current() as f64 / self.energy.maximum() as f64) * BARSIZE,
MANA_BAR_HEIGHT,
],
MANA_COLOR,
)
.x_y(
((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE,
MANA_BAR_Y, //-32.0,
)
.parent(id)
.set(state.ids.mana_bar, ui);
// Foreground
Image::new(self.imgs.enemy_health)
.w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
.x_y(0.0, MANA_BAR_Y + 6.5) //-25.5)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
.parent(id)
.set(state.ids.health_bar_fg, ui);
// Level
const LOW: Color = Color::Rgba(0.54, 0.81, 0.94, 0.4);
const HIGH: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0);
const EQUAL: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// Change visuals of the level display depending on the player level/opponent
// level
let level_comp = self.stats.level.level() as i64 - self.own_level as i64;
// + 10 level above player -> skull
// + 5-10 levels above player -> high
// -5 - +5 levels around player level -> equal
// - 5 levels below player -> low
if level_comp > 9 {
let skull_ani = ((self.pulse * 0.7/* speed factor */).cos() * 0.5 + 0.5) * 10.0; //Animation timer
Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 {
self.imgs.skull_2
} else {
self.imgs.skull
})
.w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
.x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.parent(id)
.set(state.ids.level_skull, ui);
} else {
Text::new(&format!("{}", self.stats.level.level()))
.font_id(self.fonts.cyri.conrod_id)
.font_size(if self.stats.level.level() > 9 && level_comp < 10 {
14
} else {
15
})
.color(if level_comp > 4 {
HIGH
} else if level_comp < -5 {
LOW
} else {
EQUAL
})
.x_y(-37.0 * BARSIZE, MANA_BAR_Y + 9.0)
.parent(id)
.set(state.ids.level, ui);
}
}
}