2020-05-09 02:42:51 +00:00
|
|
|
use crate::{assets, comp, npc, state::State};
|
|
|
|
use lazy_static::lazy_static;
|
2020-05-05 04:06:36 +00:00
|
|
|
use specs::prelude::{Join, WorldExt};
|
2020-05-09 02:42:51 +00:00
|
|
|
use std::{ops::Deref, str::FromStr};
|
2020-05-05 04:06:36 +00:00
|
|
|
|
|
|
|
/// Struct representing a command that a user can run from server chat.
|
2020-05-05 22:33:16 +00:00
|
|
|
pub struct ChatCommandData {
|
2020-05-05 04:06:36 +00:00
|
|
|
/// A format string for parsing arguments.
|
2020-05-05 22:33:16 +00:00
|
|
|
pub args: Vec<ArgumentSpec>,
|
2020-05-05 04:06:36 +00:00
|
|
|
/// 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
|
|
|
|
/// administrator permissions or not.
|
|
|
|
pub needs_admin: bool,
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
impl ChatCommandData {
|
|
|
|
pub fn new(args: Vec<ArgumentSpec>, description: &'static str, needs_admin: bool) -> Self {
|
2020-05-05 04:06:36 +00:00
|
|
|
Self {
|
|
|
|
args,
|
|
|
|
description,
|
|
|
|
needs_admin,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
// 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,
|
|
|
|
];
|
|
|
|
|
2020-05-09 02:42:51 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref ALIGNMENTS: Vec<String> = vec!["wild", "enemy", "npc", "pet"]
|
|
|
|
.iter()
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.collect();
|
|
|
|
static ref ENTITIES: Vec<String> = {
|
|
|
|
let npc_names = &*npc::NPC_NAMES;
|
|
|
|
npc::ALL_NPCS
|
|
|
|
.iter()
|
|
|
|
.map(|&npc| npc_names[npc].keyword.clone())
|
|
|
|
.collect()
|
|
|
|
};
|
|
|
|
static ref OBJECTS: Vec<String> = comp::object::ALL_OBJECTS
|
|
|
|
.iter()
|
|
|
|
.map(|o| o.to_string().to_string())
|
|
|
|
.collect();
|
|
|
|
static ref TIMES: Vec<String> = vec![
|
|
|
|
"midnight", "night", "dawn", "morning", "day", "noon", "dusk"
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
fn items() -> Vec<String> {
|
|
|
|
if let Ok(assets) = assets::ASSETS.read() {
|
|
|
|
assets
|
|
|
|
.iter()
|
|
|
|
.flat_map(|(k, v)| {
|
|
|
|
if v.is::<comp::item::Item>() {
|
|
|
|
Some(k.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
} else {
|
|
|
|
error!("Assets not found");
|
|
|
|
vec![]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
impl ChatCommand {
|
|
|
|
pub fn data(&self) -> ChatCommandData {
|
|
|
|
use ArgumentSpec::*;
|
2020-05-09 02:42:51 +00:00
|
|
|
use Requirement::*;
|
2020-05-05 22:33:16 +00:00
|
|
|
let cmd = ChatCommandData::new;
|
|
|
|
match self {
|
|
|
|
ChatCommand::Adminify => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![PlayerName(Required)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Temporarily gives a player admin permissions or removes them",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-09 02:42:51 +00:00
|
|
|
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", false),
|
2020-05-05 22:33:16 +00:00
|
|
|
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(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![
|
|
|
|
Integer("x", 15000, Required),
|
|
|
|
Integer("y", 15000, Required),
|
|
|
|
],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Prints some debug information about a column",
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
ChatCommand::Explosion => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Float("radius", 5.0, Required)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Explodes the ground around you",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::GiveExp => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Integer("amount", 50, Required)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Give experience to yourself",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::GiveItem => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Enum("item", items(), Required), Integer("num", 1, Optional)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Give yourself some items",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Goto => cmd(
|
|
|
|
vec![
|
2020-05-09 02:42:51 +00:00
|
|
|
Float("x", 0.0, Required),
|
|
|
|
Float("y", 0.0, Required),
|
|
|
|
Float("z", 0.0, Required),
|
2020-05-05 22:33:16 +00:00
|
|
|
],
|
|
|
|
"Teleport to a position",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Health => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Integer("hp", 100, Required)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Set your current health",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Help => ChatCommandData::new(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Command(Optional)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Display information about commands",
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
ChatCommand::Jump => cmd(
|
|
|
|
vec![
|
2020-05-09 02:42:51 +00:00
|
|
|
Float("x", 0.0, Required),
|
|
|
|
Float("y", 0.0, Required),
|
|
|
|
Float("z", 0.0, Required),
|
2020-05-05 22:33:16 +00:00
|
|
|
],
|
|
|
|
"Offset your current position",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Kill => cmd(vec![], "Kill yourself", false),
|
|
|
|
ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", true),
|
|
|
|
ChatCommand::Lantern => cmd(
|
|
|
|
vec![
|
2020-05-09 02:42:51 +00:00
|
|
|
Float("strength", 5.0, Required),
|
|
|
|
Float("r", 1.0, Optional),
|
|
|
|
Float("g", 1.0, Optional),
|
|
|
|
Float("b", 1.0, Optional),
|
2020-05-05 22:33:16 +00:00
|
|
|
],
|
|
|
|
"Change your lantern's strength and color",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Light => cmd(
|
|
|
|
vec![
|
2020-05-09 02:42:51 +00:00
|
|
|
Float("r", 1.0, Optional),
|
|
|
|
Float("g", 1.0, Optional),
|
|
|
|
Float("b", 1.0, Optional),
|
|
|
|
Float("x", 0.0, Optional),
|
|
|
|
Float("y", 0.0, Optional),
|
|
|
|
Float("z", 0.0, Optional),
|
|
|
|
Float("strength", 5.0, Optional),
|
2020-05-05 22:33:16 +00:00
|
|
|
],
|
|
|
|
"Spawn entity with light",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-09 02:42:51 +00:00
|
|
|
ChatCommand::Object => cmd(
|
|
|
|
vec![Enum("object", OBJECTS.clone(), Required)],
|
|
|
|
"Spawn an object",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Players => cmd(vec![], "Lists players currently online", false),
|
|
|
|
ChatCommand::RemoveLights => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![Float("radius", 20.0, Optional)],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Removes all lights spawned by players",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-09 02:42:51 +00:00
|
|
|
ChatCommand::SetLevel => cmd(
|
|
|
|
vec![Integer("level", 10, Required)],
|
|
|
|
"Set player Level",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Spawn => cmd(
|
|
|
|
vec![
|
|
|
|
Enum("alignment", ALIGNMENTS.clone(), Required),
|
|
|
|
Enum("entity", ENTITIES.clone(), Required),
|
|
|
|
Integer("amount", 1, Optional),
|
|
|
|
],
|
|
|
|
"Spawn a test entity",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-05 22:33:16 +00:00
|
|
|
ChatCommand::Sudo => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![PlayerName(Required), SubCommand],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Run command as if you were another player",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Tell => cmd(
|
2020-05-09 02:42:51 +00:00
|
|
|
vec![PlayerName(Required), Message],
|
2020-05-05 22:33:16 +00:00
|
|
|
"Send a message to another player",
|
|
|
|
false,
|
|
|
|
),
|
2020-05-09 02:42:51 +00:00
|
|
|
ChatCommand::Time => cmd(
|
|
|
|
vec![Enum("time", TIMES.clone(), Optional)],
|
|
|
|
"Set the time of day",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
ChatCommand::Tp => cmd(
|
|
|
|
vec![PlayerName(Optional)],
|
|
|
|
"Teleport to another player",
|
|
|
|
true,
|
|
|
|
),
|
2020-05-05 22:33:16 +00:00
|
|
|
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(_) => "{}",
|
2020-05-08 21:38:58 +00:00
|
|
|
ArgumentSpec::Float(_, _, _) => "{}",
|
2020-05-05 22:33:16 +00:00
|
|
|
ArgumentSpec::Integer(_, _, _) => "{d}",
|
|
|
|
ArgumentSpec::Any(_, _) => "{}",
|
|
|
|
ArgumentSpec::Command(_) => "{}",
|
|
|
|
ArgumentSpec::Message => "{/.*/}",
|
2020-05-08 21:38:58 +00:00
|
|
|
ArgumentSpec::SubCommand => "{} {/.*/}",
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Enum(_, _, _) => "{}", // TODO
|
2020-05-05 22:33:16 +00:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join(" ")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-09 02:42:51 +00:00
|
|
|
impl FromStr for ChatCommand {
|
2020-05-05 22:33:16 +00:00
|
|
|
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(());
|
|
|
|
}
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 02:42:51 +00:00
|
|
|
pub enum Requirement {
|
|
|
|
Required,
|
|
|
|
Optional,
|
|
|
|
}
|
|
|
|
impl Deref for Requirement {
|
|
|
|
type Target = bool;
|
|
|
|
|
|
|
|
fn deref(&self) -> &bool {
|
|
|
|
match self {
|
|
|
|
Requirement::Required => &true,
|
|
|
|
Requirement::Optional => &false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 04:06:36 +00:00
|
|
|
/// Representation for chat command arguments
|
2020-05-05 22:33:16 +00:00
|
|
|
pub enum ArgumentSpec {
|
2020-05-05 04:06:36 +00:00
|
|
|
/// The argument refers to a player by alias
|
2020-05-09 02:42:51 +00:00
|
|
|
PlayerName(Requirement),
|
2020-05-05 04:06:36 +00:00
|
|
|
/// The argument is a float. The associated values are
|
|
|
|
/// * label
|
2020-05-09 02:42:51 +00:00
|
|
|
/// * suggested tab-completion
|
2020-05-05 04:06:36 +00:00
|
|
|
/// * whether it's optional
|
2020-05-09 02:42:51 +00:00
|
|
|
Float(&'static str, f32, Requirement),
|
2020-05-05 04:06:36 +00:00
|
|
|
/// The argument is a float. The associated values are
|
|
|
|
/// * label
|
2020-05-09 02:42:51 +00:00
|
|
|
/// * suggested tab-completion
|
2020-05-05 04:06:36 +00:00
|
|
|
/// * whether it's optional
|
2020-05-09 02:42:51 +00:00
|
|
|
Integer(&'static str, i32, Requirement),
|
2020-05-05 22:33:16 +00:00
|
|
|
/// The argument is any string that doesn't contain spaces
|
2020-05-09 02:42:51 +00:00
|
|
|
Any(&'static str, Requirement),
|
|
|
|
/// The argument is a command name (such as in /help)
|
|
|
|
Command(Requirement),
|
2020-05-05 22:33:16 +00:00
|
|
|
/// This is the final argument, consuming all characters until the end of
|
|
|
|
/// input.
|
2020-05-05 04:06:36 +00:00
|
|
|
Message,
|
2020-05-08 05:35:07 +00:00
|
|
|
/// This command is followed by another command (such as in /sudo)
|
|
|
|
SubCommand,
|
2020-05-05 04:06:36 +00:00
|
|
|
/// The argument is likely an enum. The associated values are
|
|
|
|
/// * label
|
|
|
|
/// * Predefined string completions
|
|
|
|
/// * whether it's optional
|
2020-05-09 02:42:51 +00:00
|
|
|
Enum(&'static str, Vec<String>, Requirement),
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:33:16 +00:00
|
|
|
impl ArgumentSpec {
|
|
|
|
pub fn usage_string(&self) -> String {
|
|
|
|
match self {
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::PlayerName(req) => {
|
|
|
|
if **req {
|
2020-05-05 04:06:36 +00:00
|
|
|
"<player>".to_string()
|
|
|
|
} else {
|
2020-05-09 02:42:51 +00:00
|
|
|
"[player]".to_string()
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Float(label, _, req) => {
|
|
|
|
if **req {
|
2020-05-05 04:06:36 +00:00
|
|
|
format!("<{}>", label)
|
2020-05-09 02:42:51 +00:00
|
|
|
} else {
|
|
|
|
format!("[{}]", label)
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Integer(label, _, req) => {
|
|
|
|
if **req {
|
2020-05-05 04:06:36 +00:00
|
|
|
format!("<{}>", label)
|
2020-05-09 02:42:51 +00:00
|
|
|
} else {
|
|
|
|
format!("[{}]", label)
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Any(label, req) => {
|
|
|
|
if **req {
|
2020-05-05 22:33:16 +00:00
|
|
|
format!("<{}>", label)
|
2020-05-09 02:42:51 +00:00
|
|
|
} else {
|
|
|
|
format!("[{}]", label)
|
2020-05-05 22:33:16 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Command(req) => {
|
|
|
|
if **req {
|
2020-05-05 04:06:36 +00:00
|
|
|
"<[/]command>".to_string()
|
2020-05-09 02:42:51 +00:00
|
|
|
} else {
|
|
|
|
"[[/]command]".to_string()
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
},
|
2020-05-05 22:33:16 +00:00
|
|
|
ArgumentSpec::Message => "<message>".to_string(),
|
2020-05-08 05:35:07 +00:00
|
|
|
ArgumentSpec::SubCommand => "<[/]command> [args...]".to_string(),
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Enum(label, _, req) => {
|
|
|
|
if **req {
|
2020-05-05 04:06:36 +00:00
|
|
|
format! {"<{}>", label}
|
2020-05-09 02:42:51 +00:00
|
|
|
} else {
|
|
|
|
format! {"[{}]", label}
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-08 05:35:07 +00:00
|
|
|
pub fn complete(&self, part: &str, state: &State) -> Vec<String> {
|
2020-05-05 04:06:36 +00:00
|
|
|
match self {
|
2020-05-08 05:35:07 +00:00
|
|
|
ArgumentSpec::PlayerName(_) => complete_player(part, &state),
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Float(_, x, _) => {
|
|
|
|
if part.is_empty() {
|
|
|
|
vec![format!("{:.1}", x)]
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
ArgumentSpec::Integer(_, x, _) => {
|
|
|
|
if part.is_empty() {
|
|
|
|
vec![format!("{}", x)]
|
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
}
|
|
|
|
},
|
2020-05-05 22:33:16 +00:00
|
|
|
ArgumentSpec::Any(_, _) => vec![],
|
2020-05-08 05:35:07 +00:00
|
|
|
ArgumentSpec::Command(_) => complete_command(part),
|
|
|
|
ArgumentSpec::Message => complete_player(part, &state),
|
|
|
|
ArgumentSpec::SubCommand => complete_command(part),
|
2020-05-09 02:42:51 +00:00
|
|
|
ArgumentSpec::Enum(_, strings, _) => strings
|
|
|
|
.iter()
|
|
|
|
.filter(|string| string.starts_with(part))
|
|
|
|
.map(|c| c.to_string())
|
|
|
|
.collect(),
|
2020-05-05 04:06:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-08 05:35:07 +00:00
|
|
|
|
|
|
|
fn complete_player(part: &str, state: &State) -> Vec<String> {
|
2020-05-09 02:42:51 +00:00
|
|
|
let storage = state.ecs().read_storage::<comp::Player>();
|
2020-05-08 21:38:58 +00:00
|
|
|
let mut iter = storage.join();
|
|
|
|
if let Some(first) = iter.next() {
|
|
|
|
std::iter::once(first)
|
|
|
|
.chain(iter)
|
|
|
|
.filter(|player| player.alias.starts_with(part))
|
|
|
|
.map(|player| player.alias.clone())
|
|
|
|
.collect()
|
|
|
|
} else {
|
|
|
|
vec!["singleplayer".to_string()]
|
|
|
|
}
|
2020-05-08 05:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn complete_command(part: &str) -> Vec<String> {
|
|
|
|
CHAT_COMMANDS
|
|
|
|
.iter()
|
|
|
|
.map(|com| com.keyword())
|
|
|
|
.filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part))
|
2020-05-08 21:38:58 +00:00
|
|
|
.map(|c| format!("/{}", c))
|
2020-05-08 05:35:07 +00:00
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2020-05-08 21:38:58 +00:00
|
|
|
// Get the byte index of the nth word. Used in completing "/sudo p /subcmd"
|
2020-05-08 05:35:07 +00:00
|
|
|
fn nth_word(line: &str, n: usize) -> Option<usize> {
|
|
|
|
let mut is_space = false;
|
|
|
|
let mut j = 0;
|
|
|
|
for (i, c) in line.char_indices() {
|
|
|
|
match (is_space, c.is_whitespace()) {
|
2020-05-08 21:38:58 +00:00
|
|
|
(true, true) => {},
|
|
|
|
(true, false) => {
|
|
|
|
is_space = false;
|
|
|
|
j += 1;
|
|
|
|
},
|
|
|
|
(false, true) => {
|
|
|
|
is_space = true;
|
|
|
|
},
|
|
|
|
(false, false) => {},
|
2020-05-08 05:35:07 +00:00
|
|
|
}
|
|
|
|
if j == n {
|
|
|
|
return Some(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn complete(line: &str, state: &State) -> Vec<String> {
|
2020-05-08 21:38:58 +00:00
|
|
|
let word = if line.chars().last().map_or(true, char::is_whitespace) {
|
|
|
|
""
|
|
|
|
} else {
|
|
|
|
line.split_whitespace().last().unwrap_or("")
|
|
|
|
};
|
2020-05-08 05:35:07 +00:00
|
|
|
if line.chars().next() == Some('/') {
|
|
|
|
let mut iter = line.split_whitespace();
|
|
|
|
let cmd = iter.next().unwrap();
|
2020-05-08 21:38:58 +00:00
|
|
|
let i = iter.count() + if word.is_empty() { 1 } else { 0 };
|
|
|
|
if i == 0 {
|
|
|
|
// Completing chat command name
|
|
|
|
complete_command(word)
|
|
|
|
} else {
|
2020-05-08 05:35:07 +00:00
|
|
|
if let Ok(cmd) = cmd.parse::<ChatCommand>() {
|
2020-05-08 21:38:58 +00:00
|
|
|
if let Some(arg) = cmd.data().args.get(i - 1) {
|
|
|
|
// Complete ith argument
|
2020-05-08 05:35:07 +00:00
|
|
|
arg.complete(word, &state)
|
|
|
|
} else {
|
2020-05-08 21:38:58 +00:00
|
|
|
// Complete past the last argument
|
2020-05-08 05:35:07 +00:00
|
|
|
match cmd.data().args.last() {
|
|
|
|
Some(ArgumentSpec::SubCommand) => {
|
|
|
|
if let Some(index) = nth_word(line, cmd.data().args.len()) {
|
|
|
|
complete(&line[index..], &state)
|
|
|
|
} else {
|
|
|
|
error!("Could not tab-complete SubCommand");
|
|
|
|
vec![]
|
|
|
|
}
|
2020-05-08 21:38:58 +00:00
|
|
|
},
|
2020-05-08 05:35:07 +00:00
|
|
|
Some(ArgumentSpec::Message) => complete_player(word, &state),
|
2020-05-08 21:38:58 +00:00
|
|
|
_ => vec![], // End of command. Nothing to complete
|
2020-05-08 05:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Completing for unknown chat command
|
|
|
|
complete_player(word, &state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Not completing a command
|
|
|
|
complete_player(word, &state)
|
|
|
|
}
|
|
|
|
}
|