mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Rearrange PlayState system to work without loop control
This commit is contained in:
parent
515dbc30a7
commit
d1b635efa4
102
Cargo.lock
generated
102
Cargo.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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" }
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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
142
voxygen/src/run.rs
Normal 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();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user