mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'capucho/add-pause-single-player-when-in-pause-menu' into 'master'
Pause when in singleplayer and pause menu Closes #36 See merge request veloren/veloren!833
This commit is contained in:
commit
30f84c0115
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added sfx for collecting, dropping and using inventory items
|
||||
- New attack animation
|
||||
- weapon control system
|
||||
- Game pauses when in singleplayer and pause menu
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -40,11 +40,11 @@ use vek::*;
|
||||
// The duration of network inactivity until the player is kicked
|
||||
// @TODO: in the future, this should be configurable on the server
|
||||
// and be provided to the client
|
||||
const SERVER_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
const SERVER_TIMEOUT: f64 = 20.0;
|
||||
|
||||
// After this duration has elapsed, the user will begin getting kick warnings in
|
||||
// their chat window
|
||||
const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14);
|
||||
const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
|
||||
|
||||
pub enum Event {
|
||||
Chat {
|
||||
@ -64,8 +64,8 @@ pub struct Client {
|
||||
|
||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
|
||||
last_server_ping: Instant,
|
||||
last_server_pong: Instant,
|
||||
last_server_ping: f64,
|
||||
last_server_pong: f64,
|
||||
last_ping_delta: f64,
|
||||
|
||||
tick: u64,
|
||||
@ -152,8 +152,8 @@ impl Client {
|
||||
|
||||
postbox,
|
||||
|
||||
last_server_ping: Instant::now(),
|
||||
last_server_pong: Instant::now(),
|
||||
last_server_ping: 0.0,
|
||||
last_server_pong: 0.0,
|
||||
last_ping_delta: 0.0,
|
||||
|
||||
tick: 0,
|
||||
@ -481,9 +481,9 @@ impl Client {
|
||||
}
|
||||
|
||||
// Send a ping to the server once every second
|
||||
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
|
||||
if self.state.get_time() - self.last_server_ping > 1. {
|
||||
self.postbox.send_message(ClientMsg::Ping);
|
||||
self.last_server_ping = Instant::now();
|
||||
self.last_server_ping = self.state.get_time();
|
||||
}
|
||||
|
||||
// 6) Update the server about the player's physics attributes.
|
||||
@ -528,16 +528,14 @@ impl Client {
|
||||
// Check that we have an valid connection.
|
||||
// Use the last ping time as a 1s rate limiter, we only notify the user once per
|
||||
// second
|
||||
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
|
||||
let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong);
|
||||
if self.state.get_time() - self.last_server_ping > 1. {
|
||||
let duration_since_last_pong = self.state.get_time() - self.last_server_pong;
|
||||
|
||||
// Dispatch a notification to the HUD warning they will be kicked in {n} seconds
|
||||
if duration_since_last_pong.as_secs() >= SERVER_TIMEOUT_GRACE_PERIOD.as_secs() {
|
||||
if let Some(seconds_until_kick) =
|
||||
SERVER_TIMEOUT.checked_sub(duration_since_last_pong)
|
||||
{
|
||||
if duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD {
|
||||
if self.state.get_time() - duration_since_last_pong > 0. {
|
||||
frontend_events.push(Event::DisconnectionNotification(
|
||||
seconds_until_kick.as_secs(),
|
||||
(self.state.get_time() - duration_since_last_pong).round() as u64,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -591,11 +589,10 @@ impl Client {
|
||||
|
||||
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
|
||||
ServerMsg::Pong => {
|
||||
self.last_server_pong = Instant::now();
|
||||
self.last_server_pong = self.state.get_time();
|
||||
|
||||
self.last_ping_delta = Instant::now()
|
||||
.duration_since(self.last_server_ping)
|
||||
.as_secs_f64();
|
||||
self.last_ping_delta =
|
||||
(self.state.get_time() - self.last_server_ping).round();
|
||||
},
|
||||
ServerMsg::ChatMsg { message, chat_type } => {
|
||||
frontend_events.push(Event::Chat { message, chat_type })
|
||||
@ -712,7 +709,7 @@ impl Client {
|
||||
} else if let Some(err) = self.postbox.error() {
|
||||
return Err(err.into());
|
||||
// We regularily ping in the tick method
|
||||
} else if Instant::now().duration_since(self.last_server_pong) > SERVER_TIMEOUT {
|
||||
} else if self.state.get_time() - self.last_server_pong > SERVER_TIMEOUT {
|
||||
return Err(Error::ServerTimeout);
|
||||
}
|
||||
Ok(frontend_events)
|
||||
|
@ -578,6 +578,8 @@ impl Server {
|
||||
.get(entity)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn number_of_players(&self) -> i64 { self.metrics.player_online.get() }
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
|
@ -357,7 +357,7 @@ impl Show {
|
||||
|
||||
fn toggle_ui(&mut self) { self.ui = !self.ui; }
|
||||
|
||||
fn toggle_windows(&mut self) {
|
||||
fn toggle_windows(&mut self, global_state: &mut GlobalState) {
|
||||
if self.bag
|
||||
|| self.esc_menu
|
||||
|| self.map
|
||||
@ -379,9 +379,19 @@ impl Show {
|
||||
self.character_window = false;
|
||||
self.open_windows = Windows::None;
|
||||
self.want_grab = true;
|
||||
|
||||
// Unpause the game if we are on singleplayer
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(false);
|
||||
};
|
||||
} else {
|
||||
self.esc_menu = true;
|
||||
self.want_grab = false;
|
||||
|
||||
// Pause the game if we are on singleplayer
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(true);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1725,7 +1735,14 @@ impl Hud {
|
||||
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::Close => {
|
||||
// Unpause the game if we are on singleplayer so that we can logout
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(false);
|
||||
};
|
||||
|
||||
self.show.settings(false)
|
||||
},
|
||||
settings_window::Event::AdjustMousePan(sensitivity) => {
|
||||
events.push(Event::AdjustMousePan(sensitivity));
|
||||
},
|
||||
@ -1917,12 +1934,29 @@ impl Hud {
|
||||
self.show.esc_menu = false;
|
||||
self.show.want_grab = false;
|
||||
self.force_ungrab = true;
|
||||
|
||||
// Unpause the game if we are on singleplayer
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(false);
|
||||
};
|
||||
},
|
||||
Some(esc_menu::Event::Logout) => {
|
||||
// Unpause the game if we are on singleplayer so that we can logout
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(false);
|
||||
};
|
||||
|
||||
events.push(Event::Logout);
|
||||
},
|
||||
Some(esc_menu::Event::Quit) => events.push(Event::Quit),
|
||||
Some(esc_menu::Event::CharacterSelection) => events.push(Event::CharacterSelection),
|
||||
Some(esc_menu::Event::CharacterSelection) => {
|
||||
// Unpause the game if we are on singleplayer so that we can logout
|
||||
if let Some(singleplayer) = global_state.singleplayer.as_ref() {
|
||||
singleplayer.pause(false);
|
||||
};
|
||||
|
||||
events.push(Event::CharacterSelection)
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
@ -1992,7 +2026,7 @@ impl Hud {
|
||||
self.ui.focus_widget(None);
|
||||
} else {
|
||||
// Close windows on esc
|
||||
self.show.toggle_windows();
|
||||
self.show.toggle_windows(global_state);
|
||||
}
|
||||
true
|
||||
},
|
||||
|
@ -32,6 +32,7 @@ use crate::{
|
||||
menu::main::MainMenuState,
|
||||
meta::Meta,
|
||||
settings::Settings,
|
||||
singleplayer::Singleplayer,
|
||||
window::Window,
|
||||
};
|
||||
use common::assets::{load, load_expect};
|
||||
@ -45,6 +46,7 @@ pub struct GlobalState {
|
||||
window: Window,
|
||||
audio: AudioFrontend,
|
||||
info_message: Option<String>,
|
||||
singleplayer: Option<Singleplayer>,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
@ -135,6 +137,7 @@ fn main() {
|
||||
settings,
|
||||
meta,
|
||||
info_message: None,
|
||||
singleplayer: None,
|
||||
};
|
||||
|
||||
// Try to load the localization and log missing entries
|
||||
|
@ -18,7 +18,6 @@ use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||
|
||||
pub struct MainMenuState {
|
||||
main_menu_ui: MainMenuUi,
|
||||
singleplayer: Option<Singleplayer>,
|
||||
}
|
||||
|
||||
impl MainMenuState {
|
||||
@ -26,7 +25,6 @@ impl MainMenuState {
|
||||
pub fn new(global_state: &mut GlobalState) -> Self {
|
||||
Self {
|
||||
main_menu_ui: MainMenuUi::new(global_state),
|
||||
singleplayer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,7 +45,7 @@ impl PlayState for MainMenuState {
|
||||
}
|
||||
|
||||
// Reset singleplayer server if it was running already
|
||||
self.singleplayer = None;
|
||||
global_state.singleplayer = None;
|
||||
|
||||
loop {
|
||||
// Handle window events.
|
||||
@ -119,7 +117,7 @@ impl PlayState for MainMenuState {
|
||||
// client_init contains Some(ClientInit), which spawns a thread which
|
||||
// contains a TcpStream::connect() call This call is
|
||||
// blocking TODO fix when the network rework happens
|
||||
self.singleplayer = None;
|
||||
global_state.singleplayer = None;
|
||||
client_init = None;
|
||||
self.main_menu_ui.cancel_connection();
|
||||
},
|
||||
@ -127,7 +125,7 @@ impl PlayState for MainMenuState {
|
||||
MainMenuEvent::StartSingleplayer => {
|
||||
let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
|
||||
|
||||
self.singleplayer = Some(singleplayer);
|
||||
global_state.singleplayer = Some(singleplayer);
|
||||
|
||||
attempt_login(
|
||||
global_state,
|
||||
|
@ -379,6 +379,10 @@ impl PlayState for SessionState {
|
||||
|
||||
self.inputs.look_dir = cam_dir;
|
||||
|
||||
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
||||
if global_state.singleplayer.is_none()
|
||||
|| !global_state.singleplayer.as_ref().unwrap().is_paused()
|
||||
{
|
||||
// Perform an in-game tick.
|
||||
if let Err(err) = self.tick(clock.get_avg_delta()) {
|
||||
global_state.info_message =
|
||||
@ -387,6 +391,7 @@ impl PlayState for SessionState {
|
||||
|
||||
return PlayStateResult::Pop;
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain global state.
|
||||
global_state.maintain(clock.get_last_delta().as_secs_f32());
|
||||
@ -609,6 +614,10 @@ impl PlayState for SessionState {
|
||||
}
|
||||
}
|
||||
|
||||
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
||||
if global_state.singleplayer.is_none()
|
||||
|| !global_state.singleplayer.as_ref().unwrap().is_paused()
|
||||
{
|
||||
// Maintain the scene.
|
||||
self.scene.maintain(
|
||||
global_state.window.renderer_mut(),
|
||||
@ -616,6 +625,7 @@ impl PlayState for SessionState {
|
||||
&self.client.borrow(),
|
||||
global_state.settings.graphics.gamma,
|
||||
);
|
||||
}
|
||||
|
||||
// Render the session.
|
||||
self.render(global_state.window.renderer_mut());
|
||||
|
@ -4,6 +4,10 @@ use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError};
|
||||
use log::info;
|
||||
use server::{Event, Input, Server, ServerSettings};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
@ -19,6 +23,8 @@ enum Msg {
|
||||
pub struct Singleplayer {
|
||||
_server_thread: JoinHandle<()>,
|
||||
sender: Sender<Msg>,
|
||||
// Wether the server is stopped or not
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Singleplayer {
|
||||
@ -31,6 +37,9 @@ impl Singleplayer {
|
||||
let thread_pool = client.map(|c| c.thread_pool().clone());
|
||||
let settings2 = settings.clone();
|
||||
|
||||
let paused = Arc::new(AtomicBool::new(false));
|
||||
let paused1 = paused.clone();
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
let server = Server::new(settings2).expect("Failed to create server instance!");
|
||||
|
||||
@ -39,17 +48,25 @@ impl Singleplayer {
|
||||
None => server,
|
||||
};
|
||||
|
||||
run_server(server, receiver);
|
||||
run_server(server, receiver, paused1);
|
||||
});
|
||||
|
||||
(
|
||||
Singleplayer {
|
||||
_server_thread: thread,
|
||||
sender,
|
||||
paused,
|
||||
},
|
||||
settings,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns wether or not the server is paused
|
||||
pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) }
|
||||
|
||||
/// Pauses if true is passed and unpauses if false (Does nothing if in that
|
||||
/// state already)
|
||||
pub fn pause(&self, state: bool) { self.paused.store(state, Ordering::SeqCst); }
|
||||
}
|
||||
|
||||
impl Drop for Singleplayer {
|
||||
@ -59,13 +76,34 @@ impl Drop for Singleplayer {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_server(mut server: Server, rec: Receiver<Msg>) {
|
||||
fn run_server(mut server: Server, rec: Receiver<Msg>, paused: Arc<AtomicBool>) {
|
||||
info!("Starting server-cli...");
|
||||
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::start();
|
||||
|
||||
loop {
|
||||
// Check any event such as stopping and pausing
|
||||
match rec.try_recv() {
|
||||
Ok(msg) => match msg {
|
||||
Msg::Stop => break,
|
||||
},
|
||||
Err(err) => match err {
|
||||
TryRecvError::Empty => (),
|
||||
TryRecvError::Disconnected => break,
|
||||
},
|
||||
}
|
||||
|
||||
// Wait for the next tick.
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
|
||||
// Skip updating the server if it's paused
|
||||
if paused.load(Ordering::SeqCst) && server.number_of_players() < 2 {
|
||||
continue;
|
||||
} else if server.number_of_players() > 1 {
|
||||
paused.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let events = server
|
||||
.tick(Input::default(), clock.get_last_delta())
|
||||
.expect("Failed to tick server!");
|
||||
@ -80,16 +118,5 @@ fn run_server(mut server: Server, rec: Receiver<Msg>) {
|
||||
|
||||
// Clean up the server after a tick.
|
||||
server.cleanup();
|
||||
|
||||
match rec.try_recv() {
|
||||
Ok(_msg) => break,
|
||||
Err(err) => match err {
|
||||
TryRecvError::Empty => (),
|
||||
TryRecvError::Disconnected => break,
|
||||
},
|
||||
}
|
||||
|
||||
// Wait for the next tick.
|
||||
clock.tick(Duration::from_millis(1000 / TPS));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user