veloren/common/src/cmd.rs

926 lines
32 KiB
Rust
Raw Normal View History

2021-05-04 13:22:10 +00:00
use crate::{
assets,
comp::{self, buff::BuffKind, inventory::item::try_all_item_defs, AdminRole as Role, Skill},
2021-08-15 11:45:50 +00:00
generation::try_all_entity_configs,
2021-05-04 13:22:10 +00:00
npc, terrain,
};
use assets::AssetExt;
2020-12-12 01:45:46 +00:00
use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display},
str::FromStr,
};
2021-05-04 13:22:10 +00:00
use strum::IntoEnumIterator;
use tracing::warn;
/// Struct representing a command that a user can run from server chat.
pub struct ChatCommandData {
2020-05-11 22:02:21 +00:00
/// A list of arguments useful for both tab completion and parsing
pub args: Vec<ArgumentSpec>,
/// A one-line message that explains what the command does
pub description: &'static str,
/// Whether the command requires administrator permissions.
pub needs_role: Option<Role>,
}
impl ChatCommandData {
pub fn new(
args: Vec<ArgumentSpec>,
description: &'static str,
needs_role: Option<Role>,
) -> Self {
Self {
args,
description,
needs_role,
}
}
}
// Please keep this sorted alphabetically :-)
#[derive(Copy, Clone, strum_macros::EnumIter)]
pub enum ChatCommand {
Adminify,
Airship,
Alias,
2021-05-02 21:42:54 +00:00
ApplyBuff,
Ban,
BattleMode,
BattleModeForce,
Build,
2021-03-24 07:58:42 +00:00
BuildAreaAdd,
BuildAreaList,
2021-03-24 07:58:42 +00:00
BuildAreaRemove,
2020-07-31 09:34:26 +00:00
Campfire,
DebugColumn,
DisconnectAllPlayers,
DropAll,
2020-07-02 21:53:01 +00:00
Dummy,
Explosion,
Faction,
GiveItem,
Goto,
Group,
2020-12-04 02:18:42 +00:00
GroupInvite,
GroupKick,
GroupLeave,
GroupPromote,
Health,
Help,
2020-11-03 23:53:46 +00:00
Home,
JoinFaction,
Jump,
Kick,
Kill,
KillNpcs,
Kit,
Lantern,
Light,
2020-06-27 18:39:16 +00:00
MakeBlock,
2021-08-15 11:45:50 +00:00
MakeNpc,
2020-09-21 15:39:20 +00:00
MakeSprite,
2020-06-25 12:07:01 +00:00
Motd,
Object,
PermitBuild,
Players,
Region,
RemoveLights,
2021-03-24 07:58:42 +00:00
RevokeBuild,
RevokeBuildAll,
2021-02-28 23:14:59 +00:00
Safezone,
Say,
2021-04-17 17:44:22 +00:00
ServerPhysics,
SetMotd,
Site,
SkillPoint,
2021-05-08 15:47:09 +00:00
SkillPreset,
Spawn,
Sudo,
Tell,
Time,
Tp,
Unban,
Version,
Waypoint,
Whitelist,
2021-05-08 15:47:09 +00:00
Wiring,
World,
2021-11-04 12:45:08 +00:00
MakeVolume,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct KitManifest(pub HashMap<String, Vec<(String, u32)>>);
impl assets::Asset for KitManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
2021-05-08 15:47:09 +00:00
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub struct SkillPresetManifest(pub HashMap<String, Vec<(Skill, u8)>>);
impl assets::Asset for SkillPresetManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
pub const KIT_MANIFEST_PATH: &str = "server.manifests.kits";
pub const PRESET_MANIFEST_PATH: &str = "server.manifests.presets";
lazy_static! {
static ref ALIGNMENTS: Vec<String> = vec!["wild", "enemy", "npc", "pet"]
.iter()
.map(|s| s.to_string())
.collect();
static ref SKILL_TREES: Vec<String> = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre", "mining"]
.iter()
.map(|s| s.to_string())
.collect();
2020-12-12 22:14:24 +00:00
/// TODO: Make this use hot-reloading
static ref ENTITIES: Vec<String> = {
2020-12-12 22:14:24 +00:00
let npc_names = &*npc::NPC_NAMES.read();
2021-05-09 15:25:52 +00:00
let mut souls = Vec::new();
macro_rules! push_souls {
($species:tt) => {
for s in comp::$species::ALL_SPECIES.iter() {
souls.push(npc_names.$species.species[s].keyword.clone())
}
};
($base:tt, $($species:tt),+ $(,)?) => {
push_souls!($base);
push_souls!($($species),+);
}
}
for npc in npc::ALL_NPCS.iter() {
souls.push(npc_names[*npc].keyword.clone())
}
// See `[AllBodies](crate::comp::body::AllBodies)`
push_souls!(
humanoid,
quadruped_small,
quadruped_medium,
quadruped_low,
bird_medium,
bird_large,
fish_small,
fish_medium,
biped_small,
biped_large,
theropod,
dragon,
golem,
2021-07-25 15:16:57 +00:00
arthropod,
2021-05-09 15:25:52 +00:00
);
souls
};
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();
2020-05-11 22:02:21 +00:00
2021-05-04 13:22:10 +00:00
pub static ref BUFF_PARSER: HashMap<String, BuffKind> = {
let string_from_buff = |kind| match kind {
BuffKind::Burning => "burning",
BuffKind::Regeneration => "regeration",
BuffKind::Saturation => "saturation",
BuffKind::Bleeding => "bleeding",
BuffKind::Cursed => "cursed",
BuffKind::Potion => "potion",
BuffKind::CampfireHeal => "campfire_heal",
BuffKind::IncreaseMaxEnergy => "increase_max_energy",
BuffKind::IncreaseMaxHealth => "increase_max_health",
BuffKind::Invulnerability => "invulnerability",
BuffKind::ProtectingWard => "protecting_ward",
BuffKind::Frenzied => "frenzied",
BuffKind::Crippled => "crippled",
2021-05-30 15:51:47 +00:00
BuffKind::Frozen => "frozen",
BuffKind::Wet => "wet",
BuffKind::Ensnared => "ensnared",
BuffKind::Poisoned => "poisoned",
2022-02-09 01:23:23 +00:00
BuffKind::Hastened => "hastened",
2021-05-04 13:22:10 +00:00
};
let mut buff_parser = HashMap::new();
BuffKind::iter().for_each(|kind| {buff_parser.insert(string_from_buff(kind).to_string(), kind);});
buff_parser
};
pub static ref BUFF_PACK: Vec<String> = {
let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect();
// Remove invulnerability as it removes debuffs
buff_pack.retain(|kind| kind != "invulnerability");
buff_pack
};
static ref BUFFS: Vec<String> = {
let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect();
// Add all as valid command
buff_pack.push("all".to_string());
buff_pack
};
2021-05-02 21:42:54 +00:00
static ref BLOCK_KINDS: Vec<String> = terrain::block::BlockKind::iter()
.map(|bk| bk.to_string())
2020-06-27 18:39:16 +00:00
.collect();
2020-09-21 15:39:20 +00:00
static ref SPRITE_KINDS: Vec<String> = terrain::sprite::SPRITE_KINDS
.keys()
.cloned()
.collect();
static ref ROLES: Vec<String> = ["admin", "moderator"].iter().copied().map(Into::into).collect();
2020-05-11 22:02:21 +00:00
/// List of item specifiers. Useful for tab completing
pub static ref ITEM_SPECS: Vec<String> = {
let mut items = try_all_item_defs()
.unwrap_or_else(|e| {
warn!(?e, "Failed to load item specifiers");
Vec::new()
});
2020-05-11 22:02:21 +00:00
items.sort();
items
};
2021-08-15 11:45:50 +00:00
/// List of all entity configs. Useful for tab completing
static ref ENTITY_CONFIGS: Vec<String> = {
try_all_entity_configs()
.unwrap_or_else(|e| {
warn!(?e, "Failed to load entity configs");
Vec::new()
})
};
pub static ref KITS: Vec<String> = {
if let Ok(kits) = KitManifest::load(KIT_MANIFEST_PATH) {
let mut kits = kits.read().0.keys().cloned().collect::<Vec<String>>();
kits.sort();
kits
} else {
Vec::new()
}
};
2021-05-08 15:47:09 +00:00
static ref PRESETS: HashMap<String, Vec<(Skill, u8)>> = {
if let Ok(presets) = SkillPresetManifest::load(PRESET_MANIFEST_PATH) {
2021-05-08 15:47:09 +00:00
presets.read().0.clone()
} else {
warn!("Error while loading presets");
HashMap::new()
}
};
static ref PRESET_LIST: Vec<String> = {
let mut preset_list: Vec<String> = PRESETS.keys().cloned().collect();
preset_list.push("clear".to_owned());
preset_list
};
}
impl ChatCommand {
pub fn data(&self) -> ChatCommandData {
use ArgumentSpec::*;
use Requirement::*;
use Role::*;
let cmd = ChatCommandData::new;
match self {
ChatCommand::Adminify => cmd(
vec![PlayerName(Required), Enum("role", ROLES.clone(), Optional)],
"Temporarily gives a player a restricted admin role or removes the current one \
(if not given)",
Some(Admin),
),
ChatCommand::Airship => cmd(
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
"Spawns an airship",
Some(Admin),
),
2021-06-17 18:55:46 +00:00
ChatCommand::Alias => cmd(
vec![Any("name", Required)],
"Change your alias",
Some(Moderator),
),
2021-05-02 21:42:54 +00:00
ChatCommand::ApplyBuff => cmd(
vec![
Enum("buff", BUFFS.clone(), Required),
Float("strength", 0.01, Optional),
Float("duration", 10.0, Optional),
],
"Cast a buff on player",
Some(Admin),
2021-05-02 21:42:54 +00:00
),
ChatCommand::Ban => cmd(
vec![
Any("username", Required),
Boolean("overwrite", "true".to_string(), Optional),
Any("ban duration", Optional),
Message(Optional),
],
"Ban a player with a given username, for a given duration (if provided). Pass \
true for overwrite to alter an existing ban..",
Some(Moderator),
),
2021-08-27 18:49:58 +00:00
#[rustfmt::skip]
ChatCommand::BattleMode => cmd(
vec![Enum(
"battle mode",
vec!["pvp".to_owned(), "pve".to_owned()],
Optional,
)],
2021-09-04 17:56:55 +00:00
"Set your battle mode to:\n\
* pvp (player vs player)\n\
* pve (player vs environment).\n\
If called without arguments will show current battle mode.",
None,
),
ChatCommand::BattleModeForce => cmd(
vec![Enum(
"battle mode",
vec!["pvp".to_owned(), "pve".to_owned()],
Required,
)],
"Change your battle mode flag without any checks",
Some(Admin),
),
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
2021-03-24 07:58:42 +00:00
ChatCommand::BuildAreaAdd => cmd(
vec![
Any("name", Required),
Integer("xlo", 0, Required),
Integer("xhi", 10, Required),
Integer("ylo", 0, Required),
Integer("yhi", 10, Required),
Integer("zlo", 0, Required),
Integer("zhi", 10, Required),
],
"Adds a new build area",
Some(Admin),
2021-03-24 07:58:42 +00:00
),
ChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
2021-03-24 07:58:42 +00:00
ChatCommand::BuildAreaRemove => cmd(
vec![Any("name", Required)],
"Removes specified build area",
Some(Admin),
2021-03-24 07:58:42 +00:00
),
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
ChatCommand::DebugColumn => cmd(
2020-05-09 20:41:29 +00:00
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
"Prints some debug information about a column",
Some(Moderator),
),
ChatCommand::DisconnectAllPlayers => cmd(
vec![Any("confirm", Required)],
"Disconnects all players from the server",
Some(Admin),
),
ChatCommand::DropAll => cmd(
vec![],
"Drops all your items on the ground",
Some(Moderator),
),
ChatCommand::Dummy => cmd(vec![], "Spawns a training dummy", Some(Admin)),
ChatCommand::Explosion => cmd(
vec![Float("radius", 5.0, Required)],
"Explodes the ground around you",
Some(Admin),
),
ChatCommand::Faction => cmd(
vec![Message(Optional)],
"Send messages to your faction",
None,
),
ChatCommand::GiveItem => cmd(
2020-05-09 20:41:29 +00:00
vec![
2020-05-11 22:02:21 +00:00
Enum("item", ITEM_SPECS.clone(), Required),
2020-05-09 20:41:29 +00:00
Integer("num", 1, Optional),
],
2022-02-02 08:50:23 +00:00
"Give yourself some items.\nFor an example or to auto complete use Tab.",
Some(Admin),
),
ChatCommand::Goto => cmd(
vec![
Float("x", 0.0, Required),
Float("y", 0.0, Required),
Float("z", 0.0, Required),
],
"Teleport to a position",
Some(Admin),
),
ChatCommand::Group => cmd(vec![Message(Optional)], "Send messages to your group", None),
2020-12-04 02:18:42 +00:00
ChatCommand::GroupInvite => cmd(
vec![PlayerName(Required)],
"Invite a player to join a group",
None,
2020-12-04 02:18:42 +00:00
),
ChatCommand::GroupKick => cmd(
vec![PlayerName(Required)],
"Remove a player from a group",
None,
2020-12-04 02:18:42 +00:00
),
ChatCommand::GroupLeave => cmd(vec![], "Leave the current group", None),
2020-12-04 02:18:42 +00:00
ChatCommand::GroupPromote => cmd(
vec![PlayerName(Required)],
"Promote a player to group leader",
None,
2020-12-04 02:18:42 +00:00
),
ChatCommand::Health => cmd(
vec![Integer("hp", 100, Required)],
"Set your current health",
Some(Admin),
),
ChatCommand::Help => ChatCommandData::new(
vec![Command(Optional)],
"Display information about commands",
None,
),
2022-02-02 08:50:23 +00:00
ChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
ChatCommand::JoinFaction => ChatCommandData::new(
vec![Any("faction", Optional)],
"Join/leave the specified faction",
None,
),
ChatCommand::Jump => cmd(
vec![
Float("x", 0.0, Required),
Float("y", 0.0, Required),
Float("z", 0.0, Required),
],
"Offset your current position",
Some(Admin),
),
ChatCommand::Kick => cmd(
vec![Any("username", Required), Message(Optional)],
"Kick a player with a given username",
Some(Moderator),
),
ChatCommand::Kill => cmd(vec![], "Kill yourself", None),
ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", Some(Admin)),
ChatCommand::Kit => cmd(
vec![Enum("kit_name", KITS.to_vec(), Required)],
"Place a set of items into your inventory.",
Some(Admin),
),
ChatCommand::Lantern => cmd(
vec![
Float("strength", 5.0, Required),
Float("r", 1.0, Optional),
Float("g", 1.0, Optional),
Float("b", 1.0, Optional),
],
"Change your lantern's strength and color",
Some(Admin),
),
ChatCommand::Light => cmd(
vec![
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),
],
"Spawn entity with light",
Some(Admin),
),
2020-06-27 18:39:16 +00:00
ChatCommand::MakeBlock => cmd(
2021-07-23 12:04:16 +00:00
vec![
Enum("block", BLOCK_KINDS.clone(), Required),
Integer("r", 255, Optional),
Integer("g", 255, Optional),
Integer("b", 255, Optional),
],
"Make a block at your location with a color",
Some(Admin),
2020-09-21 15:39:20 +00:00
),
2021-08-15 11:45:50 +00:00
ChatCommand::MakeNpc => cmd(
vec![
Enum("entity_config", ENTITY_CONFIGS.clone(), Required),
Integer("num", 1, Optional),
],
2022-02-02 08:50:23 +00:00
"Spawn entity from config near you.\nFor an example or to auto complete use Tab.",
2021-08-15 11:45:50 +00:00
Some(Admin),
),
2020-09-21 15:39:20 +00:00
ChatCommand::MakeSprite => cmd(
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
"Make a sprite at your location",
Some(Admin),
2020-06-27 23:12:12 +00:00
),
ChatCommand::Motd => cmd(vec![Message(Optional)], "View the server description", None),
ChatCommand::Object => cmd(
vec![Enum("object", OBJECTS.clone(), Required)],
"Spawn an object",
Some(Admin),
),
ChatCommand::PermitBuild => cmd(
vec![Any("area_name", Required)],
"Grants player a bounded box they can build in",
Some(Admin),
),
ChatCommand::Players => cmd(vec![], "Lists players currently online", None),
ChatCommand::RemoveLights => cmd(
vec![Float("radius", 20.0, Optional)],
"Removes all lights spawned by players",
Some(Admin),
),
2021-03-24 07:58:42 +00:00
ChatCommand::RevokeBuild => cmd(
vec![Any("area_name", Required)],
"Revokes build area permission for player",
Some(Admin),
2021-03-24 07:58:42 +00:00
),
ChatCommand::RevokeBuildAll => cmd(
vec![],
"Revokes all build area permissions for player",
Some(Admin),
2021-03-24 07:58:42 +00:00
),
ChatCommand::Region => cmd(
vec![Message(Optional)],
"Send messages to everyone in your region of the world",
None,
),
2021-02-28 23:14:59 +00:00
ChatCommand::Safezone => cmd(
vec![Float("range", 100.0, Optional)],
"Creates a safezone",
Some(Moderator),
2021-02-28 23:14:59 +00:00
),
ChatCommand::Say => cmd(
vec![Message(Optional)],
"Send messages to everyone within shouting distance",
None,
),
2021-04-17 17:44:22 +00:00
ChatCommand::ServerPhysics => cmd(
vec![
Any("username", Required),
Boolean("enabled", "true".to_string(), Optional),
],
"Set/unset server-authoritative physics for an account",
Some(Moderator),
),
ChatCommand::SetMotd => cmd(
vec![Message(Optional)],
"Set the server description",
Some(Admin),
2021-04-17 17:44:22 +00:00
),
2021-08-15 11:45:50 +00:00
// Uses Message because site names can contain spaces,
// which would be assumed to be separators otherwise
ChatCommand::Site => cmd(
vec![Message(Required)],
"Teleport to a site",
Some(Moderator),
),
ChatCommand::SkillPoint => cmd(
vec![
Enum("skill tree", SKILL_TREES.clone(), Required),
Integer("amount", 1, Optional),
],
"Give yourself skill points for a particular skill tree",
Some(Admin),
),
2021-05-08 15:47:09 +00:00
ChatCommand::SkillPreset => cmd(
vec![Enum("preset_name", PRESET_LIST.to_vec(), Required)],
"Gives your character desired skills.",
Some(Admin),
2021-05-08 15:47:09 +00:00
),
ChatCommand::Spawn => cmd(
vec![
Enum("alignment", ALIGNMENTS.clone(), Required),
Enum("entity", ENTITIES.clone(), Required),
Integer("amount", 1, Optional),
2020-07-02 21:53:01 +00:00
Boolean("ai", "true".to_string(), Optional),
],
"Spawn a test entity",
Some(Admin),
),
ChatCommand::Sudo => cmd(
vec![PlayerName(Required), SubCommand],
"Run command as if you were another player",
Some(Moderator),
),
ChatCommand::Tell => cmd(
vec![PlayerName(Required), Message(Optional)],
"Send a message to another player",
None,
),
ChatCommand::Time => cmd(
vec![Enum("time", TIMES.clone(), Optional)],
"Set the time of day",
Some(Admin),
),
ChatCommand::Tp => cmd(
vec![PlayerName(Optional)],
"Teleport to another player",
Some(Moderator),
),
ChatCommand::Unban => cmd(
vec![Any("username", Required)],
"Remove the ban for the given username",
Some(Moderator),
),
ChatCommand::Version => cmd(vec![], "Prints server version", None),
ChatCommand::Waypoint => cmd(
vec![],
"Set your waypoint to your current position",
Some(Admin),
),
ChatCommand::Wiring => cmd(vec![], "Create wiring element", Some(Admin)),
ChatCommand::Whitelist => cmd(
vec![Any("add/remove", Required), Any("username", Required)],
"Adds/removes username to whitelist",
Some(Moderator),
),
ChatCommand::World => cmd(
vec![Message(Optional)],
"Send messages to everyone on the server",
None,
),
2021-11-04 12:45:08 +00:00
ChatCommand::MakeVolume => cmd(vec![], "Create a volume (experimental)", Some(Admin)),
}
}
2020-05-11 22:02:21 +00:00
/// The keyword used to invoke the command, omitting the leading '/'.
pub fn keyword(&self) -> &'static str {
match self {
ChatCommand::Adminify => "adminify",
ChatCommand::Airship => "airship",
ChatCommand::Alias => "alias",
2021-05-02 21:42:54 +00:00
ChatCommand::ApplyBuff => "buff",
ChatCommand::Ban => "ban",
ChatCommand::BattleMode => "battlemode",
ChatCommand::BattleModeForce => "battlemode_force",
ChatCommand::Build => "build",
2021-03-24 07:58:42 +00:00
ChatCommand::BuildAreaAdd => "build_area_add",
ChatCommand::BuildAreaList => "build_area_list",
2021-03-24 07:58:42 +00:00
ChatCommand::BuildAreaRemove => "build_area_remove",
2020-07-31 09:34:26 +00:00
ChatCommand::Campfire => "campfire",
ChatCommand::DebugColumn => "debug_column",
ChatCommand::DisconnectAllPlayers => "disconnect_all_players",
ChatCommand::DropAll => "dropall",
2020-07-02 21:53:01 +00:00
ChatCommand::Dummy => "dummy",
ChatCommand::Explosion => "explosion",
ChatCommand::Faction => "faction",
ChatCommand::GiveItem => "give_item",
ChatCommand::Goto => "goto",
ChatCommand::Group => "group",
2020-12-04 02:18:42 +00:00
ChatCommand::GroupInvite => "group_invite",
ChatCommand::GroupKick => "group_kick",
ChatCommand::GroupPromote => "group_promote",
ChatCommand::GroupLeave => "group_leave",
ChatCommand::Health => "health",
ChatCommand::JoinFaction => "join_faction",
ChatCommand::Help => "help",
2020-11-03 23:53:46 +00:00
ChatCommand::Home => "home",
ChatCommand::Jump => "jump",
ChatCommand::Kick => "kick",
ChatCommand::Kill => "kill",
ChatCommand::Kit => "kit",
ChatCommand::KillNpcs => "kill_npcs",
ChatCommand::Lantern => "lantern",
ChatCommand::Light => "light",
2020-06-27 18:39:16 +00:00
ChatCommand::MakeBlock => "make_block",
2021-08-15 11:45:50 +00:00
ChatCommand::MakeNpc => "make_npc",
2020-09-21 15:39:20 +00:00
ChatCommand::MakeSprite => "make_sprite",
2020-06-25 12:07:01 +00:00
ChatCommand::Motd => "motd",
ChatCommand::Object => "object",
ChatCommand::PermitBuild => "permit_build",
ChatCommand::Players => "players",
ChatCommand::Region => "region",
ChatCommand::RemoveLights => "remove_lights",
2021-03-24 07:58:42 +00:00
ChatCommand::RevokeBuild => "revoke_build",
ChatCommand::RevokeBuildAll => "revoke_build_all",
2021-02-28 23:14:59 +00:00
ChatCommand::Safezone => "safezone",
ChatCommand::Say => "say",
2021-04-17 17:44:22 +00:00
ChatCommand::ServerPhysics => "server_physics",
ChatCommand::SetMotd => "set_motd",
ChatCommand::Site => "site",
ChatCommand::SkillPoint => "skill_point",
2021-05-08 15:47:09 +00:00
ChatCommand::SkillPreset => "skill_preset",
ChatCommand::Spawn => "spawn",
ChatCommand::Sudo => "sudo",
ChatCommand::Tell => "tell",
ChatCommand::Time => "time",
ChatCommand::Tp => "tp",
ChatCommand::Unban => "unban",
ChatCommand::Version => "version",
ChatCommand::Waypoint => "waypoint",
2021-04-15 16:28:16 +00:00
ChatCommand::Wiring => "wiring",
ChatCommand::Whitelist => "whitelist",
ChatCommand::World => "world",
2021-11-04 12:45:08 +00:00
ChatCommand::MakeVolume => "make_volume",
}
}
/// The short keyword used to invoke the command, omitting the leading '/'.
/// Returns None if the command doesn't have a short keyword
pub fn short_keyword(&self) -> Option<&'static str> {
Some(match self {
ChatCommand::Faction => "f",
ChatCommand::Group => "g",
ChatCommand::Region => "r",
ChatCommand::Say => "s",
ChatCommand::Tell => "t",
ChatCommand::World => "w",
_ => return None,
})
}
2020-05-11 22:02:21 +00:00
/// A message that explains what the command does
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)
}
2020-05-11 22:02:21 +00:00
/// A boolean that is used to check whether the command requires
/// administrator permissions or not.
pub fn needs_role(&self) -> Option<Role> { self.data().needs_role }
2020-05-11 22:02:21 +00:00
/// Returns a format string for parsing arguments with scan_fmt
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(_, _, _) => "{}",
ArgumentSpec::Integer(_, _, _) => "{d}",
ArgumentSpec::Any(_, _) => "{}",
ArgumentSpec::Command(_) => "{}",
ArgumentSpec::Message(_) => "{/.*/}",
2020-05-08 21:38:58 +00:00
ArgumentSpec::SubCommand => "{} {/.*/}",
ArgumentSpec::Enum(_, _, _) => "{}",
2020-07-02 21:53:01 +00:00
ArgumentSpec::Boolean(_, _, _) => "{}",
})
.collect::<Vec<_>>()
.join(" ")
}
/// Produce an iterator over all the available commands
pub fn iter() -> impl Iterator<Item = Self> { <Self as strum::IntoEnumIterator>::iter() }
/// Produce an iterator that first goes over all the short keywords
/// and their associated commands and then iterates over all the normal
/// keywords with their associated commands
pub fn iter_with_keywords() -> impl Iterator<Item = (&'static str, Self)> {
Self::iter()
// Go through all the shortcuts first
.filter_map(|c| c.short_keyword().map(|s| (s, c)))
.chain(Self::iter().map(|c| (c.keyword(), c)))
}
}
impl Display for ChatCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.keyword())
}
}
impl FromStr for ChatCommand {
type Err = ();
fn from_str(keyword: &str) -> Result<ChatCommand, ()> {
let keyword = keyword.strip_prefix('/').unwrap_or(keyword);
Self::iter_with_keywords()
// Find command with matching string as keyword
.find_map(|(kwd, command)| (kwd == keyword).then(|| command))
// Return error if not found
.ok_or(())
}
}
2020-06-12 17:44:29 +00:00
#[derive(Eq, PartialEq, Debug)]
pub enum Requirement {
Required,
Optional,
}
/// Representation for chat command arguments
pub enum ArgumentSpec {
/// The argument refers to a player by alias
PlayerName(Requirement),
/// The argument is a float. The associated values are
/// * label
/// * suggested tab-completion
/// * whether it's optional
Float(&'static str, f32, Requirement),
2021-05-08 15:47:09 +00:00
/// The argument is an integer. The associated values are
/// * label
/// * suggested tab-completion
/// * whether it's optional
Integer(&'static str, i32, Requirement),
/// The argument is any string that doesn't contain spaces
Any(&'static str, Requirement),
/// The argument is a command name (such as in /help)
Command(Requirement),
/// This is the final argument, consuming all characters until the end of
/// input.
Message(Requirement),
2020-05-08 05:35:07 +00:00
/// This command is followed by another command (such as in /sudo)
SubCommand,
/// The argument is likely an enum. The associated values are
/// * label
/// * Predefined string completions
/// * whether it's optional
Enum(&'static str, Vec<String>, Requirement),
2020-07-02 21:53:01 +00:00
/// The argument is likely a boolean. The associated values are
/// * label
/// * suggested tab-completion
/// * whether it's optional
Boolean(&'static str, String, Requirement),
}
impl ArgumentSpec {
pub fn usage_string(&self) -> String {
match self {
ArgumentSpec::PlayerName(req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
"<player>".to_string()
} else {
"[player]".to_string()
}
},
ArgumentSpec::Float(label, _, req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
format!("<{}>", label)
} else {
format!("[{}]", label)
}
},
ArgumentSpec::Integer(label, _, req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
format!("<{}>", label)
} else {
format!("[{}]", label)
}
},
ArgumentSpec::Any(label, req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
format!("<{}>", label)
} else {
format!("[{}]", label)
}
},
ArgumentSpec::Command(req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
"<[/]command>".to_string()
} else {
"[[/]command]".to_string()
}
},
ArgumentSpec::Message(req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
"<message>".to_string()
} else {
2020-06-12 17:44:29 +00:00
"[message]".to_string()
}
},
2020-05-08 05:35:07 +00:00
ArgumentSpec::SubCommand => "<[/]command> [args...]".to_string(),
ArgumentSpec::Enum(label, _, req) => {
2020-06-12 17:44:29 +00:00
if &Requirement::Required == req {
2021-08-15 11:45:50 +00:00
format!("<{}>", label)
} else {
2021-08-15 11:45:50 +00:00
format!("[{}]", label)
}
},
2020-07-02 21:53:01 +00:00
ArgumentSpec::Boolean(label, _, req) => {
if &Requirement::Required == req {
format!("<{}>", label)
} else {
format!("[{}]", label)
}
},
}
}
2020-05-08 05:35:07 +00:00
}
2021-05-22 12:47:56 +00:00
#[cfg(test)]
mod tests {
use super::*;
use crate::comp::Item;
#[test]
fn test_loading_skill_presets() { SkillPresetManifest::load_expect(PRESET_MANIFEST_PATH); }
2021-05-22 12:47:56 +00:00
#[test]
fn test_load_kits() {
let kits = KitManifest::load_expect(KIT_MANIFEST_PATH).read();
for kit in kits.0.values() {
for (item_id, _) in kit.iter() {
std::mem::drop(Item::new_from_asset_expect(item_id));
}
}
2021-05-22 12:47:56 +00:00
}
}