Popup message when setting waypoint

This commit is contained in:
CapsizeGlimmer 2020-05-14 16:56:10 +00:00 committed by Monty Marz
parent 51eb928eb8
commit 0a723614f9
12 changed files with 300 additions and 69 deletions

View File

@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Player now starts with a lantern. Equipping/unequipping a lantern has the same effect as the `/lantern` command - Player now starts with a lantern. Equipping/unequipping a lantern has the same effect as the `/lantern` command
- Added tab completion in chat for player names and chat commands - Added tab completion in chat for player names and chat commands
- Added server persistence for character stats - Added server persistence for character stats
- Added a popup when setting your character's waypoint
### Changed ### Changed

View File

@ -147,6 +147,7 @@ https://account.veloren.net."#,
"hud.show_tips": "Show Tips", "hud.show_tips": "Show Tips",
"hud.quests": "Quests", "hud.quests": "Quests",
"hud.you_died": "You Died", "hud.you_died": "You Died",
"hud.waypoint_saved": "Waypoint Saved",
"hud.press_key_to_show_keybindings_fmt": "Press {key} to show keybindings", "hud.press_key_to_show_keybindings_fmt": "Press {key} to show keybindings",
"hud.press_key_to_show_debug_info_fmt": "Press {key} to show debug info", "hud.press_key_to_show_debug_info_fmt": "Press {key} to show debug info",

View File

@ -93,6 +93,7 @@ fn main() {
println!("{}", message) println!("{}", message)
}, },
Event::Notification(_) => {}, // TODO?
} }
} }

View File

@ -22,8 +22,9 @@ use common::{
}, },
event::{EventBus, SfxEvent, SfxEventItem}, event::{EventBus, SfxEvent, SfxEventItem},
msg::{ msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate, validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
RegisterError, RequestStateError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
MAX_BYTES_CHAT_MSG,
}, },
net::PostBox, net::PostBox,
state::State, state::State,
@ -59,6 +60,7 @@ pub enum Event {
}, },
Disconnect, Disconnect,
DisconnectionNotification(u64), DisconnectionNotification(u64),
Notification(Notification),
} }
pub struct Client { pub struct Client {
@ -865,6 +867,9 @@ impl Client {
warn!("CharacterActionError: {:?}.", error); warn!("CharacterActionError: {:?}.", error);
self.character_list.error = Some(error); self.character_list.error = Some(error);
}, },
ServerMsg::Notification(n) => {
frontend_events.push(Event::Notification(n));
},
} }
} }
} else if let Some(err) = self.postbox.error() { } else if let Some(err) = self.postbox.error() {

View File

@ -6,7 +6,9 @@ pub mod server;
pub use self::{ pub use self::{
client::ClientMsg, client::ClientMsg,
ecs_packet::EcsCompPacket, ecs_packet::EcsCompPacket,
server::{PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg}, server::{
Notification, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
},
}; };
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View File

@ -26,6 +26,11 @@ pub enum PlayerListUpdate {
Alias(u64, String), Alias(u64, String),
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Notification {
WaypointSaved,
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerMsg { pub enum ServerMsg {
InitialSync { InitialSync {
@ -64,6 +69,8 @@ pub enum ServerMsg {
Disconnect, Disconnect,
Shutdown, Shutdown,
TooManyPlayers, TooManyPlayers,
/// Send a popup notification such as "Waypoint Saved"
Notification(Notification),
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -9,7 +9,7 @@ use common::{
cmd::{ChatCommand, CHAT_COMMANDS}, cmd::{ChatCommand, CHAT_COMMANDS},
comp, comp,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
msg::{PlayerListUpdate, ServerMsg}, msg::{Notification, PlayerListUpdate, ServerMsg},
npc::{self, get_npc_name}, npc::{self, get_npc_name},
state::TimeOfDay, state::TimeOfDay,
sync::{Uid, WorldSyncExt}, sync::{Uid, WorldSyncExt},
@ -838,7 +838,8 @@ fn handle_waypoint(
.ecs() .ecs()
.write_storage::<comp::Waypoint>() .write_storage::<comp::Waypoint>()
.insert(target, comp::Waypoint::new(pos.0)); .insert(target, comp::Waypoint::new(pos.0));
server.notify_client(client, ServerMsg::private(String::from("Waypoint set!"))); server.notify_client(client, ServerMsg::private(String::from("Waypoint saved!")));
server.notify_client(client, ServerMsg::Notification(Notification::WaypointSaved));
}, },
None => server.notify_client( None => server.notify_client(
client, client,

View File

@ -1,7 +1,13 @@
use super::SysTimer; use super::SysTimer;
use common::comp::{Player, Pos, Waypoint, WaypointArea}; use crate::client::Client;
use common::{
comp::{Player, Pos, Waypoint, WaypointArea},
msg::{Notification, ServerMsg},
};
use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage}; use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage};
const NOTIFY_DISTANCE: f32 = 10.0;
/// This system updates player waypoints /// This system updates player waypoints
/// TODO: Make this faster by only considering local waypoints /// TODO: Make this faster by only considering local waypoints
pub struct Sys; pub struct Sys;
@ -12,19 +18,28 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Player>, ReadStorage<'a, Player>,
ReadStorage<'a, WaypointArea>, ReadStorage<'a, WaypointArea>,
WriteStorage<'a, Waypoint>, WriteStorage<'a, Waypoint>,
WriteStorage<'a, Client>,
Write<'a, SysTimer<Self>>, Write<'a, SysTimer<Self>>,
); );
fn run( fn run(
&mut self, &mut self,
(entities, positions, players, waypoint_areas, mut waypoints, mut timer): Self::SystemData, (entities, positions, players, waypoint_areas, mut waypoints, mut clients, mut timer): Self::SystemData,
) { ) {
timer.start(); timer.start();
for (entity, player_pos, _) in (&entities, &positions, &players).join() { for (entity, player_pos, _, client) in
for (waypoint_pos, waypoint_area) in (&positions, &waypoint_areas).join() { (&entities, &positions, &players, &mut clients).join()
if player_pos.0.distance_squared(waypoint_pos.0) < waypoint_area.radius().powf(2.0)
{ {
for (waypoint_pos, waypoint_area) in (&positions, &waypoint_areas).join() {
if player_pos.0.distance_squared(waypoint_pos.0) < waypoint_area.radius().powi(2) {
if let Some(wp) = waypoints.get(entity) {
if player_pos.0.distance_squared(wp.get_pos()) > NOTIFY_DISTANCE.powi(2) {
client
.postbox
.send_message(ServerMsg::Notification(Notification::WaypointSaved));
}
}
let _ = waypoints.insert(entity, Waypoint::new(player_pos.0)); let _ = waypoints.insert(entity, Waypoint::new(player_pos.0));
} }
} }

View File

@ -1,6 +1,6 @@
use super::{ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
Show, HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
}; };
use crate::ui::{fonts::ConrodVoxygenFonts, img_ids}; use crate::ui::{fonts::ConrodVoxygenFonts, img_ids};
use client::{self, Client}; use client::{self, Client};
@ -8,10 +8,9 @@ use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
use conrod_core::{ use conrod_core::{
color, position, color, position,
widget::{self, Button, Image, Rectangle, Text}, widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use specs::WorldExt; use specs::WorldExt;
use std::time::{Duration, Instant};
use vek::*; use vek::*;
widget_ids! { widget_ids! {
@ -68,8 +67,6 @@ impl<'a> MiniMap<'a> {
pub struct State { pub struct State {
ids: Ids, ids: Ids,
last_region_name: Option<String>,
last_update: Instant,
zoom: f64, zoom: f64,
} }
@ -86,8 +83,6 @@ impl<'a> Widget for MiniMap<'a> {
State { State {
ids: Ids::new(id_gen), ids: Ids::new(id_gen),
last_region_name: None,
last_update: Instant::now(),
zoom: { zoom: {
let min_world_dim = self.world_map.1.reduce_partial_min() as f64; let min_world_dim = self.world_map.1.reduce_partial_min() as f64;
min_world_dim.min( min_world_dim.min(
@ -250,57 +245,6 @@ impl<'a> Widget for MiniMap<'a> {
return Some(Event::Toggle); return Some(Event::Toggle);
} }
// Display zone name on entry
const FADE_IN: f32 = 0.5;
const FADE_HOLD: f32 = 1.0;
const FADE_OUT: f32 = 3.0;
match self.client.current_chunk() {
Some(chunk) => {
let current = chunk.meta().name();
// Check if no other popup is displayed and a new one is needed
if state.last_update.elapsed()
> Duration::from_secs_f32(FADE_IN + FADE_HOLD + FADE_OUT)
&& state
.last_region_name
.as_ref()
.map(|l| l != current)
.unwrap_or(true)
{
// Update last_region
state.update(|s| s.last_region_name = Some(current.to_owned()));
state.update(|s| s.last_update = Instant::now());
}
let seconds = state.last_update.elapsed().as_secs_f32();
let fade = if seconds < FADE_IN {
seconds / FADE_IN
} else if seconds < FADE_IN + FADE_HOLD {
1.0
} else {
(1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
};
// Region Name
Text::new(state.last_region_name.as_ref().unwrap_or(&"".to_owned()))
.mid_top_with_margin_on(ui.window, 200.0)
.font_size(self.fonts.alkhemi.scale(70))
.font_id(self.fonts.alkhemi.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.set(state.ids.zone_display_bg, ui);
Text::new(state.last_region_name.as_ref().unwrap_or(&"".to_owned()))
.top_left_with_margins_on(state.ids.zone_display_bg, -2.5, -2.5)
.font_size(self.fonts.alkhemi.scale(70))
.font_id(self.fonts.alkhemi.conrod_id)
.color(Color::Rgba(1.0, 1.0, 1.0, fade))
.set(state.ids.zone_display, ui);
},
None => Text::new(" ")
.middle_of(ui.window)
.font_size(self.fonts.alkhemi.scale(14))
.color(HP_COLOR)
.set(state.ids.zone_display, ui),
}
// TODO: Subregion name display // TODO: Subregion name display
// Title // Title

View File

@ -7,6 +7,7 @@ mod img_ids;
mod item_imgs; mod item_imgs;
mod map; mod map;
mod minimap; mod minimap;
mod popup;
mod settings_window; mod settings_window;
mod skillbar; mod skillbar;
mod slots; mod slots;
@ -26,6 +27,7 @@ use img_ids::Imgs;
use item_imgs::ItemImgs; use item_imgs::ItemImgs;
use map::Map; use map::Map;
use minimap::MiniMap; use minimap::MiniMap;
use popup::Popup;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings_window::{SettingsTab, SettingsWindow}; use settings_window::{SettingsTab, SettingsWindow};
use skillbar::Skillbar; use skillbar::Skillbar;
@ -174,6 +176,7 @@ widget_ids! {
map, map,
world_map, world_map,
character_window, character_window,
popup,
minimap, minimap,
bag, bag,
social, social,
@ -1641,6 +1644,10 @@ impl Hud {
None => {}, None => {},
} }
// Popup
Popup::new(&self.voxygen_i18n, client, &self.new_messages, &self.fonts)
.set(self.ids.popup, ui_widgets);
// MiniMap // MiniMap
match MiniMap::new( match MiniMap::new(
&self.show, &self.show,
@ -1734,6 +1741,16 @@ impl Hud {
.set(self.ids.skillbar, ui_widgets); .set(self.ids.skillbar, ui_widgets);
} }
// The chat box breaks if it has non-chat messages left in the queue, so take
// them out.
self.new_messages.retain(|msg| {
if let ClientEvent::Chat { .. } = &msg {
true
} else {
false
}
});
// Chat box // Chat box
match Chat::new( match Chat::new(
&mut self.new_messages, &mut self.new_messages,

233
voxygen/src/hud/popup.rs Normal file
View File

@ -0,0 +1,233 @@
use crate::{i18n::VoxygenLocalization, ui::fonts::ConrodVoxygenFonts};
use client::{self, Client, Event as ClientEvent};
use common::msg::Notification;
use conrod_core::{
widget::{self, Text},
widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon,
};
use std::{collections::VecDeque, time::Instant};
widget_ids! {
struct Ids {
error_bg,
error_text,
info_bg,
info_text,
message_bg,
message_text,
}
}
#[derive(WidgetCommon)]
pub struct Popup<'a> {
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
client: &'a Client,
new_messages: &'a VecDeque<ClientEvent>,
fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
/// Popup notifications for messages such as <Chunk Name>, Waypoint Saved,
/// Dungeon Cleared (TODO), and Quest Completed (TODO)
impl<'a> Popup<'a> {
pub fn new(
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
client: &'a Client,
new_messages: &'a VecDeque<ClientEvent>,
fonts: &'a ConrodVoxygenFonts,
) -> Self {
Self {
voxygen_i18n,
client,
new_messages,
fonts,
common: widget::CommonBuilder::default(),
}
}
}
pub struct State {
ids: Ids,
errors: VecDeque<String>,
infos: VecDeque<String>,
messages: VecDeque<String>,
last_error_update: Instant,
last_info_update: Instant,
last_message_update: Instant,
last_region_name: Option<String>,
}
impl<'a> Widget for Popup<'a> {
type Event = ();
type State = State;
type Style = ();
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
errors: VecDeque::new(),
infos: VecDeque::new(),
messages: VecDeque::new(),
last_error_update: Instant::now(),
last_info_update: Instant::now(),
last_message_update: Instant::now(),
last_region_name: None,
}
}
fn style(&self) -> Self::Style { () }
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args;
const FADE_IN: f32 = 0.5;
const FADE_HOLD: f32 = 1.0;
const FADE_OUT: f32 = 3.0;
let bg_color = |fade| Color::Rgba(0.0, 0.0, 0.0, fade);
let error_color = |fade| Color::Rgba(1.0, 0.0, 0.0, fade);
let info_color = |fade| Color::Rgba(1.0, 1.0, 0.0, fade);
let message_color = |fade| Color::Rgba(1.0, 1.0, 1.0, fade);
// Push chunk name to message queue
if let Some(chunk) = self.client.current_chunk() {
let current = chunk.meta().name();
// Check if no other popup is displayed and a new one is needed
if state.messages.is_empty()
&& state
.last_region_name
.as_ref()
.map(|l| l != current)
.unwrap_or(true)
{
// Update last_region
state.update(|s| {
if s.messages.is_empty() {
s.last_message_update = Instant::now();
}
s.last_region_name = Some(current.to_owned());
s.messages.push_back(current.to_owned());
});
}
}
// Push waypoint to message queue
for notification in self.new_messages {
match notification {
ClientEvent::Notification(Notification::WaypointSaved) => {
state.update(|s| {
if s.infos.is_empty() {
s.last_info_update = Instant::now();
}
let text = self.voxygen_i18n.get("hud.waypoint_saved");
s.infos.push_back(text.to_string());
});
},
_ => {},
}
}
// Get next error from queue
if !state.errors.is_empty()
&& state.last_error_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
{
state.update(|s| {
s.errors.pop_front();
s.last_error_update = Instant::now();
});
}
// Display error as popup
if let Some(error) = state.errors.front() {
let seconds = state.last_error_update.elapsed().as_secs_f32();
let fade = if seconds < FADE_IN {
seconds / FADE_IN
} else if seconds < FADE_IN + FADE_HOLD {
1.0
} else {
(1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
};
Text::new(error)
.mid_top_with_margin_on(ui.window, 50.0)
.font_size(self.fonts.cyri.scale(20))
.font_id(self.fonts.cyri.conrod_id)
.color(bg_color(fade))
.set(state.ids.error_bg, ui);
Text::new(error)
.top_left_with_margins_on(state.ids.error_bg, -1.0, -1.0)
.font_size(self.fonts.cyri.scale(20))
.font_id(self.fonts.cyri.conrod_id)
.color(error_color(fade))
.set(state.ids.error_text, ui);
}
// Get next info from queue
if !state.infos.is_empty()
&& state.last_info_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
{
state.update(|s| {
s.infos.pop_front();
s.last_info_update = Instant::now();
});
}
// Display info as popup
if let Some(info) = state.infos.front() {
let seconds = state.last_info_update.elapsed().as_secs_f32();
let fade = if seconds < FADE_IN {
seconds / FADE_IN
} else if seconds < FADE_IN + FADE_HOLD {
1.0
} else {
(1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
};
Text::new(info)
.mid_top_with_margin_on(ui.window, 100.0)
.font_size(self.fonts.cyri.scale(20))
.font_id(self.fonts.cyri.conrod_id)
.color(bg_color(fade))
.set(state.ids.info_bg, ui);
Text::new(info)
.top_left_with_margins_on(state.ids.info_bg, -1.0, -1.0)
.font_size(self.fonts.cyri.scale(20))
.font_id(self.fonts.cyri.conrod_id)
.color(info_color(fade))
.set(state.ids.info_text, ui);
}
// Get next message from queue
if !state.messages.is_empty()
&& state.last_message_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
{
state.update(|s| {
s.messages.pop_front();
s.last_message_update = Instant::now();
});
}
// Display message as popup
if let Some(message) = state.messages.front() {
let seconds = state.last_message_update.elapsed().as_secs_f32();
let fade = if seconds < FADE_IN {
seconds / FADE_IN
} else if seconds < FADE_IN + FADE_HOLD {
1.0
} else {
(1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
};
Text::new(message)
.mid_top_with_margin_on(ui.window, 200.0)
.font_size(self.fonts.alkhemi.scale(70))
.font_id(self.fonts.alkhemi.conrod_id)
.color(bg_color(fade))
.set(state.ids.message_bg, ui);
Text::new(message)
.top_left_with_margins_on(state.ids.message_bg, -2.5, -2.5)
.font_size(self.fonts.alkhemi.scale(70))
.font_id(self.fonts.alkhemi.conrod_id)
.color(message_color(fade))
.set(state.ids.message_text, ui);
}
}
}

View File

@ -15,7 +15,7 @@ use common::{
clock::Clock, clock::Clock,
comp, comp,
comp::{Pos, Vel, MAX_PICKUP_RANGE_SQR}, comp::{Pos, Vel, MAX_PICKUP_RANGE_SQR},
msg::ClientState, msg::{ClientState, Notification},
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
util::Dir, util::Dir,
vol::ReadVol, vol::ReadVol,
@ -102,6 +102,10 @@ impl SessionState {
message, message,
}); });
}, },
client::Event::Notification(Notification::WaypointSaved) => {
self.hud
.new_message(client::Event::Notification(Notification::WaypointSaved));
},
} }
} }