1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#![deny(unsafe_code)]
#![allow(incomplete_features)]
#![allow(
    clippy::identity_op,
    clippy::option_map_unit_fn,
    clippy::needless_pass_by_ref_mut //until we find a better way for specs
)]
#![deny(clippy::clone_on_ref_ptr)]
#![feature(
    extract_if,
    trait_alias,
    option_get_or_insert_default,
    map_try_insert,
    slice_as_chunks,
    let_chains,
    generic_const_exprs,
    maybe_uninit_uninit_array,
    maybe_uninit_array_assume_init,
    closure_lifetime_binder
)]
#![recursion_limit = "2048"]

#[macro_use]
pub mod ui;
pub mod audio;
pub mod cli;
pub mod cmd;
pub mod controller;
mod credits;
#[cfg(feature = "discord")] pub mod discord;
mod ecs;
pub mod error;
pub mod game_input;
pub mod hud;
pub mod key_state;
pub mod menu;
pub mod mesh;
pub mod panic_handler;
pub mod profile;
pub mod render;
pub mod run;
pub mod scene;
pub mod session;
pub mod settings;
#[cfg(feature = "singleplayer")]
pub mod singleplayer;
pub mod window;

#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::SingleplayerState;
#[cfg(feature = "egui-ui")]
use crate::ui::egui::EguiState;
use crate::{
    audio::AudioFrontend,
    profile::Profile,
    render::{Drawer, GlobalsBindGroup},
    settings::Settings,
    window::{Event, Window},
};
use common::clock::Clock;
use common_base::span;
use i18n::LocalizationHandle;
use std::path::PathBuf;

use std::sync::Arc;
use tokio::runtime::Runtime;

/// A type used to store state that is shared between all play states.
pub struct GlobalState {
    pub userdata_dir: PathBuf,
    pub config_dir: PathBuf,
    pub settings: Settings,
    pub profile: Profile,
    pub window: Window,
    pub tokio_runtime: Arc<Runtime>,
    #[cfg(feature = "egui-ui")]
    pub egui_state: EguiState,
    pub lazy_init: scene::terrain::SpriteRenderContextLazy,
    pub audio: AudioFrontend,
    pub info_message: Option<String>,
    pub clock: Clock,
    #[cfg(feature = "singleplayer")]
    pub singleplayer: SingleplayerState,
    // TODO: redo this so that the watcher doesn't have to exist for reloading to occur
    pub i18n: LocalizationHandle,
    pub clipboard: iced_winit::Clipboard,
    // Used to clear the shadow textures when entering a PlayState that doesn't utilise shadows
    pub clear_shadows_next_frame: bool,
    /// CLI arguments passed to voxygen
    pub args: crate::cli::Args,
    /// A channel that sends Discord activity updates to a background task
    #[cfg(feature = "discord")]
    pub discord: crate::discord::Discord,
}

impl GlobalState {
    /// Called after a change in play state has occurred (usually used to
    /// reverse any temporary effects a state may have made).
    pub fn on_play_state_changed(&mut self) {
        self.window.grab_cursor(false);
        self.window.needs_refresh_resize();
    }

    pub fn maintain(&mut self) {
        span!(_guard, "maintain", "GlobalState::maintain");
        self.audio.maintain();
        self.window.renderer().maintain()
    }

    #[cfg(feature = "singleplayer")]
    pub fn paused(&self) -> bool {
        self.singleplayer
            .as_running()
            .map_or(false, Singleplayer::is_paused)
    }

    #[cfg(not(feature = "singleplayer"))]
    pub fn paused(&self) -> bool { false }

    #[cfg(feature = "singleplayer")]
    pub fn unpause(&self) { self.singleplayer.as_running().map(|s| s.pause(false)); }

    #[cfg(feature = "singleplayer")]
    pub fn pause(&self) { self.singleplayer.as_running().map(|s| s.pause(true)); }
}

// TODO: appears to be currently unused by playstates
pub enum Direction {
    Forwards,
    Backwards,
}

/// 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.
    Pop,
    /// Push a new play state onto the play state stack.
    Push(Box<dyn PlayState>),
    /// Switch the current play state with a new play state.
    Switch(Box<dyn PlayState>),
}

/// A trait representing a playable game state. This may be a menu, a game
/// session, the title screen, etc.
pub trait PlayState {
    /// 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;

    /// Determines whether the play state should have an enforced FPS cap
    fn capped_fps(&self) -> bool;

    fn globals_bind_group(&self) -> &GlobalsBindGroup;

    /// Draw the play state.
    fn render(&self, drawer: &mut Drawer<'_>, settings: &Settings);

    /// Determines whether egui will be rendered for this play state
    fn egui_enabled(&self) -> bool;
}