Monty Marz 12546d6bf4 Address comments, clippy and minor adjustments
first bunch of comments addressed

change order or scatter, paths and caves being applied in worldgen to avoid floating scatter objects

campfire adjustments, reduced grass density due to FPS issues

readded item descriptions to the crafting window, item desc for craftable armour

address comments
happy clippy, happy life
clippy

clippy

more clippy
fmt

revert cargo.toml formatting
remove "allow unreachable pattern"
fmt
2020-08-21 22:37:08 +02:00

2590 lines
101 KiB
Rust

mod bag;
mod buttons;
mod chat;
mod crafting;
mod esc_menu;
mod group;
mod hotbar;
mod img_ids;
mod item_imgs;
mod map;
mod minimap;
mod overhead;
mod overitem;
mod popup;
mod settings_window;
mod skillbar;
mod slots;
mod social;
mod spell;
mod util;
use crate::{ecs::comp::HpFloaterList, hud::img_ids::ImgsRot, ui::img_ids::Rotations};
pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState};
pub use settings_window::ScaleChange;
use std::time::Duration;
use bag::Bag;
use buttons::Buttons;
use chat::Chat;
use chrono::NaiveTime;
use crafting::Crafting;
use esc_menu::EscMenu;
use group::Group;
use img_ids::Imgs;
use item_imgs::ItemImgs;
use map::Map;
use minimap::MiniMap;
use popup::Popup;
use serde::{Deserialize, Serialize};
use settings_window::{SettingsTab, SettingsWindow};
use skillbar::Skillbar;
use social::{Social, SocialTab};
use spell::Spell;
use crate::{
ecs::comp as vcomp,
i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization},
render::{Consts, Globals, RenderMode, Renderer},
scene::{
camera::{self, Camera},
lod,
},
ui::{fonts::ConrodVoxygenFonts, slot, Graphic, Ingameable, ScaleMode, Ui},
window::{Event as WinEvent, GameInput},
GlobalState,
};
use client::Client;
use common::{assets::load_expect, comp, sync::Uid, terrain::TerrainChunk, vol::RectRasterableVol};
use conrod_core::{
text::cursor::Index,
widget::{self, Button, Image, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
use specs::{Join, WorldExt};
use std::{
collections::{HashMap, VecDeque},
time::Instant,
};
use vek::*;
const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0);
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const TEXT_GRAY_COLOR: Color = Color::Rgba(0.5, 0.5, 0.5, 1.0);
const TEXT_DULL_RED_COLOR: Color = Color::Rgba(0.56, 0.2, 0.2, 1.0);
const TEXT_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
const TEXT_COLOR_GREY: Color = Color::Rgba(1.0, 1.0, 1.0, 0.5);
const MENU_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 0.4);
//const TEXT_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
const TEXT_COLOR_3: Color = Color::Rgba(1.0, 1.0, 1.0, 0.1);
const TEXT_BIND_CONFLICT_COLOR: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0);
const BLACK: Color = Color::Rgba(0.0, 0.0, 0.0, 1.0);
//const BG_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 0.8);
const HP_COLOR: Color = Color::Rgba(0.33, 0.63, 0.0, 1.0);
const LOW_HP_COLOR: Color = Color::Rgba(0.93, 0.59, 0.03, 1.0);
const CRITICAL_HP_COLOR: Color = Color::Rgba(0.79, 0.19, 0.17, 1.0);
const MANA_COLOR: Color = Color::Rgba(0.29, 0.62, 0.75, 0.9);
//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);
// Chat Colors
/// Color for chat command errors (yellow !)
const ERROR_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0);
/// Color for chat command info (blue i)
const INFO_COLOR: Color = Color::Rgba(0.28, 0.83, 0.71, 1.0);
/// Online color
const ONLINE_COLOR: Color = Color::Rgba(0.3, 1.0, 0.3, 1.0);
/// Offline color
const OFFLINE_COLOR: Color = Color::Rgba(1.0, 0.3, 0.3, 1.0);
/// Color for a private message from another player
const TELL_COLOR: Color = Color::Rgba(0.98, 0.71, 1.0, 1.0);
/// Color for local chat
const SAY_COLOR: Color = Color::Rgba(1.0, 0.8, 0.8, 1.0);
/// Color for group chat
const GROUP_COLOR: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0);
/// Color for factional chat
const FACTION_COLOR: Color = Color::Rgba(0.24, 1.0, 0.48, 1.0);
/// Color for regional chat
const REGION_COLOR: Color = Color::Rgba(0.8, 1.0, 0.8, 1.0);
/// Color for death messages
const KILL_COLOR: Color = Color::Rgba(1.0, 0.17, 0.17, 1.0);
/// Color for global messages
const WORLD_COLOR: Color = Color::Rgba(0.95, 1.0, 0.95, 1.0);
/// Color for collected loot messages
const LOOT_COLOR: Color = Color::Rgba(0.69, 0.57, 1.0, 1.0);
//Nametags
const GROUP_MEMBER: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0);
const DEFAULT_NPC: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// UI Color-Theme
const UI_MAIN: Color = Color::Rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue
//const UI_MAIN: Color = Color::Rgba(0.1, 0.1, 0.1, 0.97); // Dark
const UI_HIGHLIGHT_0: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0);
//const UI_DARK_0: Color = Color::Rgba(0.25, 0.37, 0.37, 1.0);
/// Distance at which nametags are visible for group members
const NAMETAG_GROUP_RANGE: f32 = 300.0;
/// Distance at which nametags are visible
const NAMETAG_RANGE: f32 = 40.0;
/// Time nametags stay visible after doing damage even if they are out of range
/// in seconds
const NAMETAG_DMG_TIME: f32 = 60.0;
/// Range damaged triggered nametags can be seen
const NAMETAG_DMG_RANGE: f32 = 120.0;
widget_ids! {
struct Ids {
// Crosshair
crosshair_inner,
crosshair_outer,
// SCT
player_scts[],
player_sct_bgs[],
sct_exp_bgs[],
sct_exps[],
sct_lvl_bg,
sct_lvl,
hurt_bg,
death_bg,
sct_bgs[],
scts[],
overheads[],
overitems[],
// Intro Text
intro_bg,
intro_text,
intro_close,
intro_close_2,
intro_close_3,
intro_close_4,
intro_close_5,
intro_check,
intro_check_text,
// Alpha Disclaimer
alpha_text,
// Debug
debug_bg,
fps_counter,
ping,
coordinates,
velocity,
orientation,
loaded_distance,
time,
entity_count,
num_chunks,
num_lights,
num_figures,
num_particles,
// Game Version
version,
// Help
help,
help_info,
debug_info,
lantern_info,
// Window Frames
window_frame_0,
window_frame_1,
window_frame_2,
window_frame_3,
window_frame_4,
window_frame_5,
button_help2,
button_help3,
// External
chat,
map,
world_map,
character_window,
popup,
minimap,
bag,
social,
quest,
spell,
skillbar,
buttons,
esc_menu,
small_window,
social_window,
crafting_window,
settings_window,
group_window,
// Free look indicator
free_look_txt,
free_look_bg,
// Auto walk indicator
auto_walk_txt,
auto_walk_bg,
// Example Quest
quest_bg,
q_headline_bg,
q_headline,
q_text_bg,
q_text,
accept_button,
}
}
pub struct DebugInfo {
pub tps: f64,
pub ping_ms: f64,
pub coordinates: Option<comp::Pos>,
pub velocity: Option<comp::Vel>,
pub ori: Option<comp::Ori>,
pub num_chunks: u32,
pub num_lights: u32,
pub num_visible_chunks: u32,
pub num_shadow_chunks: u32,
pub num_figures: u32,
pub num_figures_visible: u32,
pub num_particles: u32,
pub num_particles_visible: u32,
}
pub struct HudInfo {
pub is_aiming: bool,
pub is_first_person: bool,
pub target_entity: Option<specs::Entity>,
pub selected_entity: Option<(specs::Entity, std::time::Instant)>,
}
pub enum Event {
ToggleTips(bool),
SendMessage(String),
AdjustMousePan(u32),
AdjustMouseZoom(u32),
ToggleZoomInvert(bool),
ToggleMouseYInvert(bool),
ToggleSmoothPan(bool),
AdjustViewDistance(u32),
AdjustLodDetail(u32),
AdjustSpriteRenderDistance(u32),
AdjustFigureLoDRenderDistance(u32),
AdjustMusicVolume(f32),
AdjustSfxVolume(f32),
ChangeAudioDevice(String),
ChangeMaxFPS(u32),
ChangeFOV(u16),
ChangeGamma(f32),
MapZoom(f64),
AdjustWindowSize([u16; 2]),
ToggleParticlesEnabled(bool),
ToggleFullscreen,
ChangeResolution([u16; 2]),
ChangeBitDepth(Option<u16>),
ChangeRefreshRate(Option<u16>),
CrosshairTransp(f32),
ChatTransp(f32),
ChatCharName(bool),
CrosshairType(CrosshairType),
ToggleXpBar(XpBar),
Intro(Intro),
ToggleBarNumbers(BarNumbers),
ToggleShortcutNumbers(ShortcutNumbers),
Sct(bool),
SctPlayerBatch(bool),
SctDamageBatch(bool),
SpeechBubbleDarkMode(bool),
SpeechBubbleIcon(bool),
ToggleDebug(bool),
UiScale(ScaleChange),
CharacterSelection,
UseSlot(comp::slot::Slot),
SwapSlots(comp::slot::Slot, comp::slot::Slot),
DropSlot(comp::slot::Slot),
ChangeHotbarState(Box<HotbarState>),
Ability3(bool),
Logout,
Quit,
ChangeLanguage(Box<LanguageMetadata>),
ChangeBinding(GameInput),
ResetBindings,
ChangeFreeLookBehavior(PressBehavior),
ChangeRenderMode(Box<RenderMode>),
ChangeAutoWalkBehavior(PressBehavior),
ChangeStopAutoWalkOnInput(bool),
CraftRecipe(String),
InviteMember(common::sync::Uid),
AcceptInvite,
DeclineInvite,
KickMember(common::sync::Uid),
LeaveGroup,
AssignLeader(common::sync::Uid),
}
// TODO: Are these the possible layouts we want?
// TODO: Maybe replace this with bitflags.
// `map` is not here because it currently is displayed over the top of other
// open windows.
#[derive(PartialEq)]
pub enum Windows {
Settings, // Display settings window.
None,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum CrosshairType {
Round,
RoundEdges,
Edges,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum Intro {
Show,
Never,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum XpBar {
Always,
OnGain,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum BarNumbers {
Values,
Percent,
Off,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum ShortcutNumbers {
On,
Off,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum PressBehavior {
Toggle = 0,
Hold = 1,
}
pub struct Show {
ui: bool,
intro: bool,
help: bool,
crafting: bool,
debug: bool,
bag: bool,
social: bool,
spell: bool,
group: bool,
group_menu: bool,
esc_menu: bool,
open_windows: Windows,
map: bool,
mini_map: bool,
ingame: bool,
settings_tab: SettingsTab,
social_tab: SocialTab,
want_grab: bool,
stats: bool,
free_look: bool,
auto_walk: bool,
}
impl Show {
fn bag(&mut self, open: bool) {
if !self.esc_menu {
self.bag = open;
self.map = false;
self.want_grab = !open;
}
}
fn toggle_bag(&mut self) { self.bag(!self.bag); }
fn map(&mut self, open: bool) {
if !self.esc_menu {
self.map = open;
self.bag = false;
self.crafting = false;
self.social = false;
self.spell = false;
self.want_grab = !open;
}
}
fn social(&mut self, open: bool) {
if !self.esc_menu {
self.social = open;
self.spell = false;
self.want_grab = !open;
}
}
fn crafting(&mut self, open: bool) {
if !self.esc_menu {
self.crafting = open;
self.bag = open;
self.map = false;
self.want_grab = !open;
}
}
fn spell(&mut self, open: bool) {
if !self.esc_menu {
self.social = false;
self.crafting = false;
self.spell = open;
self.want_grab = !open;
}
}
fn toggle_map(&mut self) { self.map(!self.map) }
fn toggle_mini_map(&mut self) { self.mini_map = !self.mini_map; }
fn settings(&mut self, open: bool) {
if !self.esc_menu {
self.open_windows = if open {
Windows::Settings
} else {
Windows::None
};
self.bag = false;
self.social = false;
self.crafting = false;
self.spell = false;
self.want_grab = !open;
}
}
fn toggle_settings(&mut self, global_state: &GlobalState) {
match self.open_windows {
Windows::Settings => {
#[cfg(feature = "singleplayer")]
global_state.unpause();
self.settings(false);
},
_ => {
#[cfg(feature = "singleplayer")]
global_state.pause();
self.settings(true)
},
};
}
fn toggle_help(&mut self) { self.help = !self.help }
fn toggle_ui(&mut self) { self.ui = !self.ui; }
fn toggle_windows(&mut self, global_state: &mut GlobalState) {
if self.bag
|| self.esc_menu
|| self.map
|| self.social
|| self.crafting
|| self.spell
|| self.help
|| self.intro
|| !matches!(self.open_windows, Windows::None)
{
self.bag = false;
self.esc_menu = false;
self.help = false;
self.intro = false;
self.map = false;
self.social = false;
self.spell = false;
self.crafting = false;
self.open_windows = Windows::None;
self.want_grab = true;
// Unpause the game if we are on singleplayer
#[cfg(feature = "singleplayer")]
global_state.unpause();
} else {
self.esc_menu = true;
self.want_grab = false;
// Pause the game if we are on singleplayer
#[cfg(feature = "singleplayer")]
global_state.pause();
}
}
fn open_setting_tab(&mut self, tab: SettingsTab) {
self.open_windows = Windows::Settings;
self.esc_menu = false;
self.settings_tab = tab;
self.bag = false;
self.want_grab = false;
}
fn toggle_social(&mut self) {
self.social(!self.social);
self.spell = false;
}
fn toggle_crafting(&mut self) { self.crafting(!self.crafting) }
fn open_social_tab(&mut self, social_tab: SocialTab) {
self.social_tab = social_tab;
self.spell = false;
}
fn toggle_spell(&mut self) {
self.spell = !self.spell;
self.social = false;
}
}
pub struct Hud {
ui: Ui,
ids: Ids,
world_map: (/* Id */ Rotations, Vec2<u32>),
imgs: Imgs,
item_imgs: ItemImgs,
fonts: ConrodVoxygenFonts,
rot_imgs: ImgsRot,
new_messages: VecDeque<comp::ChatMsg>,
new_notifications: VecDeque<common::msg::Notification>,
speech_bubbles: HashMap<Uid, comp::SpeechBubble>,
show: Show,
//never_show: bool,
//intro: bool,
//intro_2: bool,
to_focus: Option<Option<widget::Id>>,
force_ungrab: bool,
force_chat_input: Option<String>,
force_chat_cursor: Option<Index>,
tab_complete: Option<String>,
pulse: f32,
velocity: f32,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
slot_manager: slots::SlotManager,
hotbar: hotbar::State,
events: Vec<Event>,
crosshair_opacity: f32,
}
impl Hud {
pub fn new(global_state: &mut GlobalState, client: &Client) -> Self {
let window = &mut global_state.window;
let settings = &global_state.settings;
let mut ui = Ui::new(window).unwrap();
ui.set_scaling_mode(settings.gameplay.ui_scale);
// Generate ids.
let ids = Ids::new(ui.id_generator());
// NOTE: Use a border the same color as the LOD ocean color (but with a
// translucent alpha since UI have transparency and LOD doesn't).
let mut water_color = lod::water_color();
water_color.a = 0.5;
// Load world map
let world_map = (
ui.add_graphic_with_rotations(Graphic::Image(
client.world_map.0.clone(),
Some(water_color),
)),
client.world_map.1.map(u32::from),
);
// Load images.
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
// Load rotation images.
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
// Load item images.
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
// Load language.
let voxygen_i18n = load_expect::<VoxygenLocalization>(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// Load fonts.
let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui)
.expect("Impossible to load fonts!");
// Get the server name.
let server = &client.server_info.name;
// Get the id, unwrap is safe because this CANNOT be None at this
// point.
let character_id = client.active_character_id.unwrap();
// Create a new HotbarState from the persisted slots.
let hotbar_state =
HotbarState::new(global_state.profile.get_hotbar_slots(server, character_id));
let slot_manager = slots::SlotManager::new(ui.id_generator(), Vec2::broadcast(40.0));
Self {
ui,
imgs,
world_map,
rot_imgs,
item_imgs,
fonts,
ids,
new_messages: VecDeque::new(),
new_notifications: VecDeque::new(),
speech_bubbles: HashMap::new(),
//intro: false,
//intro_2: false,
show: Show {
help: false,
intro: true,
debug: false,
bag: false,
esc_menu: false,
open_windows: Windows::None,
map: false,
crafting: false,
ui: true,
social: false,
spell: false,
group: false,
group_menu: false,
mini_map: true,
settings_tab: SettingsTab::Interface,
social_tab: SocialTab::Online,
want_grab: true,
ingame: true,
stats: false,
free_look: false,
auto_walk: false,
},
to_focus: None,
//never_show: false,
force_ungrab: false,
force_chat_input: None,
force_chat_cursor: None,
tab_complete: None,
pulse: 0.0,
velocity: 0.0,
voxygen_i18n,
slot_manager,
hotbar: hotbar_state,
events: Vec::new(),
crosshair_opacity: 0.0,
}
}
pub fn update_language(&mut self, voxygen_i18n: std::sync::Arc<VoxygenLocalization>) {
self.voxygen_i18n = voxygen_i18n;
self.fonts = ConrodVoxygenFonts::load(&self.voxygen_i18n.fonts, &mut self.ui)
.expect("Impossible to load fonts!");
}
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
#[allow(clippy::single_match)] // TODO: Pending review in #587
fn update_layout(
&mut self,
client: &Client,
global_state: &GlobalState,
debug_info: &Option<DebugInfo>,
dt: Duration,
info: HudInfo,
camera: &Camera,
) -> Vec<Event> {
let mut events = std::mem::replace(&mut self.events, Vec::new());
let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
// pulse time for pulsating elements
self.pulse = self.pulse + dt.as_secs_f32();
let version = format!(
"{}-{}",
env!("CARGO_PKG_VERSION"),
common::util::GIT_VERSION.to_string()
);
if self.show.ingame {
let ecs = client.state().ecs();
let pos = ecs.read_storage::<comp::Pos>();
let stats = ecs.read_storage::<comp::Stats>();
let energy = ecs.read_storage::<comp::Energy>();
let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>();
let uids = ecs.read_storage::<common::sync::Uid>();
let interpolated = ecs.read_storage::<vcomp::Interpolated>();
let players = ecs.read_storage::<comp::Player>();
let scales = ecs.read_storage::<comp::Scale>();
let bodies = ecs.read_storage::<comp::Body>();
let items = ecs.read_storage::<comp::Item>();
let entities = ecs.entities();
let me = client.entity();
let own_level = stats
.get(client.entity())
.map_or(0, |stats| stats.level.level());
//self.input = client.read_storage::<comp::ControllerInputs>();
if let Some(stats) = stats.get(me) {
// Hurt Frame
let hp_percentage =
stats.health.current() as f32 / stats.health.maximum() as f32 * 100.0;
if hp_percentage < 10.0 && !stats.is_dead {
let hurt_fade =
(self.pulse * (10.0 - hp_percentage as f32) * 0.1/* speed factor */).sin()
* 0.5
+ 0.6; //Animation timer
Image::new(self.imgs.hurt_bg)
.wh_of(ui_widgets.window)
.middle_of(ui_widgets.window)
.graphics_for(ui_widgets.window)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, hurt_fade)))
.set(self.ids.hurt_bg, ui_widgets);
}
// Alpha Disclaimer
Text::new(&format!("Veloren Pre-Alpha {}", env!("CARGO_PKG_VERSION")))
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(10))
.color(TEXT_COLOR)
.mid_top_with_margin_on(ui_widgets.window, 2.0)
.set(self.ids.alpha_text, ui_widgets);
// Death Frame
if stats.is_dead {
Image::new(self.imgs.death_bg)
.wh_of(ui_widgets.window)
.middle_of(ui_widgets.window)
.graphics_for(ui_widgets.window)
.color(Some(Color::Rgba(0.0, 0.0, 0.0, 1.0)))
.set(self.ids.death_bg, ui_widgets);
}
// Crosshair
let show_crosshair = (info.is_aiming || info.is_first_person) && !stats.is_dead;
self.crosshair_opacity = Lerp::lerp(
self.crosshair_opacity,
if show_crosshair { 1.0 } else { 0.0 },
5.0 * dt.as_secs_f32(),
);
if !self.show.help {
Image::new(
// TODO: Do we want to match on this every frame?
match global_state.settings.gameplay.crosshair_type {
CrosshairType::Round => self.imgs.crosshair_outer_round,
CrosshairType::RoundEdges => self.imgs.crosshair_outer_round_edges,
CrosshairType::Edges => self.imgs.crosshair_outer_edges,
},
)
.w_h(21.0 * 1.5, 21.0 * 1.5)
.middle_of(ui_widgets.window)
.color(Some(Color::Rgba(
1.0,
1.0,
1.0,
self.crosshair_opacity * global_state.settings.gameplay.crosshair_transp,
)))
.set(self.ids.crosshair_outer, ui_widgets);
Image::new(self.imgs.crosshair_inner)
.w_h(21.0 * 2.0, 21.0 * 2.0)
.middle_of(self.ids.crosshair_outer)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.6)))
.set(self.ids.crosshair_inner, ui_widgets);
}
}
// Max amount the sct font size increases when "flashing"
const FLASH_MAX: u32 = 2;
// Get player position.
let player_pos = client
.state()
.ecs()
.read_storage::<comp::Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
// SCT Output values are called hp_damage and floater.hp_change
// Numbers are currently divided by 10 and rounded
if global_state.settings.gameplay.sct {
// Render Player SCT numbers
let mut player_sct_bg_id_walker = self.ids.player_sct_bgs.walk();
let mut player_sct_id_walker = self.ids.player_scts.walk();
if let (Some(HpFloaterList { floaters, .. }), Some(stats)) = (
hp_floater_lists
.get(me)
.filter(|fl| !fl.floaters.is_empty()),
stats.get(me),
) {
if global_state.settings.gameplay.sct_player_batch {
let number_speed = 100.0; // Player Batched Numbers Speed
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
&mut ui_widgets.widget_id_generator(),
);
let player_sct_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
// Calculate total change
// Ignores healing
let hp_damage = floaters.iter().fold(0, |acc, f| f.hp_change.min(0) + acc);
// Divide by 10 to stay in the same dimension as the HP display
let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs();
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
let timer = floaters
.last()
.expect("There must be at least one floater")
.timer;
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size = 30
+ ((max_hp_frac * 10.0) as u32) * 3
+ if timer < 0.1 {
FLASH_MAX * (((1.0 - timer / 0.1) * 10.0) as u32)
} else {
0
};
// Timer sets the widget offset
let y = timer as f64 * number_speed * -1.0;
// Timer sets text transparency
let hp_fade =
((crate::ecs::sys::floater::MY_HP_SHOWTIME - timer) * 0.25) + 0.2;
Text::new(&format!("{}", hp_dmg_rounded_abs))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(if hp_damage < 0 {
Color::Rgba(0.0, 0.0, 0.0, hp_fade)
} else {
Color::Rgba(0.0, 0.0, 0.0, 0.0)
})
.mid_bottom_with_margin_on(ui_widgets.window, 297.0 + y)
.set(player_sct_bg_id, ui_widgets);
Text::new(&format!("{}", hp_dmg_rounded_abs))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(if hp_damage < 0 {
Color::Rgba(1.0, 0.1, 0.0, hp_fade)
} else {
Color::Rgba(0.0, 0.0, 0.0, 0.0)
})
.mid_bottom_with_margin_on(ui_widgets.window, 300.0 + y)
.set(player_sct_id, ui_widgets);
};
for floater in floaters {
// Healing always single numbers so just skip damage when in batch mode
if global_state.settings.gameplay.sct_player_batch && floater.hp_change < 0
{
continue;
}
let number_speed = 50.0; // Player Heal Speed
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
&mut ui_widgets.widget_id_generator(),
);
let player_sct_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
let max_hp_frac =
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size = 30
+ ((max_hp_frac * 10.0) as u32) * 3
+ if floater.timer < 0.1 {
FLASH_MAX * (((1.0 - floater.timer / 0.1) * 10.0) as u32)
} else {
0
};
// Timer sets the widget offset
let y = if floater.hp_change < 0 {
floater.timer as f64
* number_speed
* floater.hp_change.signum() as f64
//* -1.0
+ 300.0
- ui_widgets.win_h * 0.5
} else {
floater.timer as f64
* number_speed
* floater.hp_change.signum() as f64
* -1.0
+ 300.0
- ui_widgets.win_h * 0.5
};
// Healing is offset randomly
let x = if floater.hp_change < 0 {
0.0
} else {
(floater.rand as f64 - 0.5) * 0.2 * ui_widgets.win_w
};
// Timer sets text transparency
let hp_fade = ((crate::ecs::sys::floater::MY_HP_SHOWTIME - floater.timer)
* 0.25)
+ 0.2;
Text::new(&format!("{}", (floater.hp_change / 10).abs()))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, hp_fade))
.x_y(x, y - 3.0)
.set(player_sct_bg_id, ui_widgets);
Text::new(&format!("{}", (floater.hp_change / 10).abs()))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(if floater.hp_change < 0 {
Color::Rgba(1.0, 0.1, 0.0, hp_fade)
} else {
Color::Rgba(0.1, 1.0, 0.1, hp_fade)
})
.x_y(x, y)
.set(player_sct_id, ui_widgets);
}
}
// EXP Numbers
if let (Some(floaters), Some(stats)) = (
Some(&*ecs.read_resource::<crate::ecs::MyExpFloaterList>())
.map(|l| &l.floaters)
.filter(|f| !f.is_empty()),
stats.get(me),
) {
// TODO replace with setting
let batched_sct = false;
if batched_sct {
let number_speed = 50.0; // Number Speed for Cumulated EXP
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
&mut ui_widgets.widget_id_generator(),
);
let player_sct_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
// Sum xp change
let exp_change = floaters.iter().fold(0, |acc, f| f.exp_change + acc);
// Can't fail since we filtered out empty lists above
let (timer, rand) = floaters
.last()
.map(|f| (f.timer, f.rand))
.expect("Impossible");
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size_xp = 30
+ ((exp_change.abs() as f32 / stats.exp.maximum() as f32).min(1.0)
* 50.0) as u32
+ if timer < 0.1 {
FLASH_MAX * (((1.0 - timer / 0.1) * 10.0) as u32)
} else {
0
};
let y = timer as f64 * number_speed; // Timer sets the widget offset
let fade = ((4.0 - timer as f32) * 0.25) + 0.2; // Timer sets text transparency
Text::new(&format!("{} Exp", exp_change))
.font_size(font_size_xp)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.x_y(
ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25),
ui_widgets.win_h * (0.15 * rand.1 as f64) + y - 3.0,
)
.set(player_sct_bg_id, ui_widgets);
Text::new(&format!("{} Exp", exp_change))
.font_size(font_size_xp)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.59, 0.41, 0.67, fade))
.x_y(
ui_widgets.win_w * (0.5 * rand.0 as f64 - 0.25),
ui_widgets.win_h * (0.15 * rand.1 as f64) + y,
)
.set(player_sct_id, ui_widgets);
} else {
for floater in floaters {
let number_speed = 50.0; // Number Speed for Single EXP
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
&mut ui_widgets.widget_id_generator(),
);
let player_sct_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size_xp = 30
+ ((floater.exp_change.abs() as f32 / stats.exp.maximum() as f32)
.min(1.0)
* 50.0) as u32
+ if floater.timer < 0.1 {
FLASH_MAX * (((1.0 - floater.timer / 0.1) * 10.0) as u32)
} else {
0
};
let y = floater.timer as f64 * number_speed; // Timer sets the widget offset
let fade = ((4.0 - floater.timer as f32) * 0.25) + 0.2; // Timer sets text transparency
Text::new(&format!("{} Exp", floater.exp_change))
.font_size(font_size_xp)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.x_y(
ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25),
ui_widgets.win_h * (0.15 * floater.rand.1 as f64) + y - 3.0,
)
.set(player_sct_bg_id, ui_widgets);
Text::new(&format!("{} Exp", floater.exp_change))
.font_size(font_size_xp)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.59, 0.41, 0.67, fade))
.x_y(
ui_widgets.win_w * (0.5 * floater.rand.0 as f64 - 0.25),
ui_widgets.win_h * (0.15 * floater.rand.1 as f64) + y,
)
.set(player_sct_id, ui_widgets);
}
}
}
}
// Pop speech bubbles
let now = Instant::now();
self.speech_bubbles
.retain(|_uid, bubble| bubble.timeout > now);
// Push speech bubbles
for msg in self.new_messages.iter() {
if let Some((bubble, uid)) = msg.to_bubble() {
self.speech_bubbles.insert(uid, bubble);
}
}
let mut overhead_walker = self.ids.overheads.walk();
let mut overitem_walker = self.ids.overitems.walk();
let mut sct_walker = self.ids.scts.walk();
let mut sct_bg_walker = self.ids.sct_bgs.walk();
// Render overitem name
for (pos, item, distance) in (&entities, &pos, &items)
.join()
.map(|(_, pos, item)| (pos, item, pos.0.distance_squared(player_pos)))
.filter(|(_, _, distance)| distance < &common::comp::MAX_PICKUP_RANGE_SQR)
{
let overitem_id = overitem_walker.next(
&mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(),
);
let ingame_pos = pos.0 + Vec3::unit_z() * 1.2;
// Item name
overitem::Overitem::new(&item.name(), &distance, &self.fonts)
.x_y(0.0, 100.0)
.position_ingame(ingame_pos)
.set(overitem_id, ui_widgets);
}
// Render overhead name tags and health bars
for (pos, name, stats, energy, height_offset, hpfl, uid, in_group) in (
&entities,
&pos,
interpolated.maybe(),
&stats,
energy.maybe(),
players.maybe(),
scales.maybe(),
&bodies,
&hp_floater_lists,
&uids,
)
.join()
.map(|(a, b, c, d, e, f, g, h, i, uid)| {
(
a,
b,
c,
d,
e,
f,
g,
h,
i,
uid,
client.group_members().contains_key(uid),
)
})
.filter(|(entity, pos, _, stats, _, _, _, _, hpfl, _, in_group)| {
*entity != me && !stats.is_dead
&& (stats.health.current() != stats.health.maximum()
|| info.target_entity.map_or(false, |e| e == *entity)
|| info.selected_entity.map_or(false, |s| s.0 == *entity)
|| *in_group
)
// Don't show outside a certain range
&& pos.0.distance_squared(player_pos)
< (if *in_group
{
NAMETAG_GROUP_RANGE
} else if hpfl
.time_since_last_dmg_by_me
.map_or(false, |t| t < NAMETAG_DMG_TIME)
{
NAMETAG_DMG_RANGE
} else {
NAMETAG_RANGE
})
.powi(2)
})
.map(
|(
_,
pos,
interpolated,
stats,
energy,
player,
scale,
body,
hpfl,
uid,
in_group,
)| {
// TODO: This is temporary
// If the player used the default character name display their name instead
let name = if stats.name == "Character Name" {
player.map_or(&stats.name, |p| &p.alias)
} else {
&stats.name
};
(
interpolated.map_or(pos.0, |i| i.pos),
name,
stats,
energy,
// TODO: when body.height() is more accurate remove the 2.0
body.height() * 2.0 * scale.map_or(1.0, |s| s.0),
hpfl,
uid,
in_group,
)
},
)
{
let bubble = self.speech_bubbles.get(uid);
let overhead_id = overhead_walker.next(
&mut self.ids.overheads,
&mut ui_widgets.widget_id_generator(),
);
let ingame_pos = pos + Vec3::unit_z() * height_offset;
// Speech bubble, name, level, and hp bars
overhead::Overhead::new(
&name,
bubble,
stats,
energy,
own_level,
in_group,
&global_state.settings.gameplay,
self.pulse,
&self.voxygen_i18n,
&self.imgs,
&self.fonts,
)
.x_y(0.0, 100.0)
.position_ingame(ingame_pos)
.set(overhead_id, ui_widgets);
// Enemy SCT
if global_state.settings.gameplay.sct && !hpfl.floaters.is_empty() {
let floaters = &hpfl.floaters;
// Colors
const WHITE: Rgb<f32> = Rgb::new(1.0, 0.9, 0.8);
const LIGHT_OR: Rgb<f32> = Rgb::new(1.0, 0.925, 0.749);
const LIGHT_MED_OR: Rgb<f32> = Rgb::new(1.0, 0.85, 0.498);
const MED_OR: Rgb<f32> = Rgb::new(1.0, 0.776, 0.247);
const DARK_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.7, 0.0);
const RED_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.349, 0.0);
const DAMAGE_COLORS: [Rgb<f32>; 6] = [
WHITE,
LIGHT_OR,
LIGHT_MED_OR,
MED_OR,
DARK_ORANGE,
RED_ORANGE,
];
// Largest value that select the first color is 40, then it shifts colors
// every 5
let font_col = |font_size: u32| {
DAMAGE_COLORS[(font_size.saturating_sub(36) / 5).min(5) as usize]
};
if global_state.settings.gameplay.sct_damage_batch {
let number_speed = 50.0; // Damage number speed
let sct_id = sct_walker
.next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator());
let sct_bg_id = sct_bg_walker
.next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator());
// Calculate total change
// Ignores healing
let hp_damage = floaters.iter().fold(0, |acc, f| {
if f.hp_change < 0 {
acc + f.hp_change
} else {
acc
}
});
// Divide by 10 to stay in the same dimension as the HP display
let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs();
let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32;
let timer = floaters
.last()
.expect("There must be at least one floater")
.timer;
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size = 30
+ ((max_hp_frac * 10.0) as u32) * 3
+ if timer < 0.1 {
FLASH_MAX * (((1.0 - timer / 0.1) * 10.0) as u32)
} else {
0
};
let font_col = font_col(font_size);
// Timer sets the widget offset
let y = (timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64
* number_speed)
+ 100.0;
// Timer sets text transparency
let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - timer) * 0.25) + 0.2;
Text::new(&format!("{}", hp_dmg_rounded_abs))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.x_y(0.0, y - 3.0)
.position_ingame(ingame_pos)
.set(sct_bg_id, ui_widgets);
Text::new(&format!("{}", hp_dmg_rounded_abs))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.x_y(0.0, y)
.color(if hp_damage < 0 {
Color::Rgba(font_col.r, font_col.g, font_col.b, fade)
} else {
Color::Rgba(0.1, 1.0, 0.1, fade)
})
.position_ingame(ingame_pos)
.set(sct_id, ui_widgets);
} else {
for floater in floaters {
let number_speed = 250.0; // Single Numbers Speed
let sct_id = sct_walker
.next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator());
let sct_bg_id = sct_bg_walker
.next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator());
// Calculate total change
let max_hp_frac =
floater.hp_change.abs() as f32 / stats.health.maximum() as f32;
// Increase font size based on fraction of maximum health
// "flashes" by having a larger size in the first 100ms
let font_size = 30
+ ((max_hp_frac * 10.0) as u32) * 3
+ if floater.timer < 0.1 {
FLASH_MAX * (((1.0 - floater.timer / 0.1) * 10.0) as u32)
} else {
0
};
let font_col = font_col(font_size);
// Timer sets the widget offset
let y = (floater.timer as f64
/ crate::ecs::sys::floater::HP_SHOWTIME as f64
* number_speed)
+ 100.0;
// Timer sets text transparency
let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - floater.timer)
* 0.25)
+ 0.2;
Text::new(&format!("{}", (floater.hp_change / 10).abs()))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(if floater.hp_change < 0 {
Color::Rgba(0.0, 0.0, 0.0, fade)
} else {
Color::Rgba(0.0, 0.0, 0.0, 1.0)
})
.x_y(0.0, y - 3.0)
.position_ingame(ingame_pos)
.set(sct_bg_id, ui_widgets);
Text::new(&format!("{}", (floater.hp_change / 10).abs()))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.x_y(0.0, y)
.color(if floater.hp_change < 0 {
Color::Rgba(font_col.r, font_col.g, font_col.b, fade)
} else {
Color::Rgba(0.1, 1.0, 0.1, 1.0)
})
.position_ingame(ingame_pos)
.set(sct_id, ui_widgets);
}
}
}
}
}
// Temporary Example Quest
if self.show.intro && !self.show.esc_menu {
match global_state.settings.gameplay.intro_show {
Intro::Show => {
if self.pulse > 20.0 {
self.show.want_grab = false;
let quest_headline = &self.voxygen_i18n.get("hud.temp_quest_headline");
let quest_text = &self.voxygen_i18n.get("hud.temp_quest_text");
Image::new(self.imgs.quest_bg)
.w_h(404.0, 858.0)
.middle_of(ui_widgets.window)
.set(self.ids.quest_bg, ui_widgets);
Text::new(quest_headline)
.mid_top_with_margin_on(self.ids.quest_bg, 310.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_BG)
.set(self.ids.q_headline_bg, ui_widgets);
Text::new(quest_headline)
.bottom_left_with_margins_on(self.ids.q_headline_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.q_headline, ui_widgets);
Text::new(quest_text)
.down_from(self.ids.q_headline_bg, 40.0)
.font_size(self.fonts.cyri.scale(17))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_BG)
.set(self.ids.q_text_bg, ui_widgets);
Text::new(quest_text)
.bottom_left_with_margins_on(self.ids.q_text_bg, 1.0, 1.0)
.font_size(self.fonts.cyri.scale(17))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.q_text, ui_widgets);
if Button::image(self.imgs.button)
.w_h(212.0, 52.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.mid_bottom_with_margin_on(self.ids.q_text_bg, -120.0)
.label(&self.voxygen_i18n.get("common.accept"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(self.fonts.cyri.scale(22))
.label_color(TEXT_COLOR)
.label_y(conrod_core::position::Relative::Scalar(2.0))
.set(self.ids.accept_button, ui_widgets)
.was_clicked()
{
self.show.intro = !self.show.intro;
events.push(Event::Intro(Intro::Never));
self.show.want_grab = true;
}
}
},
Intro::Never => {
self.show.intro = false;
},
}
}
// Display debug window.
if let Some(debug_info) = debug_info {
self.velocity = match debug_info.velocity {
Some(velocity) => velocity.0.magnitude(),
None => 0.0,
};
// Alpha Version
Text::new(&version)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(self.ids.version, ui_widgets);
// Ticks per second
Text::new(&format!("FPS: {:.0}", debug_info.tps))
.color(TEXT_COLOR)
.down_from(self.ids.version, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.fps_counter, ui_widgets);
// Ping
Text::new(&format!("Ping: {:.0}ms", debug_info.ping_ms))
.color(TEXT_COLOR)
.down_from(self.ids.fps_counter, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.ping, ui_widgets);
// Player's position
let coordinates_text = match debug_info.coordinates {
Some(coordinates) => format!(
"Coordinates: ({:.0}, {:.0}, {:.0})",
coordinates.0.x, coordinates.0.y, coordinates.0.z,
),
None => "Player has no Pos component".to_owned(),
};
Text::new(&coordinates_text)
.color(TEXT_COLOR)
.down_from(self.ids.ping, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.coordinates, ui_widgets);
// Player's velocity
let velocity_text = match debug_info.velocity {
Some(velocity) => format!(
"Velocity: ({:.1}, {:.1}, {:.1}) [{:.1} u/s]",
velocity.0.x,
velocity.0.y,
velocity.0.z,
velocity.0.magnitude()
),
None => "Player has no Vel component".to_owned(),
};
Text::new(&velocity_text)
.color(TEXT_COLOR)
.down_from(self.ids.coordinates, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.velocity, ui_widgets);
// Player's orientation vector
let orientation_text = match debug_info.ori {
Some(ori) => format!(
"Orientation: ({:.1}, {:.1}, {:.1})",
ori.0.x, ori.0.y, ori.0.z,
),
None => "Player has no Ori component".to_owned(),
};
Text::new(&orientation_text)
.color(TEXT_COLOR)
.down_from(self.ids.velocity, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.orientation, ui_widgets);
// Loaded distance
Text::new(&format!(
"View distance: {:.2} blocks ({:.2} chunks)",
client.loaded_distance(),
client.loaded_distance() / TerrainChunk::RECT_SIZE.x as f32,
))
.color(TEXT_COLOR)
.down_from(self.ids.orientation, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.loaded_distance, ui_widgets);
// Time
let time_in_seconds = client.state().get_time_of_day();
let current_time = NaiveTime::from_num_seconds_from_midnight(
// Wraps around back to 0s if it exceeds 24 hours (24 hours = 86400s)
(time_in_seconds as u64 % 86400) as u32,
0,
);
Text::new(&format!(
"Time: {}",
current_time.format("%H:%M").to_string()
))
.color(TEXT_COLOR)
.down_from(self.ids.loaded_distance, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.time, ui_widgets);
// Number of entities
let entity_count = client.state().ecs().entities().join().count();
Text::new(&format!("Entity count: {}", entity_count))
.color(TEXT_COLOR)
.down_from(self.ids.time, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.entity_count, ui_widgets);
// Number of chunks
Text::new(&format!(
"Chunks: {} ({} visible) & {} (shadow)",
debug_info.num_chunks, debug_info.num_visible_chunks, debug_info.num_shadow_chunks,
))
.color(TEXT_COLOR)
.down_from(self.ids.entity_count, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.num_chunks, ui_widgets);
// Number of lights
Text::new(&format!("Lights: {}", debug_info.num_lights,))
.color(TEXT_COLOR)
.down_from(self.ids.num_chunks, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.num_lights, ui_widgets);
// Number of figures
Text::new(&format!(
"Figures: {} ({} visible)",
debug_info.num_figures, debug_info.num_figures_visible,
))
.color(TEXT_COLOR)
.down_from(self.ids.num_lights, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.num_figures, ui_widgets);
// Number of particles
Text::new(&format!(
"Particles: {} ({} visible)",
debug_info.num_particles, debug_info.num_particles_visible,
))
.color(TEXT_COLOR)
.down_from(self.ids.num_figures, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.num_particles, ui_widgets);
// Help Window
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.voxygen_i18n
.get("hud.press_key_to_toggle_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
.color(TEXT_COLOR)
.down_from(self.ids.num_particles, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.help_info, ui_widgets);
}
// Info about Debug Shortcut
if let Some(toggle_debug_key) = global_state
.settings
.controls
.get_binding(GameInput::ToggleDebug)
{
Text::new(
&self
.voxygen_i18n
.get("hud.press_key_to_toggle_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
.color(TEXT_COLOR)
.down_from(self.ids.help_info, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.debug_info, ui_widgets);
}
} else {
// Help Window
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.voxygen_i18n
.get("hud.press_key_to_show_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
.color(TEXT_COLOR)
.bottom_left_with_margins_on(ui_widgets.window, 210.0, 10.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(12))
.set(self.ids.help_info, ui_widgets);
}
// Info about Debug Shortcut
if let Some(toggle_debug_key) = global_state
.settings
.controls
.get_binding(GameInput::ToggleDebug)
{
Text::new(
&self
.voxygen_i18n
.get("hud.press_key_to_show_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
.color(TEXT_COLOR)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(12))
.set(self.ids.debug_info, ui_widgets);
}
// Lantern Key
if let Some(toggle_lantern_key) = global_state
.settings
.controls
.get_binding(GameInput::ToggleLantern)
{
Text::new(
&self
.voxygen_i18n
.get("hud.press_key_to_toggle_lantern_fmt")
.replace("{key}", toggle_lantern_key.to_string().as_str()),
)
.color(TEXT_COLOR)
.up_from(self.ids.help_info, 2.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(12))
.set(self.ids.lantern_info, ui_widgets);
}
}
// Help Text
if self.show.help && !self.show.map && !self.show.esc_menu {
Image::new(self.imgs.help)
.middle_of(ui_widgets.window)
.w_h(1260.0, 519.0)
.set(self.ids.help, ui_widgets);
// X-button
if Button::image(self.imgs.close_button)
.w_h(40.0, 40.0)
.hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press)
.top_right_with_margins_on(self.ids.help, 0.0, 0.0)
.color(Color::Rgba(1.0, 1.0, 1.0, 0.8))
.set(self.ids.button_help2, ui_widgets)
.was_clicked()
{
self.show.help = false;
};
}
// Bag button and nearby icons
let ecs = client.state().ecs();
let stats = ecs.read_storage::<comp::Stats>();
if let Some(player_stats) = stats.get(client.entity()) {
match Buttons::new(
client,
self.show.bag,
&self.imgs,
&self.fonts,
global_state,
&self.rot_imgs,
tooltip_manager,
&self.voxygen_i18n,
&player_stats,
)
.set(self.ids.buttons, ui_widgets)
{
Some(buttons::Event::ToggleBag) => self.show.toggle_bag(),
Some(buttons::Event::ToggleSettings) => self.show.toggle_settings(global_state),
Some(buttons::Event::ToggleSocial) => self.show.toggle_social(),
Some(buttons::Event::ToggleSpell) => self.show.toggle_spell(),
Some(buttons::Event::ToggleMap) => self.show.toggle_map(),
Some(buttons::Event::ToggleCrafting) => self.show.toggle_crafting(),
None => {},
}
}
// Popup (waypoint saved and similar notifications)
Popup::new(
&self.voxygen_i18n,
client,
&self.new_notifications,
&self.fonts,
&self.show,
)
.set(self.ids.popup, ui_widgets);
// MiniMap
match MiniMap::new(
&self.show,
client,
&self.imgs,
&self.rot_imgs,
&self.world_map,
&self.fonts,
camera.get_orientation(),
)
.set(self.ids.minimap, ui_widgets)
{
Some(minimap::Event::Toggle) => self.show.toggle_mini_map(),
None => {},
}
// Bag contents
if self.show.bag {
if let Some(player_stats) = stats.get(client.entity()) {
match Bag::new(
client,
&self.imgs,
&self.item_imgs,
&self.fonts,
&self.rot_imgs,
tooltip_manager,
&mut self.slot_manager,
self.pulse,
&self.voxygen_i18n,
&player_stats,
&self.show,
)
.set(self.ids.bag, ui_widgets)
{
Some(bag::Event::Stats) => self.show.stats = !self.show.stats,
Some(bag::Event::Close) => {
self.show.stats = false;
self.show.bag(false);
self.show.crafting(false);
if !self.show.social {
self.show.want_grab = true;
self.force_ungrab = false;
} else {
self.force_ungrab = true
};
},
None => {},
}
}
}
// Skillbar
// Get player stats
let ecs = client.state().ecs();
let entity = client.entity();
let stats = ecs.read_storage::<comp::Stats>();
let loadouts = ecs.read_storage::<comp::Loadout>();
let energies = ecs.read_storage::<comp::Energy>();
let character_states = ecs.read_storage::<comp::CharacterState>();
let controllers = ecs.read_storage::<comp::Controller>();
let inventories = ecs.read_storage::<comp::Inventory>();
if let (
Some(stats),
Some(loadout),
Some(energy),
Some(character_state),
Some(controller),
Some(inventory),
) = (
stats.get(entity),
loadouts.get(entity),
energies.get(entity),
character_states.get(entity),
controllers.get(entity).map(|c| &c.inputs),
inventories.get(entity),
) {
Skillbar::new(
global_state,
&self.imgs,
&self.item_imgs,
&self.fonts,
&self.rot_imgs,
&stats,
&loadout,
&energy,
&character_state,
self.pulse,
&controller,
&inventory,
&self.hotbar,
tooltip_manager,
&mut self.slot_manager,
&self.voxygen_i18n,
&self.show,
)
.set(self.ids.skillbar, ui_widgets);
}
// Crafting
if self.show.crafting {
if let Some(inventory) = inventories.get(entity) {
for event in Crafting::new(
//&self.show,
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
&self.rot_imgs,
tooltip_manager,
&self.item_imgs,
&inventory,
)
.set(self.ids.crafting_window, ui_widgets)
{
match event {
crafting::Event::CraftRecipe(r) => {
events.push(Event::CraftRecipe(r));
},
crafting::Event::Close => {
self.show.stats = false;
self.show.crafting(false);
self.show.bag(false);
if !self.show.social {
self.show.want_grab = true;
self.force_ungrab = false;
} else {
self.force_ungrab = true
};
},
}
}
}
}
// Don't put NPC messages in chat box.
self.new_messages
.retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _)));
// Chat box
match Chat::new(
&mut self.new_messages,
&client,
global_state,
&self.imgs,
&self.fonts,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.tab_complete.take(), |c, input| {
c.prepare_tab_completion(input)
})
.and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos))
.set(self.ids.chat, ui_widgets)
{
Some(chat::Event::TabCompletionStart(input)) => {
self.tab_complete = Some(input);
},
Some(chat::Event::SendMessage(message)) => {
events.push(Event::SendMessage(message));
},
Some(chat::Event::Focus(focus_id)) => {
self.to_focus = Some(Some(focus_id));
},
None => {},
}
self.new_messages = VecDeque::new();
self.new_notifications = VecDeque::new();
// Windows
// Char Window will always appear at the left side. Other Windows default to the
// left side, but when the Char Window is opened they will appear to the right
// of it.
// Settings
if let Windows::Settings = self.show.open_windows {
for event in SettingsWindow::new(
&global_state,
&self.show,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
)
.set(self.ids.settings_window, ui_widgets)
{
match event {
settings_window::Event::SpeechBubbleDarkMode(sbdm) => {
events.push(Event::SpeechBubbleDarkMode(sbdm));
},
settings_window::Event::SpeechBubbleIcon(sbi) => {
events.push(Event::SpeechBubbleIcon(sbi));
},
settings_window::Event::Sct(sct) => {
events.push(Event::Sct(sct));
},
settings_window::Event::SctPlayerBatch(sct_player_batch) => {
events.push(Event::SctPlayerBatch(sct_player_batch));
},
settings_window::Event::SctDamageBatch(sct_damage_batch) => {
events.push(Event::SctDamageBatch(sct_damage_batch));
},
settings_window::Event::ToggleHelp => self.show.help = !self.show.help,
settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug,
settings_window::Event::ToggleTips(loading_tips) => {
events.push(Event::ToggleTips(loading_tips));
},
settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab),
settings_window::Event::Close => {
// Unpause the game if we are on singleplayer so that we can logout
#[cfg(feature = "singleplayer")]
global_state.unpause();
self.show.want_grab = true;
self.force_ungrab = false;
self.show.settings(false)
},
settings_window::Event::AdjustMousePan(sensitivity) => {
events.push(Event::AdjustMousePan(sensitivity));
},
settings_window::Event::AdjustMouseZoom(sensitivity) => {
events.push(Event::AdjustMouseZoom(sensitivity));
},
settings_window::Event::ChatTransp(chat_transp) => {
events.push(Event::ChatTransp(chat_transp));
},
settings_window::Event::ChatCharName(chat_char_name) => {
events.push(Event::ChatCharName(chat_char_name));
},
settings_window::Event::ToggleZoomInvert(zoom_inverted) => {
events.push(Event::ToggleZoomInvert(zoom_inverted));
},
settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => {
events.push(Event::ToggleMouseYInvert(mouse_y_inverted));
},
settings_window::Event::ToggleSmoothPan(smooth_pan_enabled) => {
events.push(Event::ToggleSmoothPan(smooth_pan_enabled));
},
settings_window::Event::AdjustViewDistance(view_distance) => {
events.push(Event::AdjustViewDistance(view_distance));
},
settings_window::Event::AdjustLodDetail(lod_detail) => {
events.push(Event::AdjustLodDetail(lod_detail));
},
settings_window::Event::AdjustSpriteRenderDistance(view_distance) => {
events.push(Event::AdjustSpriteRenderDistance(view_distance));
},
settings_window::Event::AdjustFigureLoDRenderDistance(view_distance) => {
events.push(Event::AdjustFigureLoDRenderDistance(view_distance));
},
settings_window::Event::CrosshairTransp(crosshair_transp) => {
events.push(Event::CrosshairTransp(crosshair_transp));
},
settings_window::Event::AdjustMusicVolume(music_volume) => {
events.push(Event::AdjustMusicVolume(music_volume));
},
settings_window::Event::AdjustSfxVolume(sfx_volume) => {
events.push(Event::AdjustSfxVolume(sfx_volume));
},
settings_window::Event::MaximumFPS(max_fps) => {
events.push(Event::ChangeMaxFPS(max_fps));
},
settings_window::Event::ChangeAudioDevice(name) => {
events.push(Event::ChangeAudioDevice(name));
},
settings_window::Event::CrosshairType(crosshair_type) => {
events.push(Event::CrosshairType(crosshair_type));
},
settings_window::Event::ToggleXpBar(xp_bar) => {
events.push(Event::ToggleXpBar(xp_bar));
},
settings_window::Event::ToggleBarNumbers(bar_numbers) => {
events.push(Event::ToggleBarNumbers(bar_numbers));
},
settings_window::Event::ToggleShortcutNumbers(shortcut_numbers) => {
events.push(Event::ToggleShortcutNumbers(shortcut_numbers));
},
settings_window::Event::UiScale(scale_change) => {
events.push(Event::UiScale(scale_change));
},
settings_window::Event::AdjustFOV(new_fov) => {
events.push(Event::ChangeFOV(new_fov));
},
settings_window::Event::AdjustGamma(new_gamma) => {
events.push(Event::ChangeGamma(new_gamma));
},
settings_window::Event::ChangeRenderMode(new_render_mode) => {
events.push(Event::ChangeRenderMode(new_render_mode));
},
settings_window::Event::ChangeResolution(new_resolution) => {
events.push(Event::ChangeResolution(new_resolution));
},
settings_window::Event::ChangeBitDepth(new_bit_depth) => {
events.push(Event::ChangeBitDepth(new_bit_depth));
},
settings_window::Event::ChangeRefreshRate(new_refresh_rate) => {
events.push(Event::ChangeRefreshRate(new_refresh_rate));
},
settings_window::Event::ChangeLanguage(language) => {
events.push(Event::ChangeLanguage(language));
},
settings_window::Event::ToggleParticlesEnabled(particles_enabled) => {
events.push(Event::ToggleParticlesEnabled(particles_enabled));
},
settings_window::Event::ToggleFullscreen => {
events.push(Event::ToggleFullscreen);
},
settings_window::Event::AdjustWindowSize(new_size) => {
events.push(Event::AdjustWindowSize(new_size));
},
settings_window::Event::ChangeBinding(game_input) => {
events.push(Event::ChangeBinding(game_input));
},
settings_window::Event::ResetBindings => {
events.push(Event::ResetBindings);
},
settings_window::Event::ChangeFreeLookBehavior(behavior) => {
events.push(Event::ChangeFreeLookBehavior(behavior));
},
settings_window::Event::ChangeAutoWalkBehavior(behavior) => {
events.push(Event::ChangeAutoWalkBehavior(behavior));
},
settings_window::Event::ChangeStopAutoWalkOnInput(state) => {
events.push(Event::ChangeStopAutoWalkOnInput(state));
},
}
}
}
// Social Window
if self.show.social {
let ecs = client.state().ecs();
let _stats = ecs.read_storage::<comp::Stats>();
let me = client.entity();
if let Some(_stats) = stats.get(me) {
for event in Social::new(
&self.show,
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
info.selected_entity,
&self.rot_imgs,
tooltip_manager,
)
.set(self.ids.social_window, ui_widgets)
{
match event {
social::Event::Close => {
self.show.social(false);
if !self.show.bag {
self.show.want_grab = true;
self.force_ungrab = false;
} else {
self.force_ungrab = true
};
},
social::Event::ChangeSocialTab(social_tab) => {
self.show.open_social_tab(social_tab)
},
social::Event::Invite(uid) => events.push(Event::InviteMember(uid)),
}
}
}
}
// Group Window
for event in Group::new(
&mut self.show,
client,
&global_state.settings,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
self.pulse,
&global_state,
)
.set(self.ids.group_window, ui_widgets)
{
match event {
group::Event::Accept => events.push(Event::AcceptInvite),
group::Event::Decline => events.push(Event::DeclineInvite),
group::Event::Kick(uid) => events.push(Event::KickMember(uid)),
group::Event::LeaveGroup => events.push(Event::LeaveGroup),
group::Event::AssignLeader(uid) => events.push(Event::AssignLeader(uid)),
}
}
// Spellbook
if self.show.spell {
match Spell::new(
&self.show,
client,
&self.imgs,
&self.fonts,
&self.voxygen_i18n,
)
.set(self.ids.spell, ui_widgets)
{
Some(spell::Event::Close) => {
self.show.spell(false);
self.show.want_grab = true;
self.force_ungrab = false;
},
None => {},
}
}
// Map
if self.show.map {
for event in Map::new(
&self.show,
client,
&self.imgs,
&self.rot_imgs,
&self.world_map,
&self.fonts,
self.pulse,
&self.voxygen_i18n,
&global_state,
)
.set(self.ids.map, ui_widgets)
{
match event {
map::Event::Close => {
self.show.map(false);
self.show.want_grab = true;
self.force_ungrab = false;
},
map::Event::MapZoom(map_zoom) => {
events.push(Event::MapZoom(map_zoom));
},
}
}
}
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts, &self.voxygen_i18n)
.set(self.ids.esc_menu, ui_widgets)
{
Some(esc_menu::Event::OpenSettings(tab)) => {
self.show.open_setting_tab(tab);
},
Some(esc_menu::Event::Close) => {
self.show.esc_menu = false;
self.show.want_grab = true;
self.force_ungrab = false;
// Unpause the game if we are on singleplayer
#[cfg(feature = "singleplayer")]
global_state.unpause();
},
Some(esc_menu::Event::Logout) => {
// Unpause the game if we are on singleplayer so that we can logout
#[cfg(feature = "singleplayer")]
global_state.unpause();
events.push(Event::Logout);
},
Some(esc_menu::Event::Quit) => events.push(Event::Quit),
Some(esc_menu::Event::CharacterSelection) => {
// Unpause the game if we are on singleplayer so that we can logout
#[cfg(feature = "singleplayer")]
global_state.unpause();
events.push(Event::CharacterSelection)
},
None => {},
}
}
// Free look indicator
if let Some(freelook_key) = global_state
.settings
.controls
.get_binding(GameInput::FreeLook)
{
if self.show.free_look {
Text::new(
&self
.voxygen_i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
.color(TEXT_BG)
.mid_top_with_margin_on(ui_widgets.window, 130.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.free_look_bg, ui_widgets);
Text::new(
&self
.voxygen_i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
.color(KILL_COLOR)
.top_left_with_margins_on(self.ids.free_look_bg, -1.0, -1.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.free_look_txt, ui_widgets);
}
};
// Auto walk indicator
if self.show.auto_walk {
Text::new(&self.voxygen_i18n.get("hud.auto_walk_indicator"))
.color(TEXT_BG)
.mid_top_with_margin_on(
ui_widgets.window,
if self.show.free_look { 128.0 } else { 100.0 },
)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.auto_walk_bg, ui_widgets);
Text::new(&self.voxygen_i18n.get("hud.auto_walk_indicator"))
.color(KILL_COLOR)
.top_left_with_margins_on(self.ids.auto_walk_bg, -1.0, -1.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.auto_walk_txt, ui_widgets);
}
// Maintain slot manager
for event in self.slot_manager.maintain(ui_widgets) {
use comp::slot::Slot;
use slots::SlotKind::*;
let to_slot = |slot_kind| match slot_kind {
Inventory(i) => Some(Slot::Inventory(i.0)),
Equip(e) => Some(Slot::Equip(e)),
Hotbar(_) => None,
};
match event {
slot::Event::Dragged(a, b) => {
// Swap between slots
if let (Some(a), Some(b)) = (to_slot(a), to_slot(b)) {
events.push(Event::SwapSlots(a, b));
} else if let (Inventory(i), Hotbar(h)) = (a, b) {
self.hotbar.add_inventory_link(h, i.0);
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
} else if let (Hotbar(a), Hotbar(b)) = (a, b) {
self.hotbar.swap(a, b);
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
}
},
slot::Event::Dropped(from) => {
// Drop item
if let Some(from) = to_slot(from) {
events.push(Event::DropSlot(from));
} else if let Hotbar(h) = from {
self.hotbar.clear_slot(h);
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
}
},
slot::Event::Used(from) => {
// Item used (selected and then clicked again)
if let Some(from) = to_slot(from) {
events.push(Event::UseSlot(from));
} else if let Hotbar(h) = from {
self.hotbar.get(h).map(|s| {
match s {
hotbar::SlotContents::Inventory(i) => {
events.push(Event::UseSlot(comp::slot::Slot::Inventory(i)));
},
hotbar::SlotContents::Ability3 => {}, /* Event::Ability3(true),
* sticks */
}
});
}
},
}
}
self.hotbar.maintain_ability3(client);
events
}
pub fn new_message(&mut self, msg: comp::ChatMsg) { self.new_messages.push_back(msg); }
pub fn new_notification(&mut self, msg: common::msg::Notification) {
self.new_notifications.push_back(msg);
}
pub fn scale_change(&mut self, scale_change: ScaleChange) -> ScaleMode {
let scale_mode = match scale_change {
ScaleChange::Adjust(scale) => ScaleMode::Absolute(scale),
ScaleChange::ToAbsolute => self.ui.scale().scaling_mode_as_absolute(),
ScaleChange::ToRelative => self.ui.scale().scaling_mode_as_relative(),
};
self.ui.set_scaling_mode(scale_mode);
scale_mode
}
// Checks if a TextEdit widget has the keyboard captured.
fn typing(&self) -> bool {
if let Some(id) = self.ui.widget_capturing_keyboard() {
self.ui
.widget_graph()
.widget(id)
.filter(|c| {
c.type_id == std::any::TypeId::of::<<widget::TextEdit as Widget>::State>()
})
.is_some()
} else {
false
}
}
pub fn handle_event(&mut self, event: WinEvent, global_state: &mut GlobalState) -> bool {
// Helper
fn handle_slot(
slot: hotbar::Slot,
state: bool,
events: &mut Vec<Event>,
slot_manager: &mut slots::SlotManager,
hotbar: &mut hotbar::State,
) {
if let Some(slots::SlotKind::Inventory(i)) = slot_manager.selected() {
hotbar.add_inventory_link(slot, i.0);
slot_manager.idle();
} else {
let just_pressed = hotbar.process_input(slot, state);
hotbar.get(slot).map(|s| match s {
hotbar::SlotContents::Inventory(i) => {
if just_pressed {
events.push(Event::UseSlot(comp::slot::Slot::Inventory(i)));
}
},
hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)),
});
}
}
let cursor_grabbed = global_state.window.is_cursor_grabbed();
let handled = match event {
WinEvent::Ui(event) => {
if (self.typing() && event.is_keyboard() && self.show.ui)
|| !(cursor_grabbed && event.is_keyboard_or_mouse())
{
self.ui.handle_event(event);
}
true
},
WinEvent::InputUpdate(GameInput::ToggleInterface, true) if !self.typing() => {
self.show.toggle_ui();
true
},
WinEvent::InputUpdate(GameInput::ToggleCursor, true) if !self.typing() => {
self.force_ungrab = !self.force_ungrab;
true
},
_ if !self.show.ui => false,
WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(),
WinEvent::InputUpdate(GameInput::Chat, true) => {
self.ui.focus_widget(if self.typing() {
None
} else {
Some(self.ids.chat)
});
true
},
WinEvent::InputUpdate(GameInput::Escape, true) => {
if self.typing() {
self.ui.focus_widget(None);
} else {
// Close windows on esc
self.show.toggle_windows(global_state);
}
true
},
// Press key while not typing
WinEvent::InputUpdate(key, state) if !self.typing() => match key {
GameInput::Command if state => {
self.force_chat_input = Some("/".to_owned());
self.force_chat_cursor = Some(Index { line: 0, char: 1 });
self.ui.focus_widget(Some(self.ids.chat));
true
},
GameInput::Map if state => {
self.show.toggle_map();
true
},
GameInput::Bag if state => {
self.show.toggle_bag();
true
},
GameInput::Social if state => {
self.show.toggle_social();
true
},
GameInput::Crafting if state => {
self.show.toggle_crafting();
true
},
GameInput::Spellbook if state => {
self.show.toggle_spell();
true
},
GameInput::Settings if state => {
self.show.toggle_settings(global_state);
true
},
GameInput::Help if state => {
self.show.toggle_help();
true
},
GameInput::ToggleDebug if state => {
global_state.settings.gameplay.toggle_debug =
!global_state.settings.gameplay.toggle_debug;
true
},
GameInput::ToggleIngameUi if state => {
self.show.ingame = !self.show.ingame;
true
},
// Skillbar
GameInput::Slot1 => {
handle_slot(
hotbar::Slot::One,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot2 => {
handle_slot(
hotbar::Slot::Two,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot3 => {
handle_slot(
hotbar::Slot::Three,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot4 => {
handle_slot(
hotbar::Slot::Four,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot5 => {
handle_slot(
hotbar::Slot::Five,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot6 => {
handle_slot(
hotbar::Slot::Six,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot7 => {
handle_slot(
hotbar::Slot::Seven,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot8 => {
handle_slot(
hotbar::Slot::Eight,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot9 => {
handle_slot(
hotbar::Slot::Nine,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
GameInput::Slot10 => {
handle_slot(
hotbar::Slot::Ten,
state,
&mut self.events,
&mut self.slot_manager,
&mut self.hotbar,
);
true
},
_ => false,
},
// Else the player is typing in chat
WinEvent::InputUpdate(_key, _) => self.typing(),
WinEvent::Char(_) => self.typing(),
WinEvent::Focused(state) => {
self.force_ungrab = !state;
true
},
WinEvent::Moved(_) => {
// Prevent the cursor from being grabbed while the window is being moved as this
// causes the window to move erratically
self.show.want_grab = false;
true
},
_ => false,
};
// Handle cursor grab.
global_state
.window
.grab_cursor(!self.force_ungrab && self.show.want_grab);
handled
}
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
pub fn maintain(
&mut self,
client: &Client,
global_state: &mut GlobalState,
debug_info: &Option<DebugInfo>,
camera: &Camera,
dt: Duration,
info: HudInfo,
) -> Vec<Event> {
// conrod eats tabs. Un-eat a tabstop so tab completion can work
if self.ui.ui.global_input().events().any(|event| {
use conrod_core::{event, input};
matches!(event,
/* event::Event::Raw(event::Input::Press(input::Button::Keyboard(input::Key::Tab))) | */
event::Event::Ui(event::Ui::Press(
_,
event::Press {
button: event::Button::Keyboard(input::Key::Tab),
..
},
)))
}) {
self.ui
.ui
.handle_event(conrod_core::event::Input::Text("\t".to_string()));
}
if !self.show.ui {
// Optimization: skip maintaining UI when it's off.
return vec![];
}
if let Some(maybe_id) = self.to_focus.take() {
self.ui.focus_widget(maybe_id);
}
let events = self.update_layout(client, global_state, debug_info, dt, info, camera);
let camera::Dependents {
view_mat, proj_mat, ..
} = camera.dependents();
let focus_off = camera.get_focus_pos().map(f32::trunc);
// Check if item images need to be reloaded
self.item_imgs.reload_if_changed(&mut self.ui);
self.ui.maintain(
&mut global_state.window.renderer_mut(),
Some(proj_mat * view_mat * Mat4::translation_3d(-focus_off)),
);
events
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
// Don't show anything if the UI is toggled off.
if self.show.ui {
self.ui.render(renderer, Some(globals));
}
}
pub fn free_look(&mut self, free_look: bool) { self.show.free_look = free_look; }
pub fn auto_walk(&mut self, auto_walk: bool) { self.show.auto_walk = auto_walk; }
}