104 lines
3.4 KiB
Rust
104 lines
3.4 KiB
Rust
|
|
//! 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<String> {
|
||
|
|
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<String> = world
|
||
|
|
.query::<&Character>()
|
||
|
|
.iter(world)
|
||
|
|
.map(|c| c.name.clone())
|
||
|
|
.collect();
|
||
|
|
let count = world.resource::<Players>().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::Storage>() {
|
||
|
|
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<String> {
|
||
|
|
let mut parts = args.splitn(3, ' ');
|
||
|
|
match (parts.next(), parts.next(), parts.next()) {
|
||
|
|
(Some("get"), Some(key), None) => match world.resource::<config::ServerConfig>().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::<config::ServerConfig>()
|
||
|
|
.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()],
|
||
|
|
}
|
||
|
|
}
|