use super::{ cr_color, img_ids::{Imgs, ImgsRot}, item_imgs::ItemImgs, slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager}, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, }; use crate::{ game_input::GameInput, ui::{ fonts::Fonts, slot::{ContentSize, SlotMaker}, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager, Tooltipable, }, GlobalState, }; use client::Client; use common::{ assets::AssetExt, combat::{combat_rating, Damage}, comp::{ inventory::InventorySortOrder, item::{ItemDef, MaterialStatManifest, Quality}, Body, Energy, Health, Inventory, Poise, SkillSet, Stats, }, }; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Scrollbar, State as ConrodState, Text}, widget_ids, Color, Colorable, Positionable, Scalar, Sizeable, UiCell, Widget, WidgetCommon, }; use i18n::Localization; use crate::hud::slots::SlotKind; use specs::Entity as EcsEntity; use std::sync::Arc; use vek::Vec2; widget_ids! { pub struct InventoryScrollerIds { test, bag_close, inv_alignment, inv_grid_1, inv_grid_2, inv_scrollbar, inv_slots_0, inv_slots[], bg, bg_frame, char_ico, coin_ico, space_txt, coin_txt, inventory_title, inventory_title_bg, scrollbar_bg, second_phase_scrollbar_bg, scrollbar_slots, left_scrollbar_slots, } } pub struct InventoryScrollerState { ids: InventoryScrollerIds, } #[derive(WidgetCommon)] pub struct InventoryScroller<'a> { client: &'a Client, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, #[conrod(common_builder)] common: widget::CommonBuilder, item_tooltip_manager: &'a mut ItemTooltipManager, slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a Localization, show_stats: bool, show_bag_inv: bool, on_right: bool, item_tooltip: &'a ItemTooltip<'a>, playername: String, entity: EcsEntity, is_us: bool, inventory: &'a Inventory, bg_ids: &'a BackgroundIds, } impl<'a> InventoryScroller<'a> { #[allow(clippy::too_many_arguments)] pub fn new( client: &'a Client, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, item_tooltip_manager: &'a mut ItemTooltipManager, slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a Localization, show_stats: bool, show_bag_inv: bool, on_right: bool, item_tooltip: &'a ItemTooltip<'a>, playername: String, entity: EcsEntity, is_us: bool, inventory: &'a Inventory, bg_ids: &'a BackgroundIds, ) -> Self { InventoryScroller { client, imgs, item_imgs, fonts, common: widget::CommonBuilder::default(), item_tooltip_manager, slot_manager, pulse, localized_strings, show_stats, show_bag_inv, on_right, item_tooltip, playername, entity, is_us, inventory, bg_ids, } } fn background(&mut self, ui: &mut UiCell<'_>) { let bg_id = if !self.on_right { self.imgs.inv_bg_bag } else { self.imgs.player_inv_bg_bag }; let img_id = if !self.on_right { self.imgs.inv_frame_bag } else { self.imgs.player_inv_frame_bag }; let mut bg = Image::new(if self.show_stats { self.imgs.inv_bg_stats } else if self.show_bag_inv { bg_id } else { self.imgs.inv_bg_armor }) .w_h( 424.0, if self.show_bag_inv && !self.on_right { 548.0 } else { 708.0 }, ); if self.on_right { bg = bg.bottom_right_with_margins_on(ui.window, 70.0, 5.0); } else { bg = bg.bottom_left_with_margins_on(ui.window, 230.0, 5.0); } bg.color(Some(UI_MAIN)).set(self.bg_ids.bg, ui); Image::new(if self.show_bag_inv { img_id } else { self.imgs.inv_frame }) .w_h( 424.0, if self.show_bag_inv && !self.on_right { 548.0 } else { 708.0 }, ) .middle_of(self.bg_ids.bg) .color(Some(UI_HIGHLIGHT_0)) .set(self.bg_ids.bg_frame, ui); } fn title(&mut self, state: &mut ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>) { Text::new( &self .localized_strings .get("hud.bag.inventory") .replace("{playername}", &*self.playername), ) .mid_top_with_margin_on(self.bg_ids.bg_frame, 9.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.inventory_title_bg, ui); Text::new( &self .localized_strings .get("hud.bag.inventory") .replace("{playername}", &*self.playername), ) .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) .color(TEXT_COLOR) .set(state.ids.inventory_title, ui); } fn scrollbar_and_slots( &mut self, state: &mut ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>, ) { let space_max = self.inventory.slots().count(); // Slots Scrollbar if space_max > 45 && !self.show_bag_inv { // Scrollbar-BG Image::new(self.imgs.scrollbar_bg) .w_h(9.0, 173.0) .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.scrollbar_bg, ui); // Scrollbar Scrollbar::y_axis(state.ids.inv_alignment) .thickness(5.0) .h(123.0) .color(UI_MAIN) .middle_of(state.ids.scrollbar_bg) .set(state.ids.scrollbar_slots, ui); } else if space_max > 135 && self.on_right { // Scrollbar-BG Image::new(self.imgs.scrollbar_bg_big) .w_h(9.0, 592.0) .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.scrollbar_bg, ui); // Scrollbar Scrollbar::y_axis(state.ids.inv_alignment) .thickness(5.0) .h(542.0) .color(UI_MAIN) .middle_of(state.ids.scrollbar_bg) .set(state.ids.scrollbar_slots, ui); }; // This is just for the offeror inventory scrollbar if space_max >= 108 && !self.on_right && self.show_bag_inv { // Left bag scrollbar background Image::new(self.imgs.second_phase_scrollbar_bg) .w_h(9.0, 434.0) .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.second_phase_scrollbar_bg, ui); // Left bag scrollbar Scrollbar::y_axis(state.ids.inv_alignment) .thickness(5.0) .h(384.0) .color(UI_MAIN) .middle_of(state.ids.second_phase_scrollbar_bg) .set(state.ids.left_scrollbar_slots, ui); } let grid_width = if self.show_bag_inv && !self.on_right { 440.0 // This for the left bag } else if self.show_bag_inv && self.on_right { 600.0 // This for the expanded right bag } else { 200.0 }; // Alignment for Grid Rectangle::fill_with([362.0, grid_width], color::TRANSPARENT) .bottom_left_with_margins_on( self.bg_ids.bg_frame, 29.0, if self.show_bag_inv && !self.on_right { 28.0 } else { 46.5 }, ) .scroll_kids_vertically() .set(state.ids.inv_alignment, ui); // Bag Slots // Create available inventory slot widgets if state.ids.inv_slots.len() < self.inventory.capacity() { state.update(|s| { s.ids .inv_slots .resize(self.inventory.capacity(), &mut ui.widget_id_generator()); }); } // Determine the range of inventory slots that are provided by the loadout item // that the mouse is over let mouseover_loadout_slots = self .slot_manager .mouse_over_slot .and_then(|x| { if let SlotKind::Equip(e) = x { self.inventory.get_slot_range_for_equip_slot(e) } else { None } }) .unwrap_or(0usize..0usize); // Display inventory contents let mut slot_maker = SlotMaker { empty_slot: self.imgs.inv_slot, filled_slot: self.imgs.inv_slot, selected_slot: self.imgs.inv_slot_sel, background_color: Some(UI_MAIN), content_size: ContentSize { width_height_ratio: 1.0, max_fraction: 0.75, }, selected_content_scale: 1.067, amount_font: self.fonts.cyri.conrod_id, amount_margins: Vec2::new(-4.0, 0.0), amount_font_size: self.fonts.cyri.scale(12), amount_text_color: TEXT_COLOR, content_source: self.inventory, image_source: self.item_imgs, slot_manager: Some(self.slot_manager), pulse: self.pulse, }; for (i, (pos, item)) in self.inventory.slots_with_id().enumerate() { let x = i % 9; let y = i / 9; // Slot let mut slot_widget = slot_maker .fabricate( InventorySlot { slot: pos, ours: self.is_us, entity: self.entity, }, [40.0; 2], ) .top_left_with_margins_on( state.ids.inv_alignment, 0.0 + y as f64 * (40.0), 0.0 + x as f64 * (40.0), ); // Highlight slots are provided by the loadout item that the mouse is over if mouseover_loadout_slots.contains(&i) { slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 1.0, 1.0, 1.0)); } if let Some(item) = item { let quality_col_img = match item.quality() { Quality::Low => self.imgs.inv_slot_grey, Quality::Common => self.imgs.inv_slot, Quality::Moderate => self.imgs.inv_slot_green, Quality::High => self.imgs.inv_slot_blue, Quality::Epic => self.imgs.inv_slot_purple, Quality::Legendary => self.imgs.inv_slot_gold, Quality::Artifact => self.imgs.inv_slot_orange, _ => self.imgs.inv_slot_red, }; let prices_info = self .client .pending_trade() .as_ref() .and_then(|(_, _, prices)| prices.clone()); slot_widget .filled_slot(quality_col_img) .with_item_tooltip( self.item_tooltip_manager, item, &prices_info, self.item_tooltip, ) .set(state.ids.inv_slots[i], ui); } else { slot_widget.set(state.ids.inv_slots[i], ui); } } } fn footer_metrics( &mut self, state: &mut ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>, ) { let space_used = self.inventory.populated_slots(); let space_max = self.inventory.slots().count(); let bag_space = format!("{}/{}", space_used, space_max); let bag_space_percentage = space_used as f32 / space_max as f32; let coin_itemdef = Arc::::load_expect_cloned("common.items.utility.coins"); let coin_count = self.inventory.item_count(&coin_itemdef); // TODO: Reuse this to generally count a stackable item the player selected //let cheese_itemdef = // Arc::::load_expect_cloned("common.items.food.cheese"); // let cheese_count = self.inventory.item_count(&cheese_itemdef); // Coin Icon and Coin Text Image::new(self.imgs.coin_ico) .w_h(16.0, 17.0) .bottom_left_with_margins_on(self.bg_ids.bg_frame, 2.0, 43.0) .set(state.ids.coin_ico, ui); Text::new(&format!("{}", coin_count)) .bottom_left_with_margins_on(self.bg_ids.bg_frame, 6.0, 64.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .color(Color::Rgba(0.871, 0.863, 0.05, 1.0)) .set(state.ids.coin_txt, ui); // TODO: Add a customizable counter for stackable items here // TODO: Cheese is funny until it's real /*Image::new(self.imgs.cheese_ico) .w_h(16.0, 17.0) .bottom_left_with_margins_on(self.bg_ids.bg_frame, 2.0, 110.0) .set(state.ids.cheese_ico, ui); Text::new(&format!("{}", cheese_count)) .bottom_left_with_margins_on(self.bg_ids.bg_frame, 6.0, 144.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .color(Color::Rgba(0.871, 0.863, 0.05, 1.0)) .set(state.ids.cheese_txt, ui);*/ //Free Bag-Space Text::new(&bag_space) .bottom_right_with_margins_on(self.bg_ids.bg_frame, 6.0, 43.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .color(if bag_space_percentage < 0.8 { TEXT_COLOR } else if bag_space_percentage < 1.0 { LOW_HP_COLOR } else { CRITICAL_HP_COLOR }) .set(state.ids.space_txt, ui); } } impl<'a> Widget for InventoryScroller<'a> { type Event = (); type State = InventoryScrollerState; type Style = (); fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { InventoryScrollerState { ids: InventoryScrollerIds::new(id_gen), } } fn style(&self) -> Self::Style {} fn update(mut self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; self.background(ui); self.title(state, ui); self.scrollbar_and_slots(state, ui); self.footer_metrics(state, ui); } } widget_ids! { pub struct BackgroundIds { bg, bg_frame, } } widget_ids! { pub struct BagIds { test, inventory_scroller, bag_close, //tooltip[], char_ico, coin_ico, space_txt, inventory_title, inventory_title_bg, inventory_sort, scrollbar_bg, scrollbar_slots, tab_1, tab_2, tab_3, tab_4, bag_expand_btn, // Armor Slots slots_bg, head_slot, neck_slot, chest_slot, shoulders_slot, hands_slot, legs_slot, belt_slot, lantern_slot, ring1_slot, ring2_slot, feet_slot, back_slot, tabard_slot, glider_slot, active_mainhand_slot, active_offhand_slot, inactive_mainhand_slot, inactive_offhand_slot, swap_equipped_weapons_btn, bag1_slot, bag2_slot, bag3_slot, bag4_slot, // Stats stat_icons[], stat_txts[], } } #[derive(WidgetCommon)] pub struct Bag<'a> { client: &'a Client, global_state: &'a GlobalState, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, #[conrod(common_builder)] common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager, slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a Localization, stats: &'a Stats, skill_set: &'a SkillSet, health: &'a Health, energy: &'a Energy, show: &'a Show, body: &'a Body, msm: &'a MaterialStatManifest, } impl<'a> Bag<'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, tooltip_manager: &'a mut TooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager, slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a Localization, stats: &'a Stats, skill_set: &'a SkillSet, health: &'a Health, energy: &'a Energy, show: &'a Show, body: &'a Body, msm: &'a MaterialStatManifest, ) -> Self { Self { client, global_state, imgs, item_imgs, fonts, common: widget::CommonBuilder::default(), rot_imgs, tooltip_manager, item_tooltip_manager, slot_manager, pulse, localized_strings, stats, skill_set, energy, health, show, body, msm, } } } const STATS: [&str; 5] = [ "Health", "Energy", "Protection", "Combat Rating", "Stun Resilience", ]; pub struct BagState { ids: BagIds, bg_ids: BackgroundIds, } pub enum Event { BagExpand, Close, SortInventory, SwapEquippedWeapons, } impl<'a> Widget for Bag<'a> { type Event = Option; type State = BagState; type Style = (); fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State { BagState { bg_ids: BackgroundIds { bg: id_gen.next(), bg_frame: id_gen.next(), }, ids: BagIds::new(id_gen), } } fn style(&self) -> Self::Style {} fn update(self, args: widget::UpdateArgs) -> Self::Event { common_base::prof_span!("Bag::update"); let widget::UpdateArgs { state, ui, .. } = args; let i18n = &self.localized_strings; let key_layout = &self.global_state.window.key_layout; let mut event = None; let bag_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 inventories = self.client.inventories(); let inventory = match inventories.get(self.client.entity()) { Some(l) => l, None => return None, }; // 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); InventoryScroller::new( self.client, self.imgs, self.item_imgs, self.fonts, self.item_tooltip_manager, self.slot_manager, self.pulse, self.localized_strings, self.show.stats, self.show.bag_inv, true, &item_tooltip, self.stats.name.to_string(), self.client.entity(), true, inventory, &state.bg_ids, ) .set(state.ids.inventory_scroller, ui); // Char Pixel-Art Image::new(self.imgs.char_art) .w_h(40.0, 37.0) .top_left_with_margins_on(state.bg_ids.bg, 4.0, 2.0) .set(state.ids.char_ico, ui); // Button to expand bag let txt = if self.show.bag_inv { "Show Loadout" } else { "Expand Bag" }; let expand_btn = Button::image(if self.show.bag_inv { self.imgs.collapse_btn } else { self.imgs.expand_btn }) .w_h(30.0, 17.0) .hover_image(if self.show.bag_inv { self.imgs.collapse_btn_hover } else { self.imgs.expand_btn_hover }) .press_image(if self.show.bag_inv { self.imgs.collapse_btn_press } else { self.imgs.expand_btn_press }); // Only show expand button when it's needed... if inventory.slots().count() > 45 || self.show.bag_inv { let expand_btn_top = if self.show.bag_inv { 53.0 } else { 460.0 }; if expand_btn .top_right_with_margins_on(state.bg_ids.bg_frame, expand_btn_top, 37.0) .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR) .set(state.ids.bag_expand_btn, ui) .was_clicked() { event = Some(Event::BagExpand); } } // Sort inventory button let inv_sort_btn_top: Scalar = if !self.show.bag_inv { 460.0 } else { 53.0 }; if Button::image(self.imgs.inv_sort_btn) .w_h(30.0, 17.0) .hover_image(self.imgs.inv_sort_btn_hover) .press_image(self.imgs.inv_sort_btn_press) .top_left_with_margins_on(state.bg_ids.bg_frame, inv_sort_btn_top, 47.0) .with_tooltip( self.tooltip_manager, match inventory.next_sort_order() { InventorySortOrder::Name => i18n.get("hud.bag.sort_by_name"), InventorySortOrder::Quality => i18n.get("hud.bag.sort_by_quality"), InventorySortOrder::Tag => i18n.get("hud.bag.sort_by_category"), }, "", &tooltip, color::WHITE, ) .set(state.ids.inventory_sort, ui) .was_clicked() { event = Some(Event::SortInventory); } // Armor Slots let mut slot_maker = SlotMaker { empty_slot: self.imgs.armor_slot_empty, filled_slot: self.imgs.armor_slot, selected_slot: self.imgs.armor_slot_sel, background_color: Some(UI_HIGHLIGHT_0), content_size: ContentSize { width_height_ratio: 1.0, max_fraction: 0.75, /* Changes the item image size by setting a maximum * fraction * of either the width or height */ }, selected_content_scale: 1.067, amount_font: self.fonts.cyri.conrod_id, amount_margins: Vec2::new(-4.0, 0.0), amount_font_size: self.fonts.cyri.scale(12), amount_text_color: TEXT_COLOR, content_source: inventory, image_source: self.item_imgs, slot_manager: Some(self.slot_manager), pulse: self.pulse, }; // NOTE: Yes, macros considered harmful. // Though, this code mutably captures two different fields of `self` // This works because it's different branches of if-let // so in reality borrow checker allows you to do this as you // capture only one field. // // The less impossible, but still tricky part is denote type of // `$slot_maker` which has 1 lifetype parameter and 3 type parameters // in such way that it implements all traits conrod needs. // // And final part is that this uses that much of arguments // that just by passing all of them, you will get about the same // amount of lines this macro has or even more. // // So considering how many times we copy-paste this code // and how easy this macro looks it sounds like lawful evil. // // What this actually does is checks if we have equipped item on this slot // and if we do, display item tooltip for it. // If not, just show text of slot name. macro_rules! set_tooltip { ($slot_maker:expr, $slot_id:expr, $slot:expr, $desc:expr) => { if let Some(item) = inventory.equipped($slot) { let manager = &mut *self.item_tooltip_manager; $slot_maker .with_item_tooltip(manager, item, &None, &item_tooltip) .set($slot_id, ui) } else { let manager = &mut *self.tooltip_manager; $slot_maker .with_tooltip(manager, i18n.get($desc), "", &tooltip, color::WHITE) .set($slot_id, ui) } }; } let filled_slot = self.imgs.armor_slot; if !self.show.bag_inv { // Stat icons and text state.update(|s| { s.ids .stat_icons .resize(STATS.len(), &mut ui.widget_id_generator()) }); state.update(|s| { s.ids .stat_txts .resize(STATS.len(), &mut ui.widget_id_generator()) }); // Stats let combat_rating = combat_rating( inventory, self.health, self.energy, self.skill_set, *self.body, self.msm, ) .min(999.9); let indicator_col = cr_color(combat_rating); for i in STATS.iter().copied().enumerate() { let btn = Button::image(match i.1 { "Health" => self.imgs.health_ico, "Energy" => self.imgs.energy_ico, "Combat Rating" => self.imgs.combat_rating_ico, "Protection" => self.imgs.protection_ico, "Stun Resilience" => self.imgs.stun_res_ico, _ => self.imgs.nothing, }) .w_h(20.0, 20.0) .image_color(if i.1 == "Combat Rating" { indicator_col } else { TEXT_COLOR }); let protection_txt = format!( "{}%", (100.0 * Damage::compute_damage_reduction(Some(inventory), Some(self.stats), None,)) as i32 ); let health_txt = format!("{}", (self.health.maximum() as f32 / 10.0) as usize); let energy_txt = format!("{}", (self.energy.maximum() as f32 / 10.0) as usize); let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize); let stun_res_txt = format!( "{}", (100.0 * Poise::compute_poise_damage_reduction(inventory)) as i32 ); let btn = if i.0 == 0 { btn.top_left_with_margins_on(state.bg_ids.bg_frame, 55.0, 10.0) } else { btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0) }; let tooltip_head = match i.1 { "Health" => i18n.get("hud.bag.health"), "Energy" => i18n.get("hud.bag.energy"), "Combat Rating" => i18n.get("hud.bag.combat_rating"), "Protection" => i18n.get("hud.bag.protection"), "Stun Resilience" => i18n.get("hud.bag.stun_res"), _ => "", }; let tooltip_txt = match i.1 { "Combat Rating" => i18n.get("hud.bag.combat_rating_desc"), "Protection" => i18n.get("hud.bag.protection_desc"), "Stun Resilience" => i18n.get("hud.bag.stun_res_desc"), _ => "", }; btn.with_tooltip( self.tooltip_manager, tooltip_head, tooltip_txt, &bag_tooltip, TEXT_COLOR, ) .set(state.ids.stat_icons[i.0], ui); Text::new(match i.1 { "Health" => &health_txt, "Energy" => &energy_txt, "Combat Rating" => &combat_rating_txt, "Protection" => &protection_txt, "Stun Resilience" => &stun_res_txt, _ => "", }) .right_from(state.ids.stat_icons[i.0], 10.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .graphics_for(state.ids.stat_icons[i.0]) .set(state.ids.stat_txts[i.0], ui); } // Loadout Slots // Head let item_slot = EquipSlot::Armor(ArmorSlot::Head); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .mid_top_with_margin_on(state.bg_ids.bg_frame, 60.0) .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.head_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.head"); // Necklace let item_slot = EquipSlot::Armor(ArmorSlot::Neck); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.neck_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.neck"); // Chest //Image::new(self.imgs.armor_slot) // different graphics for empty/non empty let item_slot = EquipSlot::Armor(ArmorSlot::Chest); let slot = slot_maker .fabricate(item_slot, [85.0; 2]) .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0) .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.chest_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.chest"); // Shoulders let item_slot = EquipSlot::Armor(ArmorSlot::Shoulders); let slot = slot_maker .fabricate(item_slot, [70.0; 2]) .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.shoulders_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.shoulders"); // Hands let item_slot = EquipSlot::Armor(ArmorSlot::Hands); let slot = slot_maker .fabricate(item_slot, [70.0; 2]) .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0) .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.hands_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.hands"); // Belt let item_slot = EquipSlot::Armor(ArmorSlot::Belt); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0) .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.belt_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.belt"); // Legs let item_slot = EquipSlot::Armor(ArmorSlot::Legs); let slot = slot_maker .fabricate(item_slot, [85.0; 2]) .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0) .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.legs_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.legs"); // Ring let item_slot = EquipSlot::Armor(ArmorSlot::Ring1); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.ring1_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.ring"); // Ring 2 let item_slot = EquipSlot::Armor(ArmorSlot::Ring2); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.ring2_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.ring"); // Back let item_slot = EquipSlot::Armor(ArmorSlot::Back); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .down_from(state.ids.ring2_slot, 10.0) .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.back_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.back"); // Foot let item_slot = EquipSlot::Armor(ArmorSlot::Feet); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .down_from(state.ids.ring1_slot, 10.0) .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.feet_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.feet"); // Lantern let item_slot = EquipSlot::Lantern; let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .top_right_with_margins_on(state.bg_ids.bg_frame, 60.0, 5.0) .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.lantern_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.lantern"); // Glider let item_slot = EquipSlot::Glider; let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .down_from(state.ids.lantern_slot, 5.0) .with_icon(self.imgs.glider_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.glider_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.glider"); // Tabard let item_slot = EquipSlot::Armor(ArmorSlot::Tabard); let slot = slot_maker .fabricate(item_slot, [45.0; 2]) .down_from(state.ids.glider_slot, 5.0) .with_icon(self.imgs.tabard_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.tabard_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.tabard"); // Active Mainhand/Left-Slot let item_slot = EquipSlot::ActiveMainhand; let slot = slot_maker .fabricate(item_slot, [85.0; 2]) .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0) .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.active_mainhand_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.mainhand"); // Active Offhand/Right-Slot let item_slot = EquipSlot::ActiveOffhand; let slot = slot_maker .fabricate(item_slot, [85.0; 2]) .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0) .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.active_offhand_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.offhand"); // Inactive Mainhand/Left-Slot let item_slot = EquipSlot::InactiveMainhand; let slot = slot_maker .fabricate(item_slot, [40.0; 2]) .bottom_right_with_margins_on(state.ids.active_mainhand_slot, 3.0, -47.0) .with_icon(self.imgs.mainhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.inactive_mainhand_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.inactive_mainhand"); // Inactive Offhand/Right-Slot let item_slot = EquipSlot::InactiveOffhand; let slot = slot_maker .fabricate(item_slot, [40.0; 2]) .bottom_left_with_margins_on(state.ids.active_offhand_slot, 3.0, -47.0) .with_icon(self.imgs.offhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.inactive_offhand_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.inactive_offhand"); if Button::image(self.imgs.swap_equipped_weapons_btn) .hover_image(self.imgs.swap_equipped_weapons_btn_hover) .press_image(self.imgs.swap_equipped_weapons_btn_press) .w_h(32.0, 40.0) .bottom_left_with_margins_on(state.bg_ids.bg_frame, 0.0, 23.3) .align_middle_y_of(state.ids.active_mainhand_slot) .with_tooltip( self.tooltip_manager, i18n.get("hud.bag.swap_equipped_weapons_title"), if let Some(key) = self .global_state .settings .controls .get_binding(GameInput::SwapLoadout) { i18n.get("hud.bag.swap_equipped_weapons_desc") .replace("{key}", key.display_string(key_layout).as_str()) } else { "".to_string() } .as_str(), &tooltip, color::WHITE, ) .set(state.ids.swap_equipped_weapons_btn, ui) .was_clicked() { event = Some(Event::SwapEquippedWeapons); } } // Bag 1 let item_slot = EquipSlot::Armor(ArmorSlot::Bag1); let slot = slot_maker .fabricate(item_slot, [35.0; 2]) .bottom_left_with_margins_on( state.bg_ids.bg_frame, if self.show.bag_inv { 600.0 } else { 167.0 }, 3.0, ) .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.bag1_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.bag"); // Bag 2 let item_slot = EquipSlot::Armor(ArmorSlot::Bag2); let slot = slot_maker .fabricate(item_slot, [35.0; 2]) .down_from(state.ids.bag1_slot, 2.0) .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.bag2_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.bag"); // Bag 3 let item_slot = EquipSlot::Armor(ArmorSlot::Bag3); let slot = slot_maker .fabricate(item_slot, [35.0; 2]) .down_from(state.ids.bag2_slot, 2.0) .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.bag3_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.bag"); // Bag 4 let item_slot = EquipSlot::Armor(ArmorSlot::Bag4); let slot = slot_maker .fabricate(item_slot, [35.0; 2]) .down_from(state.ids.bag3_slot, 2.0) .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN)) .filled_slot(filled_slot); let slot_id = state.ids.bag4_slot; set_tooltip!(slot, slot_id, item_slot, "hud.bag.bag"); // Close button if Button::image(self.imgs.close_btn) .w_h(24.0, 25.0) .hover_image(self.imgs.close_btn_hover) .press_image(self.imgs.close_btn_press) .top_right_with_margins_on(state.bg_ids.bg, 0.0, 0.0) .set(state.ids.bag_close, ui) .was_clicked() { event = Some(Event::Close); } event } }