//! Headless dedicated server. //! //! ```text //! game-server [--port 5000] [--database URL | --no-save] [--world NAME|UUID] [--map maps/station.tmx] //! ``` use std::time::Duration; use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, prelude::*, state::app::StatesPlugin}; use bevy_replicon::prelude::*; use bevy_replicon_renet::RepliconRenetPlugins; use game_core::CorePlugin; use game_server::{ GameServerPlugin, NetworkServerPlugin, ServerSettings, persistence::{StorageSettings, WorldSelector}, }; const DEFAULT_DATABASE: &str = "sqlite://saves/server.sqlite"; const DEFAULT_WORLD: &str = "main"; fn main() -> AppExit { let mut port = game_core::DEFAULT_PORT; let mut settings = ServerSettings::default(); let mut database = Some(DEFAULT_DATABASE.to_owned()); let mut world = WorldSelector::Name(DEFAULT_WORLD.to_owned()); let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { let mut value = |name: &str| { args.next() .unwrap_or_else(|| usage(&format!("{name} needs a value"))) }; match arg.as_str() { "--port" => { port = value("--port") .parse() .unwrap_or_else(|_| usage("invalid port")) } "--database" => database = Some(value("--database")), "--world" => world = WorldSelector::parse(&value("--world")), "--no-save" => database = None, "--map" => settings.map = value("--map"), "-h" | "--help" => usage(""), other => usage(&format!("unknown argument `{other}`")), } } settings.storage = database.map(|database_url| StorageSettings { database_url, world, }); App::new() .add_plugins(( MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64( 1.0 / 60.0, ))), StatesPlugin, LogPlugin::default(), bevy::app::TerminalCtrlCHandlerPlugin, RepliconPlugins, RepliconRenetPlugins, CorePlugin, GameServerPlugin { settings }, NetworkServerPlugin { port }, )) .run() } fn usage(error: &str) -> ! { if !error.is_empty() { eprintln!("error: {error}\n"); } eprintln!( "usage: game-server [--port PORT] [--database URL | --no-save] [--world NAME|UUID] [--map MAP]\n\n\ --port UDP port to listen on (default {})\n\ --database sqlite:// or postgres:// URL (default {DEFAULT_DATABASE})\n\ --world world name or UUID in the database, created if missing (default {DEFAULT_WORLD})\n\ --no-save don't persist the world\n\ --map Tiled map relative to the assets directory (default {})", game_core::DEFAULT_PORT, game_core::DEFAULT_MAP ); std::process::exit(if error.is_empty() { 0 } else { 2 }) }