//! Server commands, typed into the server console. use bevy::prelude::*; use bevy_replicon::prelude::*; use game_core::{components::Character, protocol::GameLog}; use crate::{config, persistence, players::Players}; /// Who issued a command. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CommandSource { /// The dedicated server's terminal. Console, } pub const HELP: &[(&str, &str)] = &[ ("help", "list commands"), ("players", "list online players"), ("say MESSAGE", "message all players"), ("save", "save the world now"), ( "config get KEY", "show a setting, e.g. `config get access.whitelist`", ), ( "config set KEY VALUE", "change a setting and write it to the config file", ), ("stop", "save and shut down the server"), ]; /// Runs a command line and returns the reply. pub fn execute(world: &mut World, _source: CommandSource, line: &str) -> Vec { let line = line.trim().trim_start_matches('/'); let (command, rest) = line.split_once(' ').unwrap_or((line, "")); let rest = rest.trim(); match command { "" => vec![], "help" => HELP .iter() .map(|(usage, what)| format!("{usage:<22} {what}")) .collect(), "players" => { let names: Vec = world .query::<&Character>() .iter(world) .map(|c| c.name.clone()) .collect(); let count = world.resource::().by_client.len(); vec![format!("{count} online: {}", names.join(", "))] } "say" if !rest.is_empty() => { let text = format!("[server] {rest}"); world.commands().server_trigger(ToClients { targets: SendTargets::All, message: GameLog { text: text.clone() }, }); world.flush(); vec![text] } "save" => { if world.contains_resource::() { persistence::save_world_blocking(world); vec!["World saved.".into()] } else { vec!["Saving is disabled.".into()] } } "config" => config_command(world, rest), "stop" => { world.write_message(AppExit::Success); vec!["Shutting down.".into()] } _ => vec![format!( "Unknown command `{command}`. Type `help` for a list." )], } } fn config_command(world: &mut World, args: &str) -> Vec { let mut parts = args.splitn(3, ' '); match (parts.next(), parts.next(), parts.next()) { (Some("get"), Some(key), None) => match world.resource::().get(key) { Ok(value) => vec![format!("{key} = {value}")], Err(e) => vec![e.to_string()], }, (Some("set"), Some(key), Some(value)) => match config::set_runtime(world, key, value) { Ok(note) => { let value = world .resource::() .get(key) .unwrap_or_default(); let mut reply = format!("{key} = {value}"); if let Some(note) = note { reply.push_str(&format!(" ({note})")); } vec![reply] } Err(e) => vec![e.to_string()], }, _ => vec!["Usage: config get KEY | config set KEY VALUE".into()], } }