mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add a client-side mutelist
This commit is contained in:
parent
0ad359f3c8
commit
57ab1c5767
@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Chat commands to mute and unmute players
|
||||||
- Waypoints saved between sessions and shared with group members.
|
- Waypoints saved between sessions and shared with group members.
|
||||||
- New rocks
|
- New rocks
|
||||||
- Weapon trails
|
- Weapon trails
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
use crate::Client;
|
|
||||||
use common::cmd::*;
|
|
||||||
|
|
||||||
trait TabComplete {
|
|
||||||
fn complete(&self, part: &str, client: &Client) -> Vec<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TabComplete for ArgumentSpec {
|
|
||||||
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
|
||||||
match self {
|
|
||||||
ArgumentSpec::PlayerName(_) => complete_player(part, client),
|
|
||||||
ArgumentSpec::SiteName(_) => complete_site(part, client),
|
|
||||||
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![]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ArgumentSpec::Any(_, _) => vec![],
|
|
||||||
ArgumentSpec::Command(_) => complete_command(part),
|
|
||||||
ArgumentSpec::Message(_) => complete_player(part, client),
|
|
||||||
ArgumentSpec::SubCommand => complete_command(part),
|
|
||||||
ArgumentSpec::Enum(_, strings, _) => strings
|
|
||||||
.iter()
|
|
||||||
.filter(|string| string.starts_with(part))
|
|
||||||
.map(|c| c.to_string())
|
|
||||||
.collect(),
|
|
||||||
ArgumentSpec::Boolean(_, part, _) => vec!["true", "false"]
|
|
||||||
.iter()
|
|
||||||
.filter(|string| string.starts_with(part))
|
|
||||||
.map(|c| c.to_string())
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_player(part: &str, client: &Client) -> Vec<String> {
|
|
||||||
client
|
|
||||||
.player_list
|
|
||||||
.values()
|
|
||||||
.map(|player_info| &player_info.player_alias)
|
|
||||||
.filter(|alias| alias.starts_with(part))
|
|
||||||
.cloned()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_site(mut part: &str, client: &Client) -> Vec<String> {
|
|
||||||
if let Some(p) = part.strip_prefix('"') {
|
|
||||||
part = p;
|
|
||||||
}
|
|
||||||
client
|
|
||||||
.sites
|
|
||||||
.values()
|
|
||||||
.filter_map(|site| match site.site.kind {
|
|
||||||
common_net::msg::world_msg::SiteKind::Cave => None,
|
|
||||||
_ => site.site.name.as_ref(),
|
|
||||||
})
|
|
||||||
.filter(|name| name.starts_with(part))
|
|
||||||
.map(|name| {
|
|
||||||
if name.contains(' ') {
|
|
||||||
format!("\"{}\"", name)
|
|
||||||
} else {
|
|
||||||
name.clone()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_command(part: &str) -> Vec<String> {
|
|
||||||
let part = part.strip_prefix('/').unwrap_or(part);
|
|
||||||
|
|
||||||
ChatCommand::iter_with_keywords()
|
|
||||||
.map(|(kwd, _)| kwd)
|
|
||||||
.filter(|kwd| kwd.starts_with(part))
|
|
||||||
.map(|kwd| format!("/{}", kwd))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the byte index of the nth word. Used in completing "/sudo p /subcmd"
|
|
||||||
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()) {
|
|
||||||
(true, true) => {},
|
|
||||||
(true, false) => {
|
|
||||||
is_space = false;
|
|
||||||
j += 1;
|
|
||||||
},
|
|
||||||
(false, true) => {
|
|
||||||
is_space = true;
|
|
||||||
},
|
|
||||||
(false, false) => {},
|
|
||||||
}
|
|
||||||
if j == n {
|
|
||||||
return Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(line: &str, client: &Client) -> Vec<String> {
|
|
||||||
let word = if line.chars().last().map_or(true, char::is_whitespace) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
line.split_whitespace().last().unwrap_or("")
|
|
||||||
};
|
|
||||||
if line.starts_with('/') {
|
|
||||||
let mut iter = line.split_whitespace();
|
|
||||||
let cmd = iter.next().unwrap();
|
|
||||||
let i = iter.count() + if word.is_empty() { 1 } else { 0 };
|
|
||||||
if i == 0 {
|
|
||||||
// Completing chat command name
|
|
||||||
complete_command(word)
|
|
||||||
} else if let Ok(cmd) = cmd.parse::<ChatCommand>() {
|
|
||||||
if let Some(arg) = cmd.data().args.get(i - 1) {
|
|
||||||
// Complete ith argument
|
|
||||||
arg.complete(word, client)
|
|
||||||
} else {
|
|
||||||
// Complete past the last argument
|
|
||||||
match cmd.data().args.last() {
|
|
||||||
Some(ArgumentSpec::SubCommand) => {
|
|
||||||
if let Some(index) = nth_word(line, cmd.data().args.len()) {
|
|
||||||
complete(&line[index..], client)
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(ArgumentSpec::Message(_)) => complete_player(word, client),
|
|
||||||
_ => vec![], // End of command. Nothing to complete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Completing for unknown chat command
|
|
||||||
complete_player(word, client)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not completing a command
|
|
||||||
complete_player(word, client)
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,6 @@
|
|||||||
#![feature(label_break_value, option_zip)]
|
#![feature(label_break_value, option_zip)]
|
||||||
|
|
||||||
pub mod addr;
|
pub mod addr;
|
||||||
pub mod cmd;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
|
@ -14,6 +14,7 @@ use common::{
|
|||||||
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -229,6 +230,7 @@ pub struct PlayerInfo {
|
|||||||
pub is_online: bool,
|
pub is_online: bool,
|
||||||
pub player_alias: String,
|
pub player_alias: String,
|
||||||
pub character: Option<CharacterInfo>,
|
pub character: Option<CharacterInfo>,
|
||||||
|
pub uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use veloren_common::cmd::ChatCommand;
|
use veloren_common::cmd::ServerChatCommand;
|
||||||
|
|
||||||
/// This binary generates the markdown table used for the `players/commands.md`
|
/// This binary generates the markdown table used for the `players/commands.md`
|
||||||
/// page in the Veloren Book. It can be run with `cargo cmd-doc-gen`.
|
/// page in the Veloren Book. It can be run with `cargo cmd-doc-gen`.
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("|Command|Description|Requires|Arguments|");
|
println!("|Command|Description|Requires|Arguments|");
|
||||||
println!("|-|-|-|-|");
|
println!("|-|-|-|-|");
|
||||||
for cmd in ChatCommand::iter() {
|
for cmd in ServerChatCommand::iter() {
|
||||||
let args = cmd
|
let args = cmd
|
||||||
.data()
|
.data()
|
||||||
.args
|
.args
|
||||||
|
@ -39,82 +39,6 @@ impl ChatCommandData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Please keep this sorted alphabetically :-)
|
|
||||||
#[derive(Copy, Clone, strum::EnumIter)]
|
|
||||||
pub enum ChatCommand {
|
|
||||||
Adminify,
|
|
||||||
Airship,
|
|
||||||
Alias,
|
|
||||||
ApplyBuff,
|
|
||||||
Ban,
|
|
||||||
BattleMode,
|
|
||||||
BattleModeForce,
|
|
||||||
Build,
|
|
||||||
BuildAreaAdd,
|
|
||||||
BuildAreaList,
|
|
||||||
BuildAreaRemove,
|
|
||||||
Campfire,
|
|
||||||
DebugColumn,
|
|
||||||
DisconnectAllPlayers,
|
|
||||||
DropAll,
|
|
||||||
Dummy,
|
|
||||||
Explosion,
|
|
||||||
Faction,
|
|
||||||
GiveItem,
|
|
||||||
Goto,
|
|
||||||
Group,
|
|
||||||
GroupInvite,
|
|
||||||
GroupKick,
|
|
||||||
GroupLeave,
|
|
||||||
GroupPromote,
|
|
||||||
Health,
|
|
||||||
Help,
|
|
||||||
Home,
|
|
||||||
JoinFaction,
|
|
||||||
Jump,
|
|
||||||
Kick,
|
|
||||||
Kill,
|
|
||||||
KillNpcs,
|
|
||||||
Kit,
|
|
||||||
Lantern,
|
|
||||||
Light,
|
|
||||||
MakeBlock,
|
|
||||||
MakeNpc,
|
|
||||||
MakeSprite,
|
|
||||||
Motd,
|
|
||||||
Object,
|
|
||||||
PermitBuild,
|
|
||||||
Players,
|
|
||||||
Region,
|
|
||||||
ReloadChunks,
|
|
||||||
RemoveLights,
|
|
||||||
RevokeBuild,
|
|
||||||
RevokeBuildAll,
|
|
||||||
Safezone,
|
|
||||||
Say,
|
|
||||||
ServerPhysics,
|
|
||||||
SetMotd,
|
|
||||||
Ship,
|
|
||||||
Site,
|
|
||||||
SkillPoint,
|
|
||||||
SkillPreset,
|
|
||||||
Spawn,
|
|
||||||
Sudo,
|
|
||||||
Tell,
|
|
||||||
Time,
|
|
||||||
Tp,
|
|
||||||
Unban,
|
|
||||||
Version,
|
|
||||||
Waypoint,
|
|
||||||
Whitelist,
|
|
||||||
Wiring,
|
|
||||||
World,
|
|
||||||
MakeVolume,
|
|
||||||
Location,
|
|
||||||
CreateLocation,
|
|
||||||
DeleteLocation,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
pub enum KitSpec {
|
pub enum KitSpec {
|
||||||
Item(String),
|
Item(String),
|
||||||
@ -299,30 +223,106 @@ lazy_static! {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatCommand {
|
// Please keep this sorted alphabetically :-)
|
||||||
|
#[derive(Copy, Clone, strum::EnumIter)]
|
||||||
|
pub enum ServerChatCommand {
|
||||||
|
Adminify,
|
||||||
|
Airship,
|
||||||
|
Alias,
|
||||||
|
ApplyBuff,
|
||||||
|
Ban,
|
||||||
|
BattleMode,
|
||||||
|
BattleModeForce,
|
||||||
|
Build,
|
||||||
|
BuildAreaAdd,
|
||||||
|
BuildAreaList,
|
||||||
|
BuildAreaRemove,
|
||||||
|
Campfire,
|
||||||
|
DebugColumn,
|
||||||
|
DisconnectAllPlayers,
|
||||||
|
DropAll,
|
||||||
|
Dummy,
|
||||||
|
Explosion,
|
||||||
|
Faction,
|
||||||
|
GiveItem,
|
||||||
|
Goto,
|
||||||
|
Group,
|
||||||
|
GroupInvite,
|
||||||
|
GroupKick,
|
||||||
|
GroupLeave,
|
||||||
|
GroupPromote,
|
||||||
|
Health,
|
||||||
|
Help,
|
||||||
|
Home,
|
||||||
|
JoinFaction,
|
||||||
|
Jump,
|
||||||
|
Kick,
|
||||||
|
Kill,
|
||||||
|
KillNpcs,
|
||||||
|
Kit,
|
||||||
|
Lantern,
|
||||||
|
Light,
|
||||||
|
MakeBlock,
|
||||||
|
MakeNpc,
|
||||||
|
MakeSprite,
|
||||||
|
Motd,
|
||||||
|
Object,
|
||||||
|
PermitBuild,
|
||||||
|
Players,
|
||||||
|
Region,
|
||||||
|
ReloadChunks,
|
||||||
|
RemoveLights,
|
||||||
|
RevokeBuild,
|
||||||
|
RevokeBuildAll,
|
||||||
|
Safezone,
|
||||||
|
Say,
|
||||||
|
ServerPhysics,
|
||||||
|
SetMotd,
|
||||||
|
Ship,
|
||||||
|
Site,
|
||||||
|
SkillPoint,
|
||||||
|
SkillPreset,
|
||||||
|
Spawn,
|
||||||
|
Sudo,
|
||||||
|
Tell,
|
||||||
|
Time,
|
||||||
|
Tp,
|
||||||
|
Unban,
|
||||||
|
Version,
|
||||||
|
Waypoint,
|
||||||
|
Whitelist,
|
||||||
|
Wiring,
|
||||||
|
World,
|
||||||
|
MakeVolume,
|
||||||
|
Location,
|
||||||
|
CreateLocation,
|
||||||
|
DeleteLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerChatCommand {
|
||||||
pub fn data(&self) -> ChatCommandData {
|
pub fn data(&self) -> ChatCommandData {
|
||||||
use ArgumentSpec::*;
|
use ArgumentSpec::*;
|
||||||
use Requirement::*;
|
use Requirement::*;
|
||||||
use Role::*;
|
use Role::*;
|
||||||
let cmd = ChatCommandData::new;
|
let cmd = ChatCommandData::new;
|
||||||
match self {
|
match self {
|
||||||
ChatCommand::Adminify => cmd(
|
ServerChatCommand::Adminify => cmd(
|
||||||
vec![PlayerName(Required), Enum("role", ROLES.clone(), Optional)],
|
vec![PlayerName(Required), Enum("role", ROLES.clone(), Optional)],
|
||||||
"Temporarily gives a player a restricted admin role or removes the current one \
|
"Temporarily gives a player a restricted admin role or removes the current one \
|
||||||
(if not given)",
|
(if not given)",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Airship => cmd(
|
ServerChatCommand::Airship => cmd(
|
||||||
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
||||||
"Spawns an airship",
|
"Spawns an airship",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Alias => cmd(
|
ServerChatCommand::Alias => cmd(
|
||||||
vec![Any("name", Required)],
|
vec![Any("name", Required)],
|
||||||
"Change your alias",
|
"Change your alias",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::ApplyBuff => cmd(
|
ServerChatCommand::ApplyBuff => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("buff", BUFFS.clone(), Required),
|
Enum("buff", BUFFS.clone(), Required),
|
||||||
Float("strength", 0.01, Optional),
|
Float("strength", 0.01, Optional),
|
||||||
@ -331,7 +331,7 @@ impl ChatCommand {
|
|||||||
"Cast a buff on player",
|
"Cast a buff on player",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Ban => cmd(
|
ServerChatCommand::Ban => cmd(
|
||||||
vec![
|
vec![
|
||||||
PlayerName(Required),
|
PlayerName(Required),
|
||||||
Boolean("overwrite", "true".to_string(), Optional),
|
Boolean("overwrite", "true".to_string(), Optional),
|
||||||
@ -343,7 +343,7 @@ impl ChatCommand {
|
|||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
ChatCommand::BattleMode => cmd(
|
ServerChatCommand::BattleMode => cmd(
|
||||||
vec![Enum(
|
vec![Enum(
|
||||||
"battle mode",
|
"battle mode",
|
||||||
vec!["pvp".to_owned(), "pve".to_owned()],
|
vec!["pvp".to_owned(), "pve".to_owned()],
|
||||||
@ -354,8 +354,9 @@ impl ChatCommand {
|
|||||||
* pve (player vs environment).\n\
|
* pve (player vs environment).\n\
|
||||||
If called without arguments will show current battle mode.",
|
If called without arguments will show current battle mode.",
|
||||||
None,
|
None,
|
||||||
|
|
||||||
),
|
),
|
||||||
ChatCommand::BattleModeForce => cmd(
|
ServerChatCommand::BattleModeForce => cmd(
|
||||||
vec![Enum(
|
vec![Enum(
|
||||||
"battle mode",
|
"battle mode",
|
||||||
vec!["pvp".to_owned(), "pve".to_owned()],
|
vec!["pvp".to_owned(), "pve".to_owned()],
|
||||||
@ -364,8 +365,8 @@ impl ChatCommand {
|
|||||||
"Change your battle mode flag without any checks",
|
"Change your battle mode flag without any checks",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
|
ServerChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
|
||||||
ChatCommand::BuildAreaAdd => cmd(
|
ServerChatCommand::BuildAreaAdd => cmd(
|
||||||
vec![
|
vec![
|
||||||
Any("name", Required),
|
Any("name", Required),
|
||||||
Integer("xlo", 0, Required),
|
Integer("xlo", 0, Required),
|
||||||
@ -378,40 +379,40 @@ impl ChatCommand {
|
|||||||
"Adds a new build area",
|
"Adds a new build area",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
|
ServerChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
|
||||||
ChatCommand::BuildAreaRemove => cmd(
|
ServerChatCommand::BuildAreaRemove => cmd(
|
||||||
vec![Any("name", Required)],
|
vec![Any("name", Required)],
|
||||||
"Removes specified build area",
|
"Removes specified build area",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
|
ServerChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
|
||||||
ChatCommand::DebugColumn => cmd(
|
ServerChatCommand::DebugColumn => cmd(
|
||||||
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
||||||
"Prints some debug information about a column",
|
"Prints some debug information about a column",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::DisconnectAllPlayers => cmd(
|
ServerChatCommand::DisconnectAllPlayers => cmd(
|
||||||
vec![Any("confirm", Required)],
|
vec![Any("confirm", Required)],
|
||||||
"Disconnects all players from the server",
|
"Disconnects all players from the server",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::DropAll => cmd(
|
ServerChatCommand::DropAll => cmd(
|
||||||
vec![],
|
vec![],
|
||||||
"Drops all your items on the ground",
|
"Drops all your items on the ground",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Dummy => cmd(vec![], "Spawns a training dummy", Some(Admin)),
|
ServerChatCommand::Dummy => cmd(vec![], "Spawns a training dummy", Some(Admin)),
|
||||||
ChatCommand::Explosion => cmd(
|
ServerChatCommand::Explosion => cmd(
|
||||||
vec![Float("radius", 5.0, Required)],
|
vec![Float("radius", 5.0, Required)],
|
||||||
"Explodes the ground around you",
|
"Explodes the ground around you",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Faction => cmd(
|
ServerChatCommand::Faction => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Message(Optional)],
|
||||||
"Send messages to your faction",
|
"Send messages to your faction",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::GiveItem => cmd(
|
ServerChatCommand::GiveItem => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("item", ITEM_SPECS.clone(), Required),
|
Enum("item", ITEM_SPECS.clone(), Required),
|
||||||
Integer("num", 1, Optional),
|
Integer("num", 1, Optional),
|
||||||
@ -419,7 +420,7 @@ impl ChatCommand {
|
|||||||
"Give yourself some items.\nFor an example or to auto complete use Tab.",
|
"Give yourself some items.\nFor an example or to auto complete use Tab.",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Goto => cmd(
|
ServerChatCommand::Goto => cmd(
|
||||||
vec![
|
vec![
|
||||||
Float("x", 0.0, Required),
|
Float("x", 0.0, Required),
|
||||||
Float("y", 0.0, Required),
|
Float("y", 0.0, Required),
|
||||||
@ -428,40 +429,42 @@ impl ChatCommand {
|
|||||||
"Teleport to a position",
|
"Teleport to a position",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Group => cmd(vec![Message(Optional)], "Send messages to your group", None),
|
ServerChatCommand::Group => {
|
||||||
ChatCommand::GroupInvite => cmd(
|
cmd(vec![Message(Optional)], "Send messages to your group", None)
|
||||||
|
},
|
||||||
|
ServerChatCommand::GroupInvite => cmd(
|
||||||
vec![PlayerName(Required)],
|
vec![PlayerName(Required)],
|
||||||
"Invite a player to join a group",
|
"Invite a player to join a group",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::GroupKick => cmd(
|
ServerChatCommand::GroupKick => cmd(
|
||||||
vec![PlayerName(Required)],
|
vec![PlayerName(Required)],
|
||||||
"Remove a player from a group",
|
"Remove a player from a group",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::GroupLeave => cmd(vec![], "Leave the current group", None),
|
ServerChatCommand::GroupLeave => cmd(vec![], "Leave the current group", None),
|
||||||
ChatCommand::GroupPromote => cmd(
|
ServerChatCommand::GroupPromote => cmd(
|
||||||
vec![PlayerName(Required)],
|
vec![PlayerName(Required)],
|
||||||
"Promote a player to group leader",
|
"Promote a player to group leader",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::Health => cmd(
|
ServerChatCommand::Health => cmd(
|
||||||
vec![Integer("hp", 100, Required)],
|
vec![Integer("hp", 100, Required)],
|
||||||
"Set your current health",
|
"Set your current health",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Help => ChatCommandData::new(
|
ServerChatCommand::Help => ChatCommandData::new(
|
||||||
vec![Command(Optional)],
|
vec![Command(Optional)],
|
||||||
"Display information about commands",
|
"Display information about commands",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
|
ServerChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
|
||||||
ChatCommand::JoinFaction => ChatCommandData::new(
|
ServerChatCommand::JoinFaction => ChatCommandData::new(
|
||||||
vec![Any("faction", Optional)],
|
vec![Any("faction", Optional)],
|
||||||
"Join/leave the specified faction",
|
"Join/leave the specified faction",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::Jump => cmd(
|
ServerChatCommand::Jump => cmd(
|
||||||
vec![
|
vec![
|
||||||
Float("x", 0.0, Required),
|
Float("x", 0.0, Required),
|
||||||
Float("y", 0.0, Required),
|
Float("y", 0.0, Required),
|
||||||
@ -470,19 +473,19 @@ impl ChatCommand {
|
|||||||
"Offset your current position",
|
"Offset your current position",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Kick => cmd(
|
ServerChatCommand::Kick => cmd(
|
||||||
vec![PlayerName(Required), Message(Optional)],
|
vec![PlayerName(Required), Message(Optional)],
|
||||||
"Kick a player with a given username",
|
"Kick a player with a given username",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Kill => cmd(vec![], "Kill yourself", None),
|
ServerChatCommand::Kill => cmd(vec![], "Kill yourself", None),
|
||||||
ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", Some(Admin)),
|
ServerChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", Some(Admin)),
|
||||||
ChatCommand::Kit => cmd(
|
ServerChatCommand::Kit => cmd(
|
||||||
vec![Enum("kit_name", KITS.to_vec(), Required)],
|
vec![Enum("kit_name", KITS.to_vec(), Required)],
|
||||||
"Place a set of items into your inventory.",
|
"Place a set of items into your inventory.",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Lantern => cmd(
|
ServerChatCommand::Lantern => cmd(
|
||||||
vec![
|
vec![
|
||||||
Float("strength", 5.0, Required),
|
Float("strength", 5.0, Required),
|
||||||
Float("r", 1.0, Optional),
|
Float("r", 1.0, Optional),
|
||||||
@ -492,7 +495,7 @@ impl ChatCommand {
|
|||||||
"Change your lantern's strength and color",
|
"Change your lantern's strength and color",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Light => cmd(
|
ServerChatCommand::Light => cmd(
|
||||||
vec![
|
vec![
|
||||||
Float("r", 1.0, Optional),
|
Float("r", 1.0, Optional),
|
||||||
Float("g", 1.0, Optional),
|
Float("g", 1.0, Optional),
|
||||||
@ -505,7 +508,7 @@ impl ChatCommand {
|
|||||||
"Spawn entity with light",
|
"Spawn entity with light",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::MakeBlock => cmd(
|
ServerChatCommand::MakeBlock => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("block", BLOCK_KINDS.clone(), Required),
|
Enum("block", BLOCK_KINDS.clone(), Required),
|
||||||
Integer("r", 255, Optional),
|
Integer("r", 255, Optional),
|
||||||
@ -515,7 +518,7 @@ impl ChatCommand {
|
|||||||
"Make a block at your location with a color",
|
"Make a block at your location with a color",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::MakeNpc => cmd(
|
ServerChatCommand::MakeNpc => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("entity_config", ENTITY_CONFIGS.clone(), Required),
|
Enum("entity_config", ENTITY_CONFIGS.clone(), Required),
|
||||||
Integer("num", 1, Optional),
|
Integer("num", 1, Optional),
|
||||||
@ -523,59 +526,61 @@ impl ChatCommand {
|
|||||||
"Spawn entity from config near you.\nFor an example or to auto complete use Tab.",
|
"Spawn entity from config near you.\nFor an example or to auto complete use Tab.",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::MakeSprite => cmd(
|
ServerChatCommand::MakeSprite => cmd(
|
||||||
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
|
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
|
||||||
"Make a sprite at your location",
|
"Make a sprite at your location",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Motd => cmd(vec![Message(Optional)], "View the server description", None),
|
ServerChatCommand::Motd => {
|
||||||
ChatCommand::Object => cmd(
|
cmd(vec![Message(Optional)], "View the server description", None)
|
||||||
|
},
|
||||||
|
ServerChatCommand::Object => cmd(
|
||||||
vec![Enum("object", OBJECTS.clone(), Required)],
|
vec![Enum("object", OBJECTS.clone(), Required)],
|
||||||
"Spawn an object",
|
"Spawn an object",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::PermitBuild => cmd(
|
ServerChatCommand::PermitBuild => cmd(
|
||||||
vec![Any("area_name", Required)],
|
vec![Any("area_name", Required)],
|
||||||
"Grants player a bounded box they can build in",
|
"Grants player a bounded box they can build in",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Players => cmd(vec![], "Lists players currently online", None),
|
ServerChatCommand::Players => cmd(vec![], "Lists players currently online", None),
|
||||||
ChatCommand::ReloadChunks => cmd(
|
ServerChatCommand::ReloadChunks => cmd(
|
||||||
vec![],
|
vec![],
|
||||||
"Reloads all chunks loaded on the server",
|
"Reloads all chunks loaded on the server",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::RemoveLights => cmd(
|
ServerChatCommand::RemoveLights => cmd(
|
||||||
vec![Float("radius", 20.0, Optional)],
|
vec![Float("radius", 20.0, Optional)],
|
||||||
"Removes all lights spawned by players",
|
"Removes all lights spawned by players",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::RevokeBuild => cmd(
|
ServerChatCommand::RevokeBuild => cmd(
|
||||||
vec![Any("area_name", Required)],
|
vec![Any("area_name", Required)],
|
||||||
"Revokes build area permission for player",
|
"Revokes build area permission for player",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::RevokeBuildAll => cmd(
|
ServerChatCommand::RevokeBuildAll => cmd(
|
||||||
vec![],
|
vec![],
|
||||||
"Revokes all build area permissions for player",
|
"Revokes all build area permissions for player",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Region => cmd(
|
ServerChatCommand::Region => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Message(Optional)],
|
||||||
"Send messages to everyone in your region of the world",
|
"Send messages to everyone in your region of the world",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::Safezone => cmd(
|
ServerChatCommand::Safezone => cmd(
|
||||||
vec![Float("range", 100.0, Optional)],
|
vec![Float("range", 100.0, Optional)],
|
||||||
"Creates a safezone",
|
"Creates a safezone",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Say => cmd(
|
ServerChatCommand::Say => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Message(Optional)],
|
||||||
"Send messages to everyone within shouting distance",
|
"Send messages to everyone within shouting distance",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::ServerPhysics => cmd(
|
ServerChatCommand::ServerPhysics => cmd(
|
||||||
vec![
|
vec![
|
||||||
PlayerName(Required),
|
PlayerName(Required),
|
||||||
Boolean("enabled", "true".to_string(), Optional),
|
Boolean("enabled", "true".to_string(), Optional),
|
||||||
@ -583,24 +588,24 @@ impl ChatCommand {
|
|||||||
"Set/unset server-authoritative physics for an account",
|
"Set/unset server-authoritative physics for an account",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::SetMotd => cmd(
|
ServerChatCommand::SetMotd => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Message(Optional)],
|
||||||
"Set the server description",
|
"Set the server description",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Ship => cmd(
|
ServerChatCommand::Ship => cmd(
|
||||||
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
||||||
"Spawns a ship",
|
"Spawns a ship",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
// Uses Message because site names can contain spaces,
|
// Uses Message because site names can contain spaces,
|
||||||
// which would be assumed to be separators otherwise
|
// which would be assumed to be separators otherwise
|
||||||
ChatCommand::Site => cmd(
|
ServerChatCommand::Site => cmd(
|
||||||
vec![SiteName(Required)],
|
vec![SiteName(Required)],
|
||||||
"Teleport to a site",
|
"Teleport to a site",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::SkillPoint => cmd(
|
ServerChatCommand::SkillPoint => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("skill tree", SKILL_TREES.clone(), Required),
|
Enum("skill tree", SKILL_TREES.clone(), Required),
|
||||||
Integer("amount", 1, Optional),
|
Integer("amount", 1, Optional),
|
||||||
@ -608,12 +613,12 @@ impl ChatCommand {
|
|||||||
"Give yourself skill points for a particular skill tree",
|
"Give yourself skill points for a particular skill tree",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::SkillPreset => cmd(
|
ServerChatCommand::SkillPreset => cmd(
|
||||||
vec![Enum("preset_name", PRESET_LIST.to_vec(), Required)],
|
vec![Enum("preset_name", PRESET_LIST.to_vec(), Required)],
|
||||||
"Gives your character desired skills.",
|
"Gives your character desired skills.",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Spawn => cmd(
|
ServerChatCommand::Spawn => cmd(
|
||||||
vec![
|
vec![
|
||||||
Enum("alignment", ALIGNMENTS.clone(), Required),
|
Enum("alignment", ALIGNMENTS.clone(), Required),
|
||||||
Enum("entity", ENTITIES.clone(), Required),
|
Enum("entity", ENTITIES.clone(), Required),
|
||||||
@ -623,58 +628,60 @@ impl ChatCommand {
|
|||||||
"Spawn a test entity",
|
"Spawn a test entity",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Sudo => cmd(
|
ServerChatCommand::Sudo => cmd(
|
||||||
vec![PlayerName(Required), SubCommand],
|
vec![PlayerName(Required), SubCommand],
|
||||||
"Run command as if you were another player",
|
"Run command as if you were another player",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Tell => cmd(
|
ServerChatCommand::Tell => cmd(
|
||||||
vec![PlayerName(Required), Message(Optional)],
|
vec![PlayerName(Required), Message(Optional)],
|
||||||
"Send a message to another player",
|
"Send a message to another player",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::Time => cmd(
|
ServerChatCommand::Time => cmd(
|
||||||
vec![Enum("time", TIMES.clone(), Optional)],
|
vec![Enum("time", TIMES.clone(), Optional)],
|
||||||
"Set the time of day",
|
"Set the time of day",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Tp => cmd(
|
ServerChatCommand::Tp => cmd(
|
||||||
vec![PlayerName(Optional)],
|
vec![PlayerName(Optional)],
|
||||||
"Teleport to another player",
|
"Teleport to another player",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Unban => cmd(
|
ServerChatCommand::Unban => cmd(
|
||||||
vec![PlayerName(Required)],
|
vec![PlayerName(Required)],
|
||||||
"Remove the ban for the given username",
|
"Remove the ban for the given username",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::Version => cmd(vec![], "Prints server version", None),
|
ServerChatCommand::Version => cmd(vec![], "Prints server version", None),
|
||||||
ChatCommand::Waypoint => cmd(
|
ServerChatCommand::Waypoint => cmd(
|
||||||
vec![],
|
vec![],
|
||||||
"Set your waypoint to your current position",
|
"Set your waypoint to your current position",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ChatCommand::Wiring => cmd(vec![], "Create wiring element", Some(Admin)),
|
ServerChatCommand::Wiring => cmd(vec![], "Create wiring element", Some(Admin)),
|
||||||
ChatCommand::Whitelist => cmd(
|
ServerChatCommand::Whitelist => cmd(
|
||||||
vec![Any("add/remove", Required), PlayerName(Required)],
|
vec![Any("add/remove", Required), PlayerName(Required)],
|
||||||
"Adds/removes username to whitelist",
|
"Adds/removes username to whitelist",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::World => cmd(
|
ServerChatCommand::World => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Message(Optional)],
|
||||||
"Send messages to everyone on the server",
|
"Send messages to everyone on the server",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ChatCommand::MakeVolume => cmd(vec![], "Create a volume (experimental)", Some(Admin)),
|
ServerChatCommand::MakeVolume => {
|
||||||
ChatCommand::Location => {
|
cmd(vec![], "Create a volume (experimental)", Some(Admin))
|
||||||
|
},
|
||||||
|
ServerChatCommand::Location => {
|
||||||
cmd(vec![Any("name", Required)], "Teleport to a location", None)
|
cmd(vec![Any("name", Required)], "Teleport to a location", None)
|
||||||
},
|
},
|
||||||
ChatCommand::CreateLocation => cmd(
|
ServerChatCommand::CreateLocation => cmd(
|
||||||
vec![Any("name", Required)],
|
vec![Any("name", Required)],
|
||||||
"Create a location at the current position",
|
"Create a location at the current position",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ChatCommand::DeleteLocation => cmd(
|
ServerChatCommand::DeleteLocation => cmd(
|
||||||
vec![Any("name", Required)],
|
vec![Any("name", Required)],
|
||||||
"Delete a location",
|
"Delete a location",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
@ -682,80 +689,80 @@ impl ChatCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The keyword used to invoke the command, omitting the leading '/'.
|
/// The keyword used to invoke the command, omitting the prefix.
|
||||||
pub fn keyword(&self) -> &'static str {
|
pub fn keyword(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ChatCommand::Adminify => "adminify",
|
ServerChatCommand::Adminify => "adminify",
|
||||||
ChatCommand::Airship => "airship",
|
ServerChatCommand::Airship => "airship",
|
||||||
ChatCommand::Alias => "alias",
|
ServerChatCommand::Alias => "alias",
|
||||||
ChatCommand::ApplyBuff => "buff",
|
ServerChatCommand::ApplyBuff => "buff",
|
||||||
ChatCommand::Ban => "ban",
|
ServerChatCommand::Ban => "ban",
|
||||||
ChatCommand::BattleMode => "battlemode",
|
ServerChatCommand::BattleMode => "battlemode",
|
||||||
ChatCommand::BattleModeForce => "battlemode_force",
|
ServerChatCommand::BattleModeForce => "battlemode_force",
|
||||||
ChatCommand::Build => "build",
|
ServerChatCommand::Build => "build",
|
||||||
ChatCommand::BuildAreaAdd => "build_area_add",
|
ServerChatCommand::BuildAreaAdd => "build_area_add",
|
||||||
ChatCommand::BuildAreaList => "build_area_list",
|
ServerChatCommand::BuildAreaList => "build_area_list",
|
||||||
ChatCommand::BuildAreaRemove => "build_area_remove",
|
ServerChatCommand::BuildAreaRemove => "build_area_remove",
|
||||||
ChatCommand::Campfire => "campfire",
|
ServerChatCommand::Campfire => "campfire",
|
||||||
ChatCommand::DebugColumn => "debug_column",
|
ServerChatCommand::DebugColumn => "debug_column",
|
||||||
ChatCommand::DisconnectAllPlayers => "disconnect_all_players",
|
ServerChatCommand::DisconnectAllPlayers => "disconnect_all_players",
|
||||||
ChatCommand::DropAll => "dropall",
|
ServerChatCommand::DropAll => "dropall",
|
||||||
ChatCommand::Dummy => "dummy",
|
ServerChatCommand::Dummy => "dummy",
|
||||||
ChatCommand::Explosion => "explosion",
|
ServerChatCommand::Explosion => "explosion",
|
||||||
ChatCommand::Faction => "faction",
|
ServerChatCommand::Faction => "faction",
|
||||||
ChatCommand::GiveItem => "give_item",
|
ServerChatCommand::GiveItem => "give_item",
|
||||||
ChatCommand::Goto => "goto",
|
ServerChatCommand::Goto => "goto",
|
||||||
ChatCommand::Group => "group",
|
ServerChatCommand::Group => "group",
|
||||||
ChatCommand::GroupInvite => "group_invite",
|
ServerChatCommand::GroupInvite => "group_invite",
|
||||||
ChatCommand::GroupKick => "group_kick",
|
ServerChatCommand::GroupKick => "group_kick",
|
||||||
ChatCommand::GroupPromote => "group_promote",
|
ServerChatCommand::GroupPromote => "group_promote",
|
||||||
ChatCommand::GroupLeave => "group_leave",
|
ServerChatCommand::GroupLeave => "group_leave",
|
||||||
ChatCommand::Health => "health",
|
ServerChatCommand::Health => "health",
|
||||||
ChatCommand::JoinFaction => "join_faction",
|
ServerChatCommand::JoinFaction => "join_faction",
|
||||||
ChatCommand::Help => "help",
|
ServerChatCommand::Help => "help",
|
||||||
ChatCommand::Home => "home",
|
ServerChatCommand::Home => "home",
|
||||||
ChatCommand::Jump => "jump",
|
ServerChatCommand::Jump => "jump",
|
||||||
ChatCommand::Kick => "kick",
|
ServerChatCommand::Kick => "kick",
|
||||||
ChatCommand::Kill => "kill",
|
ServerChatCommand::Kill => "kill",
|
||||||
ChatCommand::Kit => "kit",
|
ServerChatCommand::Kit => "kit",
|
||||||
ChatCommand::KillNpcs => "kill_npcs",
|
ServerChatCommand::KillNpcs => "kill_npcs",
|
||||||
ChatCommand::Lantern => "lantern",
|
ServerChatCommand::Lantern => "lantern",
|
||||||
ChatCommand::Light => "light",
|
ServerChatCommand::Light => "light",
|
||||||
ChatCommand::MakeBlock => "make_block",
|
ServerChatCommand::MakeBlock => "make_block",
|
||||||
ChatCommand::MakeNpc => "make_npc",
|
ServerChatCommand::MakeNpc => "make_npc",
|
||||||
ChatCommand::MakeSprite => "make_sprite",
|
ServerChatCommand::MakeSprite => "make_sprite",
|
||||||
ChatCommand::Motd => "motd",
|
ServerChatCommand::Motd => "motd",
|
||||||
ChatCommand::Object => "object",
|
ServerChatCommand::Object => "object",
|
||||||
ChatCommand::PermitBuild => "permit_build",
|
ServerChatCommand::PermitBuild => "permit_build",
|
||||||
ChatCommand::Players => "players",
|
ServerChatCommand::Players => "players",
|
||||||
ChatCommand::Region => "region",
|
ServerChatCommand::Region => "region",
|
||||||
ChatCommand::ReloadChunks => "reload_chunks",
|
ServerChatCommand::ReloadChunks => "reload_chunks",
|
||||||
ChatCommand::RemoveLights => "remove_lights",
|
ServerChatCommand::RemoveLights => "remove_lights",
|
||||||
ChatCommand::RevokeBuild => "revoke_build",
|
ServerChatCommand::RevokeBuild => "revoke_build",
|
||||||
ChatCommand::RevokeBuildAll => "revoke_build_all",
|
ServerChatCommand::RevokeBuildAll => "revoke_build_all",
|
||||||
ChatCommand::Safezone => "safezone",
|
ServerChatCommand::Safezone => "safezone",
|
||||||
ChatCommand::Say => "say",
|
ServerChatCommand::Say => "say",
|
||||||
ChatCommand::ServerPhysics => "server_physics",
|
ServerChatCommand::ServerPhysics => "server_physics",
|
||||||
ChatCommand::SetMotd => "set_motd",
|
ServerChatCommand::SetMotd => "set_motd",
|
||||||
ChatCommand::Ship => "ship",
|
ServerChatCommand::Ship => "ship",
|
||||||
ChatCommand::Site => "site",
|
ServerChatCommand::Site => "site",
|
||||||
ChatCommand::SkillPoint => "skill_point",
|
ServerChatCommand::SkillPoint => "skill_point",
|
||||||
ChatCommand::SkillPreset => "skill_preset",
|
ServerChatCommand::SkillPreset => "skill_preset",
|
||||||
ChatCommand::Spawn => "spawn",
|
ServerChatCommand::Spawn => "spawn",
|
||||||
ChatCommand::Sudo => "sudo",
|
ServerChatCommand::Sudo => "sudo",
|
||||||
ChatCommand::Tell => "tell",
|
ServerChatCommand::Tell => "tell",
|
||||||
ChatCommand::Time => "time",
|
ServerChatCommand::Time => "time",
|
||||||
ChatCommand::Tp => "tp",
|
ServerChatCommand::Tp => "tp",
|
||||||
ChatCommand::Unban => "unban",
|
ServerChatCommand::Unban => "unban",
|
||||||
ChatCommand::Version => "version",
|
ServerChatCommand::Version => "version",
|
||||||
ChatCommand::Waypoint => "waypoint",
|
ServerChatCommand::Waypoint => "waypoint",
|
||||||
ChatCommand::Wiring => "wiring",
|
ServerChatCommand::Wiring => "wiring",
|
||||||
ChatCommand::Whitelist => "whitelist",
|
ServerChatCommand::Whitelist => "whitelist",
|
||||||
ChatCommand::World => "world",
|
ServerChatCommand::World => "world",
|
||||||
ChatCommand::MakeVolume => "make_volume",
|
ServerChatCommand::MakeVolume => "make_volume",
|
||||||
ChatCommand::Location => "location",
|
ServerChatCommand::Location => "location",
|
||||||
ChatCommand::CreateLocation => "create_location",
|
ServerChatCommand::CreateLocation => "create_location",
|
||||||
ChatCommand::DeleteLocation => "delete_location",
|
ServerChatCommand::DeleteLocation => "delete_location",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,16 +770,19 @@ impl ChatCommand {
|
|||||||
/// Returns None if the command doesn't have a short keyword
|
/// Returns None if the command doesn't have a short keyword
|
||||||
pub fn short_keyword(&self) -> Option<&'static str> {
|
pub fn short_keyword(&self) -> Option<&'static str> {
|
||||||
Some(match self {
|
Some(match self {
|
||||||
ChatCommand::Faction => "f",
|
ServerChatCommand::Faction => "f",
|
||||||
ChatCommand::Group => "g",
|
ServerChatCommand::Group => "g",
|
||||||
ChatCommand::Region => "r",
|
ServerChatCommand::Region => "r",
|
||||||
ChatCommand::Say => "s",
|
ServerChatCommand::Say => "s",
|
||||||
ChatCommand::Tell => "t",
|
ServerChatCommand::Tell => "t",
|
||||||
ChatCommand::World => "w",
|
ServerChatCommand::World => "w",
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce an iterator over all the available commands
|
||||||
|
pub fn iter() -> impl Iterator<Item = Self> { <Self as strum::IntoEnumIterator>::iter() }
|
||||||
|
|
||||||
/// A message that explains what the command does
|
/// A message that explains what the command does
|
||||||
pub fn help_string(&self) -> String {
|
pub fn help_string(&self) -> String {
|
||||||
let data = self.data();
|
let data = self.data();
|
||||||
@ -783,9 +793,17 @@ impl ChatCommand {
|
|||||||
format!("{}: {}", usage, data.description)
|
format!("{}: {}", usage, data.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A boolean that is used to check whether the command requires
|
/// Produce an iterator that first goes over all the short keywords
|
||||||
/// administrator permissions or not.
|
/// and their associated commands and then iterates over all the normal
|
||||||
pub fn needs_role(&self) -> Option<Role> { self.data().needs_role }
|
/// 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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_role(&self) -> Option<comp::AdminRole> { self.data().needs_role }
|
||||||
|
|
||||||
/// Returns a format string for parsing arguments with scan_fmt
|
/// Returns a format string for parsing arguments with scan_fmt
|
||||||
pub fn arg_fmt(&self) -> String {
|
pub fn arg_fmt(&self) -> String {
|
||||||
@ -807,34 +825,22 @@ impl ChatCommand {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.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 {
|
impl Display for ServerChatCommand {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
write!(f, "{}", self.keyword())
|
write!(f, "{}", self.keyword())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ChatCommand {
|
impl FromStr for ServerChatCommand {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
fn from_str(keyword: &str) -> Result<ChatCommand, ()> {
|
fn from_str(keyword: &str) -> Result<ServerChatCommand, ()> {
|
||||||
let keyword = keyword.strip_prefix('/').unwrap_or(keyword);
|
Self::iter()
|
||||||
|
// Go through all the shortcuts first
|
||||||
Self::iter_with_keywords()
|
.filter_map(|c| c.short_keyword().map(|s| (s, c)))
|
||||||
|
.chain(Self::iter().map(|c| (c.keyword(), c)))
|
||||||
// Find command with matching string as keyword
|
// Find command with matching string as keyword
|
||||||
.find_map(|(kwd, command)| (kwd == keyword).then(|| command))
|
.find_map(|(kwd, command)| (kwd == keyword).then(|| command))
|
||||||
// Return error if not found
|
// Return error if not found
|
||||||
@ -956,6 +962,38 @@ impl ArgumentSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a series of command arguments into values, including collecting all
|
||||||
|
/// trailing arguments.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! parse_cmd_args {
|
||||||
|
($args:expr, $($t:ty),* $(, ..$tail:ty)? $(,)?) => {
|
||||||
|
{
|
||||||
|
let mut args = $args.into_iter().peekable();
|
||||||
|
(
|
||||||
|
// We only consume the input argument when parsing is successful. If this fails, we
|
||||||
|
// will then attempt to parse it as the next argument type. This is done regardless
|
||||||
|
// of whether the argument is optional because that information is not available
|
||||||
|
// here. Nevertheless, if the caller only precedes to use the parsed arguments when
|
||||||
|
// all required arguments parse successfully to `Some(val)` this should not create
|
||||||
|
// any unexpected behavior.
|
||||||
|
//
|
||||||
|
// This does mean that optional arguments will be included in the trailing args or
|
||||||
|
// that one optional arg could be interpreted as another, if the user makes a
|
||||||
|
// mistake that causes an optional arg to fail to parse. But there is no way to
|
||||||
|
// discern this in the current model with the optional args and trailing arg being
|
||||||
|
// solely position based.
|
||||||
|
$({
|
||||||
|
let parsed = args.peek().and_then(|s| s.parse::<$t>().ok());
|
||||||
|
// Consume successfully parsed arg.
|
||||||
|
if parsed.is_some() { args.next(); }
|
||||||
|
parsed
|
||||||
|
}),*
|
||||||
|
$(, args.map(|s| s.to_string()).collect::<$tail>())?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -431,6 +431,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
|||||||
name: s.name.clone(),
|
name: s.name.clone(),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
uuid: player.uuid(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
let remove_player_msg = ServerGeneral::PlayerListUpdate(
|
let remove_player_msg = ServerGeneral::PlayerListUpdate(
|
||||||
|
@ -73,7 +73,7 @@ use common::{
|
|||||||
assets::AssetExt,
|
assets::AssetExt,
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
cmd::ChatCommand,
|
cmd::ServerChatCommand,
|
||||||
comp,
|
comp,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
recipe::{default_component_recipe_book, default_recipe_book},
|
recipe::{default_component_recipe_book, default_recipe_book},
|
||||||
@ -1210,7 +1210,7 @@ impl Server {
|
|||||||
|
|
||||||
fn process_command(&mut self, entity: EcsEntity, name: String, args: Vec<String>) {
|
fn process_command(&mut self, entity: EcsEntity, name: String, args: Vec<String>) {
|
||||||
// Find the command object and run its handler.
|
// Find the command object and run its handler.
|
||||||
if let Ok(command) = name.parse::<ChatCommand>() {
|
if let Ok(command) = name.parse::<ServerChatCommand>() {
|
||||||
command.execute(self, entity, args);
|
command.execute(self, entity, args);
|
||||||
} else {
|
} else {
|
||||||
#[cfg(feature = "plugins")]
|
#[cfg(feature = "plugins")]
|
||||||
|
@ -88,6 +88,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
character: stats.map(|stats| CharacterInfo {
|
character: stats.map(|stats| CharacterInfo {
|
||||||
name: stats.name.clone(),
|
name: stats.name.clone(),
|
||||||
}),
|
}),
|
||||||
|
uuid: player.uuid(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
@ -239,6 +240,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
is_online: true,
|
is_online: true,
|
||||||
is_moderator: admins.get(entity).is_some(),
|
is_moderator: admins.get(entity).is_some(),
|
||||||
character: None, // new players will be on character select.
|
character: None, // new players will be on character select.
|
||||||
|
uuid: player.uuid(),
|
||||||
}),
|
}),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{AdminCommandState, EguiAction, EguiActions};
|
use crate::{AdminCommandState, EguiAction, EguiActions};
|
||||||
use common::cmd::ChatCommand;
|
use common::cmd::ServerChatCommand;
|
||||||
use egui::{CollapsingHeader, CtxRef, Resize, Slider, Ui, Vec2, Window};
|
use egui::{CollapsingHeader, CtxRef, Resize, Slider, Ui, Vec2, Window};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ fn draw_kits(ui: &mut Ui, state: &mut AdminCommandState, egui_actions: &mut Egui
|
|||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
if ui.button("Give Kit").clicked() {
|
if ui.button("Give Kit").clicked() {
|
||||||
egui_actions.actions.push(EguiAction::ChatCommand {
|
egui_actions.actions.push(EguiAction::ChatCommand {
|
||||||
cmd: ChatCommand::Kit,
|
cmd: ServerChatCommand::Kit,
|
||||||
args: vec![common::cmd::KITS[state.kits_selected_idx].clone()],
|
args: vec![common::cmd::KITS[state.kits_selected_idx].clone()],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -67,7 +67,7 @@ fn draw_give_items(ui: &mut Ui, state: &mut AdminCommandState, egui_actions: &mu
|
|||||||
);
|
);
|
||||||
if ui.button("Give Items").clicked() {
|
if ui.button("Give Items").clicked() {
|
||||||
egui_actions.actions.push(EguiAction::ChatCommand {
|
egui_actions.actions.push(EguiAction::ChatCommand {
|
||||||
cmd: ChatCommand::GiveItem,
|
cmd: ServerChatCommand::GiveItem,
|
||||||
args: vec![
|
args: vec![
|
||||||
format!(
|
format!(
|
||||||
"common.items.{}",
|
"common.items.{}",
|
||||||
|
@ -10,6 +10,7 @@ mod widgets;
|
|||||||
|
|
||||||
use client::{Client, Join, World, WorldExt};
|
use client::{Client, Join, World, WorldExt};
|
||||||
use common::{
|
use common::{
|
||||||
|
cmd::ServerChatCommand,
|
||||||
comp,
|
comp,
|
||||||
comp::{inventory::item::armor::Friction, Poise, PoiseState},
|
comp::{inventory::item::armor::Friction, Poise, PoiseState},
|
||||||
};
|
};
|
||||||
@ -24,10 +25,7 @@ use crate::{
|
|||||||
admin::draw_admin_commands_window, character_states::draw_char_state_group,
|
admin::draw_admin_commands_window, character_states::draw_char_state_group,
|
||||||
experimental_shaders::draw_experimental_shaders_window, widgets::two_col_row,
|
experimental_shaders::draw_experimental_shaders_window, widgets::two_col_row,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::comp::{aura::AuraKind::Buff, Body, Fluid};
|
||||||
cmd::ChatCommand,
|
|
||||||
comp::{aura::AuraKind::Buff, Body, Fluid},
|
|
||||||
};
|
|
||||||
use egui_winit_platform::Platform;
|
use egui_winit_platform::Platform;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
#[cfg(feature = "use-dyn-lib")]
|
#[cfg(feature = "use-dyn-lib")]
|
||||||
@ -131,7 +129,10 @@ pub enum EguiDebugShapeAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum EguiAction {
|
pub enum EguiAction {
|
||||||
ChatCommand { cmd: ChatCommand, args: Vec<String> },
|
ChatCommand {
|
||||||
|
cmd: ServerChatCommand,
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
DebugShape(EguiDebugShapeAction),
|
DebugShape(EguiDebugShapeAction),
|
||||||
SetExperimentalShader(String, bool),
|
SetExperimentalShader(String, bool),
|
||||||
}
|
}
|
||||||
@ -613,13 +614,20 @@ fn selected_entity_window(
|
|||||||
.spacing([40.0, 4.0])
|
.spacing([40.0, 4.0])
|
||||||
.max_col_width(100.0)
|
.max_col_width(100.0)
|
||||||
.striped(true)
|
.striped(true)
|
||||||
.show(ui, |ui| #[rustfmt::skip] {
|
// Apparently, if the #[rustfmt::skip] is in front of the closure scope, rust-analyzer can't
|
||||||
|
// parse the code properly. Things will *sometimes* work if the skip is on the other side of
|
||||||
|
// the opening bracket (even though that should only skip formatting the first line of the
|
||||||
|
// closure), but things as arbitrary as adding a comment to the code cause it to be formatted
|
||||||
|
// again. Thus, there is a completely pointless inner scope in this closure, just so that the
|
||||||
|
// code doesn't take up an unreasonable amount of space when formatted. We need that space for
|
||||||
|
// interesting and educational code comments like this one.
|
||||||
|
.show(ui, |ui| { #[rustfmt::skip] {
|
||||||
ui.label("State");
|
ui.label("State");
|
||||||
poise_state_label(ui, poise);
|
poise_state_label(ui, poise);
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
|
two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
|
||||||
two_col_row(ui, "Base Max", format!("{:.1}", poise.base_max()));
|
two_col_row(ui, "Base Max", format!("{:.1}", poise.base_max()));
|
||||||
});
|
}});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
411
voxygen/src/cmd.rs
Normal file
411
voxygen/src/cmd.rs
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::GlobalState;
|
||||||
|
use client::Client;
|
||||||
|
use common::{cmd::*, parse_cmd_args, uuid::Uuid};
|
||||||
|
|
||||||
|
// Please keep this sorted alphabetically, same as with server commands :-)
|
||||||
|
#[derive(Clone, Copy, strum::EnumIter)]
|
||||||
|
pub enum ClientChatCommand {
|
||||||
|
Mute,
|
||||||
|
Unmute,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientChatCommand {
|
||||||
|
pub fn data(&self) -> ChatCommandData {
|
||||||
|
use ArgumentSpec::*;
|
||||||
|
use Requirement::*;
|
||||||
|
let cmd = ChatCommandData::new;
|
||||||
|
match self {
|
||||||
|
ClientChatCommand::Mute => cmd(
|
||||||
|
vec![PlayerName(Required)],
|
||||||
|
"Mutes chat messages from a player.",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
ClientChatCommand::Unmute => cmd(
|
||||||
|
vec![PlayerName(Required)],
|
||||||
|
"Unmutes a player muted with the 'mute' command.",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keyword(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ClientChatCommand::Mute => "mute",
|
||||||
|
ClientChatCommand::Unmute => "unmute",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(_) => "{}",
|
||||||
|
ArgumentSpec::SiteName(_) => "{/.*/}",
|
||||||
|
ArgumentSpec::Float(_, _, _) => "{}",
|
||||||
|
ArgumentSpec::Integer(_, _, _) => "{d}",
|
||||||
|
ArgumentSpec::Any(_, _) => "{}",
|
||||||
|
ArgumentSpec::Command(_) => "{}",
|
||||||
|
ArgumentSpec::Message(_) => "{/.*/}",
|
||||||
|
ArgumentSpec::SubCommand => "{} {/.*/}",
|
||||||
|
ArgumentSpec::Enum(_, _, _) => "{}",
|
||||||
|
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().map(|c| (c.keyword(), c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ClientChatCommand {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(keyword: &str) -> Result<ClientChatCommand, ()> {
|
||||||
|
Self::iter()
|
||||||
|
.map(|c| (c.keyword(), c))
|
||||||
|
.find_map(|(kwd, command)| (kwd == keyword).then(|| command))
|
||||||
|
.ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum ChatCommandKind {
|
||||||
|
Client(ClientChatCommand),
|
||||||
|
Server(ServerChatCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ChatCommandKind {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, String> {
|
||||||
|
if let Ok(cmd) = s.parse::<ClientChatCommand>() {
|
||||||
|
Ok(ChatCommandKind::Client(cmd))
|
||||||
|
} else if let Ok(cmd) = s.parse::<ServerChatCommand>() {
|
||||||
|
Ok(ChatCommandKind::Server(cmd))
|
||||||
|
} else {
|
||||||
|
Err(format!("Could not find a command named {}.", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the feedback shown to the user of a command, if any. Server
|
||||||
|
/// commands give their feedback as an event, so in those cases this will always
|
||||||
|
/// be Ok(None). An Err variant will be be displayed with the error icon and
|
||||||
|
/// text color
|
||||||
|
type CommandResult = Result<Option<String>, String>;
|
||||||
|
|
||||||
|
/// Runs a command by either sending it to the server or processing it
|
||||||
|
/// locally. Returns a String to be output to the chat.
|
||||||
|
// Note: it's not clear what data future commands will need access to, so the
|
||||||
|
// signature of this function might change
|
||||||
|
pub fn run_command(
|
||||||
|
client: &mut Client,
|
||||||
|
global_state: &mut GlobalState,
|
||||||
|
cmd: &str,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> CommandResult {
|
||||||
|
let command = ChatCommandKind::from_str(cmd)?;
|
||||||
|
|
||||||
|
match command {
|
||||||
|
ChatCommandKind::Server(cmd) => {
|
||||||
|
client.send_command(cmd.keyword().into(), args);
|
||||||
|
Ok(None) // The server will provide a response when the command is run
|
||||||
|
},
|
||||||
|
ChatCommandKind::Client(cmd) => {
|
||||||
|
Ok(Some(run_client_command(client, global_state, cmd, args)?))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_client_command(
|
||||||
|
client: &mut Client,
|
||||||
|
global_state: &mut GlobalState,
|
||||||
|
command: ClientChatCommand,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
match command {
|
||||||
|
ClientChatCommand::Mute => handle_mute(client, global_state, args),
|
||||||
|
ClientChatCommand::Unmute => handle_unmute(client, global_state, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mute(
|
||||||
|
client: &Client,
|
||||||
|
global_state: &mut GlobalState,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if let Some(alias) = parse_cmd_args!(args, String) {
|
||||||
|
let target = client
|
||||||
|
.player_list()
|
||||||
|
.values()
|
||||||
|
.find(|p| p.player_alias == alias)
|
||||||
|
.ok_or_else(|| format!("Could not find a player named {}", alias))?;
|
||||||
|
|
||||||
|
if let Some(me) = client.uid().and_then(|uid| client.player_list().get(&uid)) {
|
||||||
|
if target.uuid == me.uuid {
|
||||||
|
return Err("You cannot mute yourself.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if global_state
|
||||||
|
.profile
|
||||||
|
.mutelist
|
||||||
|
.insert(target.uuid, alias.clone())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
Ok(format!("Successfully muted player {}.", alias))
|
||||||
|
} else {
|
||||||
|
Err(format!("{} is already muted.", alias))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("You must specify a player to mute.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_unmute(
|
||||||
|
client: &Client,
|
||||||
|
global_state: &mut GlobalState,
|
||||||
|
args: Vec<String>,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
// Note that we don't care if this is a real player, so that it's possible
|
||||||
|
// to unmute someone when they're offline
|
||||||
|
if let Some(alias) = parse_cmd_args!(args, String) {
|
||||||
|
if let Some(uuid) = global_state
|
||||||
|
.profile
|
||||||
|
.mutelist
|
||||||
|
.iter()
|
||||||
|
.find(|(_, v)| **v == alias)
|
||||||
|
.map(|(k, _)| *k)
|
||||||
|
{
|
||||||
|
if let Some(me) = client.uid().and_then(|uid| client.player_list().get(&uid)) {
|
||||||
|
if uuid == me.uuid {
|
||||||
|
return Err("You cannot unmute yourself.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_state.profile.mutelist.remove(&uuid);
|
||||||
|
Ok(format!("Successfully unmuted player {}.", alias))
|
||||||
|
} else {
|
||||||
|
Err(format!("Could not find a muted player named {}.", alias))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("You must specify a player to unmute.".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper function to get the Uuid of a player with a given alias
|
||||||
|
pub fn get_player_uuid(client: &Client, alias: &String) -> Option<Uuid> {
|
||||||
|
client
|
||||||
|
.player_list()
|
||||||
|
.values()
|
||||||
|
.find(|p| p.player_alias == *alias)
|
||||||
|
.map(|p| p.uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TabComplete {
|
||||||
|
fn complete(&self, part: &str, client: &Client) -> Vec<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabComplete for ArgumentSpec {
|
||||||
|
fn complete(&self, part: &str, client: &Client) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
ArgumentSpec::PlayerName(_) => complete_player(part, client),
|
||||||
|
ArgumentSpec::SiteName(_) => complete_site(part, client),
|
||||||
|
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![]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ArgumentSpec::Any(_, _) => vec![],
|
||||||
|
ArgumentSpec::Command(_) => complete_command(part, ' '),
|
||||||
|
ArgumentSpec::Message(_) => complete_player(part, client),
|
||||||
|
ArgumentSpec::SubCommand => complete_command(part, ' '),
|
||||||
|
ArgumentSpec::Enum(_, strings, _) => strings
|
||||||
|
.iter()
|
||||||
|
.filter(|string| string.starts_with(part))
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect(),
|
||||||
|
ArgumentSpec::Boolean(_, part, _) => vec!["true", "false"]
|
||||||
|
.iter()
|
||||||
|
.filter(|string| string.starts_with(part))
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_player(part: &str, client: &Client) -> Vec<String> {
|
||||||
|
client
|
||||||
|
.player_list()
|
||||||
|
.values()
|
||||||
|
.map(|player_info| &player_info.player_alias)
|
||||||
|
.filter(|alias| alias.starts_with(part))
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_site(mut part: &str, client: &Client) -> Vec<String> {
|
||||||
|
if let Some(p) = part.strip_prefix('"') {
|
||||||
|
part = p;
|
||||||
|
}
|
||||||
|
client
|
||||||
|
.sites()
|
||||||
|
.values()
|
||||||
|
.filter_map(|site| match site.site.kind {
|
||||||
|
common_net::msg::world_msg::SiteKind::Cave => None,
|
||||||
|
_ => site.site.name.as_ref(),
|
||||||
|
})
|
||||||
|
.filter(|name| name.starts_with(part))
|
||||||
|
.map(|name| {
|
||||||
|
if name.contains(' ') {
|
||||||
|
format!("\"{}\"", name)
|
||||||
|
} else {
|
||||||
|
name.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the byte index of the nth word. Used in completing "/sudo p /subcmd"
|
||||||
|
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()) {
|
||||||
|
(true, true) => {},
|
||||||
|
(true, false) => {
|
||||||
|
is_space = false;
|
||||||
|
j += 1;
|
||||||
|
},
|
||||||
|
(false, true) => {
|
||||||
|
is_space = true;
|
||||||
|
},
|
||||||
|
(false, false) => {},
|
||||||
|
}
|
||||||
|
if j == n {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_command(part: &str, prefix: char) -> Vec<String> {
|
||||||
|
ServerChatCommand::iter_with_keywords()
|
||||||
|
.map(|(kwd, _)| kwd)
|
||||||
|
.chain(ClientChatCommand::iter_with_keywords().map(|(kwd, _)| kwd))
|
||||||
|
.filter(|kwd| kwd.starts_with(part))
|
||||||
|
.map(|kwd| format!("{}{}", prefix, kwd))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete(line: &str, client: &Client, cmd_prefix: char) -> Vec<String> {
|
||||||
|
let word = if line.chars().last().map_or(true, char::is_whitespace) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
line.split_whitespace().last().unwrap_or("")
|
||||||
|
};
|
||||||
|
|
||||||
|
if line.starts_with(cmd_prefix) {
|
||||||
|
let line = line.strip_prefix(cmd_prefix).unwrap_or(line);
|
||||||
|
let mut iter = line.split_whitespace();
|
||||||
|
let cmd = iter.next().unwrap();
|
||||||
|
let i = iter.count() + if word.is_empty() { 1 } else { 0 };
|
||||||
|
if i == 0 {
|
||||||
|
// Completing chat command name. This is the start of the line so the prefix
|
||||||
|
// will be part of it
|
||||||
|
let word = word.strip_prefix(cmd_prefix).unwrap_or(word);
|
||||||
|
return complete_command(word, cmd_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = {
|
||||||
|
if let Ok(cmd) = cmd.parse::<ServerChatCommand>() {
|
||||||
|
Some(cmd.data().args)
|
||||||
|
} else if let Ok(cmd) = cmd.parse::<ClientChatCommand>() {
|
||||||
|
Some(cmd.data().args)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(args) = args {
|
||||||
|
if let Some(arg) = args.get(i - 1) {
|
||||||
|
// Complete ith argument
|
||||||
|
arg.complete(word, client)
|
||||||
|
} else {
|
||||||
|
// Complete past the last argument
|
||||||
|
match args.last() {
|
||||||
|
Some(ArgumentSpec::SubCommand) => {
|
||||||
|
if let Some(index) = nth_word(line, args.len()) {
|
||||||
|
complete(&line[index..], client, cmd_prefix)
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(ArgumentSpec::Message(_)) => complete_player(word, client),
|
||||||
|
_ => vec![], // End of command. Nothing to complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Completing for unknown chat command
|
||||||
|
complete_player(word, client)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not completing a command
|
||||||
|
complete_player(word, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cmd_list_sorted() {
|
||||||
|
let mut list = ClientChatCommand::iter()
|
||||||
|
.map(|c| c.keyword())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Vec::is_sorted is unstable, so we do it the hard way
|
||||||
|
let list2 = list.clone();
|
||||||
|
list.sort_unstable();
|
||||||
|
assert_eq!(list, list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_command() {
|
||||||
|
assert_eq!(complete_command("mu", '/'), vec!["/mute".to_string()]);
|
||||||
|
assert_eq!(complete_command("unba", '/'), vec!["/unban".to_string()]);
|
||||||
|
assert_eq!(complete_command("make_", '/'), vec![
|
||||||
|
"/make_block".to_string(),
|
||||||
|
"/make_npc".to_string(),
|
||||||
|
"/make_sprite".to_string(),
|
||||||
|
"/make_volume".to_string()
|
||||||
|
]);
|
||||||
|
}
|
@ -2,8 +2,8 @@ use super::{
|
|||||||
img_ids::Imgs, ChatTab, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR,
|
img_ids::Imgs, ChatTab, ERROR_COLOR, FACTION_COLOR, GROUP_COLOR, INFO_COLOR, KILL_COLOR,
|
||||||
OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, WORLD_COLOR,
|
OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, WORLD_COLOR,
|
||||||
};
|
};
|
||||||
use crate::{settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState};
|
use crate::{cmd::complete, settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState};
|
||||||
use client::{cmd, Client};
|
use client::Client;
|
||||||
use common::comp::{
|
use common::comp::{
|
||||||
chat::{KillSource, KillType},
|
chat::{KillSource, KillType},
|
||||||
group::Role,
|
group::Role,
|
||||||
@ -108,7 +108,11 @@ impl<'a> Chat<'a> {
|
|||||||
|
|
||||||
pub fn prepare_tab_completion(mut self, input: String) -> Self {
|
pub fn prepare_tab_completion(mut self, input: String) -> Self {
|
||||||
self.force_completions = if let Some(index) = input.find('\t') {
|
self.force_completions = if let Some(index) = input.find('\t') {
|
||||||
Some(cmd::complete(&input[..index], self.client))
|
Some(complete(
|
||||||
|
&input[..index],
|
||||||
|
self.client,
|
||||||
|
self.global_state.settings.chat.chat_cmd_prefix,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -659,7 +663,7 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
s.history.truncate(self.history_max);
|
s.history.truncate(self.history_max);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if let Some(msg) = msg.strip_prefix('/') {
|
if let Some(msg) = msg.strip_prefix(chat_settings.chat_cmd_prefix) {
|
||||||
match parse_cmd(msg) {
|
match parse_cmd(msg) {
|
||||||
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
|
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
|
||||||
Err(err) => self.new_messages.push_back(ChatMsg {
|
Err(err) => self.new_messages.push_back(ChatMsg {
|
||||||
|
@ -53,6 +53,7 @@ use social::Social;
|
|||||||
use trade::Trade;
|
use trade::Trade;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cmd::get_player_uuid,
|
||||||
ecs::{comp as vcomp, comp::HpFloaterList},
|
ecs::{comp as vcomp, comp::HpFloaterList},
|
||||||
game_input::GameInput,
|
game_input::GameInput,
|
||||||
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
||||||
@ -1789,6 +1790,21 @@ impl Hud {
|
|||||||
self.speech_bubbles
|
self.speech_bubbles
|
||||||
.retain(|_uid, bubble| bubble.timeout > now);
|
.retain(|_uid, bubble| bubble.timeout > now);
|
||||||
|
|
||||||
|
// Don't show messages from muted players
|
||||||
|
self.new_messages.retain(|msg| match msg.uid() {
|
||||||
|
Some(uid) => match client.player_list().get(&uid) {
|
||||||
|
Some(player_info) => {
|
||||||
|
if let Some(uuid) = get_player_uuid(client, &player_info.player_alias) {
|
||||||
|
!global_state.profile.mutelist.contains_key(&uuid)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => true,
|
||||||
|
},
|
||||||
|
None => true,
|
||||||
|
});
|
||||||
|
|
||||||
// Push speech bubbles
|
// Push speech bubbles
|
||||||
for msg in self.new_messages.iter() {
|
for msg in self.new_messages.iter() {
|
||||||
if let Some((bubble, uid)) = msg.to_bubble() {
|
if let Some((bubble, uid)) = msg.to_bubble() {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
|
pub mod cmd;
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
mod credits;
|
mod credits;
|
||||||
mod ecs;
|
mod ecs;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::hud;
|
use crate::hud;
|
||||||
use common::character::CharacterId;
|
use common::{character::CharacterId, uuid::Uuid};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@ -57,6 +57,7 @@ impl Default for ServerProfile {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
pub servers: HashMap<String, ServerProfile>,
|
pub servers: HashMap<String, ServerProfile>,
|
||||||
|
pub mutelist: HashMap<Uuid, String>,
|
||||||
/// Temporary character profile, used when it should
|
/// Temporary character profile, used when it should
|
||||||
/// not be persisted to the disk.
|
/// not be persisted to the disk.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
@ -41,6 +41,7 @@ use common_net::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::sfx::SfxEvent,
|
audio::sfx::SfxEvent,
|
||||||
|
cmd::run_command,
|
||||||
error::Error,
|
error::Error,
|
||||||
game_input::GameInput,
|
game_input::GameInput,
|
||||||
hud::{
|
hud::{
|
||||||
@ -1155,7 +1156,16 @@ impl PlayState for SessionState {
|
|||||||
self.client.borrow_mut().send_chat(msg);
|
self.client.borrow_mut().send_chat(msg);
|
||||||
},
|
},
|
||||||
HudEvent::SendCommand(name, args) => {
|
HudEvent::SendCommand(name, args) => {
|
||||||
self.client.borrow_mut().send_command(name, args);
|
match run_command(&mut self.client.borrow_mut(), global_state, &name, args)
|
||||||
|
{
|
||||||
|
Ok(Some(info)) => {
|
||||||
|
self.hud.new_message(ChatType::CommandInfo.chat_msg(&info))
|
||||||
|
},
|
||||||
|
Ok(None) => {}, // Server will provide an info message
|
||||||
|
Err(error) => {
|
||||||
|
self.hud.new_message(ChatType::CommandError.chat_msg(error))
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
HudEvent::CharacterSelection => {
|
HudEvent::CharacterSelection => {
|
||||||
self.client.borrow_mut().request_remove_character()
|
self.client.borrow_mut().request_remove_character()
|
||||||
|
@ -73,6 +73,7 @@ pub struct ChatSettings {
|
|||||||
pub chat_character_name: bool,
|
pub chat_character_name: bool,
|
||||||
pub chat_tabs: Vec<ChatTab>,
|
pub chat_tabs: Vec<ChatTab>,
|
||||||
pub chat_tab_index: Option<usize>,
|
pub chat_tab_index: Option<usize>,
|
||||||
|
pub chat_cmd_prefix: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ChatSettings {
|
impl Default for ChatSettings {
|
||||||
@ -82,6 +83,7 @@ impl Default for ChatSettings {
|
|||||||
chat_character_name: true,
|
chat_character_name: true,
|
||||||
chat_tabs: vec![ChatTab::default()],
|
chat_tabs: vec![ChatTab::default()],
|
||||||
chat_tab_index: Some(0),
|
chat_tab_index: Some(0),
|
||||||
|
chat_cmd_prefix: '/',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user