veloren/voxygen/src/hud/mod.rs

1382 lines
48 KiB
Rust
Raw Normal View History

mod bag;
mod buttons;
mod character_window;
mod chat;
mod esc_menu;
mod img_ids;
2019-10-09 19:28:05 +00:00
mod item_imgs;
mod map;
mod minimap;
mod quest;
mod settings_window;
mod skillbar;
mod social;
mod spell;
2019-09-25 16:51:47 +00:00
use crate::hud::img_ids::ImgsRot;
2019-07-26 02:28:53 +00:00
pub use settings_window::ScaleChange;
use bag::Bag;
use buttons::Buttons;
use character_window::CharacterWindow;
use chat::Chat;
2019-09-01 19:04:03 +00:00
use chrono::NaiveTime;
use esc_menu::EscMenu;
use img_ids::Imgs;
2019-10-09 19:28:05 +00:00
use item_imgs::ItemImgs;
use map::Map;
use minimap::MiniMap;
use quest::Quest;
2019-07-23 01:02:57 +00:00
use serde::{Deserialize, Serialize};
use settings_window::{SettingsTab, SettingsWindow};
use skillbar::Skillbar;
use social::{Social, SocialTab};
use spell::Spell;
use crate::{
render::{AaMode, Consts, Globals, Renderer},
scene::camera::Camera,
2019-10-06 19:19:08 +00:00
//settings::ControlSettings,
ui::{Graphic, Ingameable, ScaleMode, Ui},
2019-07-26 02:28:53 +00:00
window::{Event as WinEvent, GameInput},
GlobalState,
};
2019-07-17 22:10:42 +00:00
use client::{Client, Event as ClientEvent};
common: Rework volume API See the doc comments in `common/src/vol.rs` for more information on the API itself. The changes include: * Consistent `Err`/`Error` naming. * Types are named `...Error`. * `enum` variants are named `...Err`. * Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation to an upcoming change where a “map” in the game related sense will be added. * Add volume iterators. There are two types of them: * _Position_ iterators obtained from the trait `IntoPosIterator` using the method `fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `Vec3<i32>`. * _Volume_ iterators obtained from the trait `IntoVolIterator` using the method `fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `(Vec3<i32>, &Self::Vox)`. Those traits will usually be implemented by references to volume types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some type which usually implements several volume traits, such as `Chunk`). * _Position_ iterators iterate over the positions valid for that volume. * _Volume_ iterators do the same but return not only the position but also the voxel at that position, in each iteration. * Introduce trait `RectSizedVol` for the use case which we have with `Chonk`: A `Chonk` is sized only in x and y direction. * Introduce traits `RasterableVol`, `RectRasterableVol` * `RasterableVol` represents a volume that is compile-time sized and has its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen because such a volume can be used with `VolGrid3d`. * `RectRasterableVol` represents a volume that is compile-time sized at least in x and y direction and has its lower bound at `(0, 0, z)`. There's no requirement on he lower bound or size in z direction. The name `RectRasterableVol` was chosen because such a volume can be used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
use common::{comp, terrain::TerrainChunk, vol::RectRasterableVol};
use conrod_core::{
image::Id,
2019-07-05 16:21:11 +00:00
text::cursor::Index,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
};
2019-11-30 06:41:20 +00:00
use specs::{Join, WorldExt};
use std::collections::VecDeque;
use vek::*;
2019-06-04 20:41:50 +00:00
#[cfg(feature = "discord")]
use crate::{discord, discord::DiscordUpdate};
2019-06-04 20:41:50 +00:00
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 MENU_BG: Color = Color::Rgba(0.0, 0.0, 0.0, 0.4);
2019-10-04 18:27:12 +00:00
//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 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(1.0, 0.0, 0.0, 1.0);
const MANA_COLOR: Color = Color::Rgba(0.47, 0.55, 1.0, 0.9);
//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);
2019-07-29 14:40:46 +00:00
const META_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0);
const TELL_COLOR: Color = Color::Rgba(0.98, 0.71, 1.0, 1.0);
const PRIVATE_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0); // Difference between private and tell?
const BROADCAST_COLOR: Color = Color::Rgba(0.28, 0.83, 0.71, 1.0);
const GAME_UPDATE_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0);
2019-07-29 14:40:46 +00:00
const SAY_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
const GROUP_COLOR: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0);
const FACTION_COLOR: Color = Color::Rgba(0.24, 1.0, 0.48, 1.0);
const KILL_COLOR: Color = Color::Rgba(1.0, 0.17, 0.17, 1.0);
widget_ids! {
struct Ids {
2019-07-02 19:54:38 +00:00
// Crosshair
2019-07-06 23:04:30 +00:00
crosshair_inner,
crosshair_outer,
2019-07-02 19:54:38 +00:00
// Character Names
name_tags[],
// Health Bars
health_bars[],
health_bar_backs[],
// 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,
// Test
bag_space_add,
2019-06-23 19:49:15 +00:00
// Debug
debug_bg,
fps_counter,
ping,
coordinates,
2019-08-06 21:51:13 +00:00
velocity,
2019-06-23 19:49:15 +00:00
loaded_distance,
2019-09-01 19:04:03 +00:00
time,
2019-11-29 06:04:37 +00:00
entity_count,
// Game Version
version,
// Help
help,
2019-10-06 19:19:08 +00:00
help_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,
minimap,
bag,
social,
quest,
spell,
skillbar,
buttons,
esc_menu,
small_window,
social_window,
settings_window,
}
}
font_ids! {
pub struct Fonts {
2019-10-05 21:51:08 +00:00
opensans: "voxygen.font.OpenSans-Regular",
2019-08-06 06:31:48 +00:00
metamorph: "voxygen.font.Metamorphous-Regular",
2019-10-04 18:27:12 +00:00
alkhemi: "voxygen.font.Alkhemikal",
wizard: "voxygen.font.wizard",
2019-10-05 21:51:08 +00:00
cyri:"voxygen.font.haxrcorp_4089_cyrillic_altgr",
}
}
pub struct DebugInfo {
pub tps: f64,
pub ping_ms: f64,
pub coordinates: Option<comp::Pos>,
2019-08-06 21:51:13 +00:00
pub velocity: Option<comp::Vel>,
}
2019-07-27 19:26:31 +00:00
pub enum Event {
SendMessage(String),
AdjustMousePan(u32),
AdjustMouseZoom(u32),
ToggleZoomInvert(bool),
2019-12-06 22:49:17 +00:00
ToggleMouseYInvert(bool),
AdjustViewDistance(u32),
AdjustMusicVolume(f32),
AdjustSfxVolume(f32),
ChangeAudioDevice(String),
2019-06-06 19:11:39 +00:00
ChangeMaxFPS(u32),
2019-08-05 16:37:52 +00:00
ChangeFOV(u16),
ChangeAaMode(AaMode),
2019-07-07 00:15:22 +00:00
CrosshairTransp(f32),
ChatTransp(f32),
2019-07-23 01:02:57 +00:00
CrosshairType(CrosshairType),
ToggleXpBar(XpBar),
Intro(Intro),
ToggleBarNumbers(BarNumbers),
ToggleShortcutNumbers(ShortcutNumbers),
2019-07-26 02:28:53 +00:00
UiScale(ScaleChange),
CharacterSelection,
UseInventorySlot(usize),
2019-07-25 22:52:28 +00:00
SwapInventorySlots(usize, usize),
2019-07-26 17:08:40 +00:00
DropInventorySlot(usize),
Logout,
Quit,
}
// 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,
}
2019-07-23 01:02:57 +00:00
#[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,
}
2019-07-23 01:02:57 +00:00
pub struct Show {
ui: bool,
intro: bool,
help: bool,
debug: bool,
bag: bool,
social: bool,
spell: bool,
quest: bool,
character_window: bool,
esc_menu: bool,
open_windows: Windows,
map: bool,
inventory_test_button: bool,
mini_map: bool,
ingame: bool,
settings_tab: SettingsTab,
social_tab: SocialTab,
want_grab: bool,
}
impl Show {
fn bag(&mut self, open: bool) {
self.bag = open;
self.want_grab = !open;
}
fn toggle_bag(&mut self) {
self.bag(!self.bag);
}
fn map(&mut self, open: bool) {
self.map = open;
self.bag = false;
self.want_grab = !open;
}
fn character_window(&mut self, open: bool) {
self.character_window = open;
self.bag = false;
self.want_grab = !open;
}
fn social(&mut self, open: bool) {
self.social = open;
self.spell = false;
self.quest = false;
self.want_grab = !open;
}
fn spell(&mut self, open: bool) {
self.social = false;
self.spell = open;
self.quest = false;
self.want_grab = !open;
}
fn quest(&mut self, open: bool) {
self.social = false;
self.spell = false;
self.quest = 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 toggle_char_window(&mut self) {
self.character_window = !self.character_window
}
fn settings(&mut self, open: bool) {
self.open_windows = if open {
Windows::Settings
} else {
Windows::None
};
self.bag = false;
self.social = false;
self.spell = false;
self.quest = false;
self.want_grab = !open;
}
fn toggle_settings(&mut self) {
match self.open_windows {
Windows::Settings => self.settings(false),
_ => 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) {
if self.bag
|| self.esc_menu
|| self.map
|| self.social
|| self.quest
|| self.spell
|| self.character_window
|| match self.open_windows {
Windows::None => false,
_ => true,
}
{
self.bag = false;
self.esc_menu = false;
self.map = false;
self.social = false;
self.quest = false;
self.spell = false;
self.character_window = false;
self.open_windows = Windows::None;
self.want_grab = true;
} else {
self.esc_menu = true;
self.want_grab = false;
}
}
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;
self.quest = false;
}
fn open_social_tab(&mut self, social_tab: SocialTab) {
self.social_tab = social_tab;
self.spell = false;
self.quest = false;
}
fn toggle_spell(&mut self) {
self.spell = !self.spell;
self.social = false;
self.quest = false;
}
fn toggle_quest(&mut self) {
self.quest = !self.quest;
self.spell = false;
self.social = false;
}
}
2019-09-25 20:18:40 +00:00
pub struct Hud {
ui: Ui,
ids: Ids,
world_map: Id,
imgs: Imgs,
2019-10-09 19:28:05 +00:00
item_imgs: ItemImgs,
fonts: Fonts,
2019-09-25 16:51:47 +00:00
rot_imgs: ImgsRot,
2019-07-17 22:10:42 +00:00
new_messages: VecDeque<ClientEvent>,
2019-07-06 17:00:05 +00:00
inventory_space: usize,
show: Show,
never_show: bool,
intro: bool,
intro_2: bool,
to_focus: Option<Option<widget::Id>>,
2019-07-26 21:05:39 +00:00
force_ungrab: bool,
2019-07-05 16:21:11 +00:00
force_chat_input: Option<String>,
force_chat_cursor: Option<Index>,
}
2019-09-25 20:18:40 +00:00
impl Hud {
pub fn new(global_state: &mut GlobalState, client: &Client) -> Self {
2019-07-26 02:28:53 +00:00
let window = &mut global_state.window;
let settings = &global_state.settings;
let mut ui = Ui::new(window).unwrap();
2019-07-26 02:28:53 +00:00
ui.set_scaling_mode(settings.gameplay.ui_scale);
// Generate ids.
let ids = Ids::new(ui.id_generator());
// Load world map
let world_map = ui.add_graphic(Graphic::Image(client.world_map.clone()));
// Load images.
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
2019-09-25 20:18:40 +00:00
// Load rotation images.
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
2019-10-09 19:28:05 +00:00
// Load item images.
let item_imgs = ItemImgs::new(&mut ui);
// Load fonts.
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!");
Self {
ui,
imgs,
world_map,
2019-09-25 16:51:47 +00:00
rot_imgs,
2019-10-09 19:28:05 +00:00
item_imgs,
fonts,
2019-07-26 21:05:39 +00:00
ids,
new_messages: VecDeque::new(),
2019-07-06 17:00:05 +00:00
inventory_space: 8,
intro: false,
intro_2: false,
show: Show {
help: false,
intro: true,
debug: true,
bag: false,
esc_menu: false,
open_windows: Windows::None,
map: false,
ui: true,
social: false,
quest: false,
spell: false,
character_window: false,
inventory_test_button: false,
mini_map: false,
settings_tab: SettingsTab::Interface,
social_tab: SocialTab::Online,
want_grab: true,
ingame: true,
},
to_focus: None,
never_show: false,
force_ungrab: false,
2019-07-05 16:21:11 +00:00
force_chat_input: None,
force_chat_cursor: None,
}
}
fn update_layout(
&mut self,
client: &Client,
global_state: &GlobalState,
debug_info: DebugInfo,
) -> Vec<Event> {
let mut events = Vec::new();
2019-09-25 16:51:47 +00:00
let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets();
2019-06-28 12:08:12 +00:00
2019-10-18 10:03:01 +00:00
let version = format!(
"{}-{}",
env!("CARGO_PKG_VERSION"),
common::util::GIT_VERSION.to_string()
);
if self.show.ingame {
2019-07-02 19:54:38 +00:00
// Crosshair
2019-10-06 19:19:08 +00:00
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,
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);
}
2019-07-02 19:54:38 +00:00
// Nametags and healthbars
let ecs = client.state().ecs();
let pos = ecs.read_storage::<comp::Pos>();
let stats = ecs.read_storage::<comp::Stats>();
let players = ecs.read_storage::<comp::Player>();
let scales = ecs.read_storage::<comp::Scale>();
let entities = ecs.entities();
let me = client.entity();
let view_distance = client.view_distance().unwrap_or(1);
// Get player position.
let player_pos = client
.state()
.ecs()
.read_storage::<comp::Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
let mut name_id_walker = self.ids.name_tags.walk();
let mut health_id_walker = self.ids.health_bars.walk();
let mut health_back_id_walker = self.ids.health_bar_backs.walk();
// Render Name Tags
2019-10-05 13:58:58 +00:00
for (pos, name, level, scale) in
(&entities, &pos, &stats, players.maybe(), scales.maybe())
.join()
.filter(|(entity, _, stats, _, _)| *entity != me && !stats.is_dead)
// Don't process nametags outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _)| {
Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32
})
.magnitude()
< view_distance as f32
})
.map(|(_, pos, stats, player, scale)| {
// 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
};
(pos.0, name, stats.level, scale)
})
{
2019-10-04 15:19:15 +00:00
let info = format!("{} Level {}", name, level.level());
let scale = scale.map(|s| s.0).unwrap_or(1.0);
let id = name_id_walker.next(
&mut self.ids.name_tags,
&mut ui_widgets.widget_id_generator(),
);
2019-10-04 15:19:15 +00:00
Text::new(&info)
.font_size(20)
2019-07-06 17:29:44 +00:00
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
.x_y(0.0, 0.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(id, ui_widgets);
}
// Render Health Bars
for (_entity, pos, stats, scale) in (&entities, &pos, &stats, scales.maybe())
.join()
.filter(|(entity, _, stats, _)| {
*entity != me
&& !stats.is_dead
&& stats.health.current() != stats.health.maximum()
})
// Don't process health bars outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _)| {
common: Rework volume API See the doc comments in `common/src/vol.rs` for more information on the API itself. The changes include: * Consistent `Err`/`Error` naming. * Types are named `...Error`. * `enum` variants are named `...Err`. * Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation to an upcoming change where a “map” in the game related sense will be added. * Add volume iterators. There are two types of them: * _Position_ iterators obtained from the trait `IntoPosIterator` using the method `fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `Vec3<i32>`. * _Volume_ iterators obtained from the trait `IntoVolIterator` using the method `fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `(Vec3<i32>, &Self::Vox)`. Those traits will usually be implemented by references to volume types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some type which usually implements several volume traits, such as `Chunk`). * _Position_ iterators iterate over the positions valid for that volume. * _Volume_ iterators do the same but return not only the position but also the voxel at that position, in each iteration. * Introduce trait `RectSizedVol` for the use case which we have with `Chonk`: A `Chonk` is sized only in x and y direction. * Introduce traits `RasterableVol`, `RectRasterableVol` * `RasterableVol` represents a volume that is compile-time sized and has its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen because such a volume can be used with `VolGrid3d`. * `RectRasterableVol` represents a volume that is compile-time sized at least in x and y direction and has its lower bound at `(0, 0, z)`. There's no requirement on he lower bound or size in z direction. The name `RectRasterableVol` was chosen because such a volume can be used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32
})
2019-06-23 19:49:15 +00:00
.magnitude()
< view_distance as f32
})
{
let scale = scale.map(|s| s.0).unwrap_or(1.0);
let back_id = health_back_id_walker.next(
&mut self.ids.health_bar_backs,
&mut ui_widgets.widget_id_generator(),
);
let bar_id = health_id_walker.next(
&mut self.ids.health_bars,
&mut ui_widgets.widget_id_generator(),
);
// Background
Rectangle::fill_with([120.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5))
.x_y(0.0, -25.0)
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(back_id, ui_widgets);
// % HP Filling
Rectangle::fill_with(
[
120.0 * (stats.health.current() as f64 / stats.health.maximum() as f64),
8.0,
],
HP_COLOR,
)
.x_y(0.0, -25.0)
.position_ingame(pos.0 + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(bar_id, ui_widgets);
}
}
// Introduction Text
let intro_text: &'static str =
"Welcome to the Veloren Alpha!\n\
\n\
\n\
Some tips before you start:\n\
\n\
\n\
MOST IMPORTANTLY: To set your respawn point type /waypoint into the chat.\n\
\n\
This can also be done when you are already dead!\n\
\n\
\n\
Press F1 to see the available key commands.\n\
\n\
Type /help into the chat to see chat commands\n\
\n\
\n\
There are chests and other objects randomly spawning in the World!\n\
\n\
Right-Click to collect them.\n\
\n\
To actually use whatever you loot from those chests open your inventory with 'B'.\n\
\n\
Double click the items in your bag to use or equip them.\n\
\n\
Throw them away by clicking them once and clicking outside of the bag\n\
\n\
\n\
Nights can get pretty dark in Veloren.\n\
\n\
Light your lantern by typing /lantern into the chat\n\
\n\
\n\
Want to free your cursor to close this window? Press TAB!\n\
\n\
\n\
Enjoy your stay in the World of Veloren.";
if self.show.intro && !self.show.esc_menu && !self.intro_2 {
match global_state.settings.gameplay.intro_show {
Intro::Show => {
Rectangle::fill_with([800.0, 850.0], Color::Rgba(0.0, 0.0, 0.0, 0.80))
.top_left_with_margins_on(ui_widgets.window, 180.0, 10.0)
.floating(true)
.set(self.ids.intro_bg, ui_widgets);
Text::new(intro_text)
.top_left_with_margins_on(self.ids.intro_bg, 10.0, 10.0)
.font_size(20)
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(self.ids.intro_text, ui_widgets);
if Button::image(self.imgs.button)
.w_h(100.0, 50.0)
.mid_bottom_with_margin_on(self.ids.intro_bg, 10.0)
.label("Close")
.label_font_size(20)
.label_color(TEXT_COLOR)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.set(self.ids.intro_close, ui_widgets)
.was_clicked()
{
if self.never_show {
events.push(Event::Intro(Intro::Never));
self.never_show = !self.never_show;
self.intro = false;
self.intro_2 = false;
} else {
self.show.intro = !self.show.intro;
self.intro = false;
self.intro_2 = false;
}
}
if Button::image(if self.never_show {
self.imgs.checkbox_checked
} else {
self.imgs.checkbox
})
.w_h(20.0, 20.0)
.right_from(self.ids.intro_close, 10.0)
.hover_image(if self.never_show {
self.imgs.checkbox_checked_mo
} else {
self.imgs.checkbox_mo
})
.press_image(self.imgs.checkbox_press)
.set(self.ids.intro_check, ui_widgets)
.was_clicked()
{
self.never_show = !self.never_show
};
Text::new("Don't show this on Startup")
.right_from(self.ids.intro_check, 10.0)
.font_size(10)
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(self.ids.intro_check_text, 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.intro_bg, 0.0, 0.0)
.color(Color::Rgba(1.0, 1.0, 1.0, 0.8))
.set(self.ids.intro_close_4, ui_widgets)
.was_clicked()
{
if self.never_show {
events.push(Event::Intro(Intro::Never));
self.never_show = !self.never_show;
self.intro = false;
self.intro_2 = false;
} else {
self.show.intro = !self.show.intro;
self.intro = false;
self.intro_2 = false;
}
};
}
Intro::Never => {}
}
}
if self.intro_2 && !self.show.esc_menu {
Rectangle::fill_with([800.0, 850.0], Color::Rgba(0.0, 0.0, 0.0, 0.80))
.top_left_with_margins_on(ui_widgets.window, 180.0, 10.0)
.floating(true)
.set(self.ids.intro_bg, ui_widgets);
Text::new(intro_text)
.top_left_with_margins_on(self.ids.intro_bg, 10.0, 10.0)
.font_size(20)
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(self.ids.intro_text, ui_widgets);
if Button::image(self.imgs.button)
.w_h(100.0, 50.0)
.mid_bottom_with_margin_on(self.ids.intro_bg, 10.0)
.label("Close")
.label_font_size(20)
.label_color(TEXT_COLOR)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.set(self.ids.intro_close_3, ui_widgets)
.was_clicked()
{
self.intro_2 = false;
}
// 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.intro_bg, 0.0, 0.0)
.color(Color::Rgba(1.0, 1.0, 1.0, 0.8))
.set(self.ids.intro_close_4, ui_widgets)
.was_clicked()
{
if self.never_show {
events.push(Event::Intro(Intro::Never));
self.never_show = !self.never_show;
self.intro = false;
self.intro_2 = false;
} else {
self.show.intro = !self.show.intro;
self.intro = false;
self.intro_2 = false;
}
};
}
// Display debug window.
if self.show.debug {
// Alpha Version
2019-06-28 12:08:12 +00:00
Text::new(&version)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.font_size(14)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(self.ids.version, ui_widgets);
2019-06-23 19:49:15 +00:00
// Ticks per second
2019-08-22 17:44:35 +00:00
Text::new(&format!("FPS: {:.0}", debug_info.tps))
.color(TEXT_COLOR)
.down_from(self.ids.version, 5.0)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.fps_counter, ui_widgets);
2019-06-23 19:49:15 +00:00
// Ping
2019-08-22 17:44:35 +00:00
Text::new(&format!("Ping: {:.0}ms", debug_info.ping_ms))
.color(TEXT_COLOR)
.down_from(self.ids.fps_counter, 5.0)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.ping, ui_widgets);
2019-08-06 21:51:13 +00:00
// Player's position
let coordinates_text = match debug_info.coordinates {
2019-08-22 17:44:35 +00:00
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)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.coordinates, ui_widgets);
// Player's velocity
2019-08-06 21:51:13 +00:00
let velocity_text = match debug_info.velocity {
2019-08-22 19:26:35 +00:00
Some(velocity) => format!(
"Velocity: ({:.1}, {:.1}, {:.1}) [{:.1} u/s]",
velocity.0.x,
velocity.0.y,
velocity.0.z,
velocity.0.magnitude()
),
2019-08-06 21:51:13 +00:00
None => "Player has no Vel component".to_owned(),
};
Text::new(&velocity_text)
.color(TEXT_COLOR)
.down_from(self.ids.coordinates, 5.0)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
2019-08-06 21:51:13 +00:00
.font_size(14)
.set(self.ids.velocity, ui_widgets);
2019-06-23 19:49:15 +00:00
// Loaded distance
Text::new(&format!(
"View distance: {} chunks",
client.loaded_distance().unwrap_or(0)
))
.color(TEXT_COLOR)
2019-08-06 21:51:13 +00:00
.down_from(self.ids.velocity, 5.0)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
2019-06-23 19:49:15 +00:00
.font_size(14)
.set(self.ids.loaded_distance, ui_widgets);
2019-09-01 19:04:03 +00:00
// 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)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
2019-09-01 19:04:03 +00:00
.font_size(14)
.set(self.ids.time, ui_widgets);
2019-11-29 06:04:37 +00:00
// 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)
.font_size(14)
.set(self.ids.entity_count, ui_widgets);
2019-10-06 19:19:08 +00:00
// Help Window
Text::new("Press 'F1' to show Keybindings")
.color(TEXT_COLOR)
2019-11-29 06:04:37 +00:00
.down_from(self.ids.entity_count, 5.0)
2019-10-06 19:19:08 +00:00
.font_id(self.fonts.cyri)
.font_size(14)
.set(self.ids.help_info, ui_widgets);
}
// Add Bag-Space Button.
if self.show.inventory_test_button {
2019-06-29 01:47:38 +00:00
if Button::image(self.imgs.button)
.w_h(100.0, 100.0)
.middle_of(ui_widgets.window)
.label("Add 1 Space")
.label_font_size(20)
.label_color(TEXT_COLOR)
2019-06-29 01:47:38 +00:00
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.set(self.ids.bag_space_add, ui_widgets)
.was_clicked()
{
if self.inventory_space < 100 {
self.inventory_space += 1;
} else {
}
};
}
// Help Text
2019-10-06 19:19:08 +00:00
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);
// Show tips
if Button::image(self.imgs.button)
.w_h(120.0, 50.0)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.label("Show Tips")
.label_font_size(20)
.label_color(TEXT_COLOR)
.mid_bottom_with_margin_on(self.ids.help, 20.0)
.set(self.ids.button_help3, ui_widgets)
.was_clicked()
{
self.show.help = false;
self.show.intro = false;
self.intro = false;
self.intro_2 = true;
};
// X-button
if Button::image(self.imgs.close_button)
2019-10-06 19:19:08 +00:00
.w_h(40.0, 40.0)
.hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press)
2019-10-06 19:19:08 +00:00
.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
match Buttons::new(
&self.show.open_windows,
self.show.map,
self.show.bag,
&self.imgs,
&self.fonts,
)
.set(self.ids.buttons, ui_widgets)
{
Some(buttons::Event::ToggleBag) => self.show.toggle_bag(),
Some(buttons::Event::ToggleSettings) => self.show.toggle_settings(),
Some(buttons::Event::ToggleCharacter) => self.show.toggle_char_window(),
Some(buttons::Event::ToggleSocial) => self.show.toggle_social(),
Some(buttons::Event::ToggleSpell) => self.show.toggle_spell(),
Some(buttons::Event::ToggleQuest) => self.show.toggle_quest(),
Some(buttons::Event::ToggleMap) => self.show.toggle_map(),
None => {}
}
// MiniMap
match MiniMap::new(&self.show, client, &self.imgs, self.world_map, &self.fonts)
2019-06-19 14:55:26 +00:00
.set(self.ids.minimap, ui_widgets)
{
Some(minimap::Event::Toggle) => self.show.toggle_mini_map(),
None => {}
}
// Bag contents
if self.show.bag {
2019-09-25 20:18:40 +00:00
match Bag::new(
client,
&self.imgs,
2019-10-09 19:28:05 +00:00
&self.item_imgs,
2019-09-25 20:18:40 +00:00
&self.fonts,
&self.rot_imgs,
tooltip_manager,
)
.set(self.ids.bag, ui_widgets)
{
2019-07-25 22:52:28 +00:00
Some(bag::Event::HudEvent(event)) => events.push(event),
Some(bag::Event::Close) => {
self.show.bag(false);
self.force_ungrab = true;
}
None => {}
}
}
// Skillbar
// Get player stats
2019-06-30 11:48:28 +00:00
if let Some(stats) = client
.state()
.ecs()
.read_storage::<comp::Stats>()
.get(client.entity())
2019-06-30 11:48:28 +00:00
{
Skillbar::new(global_state, &self.imgs, &self.fonts, stats)
.set(self.ids.skillbar, ui_widgets);
2019-06-30 11:48:28 +00:00
}
// Chat box
match Chat::new(
&mut self.new_messages,
global_state,
&self.imgs,
&self.fonts,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos))
.set(self.ids.chat, ui_widgets)
{
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();
// 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 {
2019-07-27 19:26:31 +00:00
for event in SettingsWindow::new(&global_state, &self.show, &self.imgs, &self.fonts)
.set(self.ids.settings_window, ui_widgets)
{
match event {
settings_window::Event::ToggleHelp => self.show.help = !self.show.help,
settings_window::Event::ToggleDebug => self.show.debug = !self.show.debug,
settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab),
settings_window::Event::Close => 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::ToggleZoomInvert(zoom_inverted) => {
events.push(Event::ToggleZoomInvert(zoom_inverted));
}
2019-12-06 22:49:17 +00:00
settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => {
events.push(Event::ToggleMouseYInvert(mouse_y_inverted));
}
settings_window::Event::AdjustViewDistance(view_distance) => {
events.push(Event::AdjustViewDistance(view_distance));
}
2019-07-07 00:15:22 +00:00
settings_window::Event::CrosshairTransp(crosshair_transp) => {
events.push(Event::CrosshairTransp(crosshair_transp));
}
settings_window::Event::Intro(intro_show) => {
events.push(Event::Intro(intro_show));
}
settings_window::Event::AdjustMusicVolume(music_volume) => {
events.push(Event::AdjustMusicVolume(music_volume));
}
settings_window::Event::AdjustSfxVolume(sfx_volume) => {
events.push(Event::AdjustSfxVolume(sfx_volume));
}
2019-06-06 17:54:11 +00:00
settings_window::Event::MaximumFPS(max_fps) => {
2019-06-06 19:11:39 +00:00
events.push(Event::ChangeMaxFPS(max_fps));
2019-06-06 17:54:11 +00:00
}
settings_window::Event::ChangeAudioDevice(name) => {
events.push(Event::ChangeAudioDevice(name));
}
2019-07-23 01:02:57 +00:00
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));
}
2019-07-26 02:28:53 +00:00
settings_window::Event::UiScale(scale_change) => {
events.push(Event::UiScale(scale_change));
}
2019-08-05 16:37:52 +00:00
settings_window::Event::AdjustFOV(new_fov) => {
events.push(Event::ChangeFOV(new_fov));
}
settings_window::Event::ChangeAaMode(new_aa_mode) => {
events.push(Event::ChangeAaMode(new_aa_mode));
}
}
}
}
// Social Window
if self.show.social {
for event in Social::new(
/*&global_state,*/ &self.show,
client,
&self.imgs,
&self.fonts,
)
.set(self.ids.social_window, ui_widgets)
{
match event {
social::Event::Close => self.show.social(false),
social::Event::ChangeSocialTab(social_tab) => {
self.show.open_social_tab(social_tab)
}
}
}
}
// Character Window
if self.show.character_window {
2019-07-27 13:04:34 +00:00
let ecs = client.state().ecs();
let stats = ecs.read_storage::<comp::Stats>();
let player_stats = stats.get(client.entity()).unwrap();
match CharacterWindow::new(&self.show, &player_stats, &self.imgs, &self.fonts)
.set(self.ids.character_window, ui_widgets)
{
Some(character_window::Event::Close) => {
self.show.character_window(false);
self.force_ungrab = true;
}
None => {}
}
}
// Spellbook
if self.show.spell {
match Spell::new(&self.show, client, &self.imgs, &self.fonts)
.set(self.ids.spell, ui_widgets)
{
Some(spell::Event::Close) => {
self.show.spell(false);
self.force_ungrab = true;
}
None => {}
}
}
// Quest Log
if self.show.quest {
match Quest::new(&self.show, client, &self.imgs, &self.fonts)
.set(self.ids.quest, ui_widgets)
{
Some(quest::Event::Close) => {
self.show.quest(false);
self.force_ungrab = true;
}
None => {}
}
}
// Map
if self.show.map {
match Map::new(&self.show, client, &self.imgs, self.world_map, &self.fonts)
2019-06-22 14:30:53 +00:00
.set(self.ids.map, ui_widgets)
{
Some(map::Event::Close) => {
self.show.map(false);
self.force_ungrab = true;
}
None => {}
}
}
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts).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 = false;
self.force_ungrab = true;
}
2019-06-04 20:41:50 +00:00
Some(esc_menu::Event::Logout) => {
events.push(Event::Logout);
#[cfg(feature = "discord")]
{
discord::send_all(vec![
DiscordUpdate::Details("Menu".into()),
DiscordUpdate::State("Idling".into()),
DiscordUpdate::LargeImg("bg_main".into()),
]);
2019-06-04 20:41:50 +00:00
}
}
Some(esc_menu::Event::Quit) => events.push(Event::Quit),
Some(esc_menu::Event::CharacterSelection) => events.push(Event::CharacterSelection),
None => {}
}
}
events
}
2019-07-17 22:10:42 +00:00
pub fn new_message(&mut self, msg: ClientEvent) {
self.new_messages.push_back(msg);
}
2019-07-26 02:28:53 +00:00
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 {
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::Enter, 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();
}
true
}
// Press key while not typing
WinEvent::InputUpdate(key, true) if !self.typing() => match key {
2019-07-05 16:21:11 +00:00
GameInput::Command => {
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 => {
self.show.toggle_map();
true
}
GameInput::Bag => {
self.show.toggle_bag();
true
}
GameInput::QuestLog => {
self.show.toggle_quest();
true
}
GameInput::CharacterWindow => {
self.show.toggle_char_window();
true
}
GameInput::Social => {
self.show.toggle_social();
true
}
GameInput::Spellbook => {
self.show.toggle_spell();
true
}
GameInput::Settings => {
self.show.toggle_settings();
true
}
GameInput::Help => {
self.show.toggle_help();
true
}
GameInput::ToggleDebug => {
self.show.debug = !self.show.debug;
true
}
GameInput::ToggleIngameUi => {
self.show.ingame = !self.show.ingame;
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
}
_ => false,
};
// Handle cursor grab.
global_state
.window
.grab_cursor(!self.force_ungrab && self.show.want_grab);
handled
}
pub fn maintain(
&mut self,
client: &Client,
global_state: &mut GlobalState,
debug_info: DebugInfo,
camera: &Camera,
) -> Vec<Event> {
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);
let (view_mat, _, _) = camera.compute_dependents(client);
let fov = camera.get_fov();
self.ui.maintain(
&mut global_state.window.renderer_mut(),
Some((view_mat, fov)),
);
2019-10-09 19:28:05 +00:00
// Check if item images need to be reloaded
self.item_imgs.reload_if_changed(&mut self.ui);
events
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
2019-06-29 14:30:46 +00:00
// Don't show anything if the UI is toggled off.
if self.show.ui {
self.ui.render(renderer, Some(globals));
}
}
}