veloren/voxygen/src/hud/skillbar.rs

1094 lines
43 KiB
Rust

use super::{
hotbar,
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
slots, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
};
use crate::{
hud::ComboFloater,
i18n::Localization,
ui::{
fonts::Fonts,
slot::{ContentSize, SlotMaker},
ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
Tooltipable,
},
window::GameInput,
GlobalState,
};
use client::{self, Client};
use common::comp::{
self,
inventory::slot::EquipSlot,
item::{
tool::{AbilityMap, Tool, ToolKind},
Hands, Item, ItemKind, MaterialStatManifest,
},
Energy, Health, Inventory,
};
use conrod_core::{
color,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
use vek::*;
widget_ids! {
struct Ids {
// Death message
death_message_1,
death_message_2,
death_message_1_bg,
death_message_2_bg,
death_bg,
// Level up message
level_up,
level_down,
level_align,
level_message,
level_message_bg,
// Hurt BG
hurt_bg,
// Skillbar
alignment,
bg,
frame,
bg_health,
frame_health,
bg_stamina,
frame_stamina,
m1_ico,
m2_ico,
// Level
level_bg,
level,
// Exp-Bar
exp_alignment,
exp_filling,
// HP-Bar
hp_alignment,
hp_filling,
hp_decayed,
hp_txt_alignment,
hp_txt_bg,
hp_txt,
decay_overlay,
// Stamina-Bar
stamina_alignment,
stamina_filling,
stamina_txt_alignment,
stamina_txt_bg,
stamina_txt,
// Combo Counter
combo_align,
combo_bg,
combo,
// Slots
m1_slot,
m1_slot_bg,
m1_text,
m1_text_bg,
m1_slot_act,
m1_content,
m2_slot,
m2_slot_bg,
m2_text,
m2_text_bg,
m2_slot_act,
m2_content,
slot1,
slot1_text,
slot1_text_bg,
slot2,
slot2_text,
slot2_text_bg,
slot3,
slot3_text,
slot3_text_bg,
slot4,
slot4_text,
slot4_text_bg,
slot5,
slot5_text,
slot5_text_bg,
slot6,
slot6_text,
slot6_text_bg,
slot7,
slot7_text,
slot7_text_bg,
slot8,
slot8_text,
slot8_text_bg,
slot9,
slot9_text,
slot9_text_bg,
slot10,
slot10_text,
slot10_text_bg,
}
}
#[derive(WidgetCommon)]
pub struct Skillbar<'a> {
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
health: &'a Health,
inventory: &'a Inventory,
energy: &'a Energy,
// character_state: &'a CharacterState,
// controller: &'a ControllerInputs,
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization,
pulse: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
ability_map: &'a AbilityMap,
msm: &'a MaterialStatManifest,
combo: Option<ComboFloater>,
}
impl<'a> Skillbar<'a> {
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn new(
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
health: &'a Health,
inventory: &'a Inventory,
energy: &'a Energy,
// character_state: &'a CharacterState,
pulse: f32,
// controller: &'a ControllerInputs,
hotbar: &'a hotbar::State,
tooltip_manager: &'a mut TooltipManager,
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization,
ability_map: &'a AbilityMap,
msm: &'a MaterialStatManifest,
combo: Option<ComboFloater>,
) -> Self {
Self {
client,
global_state,
imgs,
item_imgs,
fonts,
rot_imgs,
health,
inventory,
energy,
common: widget::CommonBuilder::default(),
// character_state,
pulse,
// controller,
hotbar,
tooltip_manager,
item_tooltip_manager,
slot_manager,
localized_strings,
ability_map,
msm,
combo,
}
}
}
pub struct State {
ids: Ids,
}
impl<'a> Widget for Skillbar<'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),
}
}
#[allow(clippy::unused_unit)] // TODO: Pending review in #587
fn style(&self) -> Self::Style { () }
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args;
let max_hp = self.health.base_max().max(self.health.maximum());
let mut hp_percentage = self.health.current() as f64 / max_hp as f64 * 100.0;
let mut energy_percentage =
self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0;
if self.health.is_dead {
hp_percentage = 0.0;
energy_percentage = 0.0;
};
let bar_values = self.global_state.settings.interface.bar_numbers;
let shortcuts = self.global_state.settings.interface.shortcut_numbers;
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 localized_strings = self.localized_strings;
let slot_offset = 3.0;
// Death message
if self.health.is_dead {
if let Some(key) = self
.global_state
.settings
.controls
.get_binding(GameInput::Respawn)
{
Text::new(&localized_strings.get("hud.you_died"))
.middle_of(ui.window)
.font_size(self.fonts.cyri.scale(50))
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_1_bg, ui);
Text::new(
&localized_strings
.get("hud.press_key_to_respawn")
.replace("{key}", key.to_string().as_str()),
)
.mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_2_bg, ui);
Text::new(&localized_strings.get("hud.you_died"))
.bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(50))
.font_id(self.fonts.cyri.conrod_id)
.color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_1, ui);
Text::new(
&localized_strings
.get("hud.press_key_to_respawn")
.replace("{key}", key.to_string().as_str()),
)
.bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id)
.color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_2, ui);
}
}
// Skillbar
// 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 Stamina bar
let show_health = self.health.current() != self.health.maximum();
let show_stamina = self.energy.current() != self.energy.maximum();
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 {
let offset = 1.0;
Image::new(self.imgs.health_bg)
.w_h(484.0, 24.0)
.mid_top_with_margin_on(state.ids.frame, -offset)
.set(state.ids.bg_health, ui);
Rectangle::fill_with([480.0, 18.0], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.bg_health, 2.0, 2.0)
.set(state.ids.hp_alignment, ui);
let health_col = match hp_percentage as u8 {
0..=20 => crit_hp_color,
21..=40 => LOW_HP_COLOR,
_ => HP_COLOR,
};
Image::new(self.imgs.bar_content)
.w_h(480.0 * hp_percentage / 100.0, 18.0)
.color(Some(health_col))
.top_left_with_margins_on(state.ids.hp_alignment, 0.0, 0.0)
.set(state.ids.hp_filling, ui);
if decayed_health > 0.0 {
let decay_bar_len = 480.0 * decayed_health;
Image::new(self.imgs.bar_content)
.w_h(decay_bar_len, 18.0)
.color(Some(QUALITY_EPIC))
.top_right_with_margins_on(state.ids.hp_alignment, 0.0, 0.0)
.crop_kids()
.set(state.ids.hp_decayed, ui);
Image::new(self.imgs.decayed_bg)
.w_h(480.0, 18.0)
.color(Some(Color::Rgba(0.58, 0.29, 0.93, (hp_ani + 0.6).min(1.0))))
.top_left_with_margins_on(state.ids.hp_alignment, 0.0, 0.0)
.parent(state.ids.hp_decayed)
.set(state.ids.decay_overlay, ui);
}
Image::new(self.imgs.health_frame)
.w_h(484.0, 24.0)
.color(Some(UI_HIGHLIGHT_0))
.middle_of(state.ids.bg_health)
.set(state.ids.frame_health, ui);
}
if show_stamina && !self.health.is_dead {
let offset = if show_health || decayed_health > 0.0 {
34.0
} else {
1.0
};
Image::new(self.imgs.stamina_bg)
.w_h(323.0, 16.0)
.mid_top_with_margin_on(state.ids.frame, -offset)
.set(state.ids.bg_stamina, ui);
Rectangle::fill_with([319.0, 10.0], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.bg_stamina, 2.0, 2.0)
.set(state.ids.stamina_alignment, ui);
Image::new(self.imgs.bar_content)
.w_h(319.0 * energy_percentage / 100.0, 10.0)
.color(Some(STAMINA_COLOR))
.top_left_with_margins_on(state.ids.stamina_alignment, 0.0, 0.0)
.set(state.ids.stamina_filling, ui);
Image::new(self.imgs.stamina_frame)
.w_h(323.0, 16.0)
.color(Some(UI_HIGHLIGHT_0))
.middle_of(state.ids.bg_stamina)
.set(state.ids.frame_stamina, ui);
}
// Bar Text
// Values
if let BarNumbers::Values = bar_values {
let mut hp_txt = format!(
"{}/{}",
(self.health.current() / 10).max(1) as u32, /* Don't show 0 health for
* living players */
(self.health.maximum() / 10) as u32
);
let mut energy_txt = format!(
"{}/{}",
(self.energy.current() / 10) as u32,
(self.energy.maximum() / 10) as u32
);
if self.health.is_dead {
hp_txt = self.localized_strings.get("hud.group.dead").to_string();
energy_txt = self.localized_strings.get("hud.group.dead").to_string();
};
Text::new(&hp_txt)
.middle_of(state.ids.frame_health)
.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.hp_txt_bg, ui);
Text::new(&hp_txt)
.bottom_left_with_margins_on(state.ids.hp_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.hp_txt, ui);
Text::new(&energy_txt)
.middle_of(state.ids.frame_stamina)
.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.stamina_txt_bg, ui);
Text::new(&energy_txt)
.bottom_left_with_margins_on(state.ids.stamina_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.stamina_txt, ui);
}
//Percentages
if let BarNumbers::Percent = bar_values {
let mut hp_txt = format!("{}%", hp_percentage as u32);
let mut energy_txt = format!("{}", energy_percentage as u32);
if self.health.is_dead {
hp_txt = self.localized_strings.get("hud.group.dead").to_string();
energy_txt = self.localized_strings.get("hud.group.dead").to_string();
};
Text::new(&hp_txt)
.middle_of(state.ids.frame_health)
.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.hp_txt_bg, ui);
Text::new(&hp_txt)
.bottom_left_with_margins_on(state.ids.hp_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.hp_txt, ui);
Text::new(&energy_txt)
.middle_of(state.ids.frame_stamina)
.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.stamina_txt_bg, ui);
Text::new(&energy_txt)
.bottom_left_with_margins_on(state.ids.stamina_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.stamina_txt, ui);
}
// Slots
let content_source = (
self.hotbar,
self.inventory,
self.energy,
self.ability_map,
self.msm,
); // TODO: avoid this
let image_source = (self.item_imgs, self.imgs);
let mut slot_maker = SlotMaker {
// TODO: is a separate image needed for the frame?
empty_slot: self.imgs.skillbar_slot,
filled_slot: self.imgs.skillbar_slot,
selected_slot: self.imgs.inv_slot_sel,
background_color: None,
content_size: ContentSize {
width_height_ratio: 1.0,
max_fraction: 0.8, /* Changes the item image size by setting a maximum fraction
* of either the width or height */
},
selected_content_scale: 1.0,
amount_font: self.fonts.cyri.conrod_id,
amount_margins: Vec2::new(1.0, 1.0),
amount_font_size: self.fonts.cyri.scale(12),
amount_text_color: TEXT_COLOR,
content_source: &content_source,
image_source: &image_source,
slot_manager: Some(self.slot_manager),
pulse: self.pulse,
};
// Tooltips
let 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);
let item_tooltip = ItemTooltip::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,
)
},
self.client,
self.imgs,
self.item_imgs,
self.pulse,
self.msm,
self.localized_strings,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
.desc_font_size(self.fonts.cyri.scale(12))
.font_id(self.fonts.cyri.conrod_id)
.desc_text_color(TEXT_COLOR);
let slot_content = |slot| {
content_source
.0
.get(slot)
.and_then(|content| match content {
hotbar::SlotContents::Inventory(i) => content_source.1.get(i),
_ => None,
})
};
// Helper
let tooltip_text = |slot| {
content_source
.0
.get(slot)
.and_then(|content| match content {
hotbar::SlotContents::Inventory(i) => content_source
.1
.get(i)
.map(|item| (item.name(), item.description())),
hotbar::SlotContents::Ability3 => content_source
.1
.equipped(EquipSlot::Mainhand)
.map(|i| i.kind())
.and_then(|kind| match kind {
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
_ => None,
}),
hotbar::SlotContents::Ability4 => {
let hands = |equip_slot| match content_source
.1
.equipped(equip_slot)
.map(|i| i.kind())
{
Some(ItemKind::Tool(tool)) => Some(tool.hands),
_ => None,
};
let active_tool_hands = hands(EquipSlot::Mainhand);
let second_tool_hands = hands(EquipSlot::Offhand);
let equip_slot = match (active_tool_hands, second_tool_hands) {
(Some(Hands::Two), _) => Some(EquipSlot::Mainhand),
(_, Some(Hands::One)) => Some(EquipSlot::Offhand),
(Some(Hands::One), _) => Some(EquipSlot::Mainhand),
(_, _) => None,
};
if let Some(equip_slot) = equip_slot {
content_source
.1
.equipped(equip_slot)
.map(|i| i.kind())
.and_then(|kind| match kind {
ItemKind::Tool(Tool { kind, .. }) => ability_description(kind),
_ => None,
})
} else {
None
}
},
})
};
// Slot 1-5
// Slot 1
slot_maker.empty_slot = self.imgs.skillbar_slot;
slot_maker.selected_slot = self.imgs.skillbar_slot;
let slot = slot_maker
.fabricate(hotbar::Slot::One, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.bottom_left_with_margins_on(state.ids.frame, 0.0, 0.0);
if let Some(item) = slot_content(hotbar::Slot::One) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot1, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::One) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot1, ui);
} else {
slot.set(state.ids.slot1, ui);
}
// Slot 2
let slot = slot_maker
.fabricate(hotbar::Slot::Two, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot1, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Two) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot2, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot2, ui);
} else {
slot.set(state.ids.slot2, ui);
}
// Slot 3
let slot = slot_maker
.fabricate(hotbar::Slot::Three, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot2, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Three) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot3, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot3, ui);
} else {
slot.set(state.ids.slot3, ui);
}
// Slot 4
let slot = slot_maker
.fabricate(hotbar::Slot::Four, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot3, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Four) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot4, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot4, ui);
} else {
slot.set(state.ids.slot4, ui);
}
// Slot 5
let slot = slot_maker
.fabricate(hotbar::Slot::Five, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot4, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Five) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot5, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot5, ui);
} else {
slot.set(state.ids.slot5, ui);
}
// Slot M1
Image::new(self.imgs.skillbar_slot)
.w_h(40.0, 40.0)
.right_from(state.ids.slot5, slot_offset)
.set(state.ids.m1_slot_bg, ui);
Button::image(
match self
.inventory
.equipped(EquipSlot::Mainhand)
.map(|i| i.kind())
{
Some(ItemKind::Tool(Tool { kind, .. })) => match kind {
ToolKind::Sword => self.imgs.twohsword_m1,
ToolKind::Dagger => self.imgs.onehdagger_m1,
ToolKind::Shield => self.imgs.onehshield_m1,
ToolKind::Hammer => self.imgs.twohhammer_m1,
ToolKind::Axe => self.imgs.twohaxe_m1,
ToolKind::Bow => self.imgs.bow_m1,
ToolKind::Sceptre => self.imgs.skill_sceptre_lifesteal,
ToolKind::Staff => self.imgs.fireball,
ToolKind::Debug => self.imgs.flyingrod_m1,
_ => self.imgs.nothing,
},
_ => self.imgs.nothing,
},
) // Insert Icon here
.w_h(36.0, 36.0)
.middle_of(state.ids.m1_slot_bg)
.set(state.ids.m1_content, ui);
// Slot M2
Image::new(self.imgs.skillbar_slot)
.w_h(40.0, 40.0)
.right_from(state.ids.m1_slot_bg, slot_offset)
.set(state.ids.m2_slot_bg, ui);
fn get_item_and_tool(
inventory: &Inventory,
equip_slot: EquipSlot,
) -> Option<(&Item, &Tool)> {
match inventory.equipped(equip_slot).map(|i| (i, i.kind())) {
Some((i, ItemKind::Tool(tool))) => Some((i, tool)),
_ => None,
}
}
let active_tool = get_item_and_tool(self.inventory, EquipSlot::Mainhand);
let second_tool = get_item_and_tool(self.inventory, EquipSlot::Offhand);
let tool = match (
active_tool.map(|(_, x)| x.hands),
second_tool.map(|(_, x)| x.hands),
) {
(Some(Hands::Two), _) => active_tool,
(_, Some(Hands::One)) => second_tool,
(Some(Hands::One), _) => active_tool,
(_, _) => None,
};
Button::image(match tool.map(|(_, t)| t.kind) {
Some(ToolKind::Sword) => self.imgs.twohsword_m2,
Some(ToolKind::Dagger) => self.imgs.onehdagger_m2,
Some(ToolKind::Shield) => self.imgs.onehshield_m2,
Some(ToolKind::Hammer) => self.imgs.hammergolf,
Some(ToolKind::Axe) => self.imgs.axespin,
Some(ToolKind::Bow) => self.imgs.bow_m2,
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_heal,
Some(ToolKind::Staff) => self.imgs.flamethrower,
Some(ToolKind::Debug) => self.imgs.flyingrod_m2,
_ => self.imgs.nothing,
})
.w_h(36.0, 36.0)
.middle_of(state.ids.m2_slot_bg)
.image_color(if let Some((item, tool)) = tool {
if self.energy.current()
>= tool
.get_abilities(&self.msm, item.components(), self.ability_map)
.secondary
.get_energy_cost()
{
Color::Rgba(1.0, 1.0, 1.0, 1.0)
} else {
Color::Rgba(0.3, 0.3, 0.3, 0.8)
}
} else {
match tool.map(|(_, t)| t.kind) {
None => Color::Rgba(1.0, 1.0, 1.0, 0.0),
_ => Color::Rgba(1.0, 1.0, 1.0, 1.0),
}
})
.set(state.ids.m2_content, ui);
// Slot 6-10
// Slot 6
slot_maker.empty_slot = self.imgs.skillbar_slot;
slot_maker.selected_slot = self.imgs.skillbar_slot;
let slot = slot_maker
.fabricate(hotbar::Slot::Six, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.m2_slot_bg, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Six) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot6, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Six) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot6, ui);
} else {
slot.set(state.ids.slot6, ui);
}
// Slot 7
let slot = slot_maker
.fabricate(hotbar::Slot::Seven, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot6, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Seven) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot7, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Seven) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot7, ui);
} else {
slot.set(state.ids.slot7, ui);
}
// Slot 8
let slot = slot_maker
.fabricate(hotbar::Slot::Eight, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot7, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Eight) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot8, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Eight) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot8, ui);
} else {
slot.set(state.ids.slot8, ui);
}
// Slot 9
let slot = slot_maker
.fabricate(hotbar::Slot::Nine, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot8, slot_offset);
if let Some(item) = slot_content(hotbar::Slot::Nine) {
slot.with_item_tooltip(self.item_tooltip_manager, item, &None, &item_tooltip)
.set(state.ids.slot9, ui);
} else if let Some((title, desc)) = tooltip_text(hotbar::Slot::Nine) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot9, ui);
} else {
slot.set(state.ids.slot9, ui);
}
// Quickslot
slot_maker.empty_slot = self.imgs.skillbar_slot;
slot_maker.selected_slot = self.imgs.skillbar_slot;
let slot = slot_maker
.fabricate(hotbar::Slot::Ten, [40.0; 2])
.filled_slot(self.imgs.skillbar_slot)
.right_from(state.ids.slot9, slot_offset);
if let Some((title, desc)) = tooltip_text(hotbar::Slot::Ten) {
slot.with_tooltip(self.tooltip_manager, title, desc, &tooltip, TEXT_COLOR)
.set(state.ids.slot10, ui);
} else {
slot.set(state.ids.slot10, ui);
}
// Shortcuts
if let ShortcutNumbers::On = shortcuts {
if let Some(slot1) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot1)
{
Text::new(slot1.to_string().as_str())
.top_right_with_margins_on(state.ids.slot1, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot1_text_bg, ui);
Text::new(slot1.to_string().as_str())
.bottom_left_with_margins_on(state.ids.slot1_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot1_text, ui);
}
if let Some(slot2) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot2)
{
Text::new(slot2.to_string().as_str())
.top_right_with_margins_on(state.ids.slot2, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot2_text_bg, ui);
Text::new(slot2.to_string().as_str())
.bottom_left_with_margins_on(state.ids.slot2_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot2_text, ui);
}
if let Some(slot3) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot3)
{
Text::new(slot3.to_string().as_str())
.top_right_with_margins_on(state.ids.slot3, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot3_text_bg, ui);
Text::new(slot3.to_string().as_str())
.bottom_left_with_margins_on(state.ids.slot3_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot3_text, ui);
}
if let Some(slot4) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot4)
{
Text::new(slot4.to_string().as_str())
.top_right_with_margins_on(state.ids.slot4, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot4_text_bg, ui);
Text::new(slot4.to_string().as_str())
.bottom_left_with_margins_on(state.ids.slot4_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot4_text, ui);
}
if let Some(slot5) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot5)
{
Text::new(slot5.to_string().as_str())
.top_right_with_margins_on(state.ids.slot5, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot5_text_bg, ui);
Text::new(slot5.to_string().as_str())
.bottom_left_with_margins_on(state.ids.slot5_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot5_text, ui);
}
if let Some(slot6) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot6)
{
Text::new(slot6.to_string().as_str())
.top_right_with_margins_on(state.ids.slot6, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot6_text_bg, ui);
Text::new(slot6.to_string().as_str())
.bottom_right_with_margins_on(state.ids.slot6_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot6_text, ui);
}
if let Some(slot7) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot7)
{
Text::new(slot7.to_string().as_str())
.top_right_with_margins_on(state.ids.slot7, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot7_text_bg, ui);
Text::new(slot7.to_string().as_str())
.bottom_right_with_margins_on(state.ids.slot7_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot7_text, ui);
}
if let Some(slot8) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot8)
{
Text::new(slot8.to_string().as_str())
.top_right_with_margins_on(state.ids.slot8, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot8_text_bg, ui);
Text::new(slot8.to_string().as_str())
.bottom_right_with_margins_on(state.ids.slot8_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot8_text, ui);
}
if let Some(slot9) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot9)
{
Text::new(slot9.to_string().as_str())
.top_right_with_margins_on(state.ids.slot9, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot9_text_bg, ui);
Text::new(slot9.to_string().as_str())
.bottom_right_with_margins_on(state.ids.slot9_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot9_text, ui);
}
if let Some(slot10) = &self
.global_state
.settings
.controls
.get_binding(GameInput::Slot10)
{
Text::new(slot10.to_string().as_str())
.top_right_with_margins_on(state.ids.slot10, 3.0, 5.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(BLACK)
.set(state.ids.slot10_text_bg, ui);
Text::new(slot10.to_string().as_str())
.bottom_right_with_margins_on(state.ids.slot10_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(8))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.slot10_text, ui);
}
};
// M1 and M2 icons
Image::new(self.imgs.m1_ico)
.w_h(16.0, 18.0)
.mid_bottom_with_margin_on(state.ids.m1_content, -11.0)
.set(state.ids.m1_ico, ui);
Image::new(self.imgs.m2_ico)
.w_h(16.0, 18.0)
.mid_bottom_with_margin_on(state.ids.m2_content, -11.0)
.set(state.ids.m2_ico, ui);
// Combo Counter
if let Some(combo) = self.combo {
if combo.combo > 0 {
let combo_txt = format!("{} Combo", combo.combo);
let combo_cnt = combo.combo as f32;
let time_since_last_update = comp::combo::COMBO_DECAY_START - combo.timer;
let alpha = (1.0 - time_since_last_update * 0.2).min(1.0) as f32;
let fnt_col = Color::Rgba(
// White -> Yellow -> Red text color gradient depending on count
(1.0 - combo_cnt / (combo_cnt + 20.0)).max(0.79),
(1.0 - combo_cnt / (combo_cnt + 80.0)).max(0.19),
(1.0 - combo_cnt / (combo_cnt + 5.0)).max(0.17),
alpha,
);
let fnt_size = ((14.0 + combo.timer as f32 * 0.8).min(30.0)) as u32
+ if (time_since_last_update) < 0.1 { 2 } else { 0 }; // Increase size for higher counts, "flash" on update by increasing the font size by 2
Rectangle::fill_with([10.0, 10.0], color::TRANSPARENT)
.middle_of(ui.window)
.set(state.ids.combo_align, ui);
Text::new(combo_txt.as_str())
.mid_bottom_with_margin_on(
state.ids.combo_align,
-350.0 + time_since_last_update * -8.0,
)
.font_size(self.fonts.cyri.scale(fnt_size))
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, alpha))
.set(state.ids.combo_bg, ui);
Text::new(combo_txt.as_str())
.bottom_right_with_margins_on(state.ids.combo_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(fnt_size))
.font_id(self.fonts.cyri.conrod_id)
.color(fnt_col)
.set(state.ids.combo, ui);
}
}
}
}
fn ability_description(tool: &ToolKind) -> Option<(&str, &str)> {
match tool {
ToolKind::Hammer => Some((
"Smash of Doom",
"\nAn AOE attack with knockback. \nLeaps to position of cursor.",
)),
ToolKind::Axe => Some(("Spin Leap", "\nA slashing running spin leap.")),
ToolKind::Staff => Some((
"Firebomb",
"\nWhirls a big fireball into the air. \nExplodes the ground and does\na big amount \
of damage",
)),
ToolKind::Sword => Some((
"Whirlwind",
"\nMove forward while spinning with \n your sword.",
)),
ToolKind::Bow => Some((
"Burst",
"\nLaunches a burst of arrows at the top \nof a running leap.",
)),
ToolKind::Debug => Some((
"Possessing Arrow",
"\nShoots a poisonous arrow.\nLets you control your target.",
)),
ToolKind::Sceptre => Some((
"Thorn Bulwark",
"\nProtects you and your group with thorns\nfor a short amount of time.",
)),
_ => None,
}
}