diff --git a/.gitignore b/.gitignore index c3f02362cc..782816ee6f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ assets/voxygen UI3.rar assets.rar +*.rar +assets/voxygen diff --git a/common/src/net/error.rs b/common/src/net/error.rs new file mode 100644 index 0000000000..c3dd162fc9 --- /dev/null +++ b/common/src/net/error.rs @@ -0,0 +1,74 @@ +#[derive(Debug)] +pub enum PostError { + InvalidMessage, + InternalError, + Disconnected, +} + +#[derive(Debug)] +pub enum PostErrorInternal { + Io(std::io::Error), + Serde(bincode::Error), + ChannelRecv(std::sync::mpsc::TryRecvError), + ChannelSend, // Empty because I couldn't figure out how to handle generic type in mpsc::TrySendError properly + MsgSizeLimitExceeded, + MioError, +} + +impl<'a, T: Into<&'a PostErrorInternal>> From for PostError { + fn from(err: T) -> Self { + match err.into() { + // TODO: Are I/O errors always disconnect errors? + PostErrorInternal::Io(_) => PostError::Disconnected, + PostErrorInternal::Serde(_) => PostError::InvalidMessage, + PostErrorInternal::MsgSizeLimitExceeded => PostError::InvalidMessage, + PostErrorInternal::MioError => PostError::InternalError, + PostErrorInternal::ChannelRecv(_) => PostError::InternalError, + PostErrorInternal::ChannelSend => PostError::InternalError, + } + } +} + +impl From for PostError { + fn from(err: PostErrorInternal) -> Self { + (&err).into() + } +} + +impl From for PostErrorInternal { + fn from(err: std::io::Error) -> Self { + PostErrorInternal::Io(err) + } +} + +impl From for PostErrorInternal { + fn from(err: bincode::Error) -> Self { + PostErrorInternal::Serde(err) + } +} + +impl From for PostErrorInternal { + fn from(err: std::sync::mpsc::TryRecvError) -> Self { + PostErrorInternal::ChannelRecv(err) + } +} + + + +impl From for PostError { + fn from(err: std::io::Error) -> Self { + (&PostErrorInternal::from(err)).into() + } +} + +impl From for PostError { + fn from(err: bincode::Error) -> Self { + (&PostErrorInternal::from(err)).into() + } +} + +impl From for PostError { + fn from(err: std::sync::mpsc::TryRecvError) -> Self { + (&PostErrorInternal::from(err)).into() + } +} diff --git a/common/src/net/postbox.rs b/common/src/net/postbox.rs new file mode 100644 index 0000000000..21d9466637 --- /dev/null +++ b/common/src/net/postbox.rs @@ -0,0 +1,270 @@ +// Standard +use std::{ + collections::VecDeque, + convert::TryFrom, + io::{ + ErrorKind, + Read, + }, + net::SocketAddr, + thread, + time::Duration, + sync::mpsc::TryRecvError, +}; + +// External +use bincode; +use mio::{net::TcpStream, Events, Poll, PollOpt, Ready, Token}; +use mio_extras::channel::{channel, Receiver, Sender}; + +// Crate +use super::{ + data::ControlMsg, + error::{ + PostError, + PostErrorInternal, + }, + PostRecv, + PostSend, +}; + +// Constants +const CTRL_TOKEN: Token = Token(0); // Token for thread control messages +const DATA_TOKEN: Token = Token(1); // Token for thread data exchange +const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread +const MESSAGE_SIZE_CAP: u64 = 1 << 20; // Maximum accepted length of a packet + +/// A high-level wrapper of [`TcpStream`](mio::net::TcpStream). +/// [`PostBox`] takes care of serializing sent packets and deserializing received packets in the background, providing a simple API for sending and receiving objects over network. +pub struct PostBox +where + S: PostSend, + R: PostRecv, +{ + handle: Option>, + ctrl: Sender, + recv: Receiver>, + send: Sender, + poll: Poll, + err: Option, +} + +impl PostBox +where + S: PostSend, + R: PostRecv, +{ + /// Creates a new [`PostBox`] connected to specified address, meant to be used by the client + pub fn to_server>(addr: A) -> Result, PostError> { + let connection = TcpStream::connect(&addr.into())?; + Self::from_tcpstream(connection) + } + + /// Creates a new [`PostBox`] from an existing connection, meant to be used by [`PostOffice`](super::PostOffice) on the server + pub fn from_tcpstream(connection: TcpStream) -> Result, PostError> { + let (ctrl_tx, ctrl_rx) = channel(); // Control messages + let (send_tx, send_rx) = channel(); // main thread -[data to be serialized and sent]> worker thread + let (recv_tx, recv_rx) = channel(); // main thread <[received and deserialized data]- worker thread + let thread_poll = Poll::new().unwrap(); + let postbox_poll = Poll::new().unwrap(); + thread_poll + .register(&connection, CONN_TOKEN, Ready::readable(), PollOpt::edge()) + .unwrap(); + thread_poll + .register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge()) + .unwrap(); + thread_poll + .register(&send_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge()) + .unwrap(); + postbox_poll + .register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge()) + .unwrap(); + let handle = thread::Builder::new() + .name("postbox_worker".into()) + .spawn(move || postbox_thread(connection, ctrl_rx, send_rx, recv_tx, thread_poll))?; + Ok(PostBox { + handle: Some(handle), + ctrl: ctrl_tx, + recv: recv_rx, + send: send_tx, + poll: postbox_poll, + err: None, + }) + } + + /// Return an `Option` indicating the current status of the `PostBox`. + pub fn status(&self) -> Option { + self.err.as_ref().map(|err| err.into()) + } + + /// Non-blocking sender method + pub fn send(&mut self, data: S) -> Result<(), PostError> { + match &mut self.err { + err @ None => if let Err(_) = self.send.send(data) { + *err = Some(PostErrorInternal::MioError); + Err(err.as_ref().unwrap().into()) + } else { + Ok(()) + }, + err => Err(err.as_ref().unwrap().into()), + } + } + + /// Non-blocking receiver method returning an iterator over already received and deserialized objects + /// # Errors + /// If the other side disconnects PostBox won't realize that until you try to send something + pub fn new_messages(&mut self) -> impl ExactSizeIterator { + let mut events = Events::with_capacity(4096); + let mut items = VecDeque::new(); + + // If an error occured, or previously occured, just give up + if let Some(_) = self.err { + return items.into_iter(); + } else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) { + self.err = Some(err.into()); + return items.into_iter(); + } + + for event in events { + match event.token() { + DATA_TOKEN => loop { + match self.recv.try_recv() { + Ok(Ok(item)) => items.push_back(item), + Err(TryRecvError::Empty) => break, + Err(err) => self.err = Some(err.into()), + Ok(Err(err)) => self.err = Some(err.into()), + } + }, + _ => (), + } + } + + items.into_iter() + } +} + +fn postbox_thread( + mut connection: TcpStream, + ctrl_rx: Receiver, + send_rx: Receiver, + recv_tx: Sender>, + poll: Poll, +) where + S: PostSend, + R: PostRecv, +{ + // Receiving related variables + let mut events = Events::with_capacity(64); + let mut recv_buff = Vec::new(); + let mut recv_nextlen: u64 = 0; + loop { + let mut disconnected = false; + poll.poll(&mut events, Some(Duration::from_millis(20))) + .expect("Failed to execute poll(), most likely fault of the OS"); + println!("FINISHED POLL!"); + for event in events.iter() { + println!("EVENT!"); + match event.token() { + CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { + ControlMsg::Shutdown => return, + }, + CONN_TOKEN => match connection.read_to_end(&mut recv_buff) { + Ok(_) => {} + // Returned when all the data has been read + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => recv_tx.send(Err(e.into())).unwrap(), + }, + DATA_TOKEN => { + let msg = send_rx.try_recv().unwrap(); + println!("Send: {:?}", msg); + let mut packet = bincode::serialize(&msg).unwrap(); + packet.splice(0..0, (packet.len() as u64).to_be_bytes().iter().cloned()); + match connection.write_bufs(&[packet.as_slice().into()]) { + Ok(_) => { println!("Sent!"); } + Err(e) => { + println!("Send error!"); + recv_tx.send(Err(e.into())).unwrap(); + } + }; + } + _ => {} + } + } + loop { + if recv_nextlen == 0 && recv_buff.len() >= 8 { + println!("Read nextlen"); + recv_nextlen = u64::from_be_bytes( + <[u8; 8]>::try_from(recv_buff.drain(0..8).collect::>().as_slice()).unwrap(), + ); + if recv_nextlen > MESSAGE_SIZE_CAP { + recv_tx.send(Err(PostErrorInternal::MsgSizeLimitExceeded)).unwrap(); + connection.shutdown(std::net::Shutdown::Both).unwrap(); + recv_buff.drain(..); + recv_nextlen = 0; + break; + } + } + if recv_buff.len() as u64 >= recv_nextlen && recv_nextlen != 0 { + match bincode::deserialize(recv_buff + .drain( + 0..usize::try_from(recv_nextlen) + .expect("Message size was larger than usize (insane message size and 32 bit OS)"), + ) + .collect::>() + .as_slice()) { + Ok(msg) => { + println!("Recv: {:?}", msg); + recv_tx + .send(Ok(msg)) + .unwrap(); + recv_nextlen = 0; + } + Err(e) => { + println!("Recv error: {:?}", e); + recv_tx.send(Err(e.into())).unwrap(); + recv_nextlen = 0; + } + } + } else { + break; + } + } + match connection.take_error().unwrap() { + Some(e) => { + if e.kind() == ErrorKind::BrokenPipe { + disconnected = true; + } + recv_tx.send(Err(e.into())).unwrap(); + } + None => {} + } + if disconnected == true { + break; + } + } + + // Loop after disconnected + loop { + poll.poll(&mut events, None) + .expect("Failed to execute poll(), most likely fault of the OS"); + for event in events.iter() { + match event.token() { + CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { + ControlMsg::Shutdown => return, + }, + _ => {} + } + } + } +} + +impl Drop for PostBox +where + S: PostSend, + R: PostRecv, +{ + fn drop(&mut self) { + self.ctrl.send(ControlMsg::Shutdown).unwrap_or(()); + self.handle.take().map(|handle| handle.join()); + } +} diff --git a/common/src/net/postoffice.rs b/common/src/net/postoffice.rs new file mode 100644 index 0000000000..e8d70330ad --- /dev/null +++ b/common/src/net/postoffice.rs @@ -0,0 +1,150 @@ +// Standard +use core::time::Duration; +use std::{ + collections::VecDeque, + net::SocketAddr, + thread, + sync::mpsc::TryRecvError, +}; + +// External +use mio::{net::TcpListener, Events, Poll, PollOpt, Ready, Token}; +use mio_extras::channel::{channel, Receiver, Sender}; + +// Crate +use super::{ + data::ControlMsg, + error::{ + PostError, + PostErrorInternal, + }, + postbox::PostBox, + PostRecv, + PostSend, +}; + +// Constants +const CTRL_TOKEN: Token = Token(0); // Token for thread control messages +const DATA_TOKEN: Token = Token(1); // Token for thread data exchange +const CONN_TOKEN: Token = Token(2); // Token for TcpStream for the PostBox child thread + +/// A high-level wrapper of [`TcpListener`](mio::net::TcpListener). +/// [`PostOffice`] listens for incoming connections in the background and wraps them into [`PostBox`]es, providing a simple non-blocking API for receiving them. +pub struct PostOffice +where + S: PostSend, + R: PostRecv, +{ + handle: Option>, + ctrl: Sender, + recv: Receiver, PostErrorInternal>>, + poll: Poll, + err: Option, +} + +impl PostOffice +where + S: PostSend, + R: PostRecv, +{ + /// Creates a new [`PostOffice`] listening on specified address + pub fn new>(addr: A) -> Result { + let listener = TcpListener::bind(&addr.into())?; + let (ctrl_tx, ctrl_rx) = channel(); + let (recv_tx, recv_rx) = channel(); + + let thread_poll = Poll::new()?; + let postbox_poll = Poll::new()?; + thread_poll.register(&listener, CONN_TOKEN, Ready::readable(), PollOpt::edge())?; + thread_poll.register(&ctrl_rx, CTRL_TOKEN, Ready::readable(), PollOpt::edge())?; + postbox_poll.register(&recv_rx, DATA_TOKEN, Ready::readable(), PollOpt::edge())?; + + let handle = thread::Builder::new() + .name("postoffice_worker".into()) + .spawn(move || postoffice_thread(listener, ctrl_rx, recv_tx, thread_poll))?; + + Ok(PostOffice { + handle: Some(handle), + ctrl: ctrl_tx, + recv: recv_rx, + poll: postbox_poll, + err: None, + }) + } + + /// Return an `Option` indicating the current status of the `PostOffice`. + pub fn status(&self) -> Option { + self.err.as_ref().map(|err| err.into()) + } + + /// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es + pub fn new_connections( + &mut self, + ) -> impl ExactSizeIterator> { + let mut events = Events::with_capacity(256); + let mut conns = VecDeque::new(); + + // If an error occured, or previously occured, just give up + if let Some(_) = self.err { + return conns.into_iter(); + } else if let Err(err) = self.poll.poll(&mut events, Some(Duration::new(0, 0))) { + self.err = Some(err.into()); + return conns.into_iter(); + } + + for event in events { + match event.token() { + DATA_TOKEN => loop { + match self.recv.try_recv() { + Ok(Ok(conn)) => conns.push_back(conn), + Err(TryRecvError::Empty) => break, + Err(err) => self.err = Some(err.into()), + Ok(Err(err)) => self.err = Some(err.into()), + } + }, + _ => (), + } + } + conns.into_iter() + } +} + +fn postoffice_thread( + listener: TcpListener, + ctrl_rx: Receiver, + recv_tx: Sender, PostErrorInternal>>, + poll: Poll, +) where + S: PostSend, + R: PostRecv, +{ + let mut events = Events::with_capacity(256); + loop { + poll.poll(&mut events, None).expect("Failed to execute recv_poll.poll() in PostOffce receiver thread, most likely fault of the OS."); + for event in events.iter() { + match event.token() { + CTRL_TOKEN => match ctrl_rx.try_recv().unwrap() { + ControlMsg::Shutdown => return, + }, + CONN_TOKEN => { + let (conn, _addr) = listener.accept().unwrap(); + recv_tx.send(PostBox::from_tcpstream(conn) + // TODO: Is it okay to count a failure to create a postbox here as an 'internal error'? + .map_err(|_| PostErrorInternal::MioError)).unwrap(); + } + _ => (), + } + } + } +} + +impl Drop for PostOffice +where + S: PostSend, + R: PostRecv, +{ + fn drop(&mut self) { + self.ctrl.send(ControlMsg::Shutdown).unwrap_or(()); // If this fails the thread is dead already + self.handle.take().map(|handle| handle.join()); + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 676bfc89d8..f4553a96d6 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -6,11 +6,11 @@ use crate::{ window::{Event as WinEvent, Key, Window}, }; use conrod_core::{ - color, + color, image::Id as ImgId, text::font::Id as FontId, widget::{Button, Image, Rectangle, Scrollbar, Text}, - widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, + widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, }; widget_ids! { @@ -62,6 +62,9 @@ widget_ids! { sb_grid_r, sb_grid_bg_l, sb_grid_bg_r, + xp_bar_progress, + health_bar_color, + mana_bar_color, // Level Display level_text, next_level_text, @@ -108,6 +111,7 @@ widget_ids! { spellbook_title, //4 Charwindow charwindow_frame, + charwindow, charwindow_bg, charwindow_icon, charwindow_close, @@ -118,9 +122,12 @@ widget_ids! { charwindow_tab1_level, charwindow_tab1_exp, charwindow_tab1_stats, + charwindow_tab1_statnames, charwindow_tab1_stats_numbers, charwindow_tab1_expbar, - charwindow_tab1_expbar_progress, + charwindow_rectangle, + charwindow_exp_rectangle, + charwindow_exp_progress_rectangle, //5 Quest-Log questlog_frame, questlog_bg, @@ -199,6 +206,7 @@ pub(self) struct Imgs { button_blank: ImgId, button_blue_mo: ImgId, button_blue_press: ImgId, + window_bg: ImgId, // Social-Window social_bg: ImgId, social_icon: ImgId, @@ -210,12 +218,12 @@ pub(self) struct Imgs { spellbook_bg: ImgId, spellbook_icon: ImgId, // Char Window - charwindow_bg: ImgId, + charwindow: ImgId, charwindow_icon: ImgId, charwindow_tab_bg: ImgId, charwindow_tab: ImgId, charwindow_expbar: ImgId, - progress_bar: ImgId, + progress_frame: ImgId, progress: ImgId, // Quest-Log Window @@ -312,6 +320,9 @@ impl Imgs { button_blue_mo: load("element/buttons/blue_mo.png"), button_blue_press: load("element/buttons/blue_press.png"), + // Window BG + window_bg: load("element/misc_backgrounds/window_bg.png"), + //Social Window social_bg: load("element/misc_backgrounds/small_bg.png"), social_icon: load("element/icons/social.png"), @@ -324,18 +335,16 @@ impl Imgs { // Spell Book Window spellbook_bg: load("element/misc_backgrounds/small_bg.png"), spellbook_icon: load("element/icons/spellbook.png"), - //Char Window - charwindow_bg: load("element/misc_backgrounds/char_bg.png"), + charwindow: load("element/misc_backgrounds/charwindow.png"), charwindow_icon: load("element/icons/charwindow.png"), charwindow_tab_bg: load("element/frames/tab.png"), charwindow_tab: load("element/buttons/tab.png"), charwindow_expbar: load("element/misc_backgrounds/small_bg.png"), - progress_bar: load("element/frames/progress_bar.png"), + progress_frame: load("element/frames/progress_bar.png"), progress: load("element/misc_backgrounds/progress.png"), - //Quest-Log Window questlog_bg: load("element/misc_backgrounds/small_bg.png"), questlog_icon: load("element/icons/questlog.png"), @@ -400,6 +409,9 @@ pub struct Hud { map_open: bool, show_ui: bool, inventory_space: u32, + xp_percentage: f64, + hp_percentage: f64, + mana_percentage: f64, inventorytest_button: bool, settings_tab: SettingsTab, } @@ -444,10 +456,13 @@ impl Hud { map_open: false, show_ui: true, inventorytest_button: false, - inventory_space: 0, - open_windows: Windows::None, + inventory_space: 0, + open_windows: Windows::None, font_metamorph, font_opensans, + xp_percentage: 0.8, + hp_percentage: 0.8, + mana_percentage: 0.8, } } @@ -538,7 +553,7 @@ impl Hud { .align_bottom_of(self.ids.mmap_frame) .set(self.ids.mmap_icons, ui_widgets); // Title - // TODO Make it display the actual Location + // Make it display the actual location Text::new("Uncanny Valley") .mid_top_with_margin_on(self.ids.mmap_frame, 5.0) .font_size(14) @@ -599,7 +614,7 @@ impl Hud { Some(Small::Social) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Social)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } @@ -619,7 +634,7 @@ impl Hud { Some(Small::Spellbook) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Spellbook)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } //4 Char-Window @@ -638,7 +653,7 @@ impl Hud { }, Windows::Small(small) => Windows::CharacterAnd(Some(small)), Windows::None => Windows::CharacterAnd(None), - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, } } //5 Quest-Log @@ -657,7 +672,7 @@ impl Hud { Some(Small::Questlog) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Questlog)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } } @@ -670,6 +685,10 @@ impl Hud { .mid_bottom_of(ui_widgets.window) .set(self.ids.xp_bar, ui_widgets); + Rectangle::fill_with([406.0 * (self.xp_percentage), 5.0], color::rgb(0.31, 0.14, 0.4)) // "W=406*[Exp. %]" + .top_left_with_margins_on(self.ids.xp_bar, 5.0, 21.0) + .set(self.ids.xp_bar_progress, ui_widgets); + // Left Grid Image::new(self.imgs.sb_grid) .w_h(2240.0 / 12.0, 448.0 / 12.0) @@ -707,19 +726,32 @@ impl Hud { .align_bottom_of(self.ids.sb_grid_bg_r) .set(self.ids.r_click, ui_widgets); - // Health and mana bars + // Health Bar Image::new(self.imgs.health_bar) .w_h(1120.0 / 6.0, 96.0 / 6.0) .left_from(self.ids.l_click, 0.0) .align_top_of(self.ids.l_click) .set(self.ids.health_bar, ui_widgets); + // Filling + Rectangle::fill_with([182.0 * (self.hp_percentage), 6.0], color::rgb(0.09, 0.36, 0.0)) // "W=182.0 * [Health. %]" + .top_right_with_margins_on(self.ids.health_bar, 5.0, 0.0) + .set(self.ids.health_bar_color, ui_widgets); + + + // Mana Bar Image::new(self.imgs.mana_bar) .w_h(1120.0 / 6.0, 96.0 / 6.0) .right_from(self.ids.r_click, 0.0) .align_top_of(self.ids.r_click) .set(self.ids.mana_bar, ui_widgets); + // Filling + Rectangle::fill_with([182.0 * (self.hp_percentage), 6.0], color::rgb(0.15, 0.14, 0.39)) // "W=182.0 * [Mana. %]" + .top_left_with_margins_on(self.ids.mana_bar, 5.0, 0.0) + .set(self.ids.mana_bar_color, ui_widgets); + + // Buffs/Debuffs // Buffs @@ -730,14 +762,14 @@ impl Hud { // Insert actual Level here Text::new("1") - .left_from(self.ids.xp_bar, -20.0) + .left_from(self.ids.xp_bar, -15.0) .font_size(14) .rgba(220.0, 220.0, 220.0, 0.8) .set(self.ids.level_text, ui_widgets); // Insert next Level here Text::new("2") - .right_from(self.ids.xp_bar, -20.0) + .right_from(self.ids.xp_bar, -15.0) .font_size(14) .rgba(220.0, 220.0, 220.0, 0.8) .set(self.ids.next_level_text, ui_widgets); @@ -777,20 +809,20 @@ impl Hud { { self.bag_open = false; } - + if self.inventory_space > 0 { - // First Slot - Button::image(self.imgs.inv_slot) - .top_left_with_margins_on(self.ids.inv_grid, 5.0, 5.0) - .w_h(40.0, 40.0) - .set(self.ids.inv_slot_0, ui_widgets); + // First Slot + Button::image(self.imgs.inv_slot) + .top_left_with_margins_on(self.ids.inv_grid, 5.0, 5.0) + .w_h(40.0, 40.0) + .set(self.ids.inv_slot_0, ui_widgets); } // if self.ids.inv_slot.len() < self.inventory_space { // self.ids.inv_slot.resize(self.inventory_space, &mut ui_widgets.widget_id_generator()); //} //let num = self.ids.inv_slot.len(); - //println!("self.ids.inv_slot.len(): {:?}", num); + //println!("self.ids.inv_slot.len(): {:?}", num); //if num > 0 { //Button::image(self.imgs.inv_slot) //.top_left_with_margins_on(self.ids.inv_grid, 5.0, 5.0) @@ -799,11 +831,11 @@ impl Hud { //} //for i in 1..5 { //Button::image(self.imgs.inv_slot) - //.right(10.0) - //.label(&format!("{}", i + 1)) - //.label_rgba(220.0, 220.0, 220.0, 0.8) - //.label_font_size(5) - //.set(self.ids.inv_slot[i], ui_widgets);} + //.right(10.0) + //.label(&format!("{}", i + 1)) + //.label_rgba(220.0, 220.0, 220.0, 0.8) + //.label_font_size(5) + //.set(self.ids.inv_slot[i], ui_widgets);} } } // Bag @@ -1038,7 +1070,7 @@ impl Hud { self.open_windows = match self.open_windows { Windows::Small(_) => Windows::None, Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => unreachable!(), + _ => Windows::Settings, } } // Title @@ -1085,7 +1117,7 @@ impl Hud { self.open_windows = match self.open_windows { Windows::Small(_) => Windows::None, Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => unreachable!(), + _ => Windows::Settings, } } // Title @@ -1132,7 +1164,7 @@ impl Hud { self.open_windows = match self.open_windows { Windows::Small(_) => Windows::None, Windows::CharacterAnd(_) => Windows::CharacterAnd(None), - _ => unreachable!(), + _ => Windows::Settings, } } // Title @@ -1144,27 +1176,32 @@ impl Hud { } } - //4 Char-Window + // 4 Char-Window if let Windows::CharacterAnd(small) = self.open_windows { - //Frame + // Frame Image::new(self.imgs.window_frame) .top_left_with_margins_on(ui_widgets.window, 200.0, 215.0) .w_h(1648.0 / 4.0, 1952.0 / 4.0) .set(self.ids.charwindow_frame, ui_widgets); - //BG - Image::new(self.imgs.charwindow_bg) + // BG + Image::new(self.imgs.window_bg) .w_h(348.0, 404.0) .mid_top_with_margin_on(self.ids.charwindow_frame, 48.0) .set(self.ids.charwindow_bg, ui_widgets); + // Overlay + Image::new(self.imgs.charwindow) + .middle_of(self.ids.charwindow_bg) + .set(self.ids.charwindow, ui_widgets); + //Icon //Image::new(self.imgs.charwindow_icon) - //.w_h(224.0 / 3.0, 224.0 / 3.0) - //.top_left_with_margins_on(self.ids.charwindow_frame, -10.0, -10.0) - //.set(self.ids.charwindow_icon, ui_widgets); + //.w_h(224.0 / 3.0, 224.0 / 3.0) + //.top_left_with_margins_on(self.ids.charwindow_frame, -10.0, -10.0) + //.set(self.ids.charwindow_icon, ui_widgets); - //X-Button + // X-Button if Button::image(self.imgs.close_button) .w_h(244.0 * 0.22 / 4.0, 244.0 * 0.22 / 4.0) .hover_image(self.imgs.close_button_hover) @@ -1178,6 +1215,7 @@ impl Hud { None => Windows::None, } } + // Title Text::new("Character Name") //Add in actual Character Name .mid_top_with_margin_on(self.ids.charwindow_frame, 7.0) @@ -1188,6 +1226,10 @@ impl Hud { .w_h(205.0, 412.0) .mid_left_with_margin_on(self.ids.charwindow_frame, -205.0) .set(self.ids.charwindow_tab_bg, ui_widgets); + // Tab Rectangle + Rectangle::fill_with([192.0, 371.0], color::rgba(0.0, 0.0, 0.0, 0.8)) + .top_right_with_margins_on(self.ids.charwindow_tab_bg, 20.0, 0.0) + .set(self.ids.charwindow_rectangle, ui_widgets); // Tab Button Button::image(self.imgs.charwindow_tab) .w_h(65.0, 23.0) @@ -1197,25 +1239,63 @@ impl Hud { .label_font_id(self.font_opensans) .label_font_size(14) .set(self.ids.charwindow_tab1, ui_widgets); - Text::new("1") //Add in actual Character Level later - .mid_top_with_margin_on(self.ids.charwindow_tab_bg, 14.0) + Text::new("1") //Add in actual Character Level + .mid_top_with_margin_on(self.ids.charwindow_rectangle, 10.0) .font_id(self.font_opensans) .font_size(30) .rgba(220.0, 220.0, 220.0, 0.8) .set(self.ids.charwindow_tab1_level, ui_widgets); - // Stats - Text::new("Stat 1") - .top_left_with_margins_on(self.ids.charwindow_tab_bg, 40.0, 20.0) + // Exp-Bar Background + Rectangle::fill_with([170.0, 10.0], color::BLACK) + .mid_top_with_margin_on(self.ids.charwindow_rectangle, 50.0) + .set(self.ids.charwindow_exp_rectangle, ui_widgets); + // Exp-Bar Progress + Rectangle::fill_with([170.0 * (self.xp_percentage), 6.0], color::rgb(0.31, 0.14, 0.40)) // 0.8 = Experience percantage + .mid_left_with_margin_on(self.ids.charwindow_tab1_expbar, 1.0) + .set(self.ids.charwindow_exp_progress_rectangle, ui_widgets); + // Exp-Bar Foreground Frame + Image::new(self.imgs.progress_frame) + .w_h(170.0, 10.0) + .middle_of(self.ids.charwindow_exp_rectangle) + .set(self.ids.charwindow_tab1_expbar, ui_widgets); + // Exp-Text + Text::new("120/170") // Shows the Exp / Exp to reach the next level + .mid_top_with_margin_on(self.ids.charwindow_tab1_expbar, 10.0) .font_id(self.font_opensans) - .font_size(20) + .font_size(15) .rgba(220.0, 220.0, 220.0, 0.8) - .set(self.ids.charwindow_tab1_stats, ui_widgets); - - - - + .set(self.ids.charwindow_tab1_exp, ui_widgets); + // Stats + Text::new( + "Stamina\n\ + \n\ + Strength\n\ + \n\ + Dexterity\n\ + \n\ + Intelligence", + ) + .top_left_with_margins_on(self.ids.charwindow_rectangle, 100.0, 20.0) + .font_id(self.font_opensans) + .font_size(16) + .rgba(220.0, 220.0, 220.0, 0.8) + .set(self.ids.charwindow_tab1_statnames, ui_widgets); + Text::new( + "1234\n\ + \n\ + 12312\n\ + \n\ + 12414\n\ + \n\ + 124124", + ) + .right_from(self.ids.charwindow_tab1_statnames, 10.0) + .font_id(self.font_opensans) + .font_size(16) + .rgba(220.0, 220.0, 220.0, 0.8) + .set(self.ids.charwindow_tab1_stats, ui_widgets); } //2 Map @@ -1372,7 +1452,7 @@ impl Hud { Some(Small::Questlog) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Questlog)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } pub fn toggle_map(&mut self) { @@ -1387,7 +1467,7 @@ impl Hud { }, Windows::Small(small) => Windows::CharacterAnd(Some(small)), Windows::None => Windows::CharacterAnd(None), - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, } } pub fn toggle_social(&mut self) { @@ -1398,7 +1478,7 @@ impl Hud { Some(Small::Social) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Social)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } pub fn toggle_spellbook(&mut self) { @@ -1409,7 +1489,7 @@ impl Hud { Some(Small::Spellbook) => Windows::CharacterAnd(None), _ => Windows::CharacterAnd(Some(Small::Spellbook)), }, - Windows::Settings => unreachable!(), + Windows::Settings => Windows::Settings, }; } pub fn toggle_settings(&mut self) { diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 50b95b9b6f..ac74a26003 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -460,8 +460,7 @@ impl CharSelectionUi { .set(self.ids.test_char_l_button, ui_widgets) .was_clicked() { - self.selected_char_no = Some(1); - self.creation_state = CreationState::Race; + self.selected_char_no = Some(1); } // Veloren Logo and Alpha Version @@ -475,7 +474,7 @@ impl CharSelectionUi { .label_x(conrod_core::position::Relative::Scalar(-100.0)) .set(self.ids.v_logo, ui_widgets); - if let Some(no) = self.selected_char_no { + if let Some(no) = self.selected_char_no { // Selection_Window Image::new(self.imgs.selection_window) .w_h(522.0, 722.0)