Rearrange PlayState system to work without loop control

This commit is contained in:
Imbris 2019-10-27 03:11:18 -04:00 committed by Imbris
parent 515dbc30a7
commit d1b635efa4
11 changed files with 897 additions and 682 deletions

102
Cargo.lock generated
View File

@ -474,10 +474,11 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cgl"
version = "0.3.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
checksum = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49"
dependencies = [
"gleam",
"libc",
]
@ -538,6 +539,21 @@ dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54"
dependencies = [
"bitflags",
"block",
"core-foundation 0.6.4",
"core-graphics 0.17.3",
"foreign-types",
"libc",
"objc",
]
[[package]]
name = "cocoa"
version = "0.19.1"
@ -1028,6 +1044,17 @@ dependencies = [
"byteorder 1.3.4",
]
[[package]]
name = "derivative"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]]
name = "deunicode"
version = "1.1.1"
@ -1656,6 +1683,15 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "gleam"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5"
dependencies = [
"gl_generator 0.13.1",
]
[[package]]
name = "glib"
version = "0.5.0"
@ -1698,15 +1734,16 @@ dependencies = [
[[package]]
name = "glutin"
version = "0.22.1"
version = "0.22.0-alpha3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "639246c8838b02a83b9339bd87da3714e73e52ecfaa758e15d761eb77b2290b5"
checksum = "fcdd3592b515014281a21a42addd91f41eaff0c5f54295ee28dac8ea6bbbceba"
dependencies = [
"android_glue",
"cgl",
"cocoa",
"cocoa 0.18.5",
"core-foundation 0.6.4",
"core-graphics 0.17.3",
"derivative",
"glutin_egl_sys",
"glutin_emscripten_sys",
"glutin_gles2_sys",
@ -1717,7 +1754,7 @@ dependencies = [
"log",
"objc",
"osmesa-sys",
"parking_lot 0.10.2",
"parking_lot 0.8.0",
"wayland-client",
"winapi 0.3.8",
"winit",
@ -2270,6 +2307,15 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "lock_api"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff"
dependencies = [
"scopeguard",
]
[[package]]
name = "lock_api"
version = "0.3.4"
@ -2469,7 +2515,7 @@ name = "msgbox"
version = "0.4.0"
source = "git+https://github.com/bekker/msgbox-rs.git?rev=68fe39a#68fe39a60019b38a1569ae4e9ed796a0f0542673"
dependencies = [
"cocoa",
"cocoa 0.19.1",
"gtk",
"objc",
"winapi 0.3.8",
@ -2804,13 +2850,24 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "parking_lot"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7"
dependencies = [
"lock_api 0.2.0",
"parking_lot_core 0.5.0",
"rustc_version",
]
[[package]]
name = "parking_lot"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
dependencies = [
"lock_api",
"lock_api 0.3.4",
"parking_lot_core 0.6.2",
"rustc_version",
]
@ -2821,10 +2878,26 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api",
"lock_api 0.3.4",
"parking_lot_core 0.7.2",
]
[[package]]
name = "parking_lot_core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c"
dependencies = [
"cfg-if",
"cloudabi",
"libc",
"rand 0.6.5",
"redox_syscall",
"rustc_version",
"smallvec 0.6.13",
"winapi 0.3.8",
]
[[package]]
name = "parking_lot_core"
version = "0.6.2"
@ -4850,13 +4923,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winit"
version = "0.20.0"
version = "0.20.0-alpha4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ba128780050481f453bec2a115b916dbc6ae79c303dee9bad8b9080bdccd4f5"
checksum = "56c565622ccb05351d92445415952ca09dade6a53e75dd9e75d9bd35d4e99333"
dependencies = [
"android_glue",
"bitflags",
"cocoa",
"calloop",
"cocoa 0.19.1",
"core-foundation 0.6.4",
"core-graphics 0.17.3",
"core-video-sys",
@ -4865,10 +4939,8 @@ dependencies = [
"lazy_static",
"libc",
"log",
"mio",
"mio-extras",
"objc",
"parking_lot 0.10.2",
"parking_lot 0.9.0",
"percent-encoding 2.1.0",
"raw-window-handle",
"serde",

View File

@ -255,6 +255,7 @@ impl State {
.map(|(key, _)| key)
.collect::<Vec<_>>();
// TODO: fix that this does nothing
for key in keys {
self.remove_chunk(key);
}

View File

@ -26,8 +26,9 @@ anim = { package = "veloren-voxygen-anim", path = "src/anim", default-features =
gfx = "0.18.2"
gfx_device_gl = { version = "0.16.2", optional = true }
gfx_window_glutin = {git = "https://github.com/Imberflur/gfx_window_glutin.git" }
glutin = "0.22.0-alpha3"
winit = { version = "0.20.0-alpha4", features = ["serde"] }
# TODO: change to non alpha
glutin = "=0.22.0-alpha3"
winit = { version = "=0.20.0-alpha4", features = ["serde"] }
conrod_core = { git = "https://gitlab.com/veloren/conrod.git", branch = "hide_text" }
conrod_winit = { git = "https://gitlab.com/veloren/conrod.git", branch = "hide_text" }
euc = { git = "https://github.com/zesterer/euc.git" }

View File

@ -17,6 +17,7 @@ pub mod menu;
pub mod mesh;
pub mod profile;
pub mod render;
pub mod run;
pub mod scene;
pub mod session;
pub mod settings;
@ -28,9 +29,14 @@ pub mod window;
pub use crate::error::Error;
use crate::{
audio::AudioFrontend, profile::Profile, settings::Settings, singleplayer::Singleplayer,
window::Window,
audio::AudioFrontend,
profile::Profile,
render::Renderer,
settings::Settings,
singleplayer::Singleplayer,
window::{Event, Window},
};
use common::{assets::watch, clock::Clock};
/// A type used to store state that is shared between all play states.
pub struct GlobalState {
@ -39,7 +45,11 @@ pub struct GlobalState {
pub window: Window,
pub audio: AudioFrontend,
pub info_message: Option<String>,
pub clock: Clock,
#[cfg(feature = "singleplayer")]
pub singleplayer: Option<Singleplayer>,
// TODO: redo this so that the watcher doesn't have to exist for reloading to occur
localization_watcher: watch::ReloadIndicator,
}
impl GlobalState {
@ -61,6 +71,8 @@ pub enum Direction {
/// States can either close (and revert to a previous state), push a new state
/// on top of themselves, or switch to a totally different state.
pub enum PlayStateResult {
/// Keep running this play state.
Continue,
/// Pop all play states in reverse order and shut down the program.
Shutdown,
/// Close the current play state and pop it from the play state stack.
@ -74,10 +86,16 @@ pub enum PlayStateResult {
/// A trait representing a playable game state. This may be a menu, a game
/// session, the title screen, etc.
pub trait PlayState {
/// Play the state until some change of state is required (i.e: a menu is
/// opened or the game is closed).
fn play(&mut self, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult;
/// Get a descriptive name for this state type.
/// Called when entering this play state from another
fn enter(&mut self, global_state: &mut GlobalState, direction: Direction);
/// Tick the play state
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult;
/// Get a descriptive name for this state type.
fn name(&self) -> &'static str;
/// Draw the play state.
fn render(&mut self, renderer: &mut Renderer, settings: &Settings);
}

View File

@ -14,7 +14,10 @@ use veloren_voxygen::{
Direction, GlobalState, PlayState, PlayStateResult,
};
use common::assets::{load, load_expect};
use common::{
assets::{load_watched, watch},
clock::Clock,
};
use std::{mem, panic};
use tracing::{debug, error, warn};
@ -26,57 +29,14 @@ fn main() {
// Note: This won't log anything due to it being called before
// `logging::init`. The issue is we need to read a setting to decide
// whether we create a log file or not.
let settings = Settings::load();
// Init logging and hold the guards.
let _guards = logging::init(&settings);
// Save settings to add new fields or create the file if it is not already
// there.
let mut settings = Settings::load();
// Save settings to add new fields or create the file if it is not already there
if let Err(err) = settings.save_to_file() {
panic!("Failed to save settings: {:?}", err);
}
let mut audio = match settings.audio.output {
AudioOutput::Off => None,
AudioOutput::Automatic => audio::get_default_device(),
AudioOutput::Device(ref dev) => Some(dev.clone()),
}
.map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels))
.unwrap_or_else(AudioFrontend::no_audio);
audio.set_music_volume(settings.audio.music_volume);
audio.set_sfx_volume(settings.audio.sfx_volume);
// Load the profile.
let profile = Profile::load();
let mut global_state = GlobalState {
audio,
profile,
window: Window::new(&settings).expect("Failed to create window!"),
settings,
info_message: None,
singleplayer: None,
};
// Try to load the localization and log missing entries
let localized_strings = load::<VoxygenLocalization>(&i18n_asset_key(
&global_state.settings.language.selected_language,
))
.unwrap_or_else(|e| {
let preferred_language = &global_state.settings.language.selected_language;
warn!(
?e,
?preferred_language,
"Impossible to load language: change to the default language (English) instead.",
);
global_state.settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
load_expect::<VoxygenLocalization>(&i18n_asset_key(
&global_state.settings.language.selected_language,
))
});
localized_strings.log_missing_entries();
// Init logging and hold the guards.
let _guards = logging::init(&settings);
// Set up panic handler to relay swish panic messages to the user
let default_hook = panic::take_hook();
@ -159,68 +119,56 @@ fn main() {
#[cfg(feature = "hot-anim")]
anim::init();
// Set up the initial play state.
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
states.last().map(|current_state| {
let current_state = current_state.name();
debug!(?current_state, "Started game with state")
});
// What's going on here?
// ---------------------
// The state system used by Voxygen allows for the easy development of
// stack-based menus. For example, you may want a "title" state that can
// push a "main menu" state on top of it, which can in turn push a
// "settings" state or a "game session" state on top of it. The code below
// manages the state transfer logic automatically so that we don't have to
// re-engineer it for each menu we decide to add to the game.
let mut direction = Direction::Forwards;
while let Some(state_result) = states
.last_mut()
.map(|last| last.play(direction, &mut global_state))
{
// Implement state transfer logic.
match state_result {
PlayStateResult::Shutdown => {
direction = Direction::Backwards;
debug!("Shutting down all states...");
while states.last().is_some() {
states.pop().map(|old_state| {
let old_state = old_state.name();
debug!(?old_state, "Popped state");
global_state.on_play_state_changed();
});
}
},
PlayStateResult::Pop => {
direction = Direction::Backwards;
states.pop().map(|old_state| {
let old_state = old_state.name();
debug!(?old_state, "Popped state");
global_state.on_play_state_changed();
});
},
PlayStateResult::Push(new_state) => {
direction = Direction::Forwards;
debug!("Pushed state '{}'.", new_state.name());
states.push(new_state);
global_state.on_play_state_changed();
},
PlayStateResult::Switch(mut new_state_box) => {
direction = Direction::Forwards;
states.last_mut().map(|old_state_box| {
let old_state = old_state_box.name();
let new_state = new_state_box.name();
debug!(?old_state, ?new_state, "Switching to states",);
mem::swap(old_state_box, &mut new_state_box);
global_state.on_play_state_changed();
});
},
}
// Setup audio
let mut audio = match settings.audio.output {
AudioOutput::Off => None,
AudioOutput::Automatic => audio::get_default_device(),
AudioOutput::Device(ref dev) => Some(dev.clone()),
}
.map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels))
.unwrap_or_else(AudioFrontend::no_audio);
// Save any unsaved changes to profile.
global_state.profile.save_to_file_warn();
// Save any unsaved changes to settings.
global_state.settings.save_to_file_warn();
audio.set_music_volume(settings.audio.music_volume);
audio.set_sfx_volume(settings.audio.sfx_volume);
// Load the profile.
let profile = Profile::load();
let mut localization_watcher = watch::ReloadIndicator::new();
let localized_strings = load_watched::<VoxygenLocalization>(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)
.unwrap_or_else(|error| {
let preferred_language = &global_state.settings.language.selected_language;
warn!(
?e,
?preferred_language,
"Impossible to load language: change to the default language (English) instead.",
);
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
load_watched::<VoxygenLocalization>(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)
.unwrap()
});
localized_strings.log_missing_entries();
// Create window
let (window, event_loop) = Window::new(&settings).expect("Failed to create window!");
let global_state = GlobalState {
audio,
profile,
window,
settings,
clock: Clock::start(),
info_message: None,
#[cfg(feature = "singleplayer")]
singleplayer: None,
localization_watcher,
};
run::run(global_state, event_loop);
}

View File

@ -2,15 +2,17 @@ mod ui;
use crate::{
i18n::{i18n_asset_key, VoxygenLocalization},
render::Renderer,
scene::simple::{self as scene, Scene},
session::SessionState,
settings::Settings,
window::Event as WinEvent,
Direction, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{assets, clock::Clock, comp, msg::ClientState, state::DeltaTime};
use common::{assets, comp, msg::ClientState, state::DeltaTime};
use specs::WorldExt;
use std::{cell::RefCell, rc::Rc, time::Duration};
use std::{cell::RefCell, rc::Rc};
use tracing::error;
use ui::CharSelectionUi;
@ -32,20 +34,34 @@ impl CharSelectionState {
),
}
}
fn get_humanoid_body(&self) -> Option<comp::humanoid::Body> {
self.char_selection_ui
.get_character_list()
.and_then(|data| {
if let Some(character) = data.get(self.char_selection_ui.selected_character) {
match character.body {
comp::Body::Humanoid(body) => Some(body),
_ => None,
}
} else {
None
}
})
}
}
impl PlayState for CharSelectionState {
fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult {
// Set up an fps clock.
let mut clock = Clock::start();
fn enter(&mut self, _: &mut GlobalState, _: Direction) {
// Load the player's character list
self.client.borrow_mut().load_character_list();
}
let mut current_client_state = self.client.borrow().get_client_state();
while let ClientState::Pending | ClientState::Registered = current_client_state {
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
let client_state = self.client.borrow().get_client_state();
if let ClientState::Pending | ClientState::Registered = client_state {
// Handle window events
for event in global_state.window.fetch_events(&mut global_state.settings) {
for event in events {
if self.char_selection_ui.handle_event(event.clone()) {
continue;
}
@ -60,8 +76,6 @@ impl PlayState for CharSelectionState {
}
}
global_state.window.renderer_mut().clear();
// Maintain the UI.
let events = self
.char_selection_ui
@ -100,9 +114,6 @@ impl PlayState for CharSelectionState {
}
}
// Maintain global state.
global_state.maintain(clock.get_last_delta().as_secs_f32());
let humanoid_body = self
.char_selection_ui
.get_character_list()
@ -117,6 +128,7 @@ impl PlayState for CharSelectionState {
}
});
let humanoid_body = self.get_humanoid_body();
let loadout = self.char_selection_ui.get_loadout();
// Maintain the scene.
@ -141,17 +153,6 @@ impl PlayState for CharSelectionState {
loadout.as_ref(),
);
}
// Render the scene.
self.scene.render(
global_state.window.renderer_mut(),
self.client.borrow().get_tick(),
humanoid_body,
loadout.as_ref(),
);
// Draw the UI to the screen.
self.char_selection_ui
.render(global_state.window.renderer_mut(), self.scene.globals());
// Tick the client (currently only to keep the connection alive).
let localized_strings = assets::load_expect::<VoxygenLocalization>(&i18n_asset_key(
@ -160,7 +161,7 @@ impl PlayState for CharSelectionState {
match self.client.borrow_mut().tick(
comp::ControllerInputs::default(),
clock.get_last_delta(),
global_state.clock.get_last_delta(),
|_| {},
) {
Ok(events) => {
@ -190,25 +191,33 @@ impl PlayState for CharSelectionState {
},
}
// TODO: make sure rendering is not relying on cleaned up stuff
self.client.borrow_mut().cleanup();
// Finish the frame.
global_state.window.renderer_mut().flush();
global_state
.window
.swap_buffers()
.expect("Failed to swap window buffers");
// Wait for the next tick.
clock.tick(Duration::from_millis(
1000 / (global_state.settings.graphics.max_fps as u64),
));
current_client_state = self.client.borrow().get_client_state();
PlayStateResult::Continue
} else {
error!("Client not in pending or registered state. Popping char selection play state");
// TODO set global_state.info_message
PlayStateResult::Pop
}
PlayStateResult::Pop
}
fn name(&self) -> &'static str { "Title" }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
let humanoid_body = self.get_humanoid_body();
let loadout = self.char_selection_ui.get_loadout();
// Render the scene.
self.scene.render(
renderer,
self.client.borrow().get_tick(),
humanoid_body,
loadout.as_ref(),
);
// Draw the UI to the screen.
self.char_selection_ui
.render(renderer, self.scene.globals());
}
}

View File

@ -1,19 +1,25 @@
mod client_init;
#[cfg(feature = "singleplayer")] mod ui;
mod ui;
// TODO: make sure turning off singleplayer feature still works (also make sure
// all singleplayer stuff is still hidden with it off)
use super::char_selection::CharSelectionState;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
use crate::{
singleplayer::Singleplayer, window::Event, Direction, GlobalState, PlayState, PlayStateResult,
render::Renderer, settings::Settings, window::Event, Direction, GlobalState, PlayState,
PlayStateResult,
};
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
use common::{assets::load_expect, clock::Clock, comp};
#[cfg(feature = "singleplayer")]
use std::time::Duration;
use common::{assets::load_expect, comp};
use tracing::{error, warn};
use ui::{Event as MainMenuEvent, MainMenuUi};
pub struct MainMenuState {
main_menu_ui: MainMenuUi,
title_music_channel: Option<usize>,
// Used for client creation.
client_init: Option<ClientInit>,
}
impl MainMenuState {
@ -21,6 +27,8 @@ impl MainMenuState {
pub fn new(global_state: &mut GlobalState) -> Self {
Self {
main_menu_ui: MainMenuUi::new(global_state),
title_music_channel: None,
client_init: None,
}
}
}
@ -28,237 +36,224 @@ impl MainMenuState {
const DEFAULT_PORT: u16 = 14004;
impl PlayState for MainMenuState {
#[allow(clippy::useless_format)] // TODO: Pending review in #587
fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult {
// Set up an fps clock.
let mut clock = Clock::start();
// Used for client creation.
let mut client_init: Option<ClientInit> = None;
fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
// Kick off title music
if global_state.settings.audio.output.is_enabled() && global_state.audio.music_enabled() {
global_state.audio.play_title_music();
}
// Reset singleplayer server if it was running already
global_state.singleplayer = None;
#[cfg(feature = "singleplayer")]
{
global_state.singleplayer = None;
}
}
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
let localized_strings = load_expect::<crate::i18n::VoxygenLocalization>(
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
);
loop {
// Handle window events.
for event in global_state.window.fetch_events(&mut global_state.settings) {
match event {
Event::Close => return PlayStateResult::Shutdown,
// Pass events to ui.
Event::Ui(event) => {
self.main_menu_ui.handle_event(event);
},
// Ignore all other events.
_ => {},
}
}
global_state.window.renderer_mut().clear();
// Poll client creation.
match client_init.as_ref().and_then(|init| init.poll()) {
Some(InitMsg::Done(Ok(mut client))) => {
self.main_menu_ui.connected();
// Register voxygen components / resources
crate::ecs::init(client.state_mut().ecs_mut());
return PlayStateResult::Push(Box::new(CharSelectionState::new(
global_state,
std::rc::Rc::new(std::cell::RefCell::new(client)),
)));
// Handle window events.
for event in events {
match event {
Event::Close => return PlayStateResult::Shutdown,
// Pass events to ui.
Event::Ui(event) => {
self.main_menu_ui.handle_event(event);
},
Some(InitMsg::Done(Err(e))) => {
client_init = None;
global_state.info_message = Some({
let err = match e {
InitError::BadAddress(_) | InitError::NoAddress => {
localized_strings.get("main.login.server_not_found").into()
// Ignore all other events.
_ => {},
}
}
// Poll client creation.
match self.client_init.as_ref().and_then(|init| init.poll()) {
Some(InitMsg::Done(Ok(mut client))) => {
self.main_menu_ui.connected();
// Register voxygen components / resources
crate::ecs::init(client.state_mut().ecs_mut());
return PlayStateResult::Push(Box::new(CharSelectionState::new(
global_state,
std::rc::Rc::new(std::cell::RefCell::new(client)),
)));
},
Some(InitMsg::Done(Err(err))) => {
self.client_init = None;
global_state.info_message = Some({
let err = match err {
InitError::BadAddress(_) | InitError::NoAddress => {
localized_strings.get("main.login.server_not_found").into()
},
InitError::ClientError(err) => match err {
client::Error::AuthErr(e) => format!(
"{}: {}",
localized_strings.get("main.login.authentication_error"),
e
),
client::Error::TooManyPlayers => {
localized_strings.get("main.login.server_full").into()
},
InitError::ClientError(err) => match err {
client::Error::AuthErr(e) => format!(
client::Error::AuthServerNotTrusted => localized_strings
.get("main.login.untrusted_auth_server")
.into(),
client::Error::ServerWentMad => localized_strings
.get("main.login.outdated_client_or_server")
.into(),
client::Error::ServerTimeout => {
localized_strings.get("main.login.timeout").into()
},
client::Error::ServerShutdown => {
localized_strings.get("main.login.server_shut_down").into()
},
client::Error::AlreadyLoggedIn => {
localized_strings.get("main.login.already_logged_in").into()
},
client::Error::NotOnWhitelist => {
localized_strings.get("main.login.not_on_whitelist").into()
},
client::Error::InvalidCharacter => {
localized_strings.get("main.login.invalid_character").into()
},
client::Error::NetworkErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
),
client::Error::ParticipantErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
),
client::Error::StreamErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
),
client::Error::Other(e) => {
format!("{}: {}", localized_strings.get("common.error"), e)
},
client::Error::AuthClientError(e) => match e {
client::AuthClientError::JsonError(e) => format!(
"{}: {}",
localized_strings.get("main.login.authentication_error"),
localized_strings.get("common.fatal_error"),
e
),
client::Error::TooManyPlayers => {
localized_strings.get("main.login.server_full").into()
},
client::Error::AuthServerNotTrusted => localized_strings
.get("main.login.untrusted_auth_server")
.into(),
client::Error::ServerWentMad => localized_strings
.get("main.login.outdated_client_or_server")
.into(),
client::Error::ServerTimeout => {
localized_strings.get("main.login.timeout").into()
},
client::Error::ServerShutdown => {
localized_strings.get("main.login.server_shut_down").into()
},
client::Error::AlreadyLoggedIn => {
localized_strings.get("main.login.already_logged_in").into()
},
client::Error::NotOnWhitelist => {
localized_strings.get("main.login.not_on_whitelist").into()
},
client::Error::NetworkErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
// TODO: remove parentheses
client::AuthClientError::RequestError() => format!(
"{}",
localized_strings.get("main.login.failed_sending_request"),
),
client::Error::ParticipantErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
),
client::Error::StreamErr(e) => format!(
"{}: {:?}",
localized_strings.get("main.login.network_error"),
e
),
client::Error::Other(e) => {
format!("{}: {}", localized_strings.get("common.error"), e)
},
client::Error::AuthClientError(e) => match e {
client::AuthClientError::JsonError(e) => format!(
"{}: {}",
localized_strings.get("common.fatal_error"),
e
),
client::AuthClientError::RequestError() => format!(
"{}",
localized_strings.get("main.login.failed_sending_request")
),
client::AuthClientError::ServerError(_, e) => format!("{}", e),
},
client::Error::InvalidCharacter => {
localized_strings.get("main.login.invalid_character").into()
},
client::AuthClientError::ServerError(_, e) => format!("{}", e),
},
InitError::ClientCrashed => {
localized_strings.get("main.login.client_crashed").into()
},
};
// Log error for possible additional use later or incase that the error
// displayed is cut of.
error!("{}", err);
err
});
},
Some(InitMsg::IsAuthTrusted(auth_server)) => {
if global_state
.settings
.networking
.trusted_auth_servers
.contains(&auth_server)
{
// Can't fail since we just polled it, it must be Some
client_init.as_ref().unwrap().auth_trust(auth_server, true);
} else {
// Show warning that auth server is not trusted and prompt for approval
self.main_menu_ui.auth_trust_prompt(auth_server);
}
},
None => {},
}
},
InitError::ClientCrashed => {
localized_strings.get("main.login.client_crashed").into()
},
};
// Log error for possible additional use later or incase that the error
// displayed is cut of.
error!("{}", err);
err
});
},
Some(InitMsg::IsAuthTrusted(auth_server)) => {
if global_state
.settings
.networking
.trusted_auth_servers
.contains(&auth_server)
{
// Can't fail since we just polled it, it must be Some
self.client_init
.as_ref()
.unwrap()
.auth_trust(auth_server, true);
} else {
// Show warning that auth server is not trusted and prompt for approval
self.main_menu_ui.auth_trust_prompt(auth_server);
}
},
None => {},
}
// Maintain global_state
global_state.maintain(clock.get_last_delta().as_secs_f32());
// Maintain the UI.
for event in self
.main_menu_ui
.maintain(global_state, clock.get_last_delta())
{
match event {
MainMenuEvent::LoginAttempt {
// Maintain the UI.
for event in self
.main_menu_ui
.maintain(global_state, global_state.clock.get_last_delta())
{
match event {
MainMenuEvent::LoginAttempt {
username,
password,
server_address,
} => {
attempt_login(
global_state,
username,
password,
server_address,
} => {
attempt_login(
global_state,
username,
password,
server_address,
DEFAULT_PORT,
&mut client_init,
);
},
MainMenuEvent::CancelLoginAttempt => {
// 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
global_state.singleplayer = None;
client_init = None;
self.main_menu_ui.cancel_connection();
},
DEFAULT_PORT,
&mut self.client_init,
);
},
MainMenuEvent::CancelLoginAttempt => {
// 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
#[cfg(feature = "singleplayer")]
MainMenuEvent::StartSingleplayer => {
let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
{
global_state.singleplayer = None;
}
self.client_init = None;
self.main_menu_ui.cancel_connection();
},
#[cfg(feature = "singleplayer")]
MainMenuEvent::StartSingleplayer => {
let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
global_state.singleplayer = Some(singleplayer);
attempt_login(
global_state,
"singleplayer".to_owned(),
"".to_owned(),
server_settings.gameserver_address.ip().to_string(),
server_settings.gameserver_address.port(),
&mut client_init,
);
},
MainMenuEvent::Settings => {}, // TODO
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
/*MainMenuEvent::DisclaimerClosed => {
global_state.settings.show_disclaimer = false
},*/
MainMenuEvent::AuthServerTrust(auth_server, trust) => {
if trust {
global_state
.settings
.networking
.trusted_auth_servers
.insert(auth_server.clone());
global_state.settings.save_to_file_warn();
}
client_init
.as_ref()
.map(|init| init.auth_trust(auth_server, trust));
},
}
attempt_login(
global_state,
"singleplayer".to_owned(),
"".to_owned(),
server_settings.gameserver_address.ip().to_string(),
server_settings.gameserver_address.port(),
&mut self.client_init,
);
},
MainMenuEvent::Settings => {}, // TODO
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
/*MainMenuEvent::DisclaimerClosed => {
global_state.settings.show_disclaimer = false
},*/
MainMenuEvent::AuthServerTrust(auth_server, trust) => {
if trust {
global_state
.settings
.networking
.trusted_auth_servers
.insert(auth_server.clone());
global_state.settings.save_to_file_warn();
}
self.client_init
.as_ref()
.map(|init| init.auth_trust(auth_server, trust));
},
}
if let Some(info) = global_state.info_message.take() {
self.main_menu_ui.show_info(info);
}
// Draw the UI to the screen.
self.main_menu_ui.render(global_state.window.renderer_mut());
// Finish the frame.
global_state.window.renderer_mut().flush();
global_state
.window
.swap_buffers()
.expect("Failed to swap window buffers!");
// Wait for the next tick
clock.tick(Duration::from_millis(
1000 / (global_state.settings.graphics.max_fps as u64),
));
}
if let Some(info) = global_state.info_message.take() {
self.main_menu_ui.show_info(info);
}
PlayStateResult::Continue
}
fn name(&self) -> &'static str { "Title" }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
// Draw the UI to the screen.
self.main_menu_ui.render(renderer);
}
}
fn attempt_login(

142
voxygen/src/run.rs Normal file
View File

@ -0,0 +1,142 @@
use crate::{
menu::main::MainMenuState,
ui,
window::{Event, EventLoop},
Direction, GlobalState, PlayState, PlayStateResult,
};
use std::{mem, time::Duration};
use tracing::debug;
pub fn run(mut global_state: GlobalState, event_loop: EventLoop) {
// Set up the initial play state.
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
states.last_mut().map(|current_state| {
current_state.enter(&mut global_state, Direction::Forwards);
let current_state = current_state.name();
debug!(?current_state, "Started game with state");
});
event_loop.run(move |event, _, control_flow| {
// Get events for the ui.
if let Some(event) = ui::Event::try_from(event.clone(), global_state.window.window()) {
global_state.window.send_event(Event::Ui(event));
}
match event {
winit::event::Event::EventsCleared => {
// Run tick here
// What's going on here?
// ---------------------
// The state system used by Voxygen allows for the easy development of
// stack-based menus. For example, you may want a "title" state
// that can push a "main menu" state on top of it, which can in
// turn push a "settings" state or a "game session" state on top of it.
// The code below manages the state transfer logic automatically so that we
// don't have to re-engineer it for each menu we decide to add
// to the game.
let mut exit = true;
while let Some(state_result) = states.last_mut().map(|last| {
let events = global_state.window.fetch_events();
last.tick(&mut global_state, events)
}) {
// Implement state transfer logic.
match state_result {
PlayStateResult::Continue => {
// Wait for the next tick.
global_state.clock.tick(Duration::from_millis(
1000 / global_state.settings.graphics.max_fps as u64,
));
// Maintain global state.
global_state
.maintain(global_state.clock.get_last_delta().as_secs_f32());
exit = false;
break;
},
PlayStateResult::Shutdown => {
debug!("Shutting down all states...");
while states.last().is_some() {
states.pop().map(|old_state| {
debug!("Popped state '{}'.", old_state.name());
global_state.on_play_state_changed();
});
}
},
PlayStateResult::Pop => {
states.pop().map(|old_state| {
debug!("Popped state '{}'.", old_state.name());
global_state.on_play_state_changed();
});
states.last_mut().map(|new_state| {
new_state.enter(&mut global_state, Direction::Forwards);
});
},
PlayStateResult::Push(mut new_state) => {
new_state.enter(&mut global_state, Direction::Forwards);
debug!("Pushed state '{}'.", new_state.name());
states.push(new_state);
global_state.on_play_state_changed();
},
PlayStateResult::Switch(mut new_state) => {
new_state.enter(&mut global_state, Direction::Forwards);
states.last_mut().map(|old_state| {
debug!(
"Switching to state '{}' from state '{}'.",
new_state.name(),
old_state.name()
);
mem::swap(old_state, &mut new_state);
global_state.on_play_state_changed();
});
},
}
}
*control_flow = if exit {
winit::event_loop::ControlFlow::Exit
} else {
winit::event_loop::ControlFlow::Poll
};
// TODO: move
if let Some(last) = states.last_mut() {
global_state.window.renderer_mut().clear();
last.render(
&mut global_state.window.renderer_mut(),
&global_state.settings,
);
// Finish the frame.
global_state.window.renderer_mut().flush();
global_state
.window
.swap_buffers()
.expect("Failed to swap window buffers!");
}
//global_state.window.request_redraw();
},
/*winit::event::Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
// render here
}
..
*control_flow = ControlFlow::Exit;
}*/
winit::event::Event::WindowEvent { event, .. } => global_state
.window
.handle_window_event(event, &mut global_state.settings),
winit::event::Event::DeviceEvent { event, .. } => {
global_state.window.handle_device_event(event)
},
winit::event::Event::LoopDestroyed => {
// Save any unsaved changes to settings and profile
global_state.settings.save_to_file_warn();
global_state.profile.save_to_file_warn();
},
_ => {},
}
});
}

View File

@ -5,15 +5,15 @@ use crate::{
i18n::{i18n_asset_key, VoxygenLocalization},
key_state::KeyState,
menu::char_selection::CharSelectionState,
render::Renderer,
scene::{camera, Scene, SceneData},
settings::{AudioOutput, ControlSettings},
settings::{AudioOutput, ControlSettings, Settings},
window::{AnalogGameInput, Event, GameInput},
Direction, Error, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{
assets::{load_expect, load_watched, watch},
clock::Clock,
assets::{load_expect, load_watched},
comp,
comp::{
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR,
@ -46,6 +46,12 @@ pub struct SessionState {
inputs: comp::ControllerInputs,
selected_block: Block,
voxygen_i18n: std::sync::Arc<VoxygenLocalization>,
walk_forward_dir: Vec2<f32>,
walk_right_dir: Vec2<f32>,
freefly_vel: Vec3<f32>,
free_look: bool,
auto_walk: bool,
is_aiming: bool,
}
/// Represents an active game session (i.e., the one being played).
@ -67,6 +73,9 @@ impl SessionState {
let voxygen_i18n = load_expect::<VoxygenLocalization>(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy();
Self {
scene,
client,
@ -75,6 +84,12 @@ impl SessionState {
hud,
selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)),
voxygen_i18n,
walk_forward_dir,
walk_right_dir,
freefly_vel: Vec3::zero(),
free_look: false,
auto_walk: false,
is_aiming: false,
}
}
}
@ -153,12 +168,10 @@ impl SessionState {
}
impl PlayState for SessionState {
fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult {
fn enter(&mut self, global_state: &mut GlobalState, _: Direction) {
// Trap the cursor.
global_state.window.grab_cursor(true);
// Set up an fps clock.
let mut clock = Clock::start();
self.client.borrow_mut().clear_terrain();
// Send startup commands to the server
@ -167,30 +180,22 @@ impl PlayState for SessionState {
self.client.borrow_mut().send_chat(cmd.to_string());
}
}
}
// Keep a watcher on the language
let mut localization_watcher = watch::ReloadIndicator::new();
let mut localized_strings = load_watched::<VoxygenLocalization>(
&i18n_asset_key(&global_state.settings.language.selected_language),
&mut localization_watcher,
)
.unwrap();
let mut walk_forward_dir = self.scene.camera().forward_xy();
let mut walk_right_dir = self.scene.camera().right_xy();
let mut freefly_vel = Vec3::zero();
let mut free_look = false;
let mut auto_walk = false;
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
let localized_strings = load_expect::<VoxygenLocalization>(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// TODO: can this be a method on the session or are there borrowcheck issues?
fn stop_auto_walk(auto_walk: &mut bool, key_state: &mut KeyState, hud: &mut Hud) {
*auto_walk = false;
hud.auto_walk(false);
key_state.auto_walk = false;
}
// Game loop
let mut current_client_state = self.client.borrow().get_client_state();
while let ClientState::Pending | ClientState::Character = current_client_state {
let client_state = self.client.borrow().get_client_state();
if let ClientState::Pending | ClientState::Character = client_state {
// Compute camera data
self.scene
.camera_mut()
@ -229,6 +234,7 @@ impl PlayState for SessionState {
},
)
};
self.is_aiming = is_aiming;
let cam_dir: Vec3<f32> = Vec3::from(view_mat.inverted() * -Vec4::unit_z());
@ -278,7 +284,7 @@ impl PlayState for SessionState {
}));
// Handle window events.
for event in global_state.window.fetch_events(&mut global_state.settings) {
for event in events {
// Pass all events to the ui first.
if self.hud.handle_event(event.clone(), global_state) {
continue;
@ -348,7 +354,7 @@ impl PlayState for SessionState {
{
self.key_state.toggle_sit = state;
if state {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
self.client.borrow_mut().toggle_sit();
}
}
@ -357,31 +363,31 @@ impl PlayState for SessionState {
{
self.key_state.toggle_dance = state;
if state {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
self.client.borrow_mut().toggle_dance();
}
}
Event::InputUpdate(GameInput::MoveForward, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
}
self.key_state.up = state
},
Event::InputUpdate(GameInput::MoveBack, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
}
self.key_state.down = state
},
Event::InputUpdate(GameInput::MoveLeft, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
}
self.key_state.left = state
},
Event::InputUpdate(GameInput::MoveRight, state) => {
if state && global_state.settings.gameplay.stop_auto_walk_on_input {
stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud);
stop_auto_walk(&mut self.auto_walk, &mut self.key_state, &mut self.hud);
}
self.key_state.right = state
},
@ -509,12 +515,12 @@ impl PlayState for SessionState {
Event::InputUpdate(GameInput::FreeLook, state) => {
match (global_state.settings.gameplay.free_look_behavior, state) {
(PressBehavior::Toggle, true) => {
free_look = !free_look;
self.hud.free_look(free_look);
self.free_look = !self.free_look;
self.hud.free_look(self.free_look);
},
(PressBehavior::Hold, state) => {
free_look = state;
self.hud.free_look(free_look);
self.free_look = state;
self.hud.free_look(self.free_look);
},
_ => {},
};
@ -522,14 +528,14 @@ impl PlayState for SessionState {
Event::InputUpdate(GameInput::AutoWalk, state) => {
match (global_state.settings.gameplay.auto_walk_behavior, state) {
(PressBehavior::Toggle, true) => {
auto_walk = !auto_walk;
self.key_state.auto_walk = auto_walk;
self.hud.auto_walk(auto_walk);
self.auto_walk = !self.auto_walk;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
},
(PressBehavior::Hold, state) => {
auto_walk = state;
self.key_state.auto_walk = auto_walk;
self.hud.auto_walk(auto_walk);
self.auto_walk = state;
self.key_state.auto_walk = self.auto_walk;
self.hud.auto_walk(self.auto_walk);
},
_ => {},
}
@ -567,9 +573,9 @@ impl PlayState for SessionState {
}
}
if !free_look {
walk_forward_dir = self.scene.camera().forward_xy();
walk_right_dir = self.scene.camera().right_xy();
if !self.free_look {
self.walk_forward_dir = self.scene.camera().forward_xy();
self.walk_right_dir = self.scene.camera().right_xy();
self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
}
@ -581,8 +587,9 @@ impl PlayState for SessionState {
camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => {
// Move the player character based on their walking direction.
// This could be different from the camera direction if free look is enabled.
self.inputs.move_dir = walk_right_dir * axis_right + walk_forward_dir * axis_up;
freefly_vel = Vec3::zero();
self.inputs.move_dir =
self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up;
self.freefly_vel = Vec3::zero();
},
camera::CameraMode::Freefly => {
@ -596,27 +603,27 @@ impl PlayState for SessionState {
let right = self.scene.camera().right();
let dir = right * axis_right + forward * axis_up;
let dt = clock.get_last_delta().as_secs_f32();
if freefly_vel.magnitude_squared() > 0.01 {
let new_vel =
freefly_vel - freefly_vel.normalized() * (FREEFLY_DAMPING * dt);
if freefly_vel.dot(new_vel) > 0.0 {
freefly_vel = new_vel;
let dt = global_state.clock.get_last_delta().as_secs_f32();
if self.freefly_vel.magnitude_squared() > 0.01 {
let new_vel = self.freefly_vel
- self.freefly_vel.normalized() * (FREEFLY_DAMPING * dt);
if self.freefly_vel.dot(new_vel) > 0.0 {
self.freefly_vel = new_vel;
} else {
freefly_vel = Vec3::zero();
self.freefly_vel = Vec3::zero();
}
}
if dir.magnitude_squared() > 0.01 {
freefly_vel += dir * (FREEFLY_ACCEL * dt);
if freefly_vel.magnitude() > FREEFLY_MAX_SPEED {
freefly_vel = freefly_vel.normalized() * FREEFLY_MAX_SPEED;
self.freefly_vel += dir * (FREEFLY_ACCEL * dt);
if self.freefly_vel.magnitude() > FREEFLY_MAX_SPEED {
self.freefly_vel = self.freefly_vel.normalized() * FREEFLY_MAX_SPEED;
}
}
let pos = self.scene.camera().get_focus_pos();
self.scene
.camera_mut()
.set_focus_pos(pos + freefly_vel * dt);
.set_focus_pos(pos + self.freefly_vel * dt);
// Do not apply any movement to the player character
self.inputs.move_dir = Vec2::zero();
@ -630,7 +637,7 @@ impl PlayState for SessionState {
|| !global_state.singleplayer.as_ref().unwrap().is_paused()
{
// Perform an in-game tick.
match self.tick(clock.get_avg_delta(), global_state) {
match self.tick(global_state.clock.get_avg_delta(), global_state) {
Ok(TickAction::Continue) => {}, // Do nothing
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
Err(err) => {
@ -643,19 +650,17 @@ impl PlayState for SessionState {
}
}
// Maintain global state.
global_state.maintain(clock.get_last_delta().as_secs_f32());
// Recompute dependents just in case some input modified the camera
self.scene
.camera_mut()
.compute_dependents(&*self.client.borrow().state().terrain());
// Extract HUD events ensuring the client borrow gets dropped.
let mut hud_events = self.hud.maintain(
&self.client.borrow(),
global_state,
DebugInfo {
tps: clock.get_tps(),
tps: global_state.clock.get_tps(),
ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
coordinates: self
.client
@ -687,7 +692,7 @@ impl PlayState for SessionState {
num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32,
},
&self.scene.camera(),
clock.get_last_delta(),
global_state.clock.get_last_delta(),
HudInfo {
is_aiming,
is_first_person: matches!(
@ -697,8 +702,9 @@ impl PlayState for SessionState {
},
);
// TODO: dont
// Look for changes in the localization files
if localization_watcher.reloaded() {
if global_state.localization_watcher.reloaded() {
hud_events.push(HudEvent::ChangeLanguage(localized_strings.metadata.clone()));
}
@ -916,9 +922,9 @@ impl PlayState for SessionState {
HudEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
localized_strings = load_watched::<VoxygenLocalization>(
let localized_strings = load_watched::<VoxygenLocalization>(
&i18n_asset_key(&global_state.settings.language.selected_language),
&mut localization_watcher,
&mut global_state.localization_watcher,
)
.unwrap();
localized_strings.log_missing_entries();
@ -1005,32 +1011,53 @@ impl PlayState for SessionState {
renderer.flush();
}
// Display the frame on the window.
global_state
.window
.swap_buffers()
.expect("Failed to swap window buffers!");
// Wait for the next tick.
clock.tick(Duration::from_millis(
1000 / global_state.settings.graphics.max_fps as u64,
));
// Clean things up after the tick.
self.cleanup();
current_client_state = self.client.borrow().get_client_state();
}
if let ClientState::Registered = current_client_state {
return PlayStateResult::Switch(Box::new(CharSelectionState::new(
PlayStateResult::Continue
} else if let ClientState::Registered = client_state {
PlayStateResult::Switch(Box::new(CharSelectionState::new(
global_state,
self.client.clone(),
)));
)))
} else {
error!("Client not in the expected state, exiting session play state");
PlayStateResult::Pop
}
PlayStateResult::Pop
}
fn name(&self) -> &'static str { "Session" }
/// Render the session to the screen.
///
/// This method should be called once per frame.
fn render(&mut self, renderer: &mut Renderer, settings: &Settings) {
// Render the screen using the global renderer
{
let client = self.client.borrow();
let scene_data = SceneData {
state: client.state(),
player_entity: client.entity(),
loaded_distance: client.loaded_distance(),
view_distance: client.view_distance().unwrap_or(1),
tick: client.get_tick(),
thread_pool: client.thread_pool(),
gamma: settings.graphics.gamma,
mouse_smoothing: settings.gameplay.smooth_pan_enable,
sprite_render_distance: settings.graphics.sprite_render_distance as f32,
figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32,
is_aiming: self.is_aiming,
};
self.scene.render(
renderer,
client.state(),
client.entity(),
client.get_tick(),
&scene_data,
);
}
// Draw the UI to the screen
self.hud.render(renderer, self.scene.globals());
}
}

View File

@ -20,7 +20,7 @@ impl Event {
Some(winit::window::Window::inner_size(&self.0).into())
}
fn hidpi_factor(&self) -> f32 { winit::window::Window::get_hidpi_factor(&self.0) as _ }
fn hidpi_factor(&self) -> f32 { winit::window::Window::hidpi_factor(&self.0) as _ }
}
convert_event!(event, &WindowRef(window.window())).map(|input| Self(input))
}

View File

@ -282,17 +282,18 @@ pub enum Event {
pub type MouseButton = winit::event::MouseButton;
pub type PressState = winit::event::ElementState;
pub type EventLoop = winit::event_loop::EventLoop<()>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum KeyMouse {
Key(glutin::event::VirtualKeyCode),
Mouse(glutin::event::MouseButton),
Key(winit::event::VirtualKeyCode),
Mouse(winit::event::MouseButton),
}
impl fmt::Display for KeyMouse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::KeyMouse::*;
use glutin::event::{MouseButton, VirtualKeyCode::*};
use winit::event::{MouseButton, VirtualKeyCode::*};
write!(f, "{}", match self {
Key(Key1) => "1",
Key(Key2) => "2",
@ -466,7 +467,6 @@ impl fmt::Display for KeyMouse {
}
pub struct Window {
events_loop: glutin::event_loop::EventLoop<()>,
renderer: Renderer,
window: glutin::ContextWrapper<glutin::PossiblyCurrent, winit::window::Window>,
cursor_grabbed: bool,
@ -479,6 +479,7 @@ pub struct Window {
keypress_map: HashMap<GameInput, winit::event::ElementState>,
pub remapping_keybindings: Option<GameInput>,
supplement_events: Vec<Event>,
events: Vec<Event>,
focused: bool,
gilrs: Option<Gilrs>,
controller_settings: ControllerSettings,
@ -490,14 +491,14 @@ pub struct Window {
}
impl Window {
pub fn new(settings: &Settings) -> Result<Window, Error> {
let events_loop = glutin::event_loop::EventLoop::new();
pub fn new(settings: &Settings) -> Result<(Window, EventLoop), Error> {
let event_loop = EventLoop::new();
let size = settings.graphics.window_size;
let win_builder = glutin::window::WindowBuilder::new()
.with_title("Veloren")
.with_dimensions(glutin::dpi::LogicalSize::new(
.with_inner_size(glutin::dpi::LogicalSize::new(
size[0] as f64,
size[1] as f64,
))
@ -508,10 +509,10 @@ impl Window {
.with_vsync(false);
let (window, device, factory, win_color_view, win_depth_view) =
gfx_window_glutin::init::<WinColorFmt, WinDepthFmt, ()>(
gfx_window_glutin::init::<WinColorFmt, WinDepthFmt, _>(
win_builder,
ctx_builder,
&events_loop,
&event_loop,
)
.map_err(|err| Error::BackendError(Box::new(err)))?;
@ -559,7 +560,6 @@ impl Window {
) = channel::unbounded::<String>();
let mut this = Self {
events_loop,
renderer: Renderer::new(
device,
factory,
@ -579,7 +579,8 @@ impl Window {
needs_refresh_resize: false,
keypress_map,
remapping_keybindings: None,
supplement_events: vec![],
supplement_events: Vec::new(),
events: Vec::new(),
focused: true,
gilrs,
controller_settings,
@ -592,7 +593,13 @@ impl Window {
this.fullscreen(settings.graphics.fullscreen);
Ok(this)
Ok((this, event_loop))
}
pub fn window(
&self,
) -> &glutin::ContextWrapper<glutin::PossiblyCurrent, winit::window::Window> {
&self.window
}
pub fn renderer(&self) -> &Renderer { &self.renderer }
@ -600,12 +607,15 @@ impl Window {
pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
#[allow(clippy::match_bool)] // TODO: Pending review in #587
pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec<Event> {
let mut events = vec![];
pub fn fetch_events(&mut self) -> Vec<Event> {
let mut events = std::mem::take(&mut self.events);
events.append(&mut self.supplement_events);
// Refresh ui size (used when changing playstates)
if self.needs_refresh_resize {
events.push(Event::Ui(ui::Event::new_resize(self.logical_size())));
self.events
.push(Event::Ui(ui::Event::new_resize(self.logical_size())));
self.needs_refresh_resize = false;
}
@ -614,181 +624,6 @@ impl Window {
.try_iter()
.for_each(|message| events.push(Event::ScreenshotMessage(message)));
// Copy data that is needed by the events closure to avoid lifetime errors.
// TODO: Remove this if/when the compiler permits it.
let cursor_grabbed = self.cursor_grabbed;
let renderer = &mut self.renderer;
let window = &mut self.window;
let remapping_keybindings = &mut self.remapping_keybindings;
let focused = &mut self.focused;
let controls = &mut settings.controls;
let keypress_map = &mut self.keypress_map;
let pan_sensitivity = self.pan_sensitivity;
let zoom_sensitivity = self.zoom_sensitivity;
let zoom_inversion = match self.zoom_inversion {
true => -1.0,
false => 1.0,
};
let mouse_y_inversion = match self.mouse_y_inversion {
true => -1.0,
false => 1.0,
};
let mut toggle_fullscreen = false;
let mut take_screenshot = false;
let mut cursor_position = None;
self.events_loop.poll_events(|event| {
// Get events for ui.
if let Some(event) = ui::Event::try_from(event.clone(), window) {
events.push(Event::Ui(event));
}
match event {
glutin::event::Event::WindowEvent { event, .. } => match event {
glutin::event::WindowEvent::CloseRequested => events.push(Event::Close),
glutin::event::WindowEvent::Resized(glutin::dpi::LogicalSize {
width,
height,
}) => {
let (mut color_view, mut depth_view) = renderer.win_views_mut();
gfx_window_glutin::update_views(window, &mut color_view, &mut depth_view);
renderer.on_resize().unwrap();
events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
},
glutin::event::WindowEvent::Moved(glutin::dpi::LogicalPosition { x, y }) => {
events.push(Event::Moved(Vec2::new(x as u32, y as u32)))
},
glutin::event::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::event::WindowEvent::MouseInput { button, state, .. } => {
if let (true, Some(game_inputs)) = (
cursor_grabbed,
Window::map_input(
KeyMouse::Mouse(button),
controls,
remapping_keybindings,
),
) {
for game_input in game_inputs {
events.push(Event::InputUpdate(
*game_input,
state == glutin::event::ElementState::Pressed,
));
}
}
events.push(Event::MouseButton(button, state));
},
glutin::event::WindowEvent::KeyboardInput { input, .. } => {
if let Some(key) = input.virtual_keycode {
if let Some(game_inputs) = Window::map_input(
KeyMouse::Key(key),
controls,
remapping_keybindings,
) {
for game_input in game_inputs {
match game_input {
GameInput::Fullscreen => {
if input.state == glutin::event::ElementState::Pressed
&& !Self::is_pressed(
keypress_map,
GameInput::Fullscreen,
)
{
toggle_fullscreen = !toggle_fullscreen;
}
Self::set_pressed(
keypress_map,
GameInput::Fullscreen,
input.state,
);
},
GameInput::Screenshot => {
take_screenshot = input.state
== glutin::event::ElementState::Pressed
&& !Self::is_pressed(
keypress_map,
GameInput::Screenshot,
);
Self::set_pressed(
keypress_map,
GameInput::Screenshot,
input.state,
);
},
_ => events.push(Event::InputUpdate(
*game_input,
input.state == glutin::event::ElementState::Pressed,
)),
}
}
}
}
},
glutin::event::WindowEvent::Focused(state) => {
*focused = state;
events.push(Event::Focused(state));
},
glutin::event::WindowEvent::CursorMoved { position, .. } => {
cursor_position = Some(position);
},
_ => {},
},
glutin::event::Event::DeviceEvent { event, .. } => match event {
glutin::event::DeviceEvent::MouseMotion {
delta: (dx, dy), ..
} if *focused => {
let delta = Vec2::new(
dx as f32 * (pan_sensitivity as f32 / 100.0),
dy as f32 * (pan_sensitivity as f32 * mouse_y_inversion / 100.0),
);
if cursor_grabbed {
events.push(Event::CursorPan(delta));
} else {
events.push(Event::CursorMove(delta));
}
},
glutin::event::DeviceEvent::MouseWheel { delta, .. }
if cursor_grabbed && *focused =>
{
events.push(Event::Zoom({
// Since scrolling apparently acts different depending on platform
#[cfg(target_os = "windows")]
const PLATFORM_FACTOR: f32 = -4.0;
#[cfg(not(target_os = "windows"))]
const PLATFORM_FACTOR: f32 = 1.0;
let y = match delta {
glutin::event::MouseScrollDelta::LineDelta(_x, y) => y,
// TODO: Check to see if there is a better way to find the "line
// height" than just hardcoding 16.0 pixels. Alternately we could
// get rid of this and have the user set zoom sensitivity, since
// it's unlikely people would expect a configuration file to work
// across operating systems.
glutin::event::MouseScrollDelta::PixelDelta(pos) => {
(pos.y / 16.0) as f32
},
};
y * (zoom_sensitivity as f32 / 100.0) * zoom_inversion * PLATFORM_FACTOR
}))
}
_ => {},
},
_ => {},
}
});
if let Some(pos) = cursor_position {
self.cursor_position = pos;
}
if take_screenshot {
self.take_screenshot(&settings);
}
if toggle_fullscreen {
self.toggle_fullscreen(settings);
}
if let Some(gilrs) = &mut self.gilrs {
while let Some(event) = gilrs.next_event() {
fn handle_buttons(
@ -814,7 +649,7 @@ impl Window {
| EventType::ButtonRepeated(button, code) => {
handle_buttons(
&self.controller_settings,
&mut events,
&mut self.events,
&Button::from((button, code)),
true,
);
@ -822,7 +657,7 @@ impl Window {
EventType::ButtonReleased(button, code) => {
handle_buttons(
&self.controller_settings,
&mut events,
&mut self.events,
&Button::from((button, code)),
false,
);
@ -871,17 +706,17 @@ impl Window {
for action in actions {
match *action {
AxisGameAction::MovementX => {
events.push(Event::AnalogGameInput(
self.events.push(Event::AnalogGameInput(
AnalogGameInput::MovementX(value),
));
},
AxisGameAction::MovementY => {
events.push(Event::AnalogGameInput(
self.events.push(Event::AnalogGameInput(
AnalogGameInput::MovementY(value),
));
},
AxisGameAction::CameraX => {
events.push(Event::AnalogGameInput(
self.events.push(Event::AnalogGameInput(
AnalogGameInput::CameraX(
value
* self.controller_settings.pan_sensitivity
@ -891,7 +726,7 @@ impl Window {
));
},
AxisGameAction::CameraY => {
events.push(Event::AnalogGameInput(
self.events.push(Event::AnalogGameInput(
AnalogGameInput::CameraY(
value
* self.controller_settings.pan_sensitivity
@ -912,22 +747,22 @@ impl Window {
for action in actions {
match *action {
AxisMenuAction::MoveX => {
events.push(Event::AnalogMenuInput(
self.events.push(Event::AnalogMenuInput(
AnalogMenuInput::MoveX(value),
));
},
AxisMenuAction::MoveY => {
events.push(Event::AnalogMenuInput(
self.events.push(Event::AnalogMenuInput(
AnalogMenuInput::MoveY(value),
));
},
AxisMenuAction::ScrollX => {
events.push(Event::AnalogMenuInput(
self.events.push(Event::AnalogMenuInput(
AnalogMenuInput::ScrollX(value),
));
},
AxisMenuAction::ScrollY => {
events.push(Event::AnalogMenuInput(
self.events.push(Event::AnalogMenuInput(
AnalogMenuInput::ScrollY(value),
));
},
@ -945,7 +780,8 @@ impl Window {
// Mouse emulation for the menus, to be removed when a proper menu navigation
// system is available
if !self.cursor_grabbed {
events = events
self.events = self
.events
.into_iter()
.filter_map(|event| match event {
Event::AnalogMenuInput(input) => match input {
@ -981,23 +817,186 @@ impl Window {
_ => Some(event),
})
.collect();
let sensitivity = self.controller_settings.mouse_emulation_sensitivity;
if self.mouse_emulation_vec != Vec2::zero() {
self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32)
.unwrap_or(());
}
// TODO: make this independent of framerate
self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32);
}
std::mem::replace(&mut self.events, Vec::new())
}
pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) {
use winit::event::DeviceEvent;
let mouse_y_inversion = match self.mouse_y_inversion {
true => -1.0,
false => 1.0,
};
match event {
DeviceEvent::MouseMotion {
delta: (dx, dy), ..
} if self.focused => {
let delta = Vec2::new(
dx as f32 * (self.pan_sensitivity as f32 / 100.0),
dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0),
);
if self.cursor_grabbed {
self.events.push(Event::CursorPan(delta));
} else {
self.events.push(Event::CursorMove(delta));
}
},
DeviceEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => {
self.events.push(Event::Zoom({
// Since scrolling apparently acts different depending on platform
#[cfg(target_os = "windows")]
const PLATFORM_FACTOR: f32 = -4.0;
#[cfg(not(target_os = "windows"))]
const PLATFORM_FACTOR: f32 = 1.0;
let y = match delta {
winit::event::MouseScrollDelta::LineDelta(_x, y) => y,
// TODO: Check to see if there is a better way to find the "line
// height" than just hardcoding 16.0 pixels. Alternately we could
// get rid of this and have the user set zoom sensitivity, since
// it's unlikely people would expect a configuration file to work
// across operating systems.
winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32,
};
y * (self.zoom_sensitivity as f32 / 100.0)
* if self.zoom_inversion { -1.0 } else { 1.0 }
* PLATFORM_FACTOR
}))
},
_ => {},
}
}
pub fn handle_window_event(
&mut self,
event: winit::event::WindowEvent,
settings: &mut Settings,
) {
use winit::event::WindowEvent;
let controls = &mut settings.controls;
// TODO: these used to be used to deduplicate events which they no longer do
// this needs to be handled elsewhere
let mut toggle_fullscreen = false;
let mut take_screenshot = false;
match event {
WindowEvent::CloseRequested => self.events.push(Event::Close),
WindowEvent::Resized(winit::dpi::LogicalSize { width, height }) => {
let (mut color_view, mut depth_view) = self.renderer.win_views_mut();
gfx_window_glutin::update_views(&self.window, &mut color_view, &mut depth_view);
self.renderer.on_resize().unwrap();
self.events
.push(Event::Resize(Vec2::new(width as u32, height as u32)));
},
WindowEvent::ReceivedCharacter(c) => self.events.push(Event::Char(c)),
WindowEvent::MouseInput { button, state, .. } => {
if let (true, Some(game_inputs)) =
// Mouse input not mapped to input if it is not grabbed
(
self.cursor_grabbed,
Window::map_input(
KeyMouse::Mouse(button),
controls,
&mut self.remapping_keybindings,
),
) {
for game_input in game_inputs {
self.events.push(Event::InputUpdate(
*game_input,
state == winit::event::ElementState::Pressed,
));
}
}
self.events.push(Event::MouseButton(button, state));
},
WindowEvent::KeyboardInput { input, .. } => {
if let Some(key) = input.virtual_keycode {
if let Some(game_inputs) = Window::map_input(
KeyMouse::Key(key),
controls,
&mut self.remapping_keybindings,
) {
for game_input in game_inputs {
match game_input {
GameInput::Fullscreen => {
if input.state == winit::event::ElementState::Pressed
&& !Self::is_pressed(
&mut self.keypress_map,
GameInput::Fullscreen,
)
{
toggle_fullscreen = !toggle_fullscreen;
}
Self::set_pressed(
&mut self.keypress_map,
GameInput::Fullscreen,
input.state,
);
},
GameInput::Screenshot => {
take_screenshot = input.state
== winit::event::ElementState::Pressed
&& !Self::is_pressed(
&mut self.keypress_map,
GameInput::Screenshot,
);
Self::set_pressed(
&mut self.keypress_map,
GameInput::Screenshot,
input.state,
);
},
_ => self.events.push(Event::InputUpdate(
*game_input,
input.state == winit::event::ElementState::Pressed,
)),
}
}
}
}
},
WindowEvent::Focused(state) => {
self.focused = state;
self.events.push(Event::Focused(state));
},
WindowEvent::CursorMoved { position, .. } => {
self.cursor_position = position;
},
_ => {},
}
if take_screenshot {
self.take_screenshot(&settings);
}
if toggle_fullscreen {
self.toggle_fullscreen(settings);
}
events
}
/// Moves cursor by an offset
pub fn offset_cursor(&self, d: Vec2<f32>) -> Result<(), String> {
self.window
.window()
.set_cursor_position(winit::dpi::LogicalPosition::new(
d.x as f64 + self.cursor_position.x,
d.y as f64 + self.cursor_position.y,
))
pub fn offset_cursor(&self, d: Vec2<f32>) {
if d != Vec2::zero() {
if let Err(err) =
self.window
.window()
.set_cursor_position(winit::dpi::LogicalPosition::new(
d.x as f64 + self.cursor_position.x,
d.y as f64 + self.cursor_position.y,
))
{
error!("Error setting cursor position: {:?}", err);
}
}
}
pub fn swap_buffers(&self) -> Result<(), Error> {
@ -1051,6 +1050,8 @@ impl Window {
));
}
pub fn send_event(&mut self, event: Event) { self.events.push(event) }
pub fn send_supplement_event(&mut self, event: Event) { self.supplement_events.push(event) }
pub fn take_screenshot(&mut self, settings: &Settings) {
@ -1090,19 +1091,19 @@ impl Window {
}
fn is_pressed(
map: &mut HashMap<GameInput, glutin::event::ElementState>,
map: &mut HashMap<GameInput, winit::event::ElementState>,
input: GameInput,
) -> bool {
*(map
.entry(input)
.or_insert(glutin::event::ElementState::Released))
== glutin::event::ElementState::Pressed
.or_insert(winit::event::ElementState::Released))
== winit::event::ElementState::Pressed
}
fn set_pressed(
map: &mut HashMap<GameInput, glutin::event::ElementState>,
map: &mut HashMap<GameInput, winit::event::ElementState>,
input: GameInput,
state: glutin::event::ElementState,
state: winit::event::ElementState,
) {
map.insert(input, state);
}
@ -1117,6 +1118,7 @@ impl Window {
remapping: &mut Option<GameInput>,
) -> Option<impl Iterator<Item = &'a GameInput>> {
match *remapping {
// TODO: save settings
Some(game_input) => {
controls.modify_binding(game_input, key_mouse);
*remapping = None;