Server server::cmd depends on common::cmd

This commit is contained in:
CapsizeGlimmer 2020-05-05 18:33:16 -04:00
parent 307e478671
commit 7ecea34f85
3 changed files with 440 additions and 353 deletions

View File

@ -1,14 +1,10 @@
use crate::{assets, comp::Player, state::State};
use lazy_static::lazy_static;
use specs::prelude::{Join, WorldExt};
/// Struct representing a command that a user can run from server chat.
pub struct ChatCommand {
/// The keyword used to invoke the command, omitting the leading '/'.
pub keyword: &'static str,
pub struct ChatCommandData {
/// A format string for parsing arguments.
pub args: Vec<ArgumentSyntax>,
pub args: Vec<ArgumentSpec>,
/// A one-line message that explains what the command does
pub description: &'static str,
/// A boolean that is used to check whether the command requires
@ -16,15 +12,9 @@ pub struct ChatCommand {
pub needs_admin: bool,
}
impl ChatCommand {
pub fn new(
keyword: &'static str,
args: Vec<ArgumentSyntax>,
description: &'static str,
needs_admin: bool,
) -> Self {
impl ChatCommandData {
pub fn new(args: Vec<ArgumentSpec>, description: &'static str, needs_admin: bool) -> Self {
Self {
keyword,
args,
description,
needs_admin,
@ -32,17 +22,267 @@ impl ChatCommand {
}
}
lazy_static! {
static ref CHAT_COMMANDS: Vec<ChatCommand> = {
use ArgumentSyntax::*;
vec![
ChatCommand::new("help", vec![Command(true)], "Display information about commands", false),
]
};
// Please keep this sorted alphabetically :-)
#[derive(Copy, Clone)]
pub enum ChatCommand {
Adminify,
Alias,
Build,
Debug,
DebugColumn,
Explosion,
GiveExp,
GiveItem,
Goto,
Health,
Help,
Jump,
Kill,
KillNpcs,
Lantern,
Light,
Object,
Players,
RemoveLights,
SetLevel,
Spawn,
Sudo,
Tell,
Time,
Tp,
Version,
Waypoint,
}
// Thank you for keeping this sorted alphabetically :-)
pub static CHAT_COMMANDS: &'static [ChatCommand] = &[
ChatCommand::Adminify,
ChatCommand::Alias,
ChatCommand::Build,
ChatCommand::Debug,
ChatCommand::DebugColumn,
ChatCommand::Explosion,
ChatCommand::GiveExp,
ChatCommand::GiveItem,
ChatCommand::Goto,
ChatCommand::Health,
ChatCommand::Help,
ChatCommand::Jump,
ChatCommand::Kill,
ChatCommand::KillNpcs,
ChatCommand::Lantern,
ChatCommand::Light,
ChatCommand::Object,
ChatCommand::Players,
ChatCommand::RemoveLights,
ChatCommand::SetLevel,
ChatCommand::Spawn,
ChatCommand::Sudo,
ChatCommand::Tell,
ChatCommand::Time,
ChatCommand::Tp,
ChatCommand::Version,
ChatCommand::Waypoint,
];
impl ChatCommand {
pub fn data(&self) -> ChatCommandData {
use ArgumentSpec::*;
let cmd = ChatCommandData::new;
match self {
ChatCommand::Adminify => cmd(
vec![PlayerName(false)],
"Temporarily gives a player admin permissions or removes them",
true,
),
ChatCommand::Alias => cmd(vec![Any("name", false)], "Change your alias", false),
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", true),
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", true),
ChatCommand::DebugColumn => cmd(
vec![Float("x", f32::NAN, false), Float("y", f32::NAN, false)],
"Prints some debug information about a column",
false,
),
ChatCommand::Explosion => cmd(
vec![Float("radius", 5.0, false)],
"Explodes the ground around you",
true,
),
ChatCommand::GiveExp => cmd(
vec![Integer("amount", 50, false)],
"Give experience to yourself",
true,
),
ChatCommand::GiveItem => cmd(
vec![ItemSpec(false), Integer("num", 1, true)],
"Give yourself some items",
true,
),
ChatCommand::Goto => cmd(
vec![
Float("x", 0.0, false),
Float("y", 0.0, false),
Float("z", 0.0, false),
],
"Teleport to a position",
true,
),
ChatCommand::Health => cmd(
vec![Integer("hp", 100, false)],
"Set your current health",
true,
),
ChatCommand::Help => ChatCommandData::new(
vec![Command(true)],
"Display information about commands",
false,
),
ChatCommand::Jump => cmd(
vec![
Float("x", 0.0, false),
Float("y", 0.0, false),
Float("z", 0.0, false),
],
"Offset your current position",
true,
),
ChatCommand::Kill => cmd(vec![], "Kill yourself", false),
ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", true),
ChatCommand::Lantern => cmd(
vec![
Float("strength", 5.0, false),
Float("r", 1.0, true),
Float("g", 1.0, true),
Float("b", 1.0, true),
],
"Change your lantern's strength and color",
true,
),
ChatCommand::Light => cmd(
vec![
Float("r", 1.0, true),
Float("g", 1.0, true),
Float("b", 1.0, true),
Float("x", 0.0, true),
Float("y", 0.0, true),
Float("z", 0.0, true),
Float("strength", 5.0, true),
],
"Spawn entity with light",
true,
),
ChatCommand::Object => cmd(vec![/*TODO*/], "Spawn an object", true),
ChatCommand::Players => cmd(vec![], "Lists players currently online", false),
ChatCommand::RemoveLights => cmd(
vec![Float("radius", 20.0, true)],
"Removes all lights spawned by players",
true,
),
ChatCommand::SetLevel => {
cmd(vec![Integer("level", 10, false)], "Set player Level", true)
},
ChatCommand::Spawn => cmd(vec![/*TODO*/], "Spawn a test entity", true),
ChatCommand::Sudo => cmd(
vec![PlayerName(false), Command(false), Message /* TODO */],
"Run command as if you were another player",
true,
),
ChatCommand::Tell => cmd(
vec![PlayerName(false), Message],
"Send a message to another player",
false,
),
ChatCommand::Time => cmd(vec![/*TODO*/], "Set the time of day", true),
ChatCommand::Tp => cmd(vec![PlayerName(true)], "Teleport to another player", true),
ChatCommand::Version => cmd(vec![], "Prints server version", false),
ChatCommand::Waypoint => {
cmd(vec![], "Set your waypoint to your current position", true)
},
}
}
pub fn keyword(&self) -> &'static str {
match self {
ChatCommand::Adminify => "adminify",
ChatCommand::Alias => "alias",
ChatCommand::Build => "build",
ChatCommand::Debug => "debug",
ChatCommand::DebugColumn => "debug_column",
ChatCommand::Explosion => "explosion",
ChatCommand::GiveExp => "give_exp",
ChatCommand::GiveItem => "give_item",
ChatCommand::Goto => "goto",
ChatCommand::Health => "health",
ChatCommand::Help => "help",
ChatCommand::Jump => "jump",
ChatCommand::Kill => "kill",
ChatCommand::KillNpcs => "kill_npcs",
ChatCommand::Lantern => "lantern",
ChatCommand::Light => "light",
ChatCommand::Object => "object",
ChatCommand::Players => "players",
ChatCommand::RemoveLights => "remove_lights",
ChatCommand::SetLevel => "set_level",
ChatCommand::Spawn => "spawn",
ChatCommand::Sudo => "sudo",
ChatCommand::Tell => "tell",
ChatCommand::Time => "time",
ChatCommand::Tp => "tp",
ChatCommand::Version => "version",
ChatCommand::Waypoint => "waypoint",
}
}
pub fn help_string(&self) -> String {
let data = self.data();
let usage = std::iter::once(format!("/{}", self.keyword()))
.chain(data.args.iter().map(|arg| arg.usage_string()))
.collect::<Vec<_>>()
.join(" ");
format!("{}: {}", usage, data.description)
}
pub fn needs_admin(&self) -> bool { self.data().needs_admin }
pub fn arg_fmt(&self) -> String {
self.data()
.args
.iter()
.map(|arg| match arg {
ArgumentSpec::PlayerName(_) => "{}",
ArgumentSpec::ItemSpec(_) => "{}",
ArgumentSpec::Float(_, _, _) => "{f}",
ArgumentSpec::Integer(_, _, _) => "{d}",
ArgumentSpec::Any(_, _) => "{}",
ArgumentSpec::Command(_) => "{}",
ArgumentSpec::Message => "{/.*/}",
ArgumentSpec::OneOf(_, _, _, _) => "{}", // TODO
})
.collect::<Vec<_>>()
.join(" ")
}
}
impl std::str::FromStr for ChatCommand {
type Err = ();
fn from_str(keyword: &str) -> Result<ChatCommand, ()> {
let kwd = if keyword.chars().next() == Some('/') {
&keyword[1..]
} else {
&keyword[..]
};
for c in CHAT_COMMANDS {
if kwd == c.keyword() {
return Ok(*c);
}
}
return Err(());
}
}
/// Representation for chat command arguments
pub enum ArgumentSyntax {
pub enum ArgumentSpec {
/// The argument refers to a player by alias
PlayerName(bool),
/// The argument refers to an item asset by path
@ -56,61 +296,74 @@ pub enum ArgumentSyntax {
/// * label
/// * default tab-completion
/// * whether it's optional
Integer(&'static str, f32, bool),
Integer(&'static str, i32, bool),
/// The argument is any string that doesn't contain spaces
Any(&'static str, bool),
/// The argument is a command name
Command(bool),
/// This is the final argument, consuming all characters until the end of input.
/// This is the final argument, consuming all characters until the end of
/// input.
Message,
/// The argument is likely an enum. The associated values are
/// * label
/// * Predefined string completions
/// * Other completion types
/// * whether it's optional
OneOf(&'static str, &'static [&'static str], Vec<Box<ArgumentSyntax>>, bool),
OneOf(
&'static str,
&'static [&'static str],
Vec<Box<ArgumentSpec>>,
bool,
),
}
impl ArgumentSyntax {
pub fn help_string(arg: &ArgumentSyntax) -> String {
match arg {
ArgumentSyntax::PlayerName(optional) => {
impl ArgumentSpec {
pub fn usage_string(&self) -> String {
match self {
ArgumentSpec::PlayerName(optional) => {
if *optional {
"[player]".to_string()
} else {
"<player>".to_string()
}
},
ArgumentSyntax::ItemSpec(optional) => {
ArgumentSpec::ItemSpec(optional) => {
if *optional {
"[item]".to_string()
} else {
"<item>".to_string()
}
},
ArgumentSyntax::Float(label, _, optional) => {
ArgumentSpec::Float(label, _, optional) => {
if *optional {
format!("[{}]", label)
} else {
format!("<{}>", label)
}
},
ArgumentSyntax::Integer(label, _, optional) => {
ArgumentSpec::Integer(label, _, optional) => {
if *optional {
format!("[{}]", label)
} else {
format!("<{}>", label)
}
},
ArgumentSyntax::Command(optional) => {
ArgumentSpec::Any(label, optional) => {
if *optional {
format!("[{}]", label)
} else {
format!("<{}>", label)
}
},
ArgumentSpec::Command(optional) => {
if *optional {
"[[/]command]".to_string()
} else {
"<[/]command>".to_string()
}
},
ArgumentSyntax::Message => {
"<message>".to_string()
},
ArgumentSyntax::OneOf(label, _, _, optional) => {
ArgumentSpec::Message => "<message>".to_string(),
ArgumentSpec::OneOf(label, _, _, optional) => {
if *optional {
format! {"[{}]", label}
} else {
@ -122,25 +375,26 @@ impl ArgumentSyntax {
pub fn complete(&self, state: &State, part: &String) -> Vec<String> {
match self {
ArgumentSyntax::PlayerName(_) => (&state.ecs().read_storage::<Player>())
ArgumentSpec::PlayerName(_) => (&state.ecs().read_storage::<Player>())
.join()
.filter(|player| player.alias.starts_with(part))
.map(|player| player.alias.clone())
.collect(),
ArgumentSyntax::ItemSpec(_) => assets::iterate()
ArgumentSpec::ItemSpec(_) => assets::iterate()
.filter(|asset| asset.starts_with(part))
.map(|c| c.to_string())
.collect(),
ArgumentSyntax::Float(_, x, _) => vec![format!("{}", x)],
ArgumentSyntax::Integer(_, x, _) => vec![format!("{}", x)],
ArgumentSyntax::Command(_) => CHAT_COMMANDS
ArgumentSpec::Float(_, x, _) => vec![format!("{}", x)],
ArgumentSpec::Integer(_, x, _) => vec![format!("{}", x)],
ArgumentSpec::Any(_, _) => vec![],
ArgumentSpec::Command(_) => CHAT_COMMANDS
.iter()
.map(|com| com.keyword.clone())
.map(|com| com.keyword())
.filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part))
.map(|c| c.to_string())
.collect(),
ArgumentSyntax::Message => vec![],
ArgumentSyntax::OneOf(_, strings, alts, _) => {
ArgumentSpec::Message => vec![],
ArgumentSpec::OneOf(_, strings, alts, _) => {
let string_completions = strings
.iter()
.filter(|string| string.starts_with(part))
@ -150,7 +404,7 @@ impl ArgumentSyntax {
.flat_map(|b| (*b).complete(&state, part))
.map(|c| c.to_string());
string_completions.chain(alt_completions).collect()
}
},
}
}
}

View File

@ -5,7 +5,9 @@
use crate::{Server, StateExt};
use chrono::{NaiveTime, Timelike};
use common::{
assets, comp,
assets,
cmd::{ChatCommand, CHAT_COMMANDS},
comp,
event::{EventBus, ServerEvent},
msg::{PlayerListUpdate, ServerMsg},
npc::{self, get_npc_name},
@ -20,274 +22,70 @@ use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use vek::*;
use world::util::Sampler;
use lazy_static::lazy_static;
use log::error;
use scan_fmt::{scan_fmt, scan_fmt_some};
/// Struct representing a command that a user can run from server chat.
pub struct ChatCommand {
/// The keyword used to invoke the command, omitting the leading '/'.
pub keyword: &'static str,
/// A format string for parsing arguments.
arg_fmt: &'static str,
/// A message that explains how the command is used.
help_string: &'static str,
/// A boolean that is used to check whether the command requires
/// administrator permissions or not.
needs_admin: bool,
/// Handler function called when the command is executed.
/// # Arguments
/// * `&mut Server` - the `Server` instance executing the command.
/// * `EcsEntity` - an `Entity` corresponding to the player that invoked the
/// command.
/// * `EcsEntity` - an `Entity` for the player on whom the command is
/// invoked. This differs from the previous argument when using /sudo
/// * `String` - a `String` containing the part of the command after the
/// keyword.
/// * `&ChatCommand` - the command to execute with the above arguments.
/// Handler functions must parse arguments from the the given `String`
/// (`scan_fmt!` is included for this purpose).
handler: fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand),
pub trait ChatCommandExt {
fn execute(&self, server: &mut Server, entity: EcsEntity, args: String);
}
impl ChatCommand {
/// Creates a new chat command.
pub fn new(
keyword: &'static str,
arg_fmt: &'static str,
help_string: &'static str,
needs_admin: bool,
handler: fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand),
) -> Self {
Self {
keyword,
arg_fmt,
help_string,
needs_admin,
handler,
}
}
/// Calls the contained handler function, passing `&self` as the last
/// argument.
pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) {
if self.needs_admin {
if !server.entity_is_admin(entity) {
server.notify_client(
entity,
ServerMsg::private(format!(
"You don't have permission to use '/{}'.",
self.keyword
)),
);
return;
} else {
(self.handler)(server, entity, entity, args, self);
}
impl ChatCommandExt for ChatCommand {
fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) {
let cmd_data = self.data();
if cmd_data.needs_admin && !server.entity_is_admin(entity) {
server.notify_client(
entity,
ServerMsg::private(format!(
"You don't have permission to use '/{}'.",
self.keyword()
)),
);
return;
} else {
(self.handler)(server, entity, entity, args, self);
get_handler(self)(server, entity, entity, args, &self);
}
}
}
lazy_static! {
/// Static list of chat commands available to the server.
pub static ref CHAT_COMMANDS: Vec<ChatCommand> = vec![
ChatCommand::new(
"give_item",
"{} {d}",
"/give_item <path to item> [num]\n\
Example items: common/items/apple, common/items/debug/boost",
true,
handle_give,),
ChatCommand::new(
"jump",
"{d} {d} {d}",
"/jump <dx> <dy> <dz> : Offset your current position",
true,
handle_jump,
),
ChatCommand::new(
"goto",
"{d} {d} {d}",
"/goto <x> <y> <z> : Teleport to a position",
true,
handle_goto,
),
ChatCommand::new(
"alias",
"{}",
"/alias <name> : Change your alias",
false,
handle_alias,
),
ChatCommand::new(
"tp",
"{}",
"/tp <alias> : Teleport to another player",
true,
handle_tp,
),
ChatCommand::new(
"kill",
"{}",
"/kill : Kill yourself",
false,
handle_kill,
),
ChatCommand::new(
"time",
"{} {s}",
"/time <XY:XY> or [Time of day] : Set the time of day",
true,
handle_time,
),
ChatCommand::new(
"spawn",
"{} {} {d}",
"/spawn <alignment> <entity> [amount] : Spawn a test entity",
true,
handle_spawn,
),
ChatCommand::new(
"players",
"{}",
"/players : Lists players currently online",
false,
handle_players,
),
ChatCommand::new(
"help", "", "/help: Display this message", false, handle_help),
ChatCommand::new(
"health",
"{}",
"/health : Set your current health",
true,
handle_health,
),
ChatCommand::new(
"build",
"",
"/build : Toggles build mode on and off",
true,
handle_build,
),
ChatCommand::new(
"tell",
"{}",
"/tell <alias> <message>: Send a message to another player",
false,
handle_tell,
),
ChatCommand::new(
"killnpcs",
"{}",
"/killnpcs : Kill the NPCs",
true,
handle_killnpcs,
),
ChatCommand::new(
"object",
"{}",
"/object [Name]: Spawn an object",
true,
handle_object,
),
ChatCommand::new(
"light",
"{} {} {} {} {} {} {}",
"/light <opt: <<cr> <cg> <cb>> <<ox> <oy> <oz>> <<strength>>>: Spawn entity with light",
true,
handle_light,
),
ChatCommand::new(
"lantern",
"{} {} {} {}",
"/lantern <strength> [<r> <g> <b>]: Change your lantern's strength and color",
true,
handle_lantern,
),
ChatCommand::new(
"explosion",
"{}",
"/explosion <radius> : Explodes the ground around you",
true,
handle_explosion,
),
ChatCommand::new(
"waypoint",
"{}",
"/waypoint : Set your waypoint to your current position",
true,
handle_waypoint,
),
ChatCommand::new(
"adminify",
"{}",
"/adminify <playername> : Temporarily gives a player admin permissions or removes them",
true,
handle_adminify,
),
ChatCommand::new(
"debug_column",
"{} {}",
"/debug_column <x> <y> : Prints some debug information about a column",
false,
handle_debug_column,
),
ChatCommand::new(
"give_exp",
"{d} {}",
"/give_exp <amount> <playername?> : Give experience to yourself or specify a target player",
true,
handle_exp,
),
ChatCommand::new(
"set_level",
"{d} {}",
"/set_level <level> <playername?> : Set own Level or specify a target player",
true,
handle_level
),
ChatCommand::new(
"removelights",
"{}",
"/removelights [radius] : Removes all lights spawned by players",
true,
handle_remove_lights,
),
ChatCommand::new(
"debug",
"",
"/debug : Place all debug items into your pack.",
true,
handle_debug,
),
ChatCommand::new(
"sudo",
"{} {} {/.*/}",
"/sudo <player> /<command> [args...] : Run command as if you were another player",
true,
handle_sudo,
),
ChatCommand::new(
"version",
"",
"/version : Prints server version",
false,
handle_version,
),
];
type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand);
fn get_handler(cmd: &ChatCommand) -> CommandHandler {
match cmd {
ChatCommand::Adminify => handle_adminify,
ChatCommand::Alias => handle_alias,
ChatCommand::Build => handle_build,
ChatCommand::Debug => handle_debug,
ChatCommand::DebugColumn => handle_debug_column,
ChatCommand::Explosion => handle_explosion,
ChatCommand::GiveExp => handle_give_exp,
ChatCommand::GiveItem => handle_give_item,
ChatCommand::Goto => handle_goto,
ChatCommand::Health => handle_health,
ChatCommand::Help => handle_help,
ChatCommand::Jump => handle_jump,
ChatCommand::Kill => handle_kill,
ChatCommand::KillNpcs => handle_kill_npcs,
ChatCommand::Lantern => handle_lantern,
ChatCommand::Light => handle_light,
ChatCommand::Object => handle_object,
ChatCommand::Players => handle_players,
ChatCommand::RemoveLights => handle_remove_lights,
ChatCommand::SetLevel => handle_set_level,
ChatCommand::Spawn => handle_spawn,
ChatCommand::Sudo => handle_sudo,
ChatCommand::Tell => handle_tell,
ChatCommand::Time => handle_time,
ChatCommand::Tp => handle_tp,
ChatCommand::Version => handle_version,
ChatCommand::Waypoint => handle_waypoint,
}
}
fn handle_give(
fn handle_give_item(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
if let (Some(item_name), give_amount_opt) = scan_fmt_some!(&args, action.arg_fmt, String, u32) {
if let (Some(item_name), give_amount_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, u32) {
let give_amount = give_amount_opt.unwrap_or(1);
if let Ok(item) = assets::load_cloned(&item_name) {
let mut item: Item = item;
@ -346,7 +144,7 @@ fn handle_give(
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(client, ServerMsg::private(String::from(action.help_string())));
}
}
@ -357,7 +155,7 @@ fn handle_jump(
args: String,
action: &ChatCommand,
) {
if let Ok((x, y, z)) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32) {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
match server.state.read_component_cloned::<comp::Pos>(target) {
Some(current_pos) => {
server
@ -380,7 +178,7 @@ fn handle_goto(
args: String,
action: &ChatCommand,
) {
if let Ok((x, y, z)) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32) {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
if server
.state
.read_component_cloned::<comp::Pos>(target)
@ -397,7 +195,10 @@ fn handle_goto(
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -432,7 +233,7 @@ fn handle_time(
args: String,
action: &ChatCommand,
) {
let time = scan_fmt_some!(&args, action.arg_fmt, String);
let time = scan_fmt_some!(&args, &action.arg_fmt(), String);
let new_time = match time.as_ref().map(|s| s.as_str()) {
Some("midnight") => NaiveTime::from_hms(0, 0, 0),
Some("night") => NaiveTime::from_hms(20, 0, 0),
@ -490,7 +291,7 @@ fn handle_health(
args: String,
action: &ChatCommand,
) {
if let Ok(hp) = scan_fmt!(&args, action.arg_fmt, u32) {
if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) {
if let Some(stats) = server
.state
.ecs()
@ -519,7 +320,7 @@ fn handle_alias(
args: String,
action: &ChatCommand,
) {
if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) {
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
server
.state
.ecs_mut()
@ -540,7 +341,10 @@ fn handle_alias(
server.state.notify_registered_clients(msg);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -551,7 +355,7 @@ fn handle_tp(
args: String,
action: &ChatCommand,
) {
if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) {
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
@ -576,7 +380,7 @@ fn handle_tp(
);
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string)),
ServerMsg::private(String::from(action.help_string())),
);
},
},
@ -585,7 +389,10 @@ fn handle_tp(
},
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -596,7 +403,7 @@ fn handle_spawn(
args: String,
action: &ChatCommand,
) {
match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) {
match scan_fmt_some!(&args, &action.arg_fmt(), String, npc::NpcBody, String) {
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => {
if let Some(alignment) = parse_alignment(target, &opt_align) {
let amount = opt_amount
@ -659,7 +466,10 @@ fn handle_spawn(
}
},
_ => {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
},
}
}
@ -733,12 +543,16 @@ fn handle_help(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_action: &ChatCommand,
args: String,
action: &ChatCommand,
) {
for cmd in CHAT_COMMANDS.iter() {
if !cmd.needs_admin || server.entity_is_admin(client) {
server.notify_client(client, ServerMsg::private(String::from(cmd.help_string)));
if let Some(cmd) = scan_fmt_some!(&args, &action.arg_fmt(), ChatCommand) {
server.notify_client(client, ServerMsg::private(String::from(cmd.help_string())));
} else {
for cmd in CHAT_COMMANDS.iter() {
if !cmd.needs_admin() || server.entity_is_admin(client) {
server.notify_client(client, ServerMsg::private(String::from(cmd.help_string())));
}
}
}
}
@ -753,7 +567,7 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option<comp::Alignment>
}
}
fn handle_killnpcs(
fn handle_kill_npcs(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
@ -781,9 +595,9 @@ fn handle_object(
client: EcsEntity,
target: EcsEntity,
args: String,
_action: &ChatCommand,
action: &ChatCommand,
) {
let obj_type = scan_fmt!(&args, _action.arg_fmt, String);
let obj_type = scan_fmt!(&args, &action.arg_fmt(), String);
let pos = server
.state
@ -894,7 +708,7 @@ fn handle_light(
action: &ChatCommand,
) {
let (opt_r, opt_g, opt_b, opt_x, opt_y, opt_z, opt_s) =
scan_fmt_some!(&args, action.arg_fmt, f32, f32, f32, f32, f32, f32, f32);
scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32, f32, f32, f32);
let mut light_emitter = comp::LightEmitter::default();
let mut light_offset_opt = None;
@ -955,7 +769,8 @@ fn handle_lantern(
args: String,
action: &ChatCommand,
) {
if let (Some(s), r, g, b) = scan_fmt_some!(&args, action.arg_fmt, f32, f32, f32, f32) {
println!("args: '{}', fmt: '{}'", &args, &action.arg_fmt());
if let (Some(s), r, g, b) = scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32) {
if let Some(light) = server
.state
.ecs()
@ -987,7 +802,10 @@ fn handle_lantern(
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -998,7 +816,7 @@ fn handle_explosion(
args: String,
action: &ChatCommand,
) {
let power = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0);
let power = scan_fmt!(&args, &action.arg_fmt(), f32).unwrap_or(8.0);
if power > 512.0 {
server.notify_client(
@ -1062,7 +880,7 @@ fn handle_adminify(
args: String,
action: &ChatCommand,
) {
if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) {
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
@ -1082,11 +900,17 @@ fn handle_adminify(
client,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
},
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -1104,7 +928,7 @@ fn handle_tell(
);
return;
}
if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) {
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
let ecs = server.state.ecs();
let msg = &args[alias.len()..args.len()];
if let Some(player) = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
@ -1152,7 +976,10 @@ fn handle_tell(
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -1180,7 +1007,7 @@ fn handle_debug_column(
) {
let sim = server.world.sim();
let sampler = server.world.sample_columns();
if let Ok((x, y)) = scan_fmt!(&args, action.arg_fmt, i32, i32) {
if let Ok((x, y)) = scan_fmt!(&args, &action.arg_fmt(), i32, i32) {
let wpos = Vec2::new(x, y);
/* let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e / sz as i32
@ -1244,7 +1071,10 @@ spawn_rate {:?} "#,
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -1264,14 +1094,14 @@ fn find_target(
}
}
fn handle_exp(
fn handle_give_exp(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
let (a_exp, a_alias) = scan_fmt_some!(&args, action.arg_fmt, i64, String);
let (a_exp, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), i64, String);
if let Some(exp) = a_exp {
let ecs = server.state.ecs_mut();
@ -1298,14 +1128,14 @@ fn handle_exp(
}
}
fn handle_level(
fn handle_set_level(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
) {
let (a_lvl, a_alias) = scan_fmt_some!(&args, action.arg_fmt, u32, String);
let (a_lvl, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), u32, String);
if let Some(lvl) = a_lvl {
let ecs = server.state.ecs_mut();
@ -1378,7 +1208,7 @@ fn handle_remove_lights(
args: String,
action: &ChatCommand,
) {
let opt_radius = scan_fmt_some!(&args, action.arg_fmt, f32);
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
let opt_player_pos = server.state.read_component_cloned::<comp::Pos>(target);
let mut to_delete = vec![];
@ -1430,7 +1260,7 @@ fn handle_sudo(
action: &ChatCommand,
) {
if let (Some(player_alias), Some(cmd), cmd_args) =
scan_fmt_some!(&args, action.arg_fmt, String, String, String)
scan_fmt_some!(&args, &action.arg_fmt(), String, String, String)
{
let cmd_args = cmd_args.unwrap_or(String::from(""));
let cmd = if cmd.chars().next() == Some('/') {
@ -1438,14 +1268,14 @@ fn handle_sudo(
} else {
cmd
};
if let Some(action) = CHAT_COMMANDS.iter().find(|c| c.keyword == cmd) {
if let Some(action) = CHAT_COMMANDS.iter().find(|c| c.keyword() == cmd) {
let ecs = server.state.ecs();
let entity_opt = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.alias == player_alias)
.map(|(entity, _)| entity);
if let Some(entity) = entity_opt {
(action.handler)(server, client, entity, cmd_args, action);
get_handler(action)(server, client, entity, cmd_args, action);
} else {
server.notify_client(
client,
@ -1459,7 +1289,10 @@ fn handle_sudo(
);
}
} else {
server.notify_client(client, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
}
}
@ -1479,3 +1312,4 @@ fn handle_version(
)),
);
}

View File

@ -22,11 +22,12 @@ use crate::{
auth_provider::AuthProvider,
chunk_generator::ChunkGenerator,
client::{Client, RegionSubscription},
cmd::CHAT_COMMANDS,
cmd::ChatCommandExt,
state_ext::StateExt,
sys::sentinel::{DeletedEntities, TrackedComps},
};
use common::{
cmd::ChatCommand,
comp,
event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, ServerInfo, ServerMsg},
@ -534,18 +535,16 @@ impl Server {
};
// Find the command object and run its handler.
let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd);
match action_opt {
Some(action) => action.execute(self, entity, args),
// Unknown command
None => {
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.notify(ServerMsg::private(format!(
"Unknown command '/{}'.\nType '/help' for available commands",
kwd
)));
}
},
if let Ok(command) = kwd.parse::<ChatCommand>() {
command.execute(self, entity, args);
} else {
self.notify_client(
entity,
ServerMsg::private(format!(
"Unknown command '/{}'.\nType '/help' for available commands",
kwd
)),
);
}
}