station/crates/game_server/src/lib.rs

132 lines
4.3 KiB
Rust

//! Authoritative game simulation.
//!
//! Used by the dedicated server binary and embedded in the client for
//! singleplayer and hosting. Clients only send intents (see
//! [`game_core::protocol`]); all game rules run here.
pub mod actions;
pub mod commands;
pub mod config;
pub mod console;
pub mod crafting;
pub mod embedded;
pub mod machines;
pub mod movement;
pub mod network;
pub mod persistence;
pub mod players;
pub mod scripting;
pub mod survival;
pub mod world;
use std::{sync::Arc, time::Duration};
use bevy::prelude::*;
use game_core::GameData;
pub use network::NetworkServerPlugin;
/// Configuration of the simulation.
#[derive(Resource, Clone, Debug)]
pub struct ServerSettings {
/// Tiled map, relative to the assets directory.
pub map: String,
/// Database and world to persist to. `None` disables saving.
pub storage: Option<persistence::StorageSettings>,
pub autosave_interval: Duration,
/// Player id that always has admin rights and bypasses access restrictions
/// (the host of an embedded server).
pub owner: Option<String>,
}
impl Default for ServerSettings {
fn default() -> Self {
Self {
map: game_core::DEFAULT_MAP.to_owned(),
storage: None,
autosave_interval: Duration::from_secs(60),
owner: None,
}
}
}
/// Runs the game world. Requires [`game_core::CorePlugin`] and the replicon plugins.
pub struct GameServerPlugin {
pub settings: ServerSettings,
}
impl Plugin for GameServerPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(self.settings.clone())
.insert_resource(persistence::AutosaveTimer(Timer::new(
self.settings.autosave_interval,
TimerMode::Repeating,
)))
.init_resource::<config::ServerConfig>()
.init_resource::<players::Players>()
.init_resource::<players::CharacterStore>()
.init_resource::<players::RejectedLogins>()
.init_resource::<survival::SurvivalTimer>()
.add_systems(
Startup,
(
load_scripts,
world::setup_world.run_if(not(resource_exists::<ServerError>)),
)
.chain(),
)
.add_systems(
FixedUpdate,
(
movement::move_characters,
survival::tick_survival,
machines::tick_machines,
)
.run_if(resource_exists::<world::WorldLayout>),
)
.add_systems(
Update,
(
persistence::autosave,
players::enforce_access.run_if(resource_changed::<config::ServerConfig>),
),
)
.add_systems(Last, persistence::save_on_exit)
.add_observer(players::on_login)
.add_observer(players::on_disconnect)
.add_observer(players::on_chat)
.add_observer(movement::on_set_move_dir)
.add_observer(actions::on_pick_up)
.add_observer(actions::on_drop_item)
.add_observer(actions::on_use_item)
.add_observer(actions::on_interact)
.add_observer(actions::on_craft)
.add_observer(actions::on_construct)
.add_observer(actions::on_deconstruct)
.add_observer(machines::on_machine_insert)
.add_observer(machines::on_machine_take);
}
}
/// Why the server failed to start. Inserted together with an error [`AppExit`].
#[derive(Resource, Clone, Debug)]
pub struct ServerError(pub String);
/// Reports a fatal startup error and requests exit.
pub fn fail(commands: &mut Commands, message: String) {
error!("{message}");
commands.insert_resource(ServerError(message));
commands.write_message(AppExit::error());
}
fn load_scripts(mut commands: Commands, data: Res<GameData>) {
let dir = game_core::asset_path("scripts");
let engine = match scripting::ScriptEngine::load(&dir, &data) {
Ok(engine) => engine,
Err(e) => return fail(&mut commands, e.to_string()),
};
info!("compiled scripts from `{}`", dir.display());
commands.insert_resource(engine);
commands.insert_resource(actions::SharedData(Arc::new(data.clone())));
}