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]
|
||||
|
||||
### Added
|
||||
|
||||
- Chat commands to mute and unmute players
|
||||
- Waypoints saved between sessions and shared with group members.
|
||||
- New rocks
|
||||
- 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)]
|
||||
|
||||
pub mod addr;
|
||||
pub mod cmd;
|
||||
pub mod error;
|
||||
|
||||
// Reexports
|
||||
|
@ -14,6 +14,7 @@ use common::{
|
||||
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||
uid::Uid,
|
||||
uuid::Uuid,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -229,6 +230,7 @@ pub struct PlayerInfo {
|
||||
pub is_online: bool,
|
||||
pub player_alias: String,
|
||||
pub character: Option<CharacterInfo>,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
#[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`
|
||||
/// page in the Veloren Book. It can be run with `cargo cmd-doc-gen`.
|
||||
fn main() {
|
||||
println!("|Command|Description|Requires|Arguments|");
|
||||
println!("|-|-|-|-|");
|
||||
for cmd in ChatCommand::iter() {
|
||||
for cmd in ServerChatCommand::iter() {
|
||||
let args = cmd
|
||||
.data()
|
||||
.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)]
|
||||
pub enum KitSpec {
|
||||
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 {
|
||||
use ArgumentSpec::*;
|
||||
use Requirement::*;
|
||||
use Role::*;
|
||||
let cmd = ChatCommandData::new;
|
||||
match self {
|
||||
ChatCommand::Adminify => cmd(
|
||||
ServerChatCommand::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(
|
||||
ServerChatCommand::Airship => cmd(
|
||||
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
||||
"Spawns an airship",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Alias => cmd(
|
||||
ServerChatCommand::Alias => cmd(
|
||||
vec![Any("name", Required)],
|
||||
"Change your alias",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::ApplyBuff => cmd(
|
||||
ServerChatCommand::ApplyBuff => cmd(
|
||||
vec![
|
||||
Enum("buff", BUFFS.clone(), Required),
|
||||
Float("strength", 0.01, Optional),
|
||||
@ -331,7 +331,7 @@ impl ChatCommand {
|
||||
"Cast a buff on player",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Ban => cmd(
|
||||
ServerChatCommand::Ban => cmd(
|
||||
vec![
|
||||
PlayerName(Required),
|
||||
Boolean("overwrite", "true".to_string(), Optional),
|
||||
@ -343,7 +343,7 @@ impl ChatCommand {
|
||||
Some(Moderator),
|
||||
),
|
||||
#[rustfmt::skip]
|
||||
ChatCommand::BattleMode => cmd(
|
||||
ServerChatCommand::BattleMode => cmd(
|
||||
vec![Enum(
|
||||
"battle mode",
|
||||
vec!["pvp".to_owned(), "pve".to_owned()],
|
||||
@ -354,8 +354,9 @@ impl ChatCommand {
|
||||
* pve (player vs environment).\n\
|
||||
If called without arguments will show current battle mode.",
|
||||
None,
|
||||
|
||||
),
|
||||
ChatCommand::BattleModeForce => cmd(
|
||||
ServerChatCommand::BattleModeForce => cmd(
|
||||
vec![Enum(
|
||||
"battle mode",
|
||||
vec!["pvp".to_owned(), "pve".to_owned()],
|
||||
@ -364,8 +365,8 @@ impl ChatCommand {
|
||||
"Change your battle mode flag without any checks",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
|
||||
ChatCommand::BuildAreaAdd => cmd(
|
||||
ServerChatCommand::Build => cmd(vec![], "Toggles build mode on and off", None),
|
||||
ServerChatCommand::BuildAreaAdd => cmd(
|
||||
vec![
|
||||
Any("name", Required),
|
||||
Integer("xlo", 0, Required),
|
||||
@ -378,40 +379,40 @@ impl ChatCommand {
|
||||
"Adds a new build area",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
|
||||
ChatCommand::BuildAreaRemove => cmd(
|
||||
ServerChatCommand::BuildAreaList => cmd(vec![], "List all build areas", Some(Admin)),
|
||||
ServerChatCommand::BuildAreaRemove => cmd(
|
||||
vec![Any("name", Required)],
|
||||
"Removes specified build area",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
|
||||
ChatCommand::DebugColumn => cmd(
|
||||
ServerChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Some(Admin)),
|
||||
ServerChatCommand::DebugColumn => cmd(
|
||||
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
||||
"Prints some debug information about a column",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::DisconnectAllPlayers => cmd(
|
||||
ServerChatCommand::DisconnectAllPlayers => cmd(
|
||||
vec![Any("confirm", Required)],
|
||||
"Disconnects all players from the server",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::DropAll => cmd(
|
||||
ServerChatCommand::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(
|
||||
ServerChatCommand::Dummy => cmd(vec![], "Spawns a training dummy", Some(Admin)),
|
||||
ServerChatCommand::Explosion => cmd(
|
||||
vec![Float("radius", 5.0, Required)],
|
||||
"Explodes the ground around you",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Faction => cmd(
|
||||
ServerChatCommand::Faction => cmd(
|
||||
vec![Message(Optional)],
|
||||
"Send messages to your faction",
|
||||
None,
|
||||
),
|
||||
ChatCommand::GiveItem => cmd(
|
||||
ServerChatCommand::GiveItem => cmd(
|
||||
vec![
|
||||
Enum("item", ITEM_SPECS.clone(), Required),
|
||||
Integer("num", 1, Optional),
|
||||
@ -419,7 +420,7 @@ impl ChatCommand {
|
||||
"Give yourself some items.\nFor an example or to auto complete use Tab.",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Goto => cmd(
|
||||
ServerChatCommand::Goto => cmd(
|
||||
vec![
|
||||
Float("x", 0.0, Required),
|
||||
Float("y", 0.0, Required),
|
||||
@ -428,40 +429,42 @@ impl ChatCommand {
|
||||
"Teleport to a position",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Group => cmd(vec![Message(Optional)], "Send messages to your group", None),
|
||||
ChatCommand::GroupInvite => cmd(
|
||||
ServerChatCommand::Group => {
|
||||
cmd(vec![Message(Optional)], "Send messages to your group", None)
|
||||
},
|
||||
ServerChatCommand::GroupInvite => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Invite a player to join a group",
|
||||
None,
|
||||
),
|
||||
ChatCommand::GroupKick => cmd(
|
||||
ServerChatCommand::GroupKick => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Remove a player from a group",
|
||||
None,
|
||||
),
|
||||
ChatCommand::GroupLeave => cmd(vec![], "Leave the current group", None),
|
||||
ChatCommand::GroupPromote => cmd(
|
||||
ServerChatCommand::GroupLeave => cmd(vec![], "Leave the current group", None),
|
||||
ServerChatCommand::GroupPromote => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Promote a player to group leader",
|
||||
None,
|
||||
),
|
||||
ChatCommand::Health => cmd(
|
||||
ServerChatCommand::Health => cmd(
|
||||
vec![Integer("hp", 100, Required)],
|
||||
"Set your current health",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Help => ChatCommandData::new(
|
||||
ServerChatCommand::Help => ChatCommandData::new(
|
||||
vec![Command(Optional)],
|
||||
"Display information about commands",
|
||||
None,
|
||||
),
|
||||
ChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
|
||||
ChatCommand::JoinFaction => ChatCommandData::new(
|
||||
ServerChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
|
||||
ServerChatCommand::JoinFaction => ChatCommandData::new(
|
||||
vec![Any("faction", Optional)],
|
||||
"Join/leave the specified faction",
|
||||
None,
|
||||
),
|
||||
ChatCommand::Jump => cmd(
|
||||
ServerChatCommand::Jump => cmd(
|
||||
vec![
|
||||
Float("x", 0.0, Required),
|
||||
Float("y", 0.0, Required),
|
||||
@ -470,19 +473,19 @@ impl ChatCommand {
|
||||
"Offset your current position",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Kick => cmd(
|
||||
ServerChatCommand::Kick => cmd(
|
||||
vec![PlayerName(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(
|
||||
ServerChatCommand::Kill => cmd(vec![], "Kill yourself", None),
|
||||
ServerChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", Some(Admin)),
|
||||
ServerChatCommand::Kit => cmd(
|
||||
vec![Enum("kit_name", KITS.to_vec(), Required)],
|
||||
"Place a set of items into your inventory.",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Lantern => cmd(
|
||||
ServerChatCommand::Lantern => cmd(
|
||||
vec![
|
||||
Float("strength", 5.0, Required),
|
||||
Float("r", 1.0, Optional),
|
||||
@ -492,7 +495,7 @@ impl ChatCommand {
|
||||
"Change your lantern's strength and color",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Light => cmd(
|
||||
ServerChatCommand::Light => cmd(
|
||||
vec![
|
||||
Float("r", 1.0, Optional),
|
||||
Float("g", 1.0, Optional),
|
||||
@ -505,7 +508,7 @@ impl ChatCommand {
|
||||
"Spawn entity with light",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::MakeBlock => cmd(
|
||||
ServerChatCommand::MakeBlock => cmd(
|
||||
vec![
|
||||
Enum("block", BLOCK_KINDS.clone(), Required),
|
||||
Integer("r", 255, Optional),
|
||||
@ -515,7 +518,7 @@ impl ChatCommand {
|
||||
"Make a block at your location with a color",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::MakeNpc => cmd(
|
||||
ServerChatCommand::MakeNpc => cmd(
|
||||
vec![
|
||||
Enum("entity_config", ENTITY_CONFIGS.clone(), Required),
|
||||
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.",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::MakeSprite => cmd(
|
||||
ServerChatCommand::MakeSprite => cmd(
|
||||
vec![Enum("sprite", SPRITE_KINDS.clone(), Required)],
|
||||
"Make a sprite at your location",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Motd => cmd(vec![Message(Optional)], "View the server description", None),
|
||||
ChatCommand::Object => cmd(
|
||||
ServerChatCommand::Motd => {
|
||||
cmd(vec![Message(Optional)], "View the server description", None)
|
||||
},
|
||||
ServerChatCommand::Object => cmd(
|
||||
vec![Enum("object", OBJECTS.clone(), Required)],
|
||||
"Spawn an object",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::PermitBuild => cmd(
|
||||
ServerChatCommand::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::ReloadChunks => cmd(
|
||||
ServerChatCommand::Players => cmd(vec![], "Lists players currently online", None),
|
||||
ServerChatCommand::ReloadChunks => cmd(
|
||||
vec![],
|
||||
"Reloads all chunks loaded on the server",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::RemoveLights => cmd(
|
||||
ServerChatCommand::RemoveLights => cmd(
|
||||
vec![Float("radius", 20.0, Optional)],
|
||||
"Removes all lights spawned by players",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::RevokeBuild => cmd(
|
||||
ServerChatCommand::RevokeBuild => cmd(
|
||||
vec![Any("area_name", Required)],
|
||||
"Revokes build area permission for player",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::RevokeBuildAll => cmd(
|
||||
ServerChatCommand::RevokeBuildAll => cmd(
|
||||
vec![],
|
||||
"Revokes all build area permissions for player",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Region => cmd(
|
||||
ServerChatCommand::Region => cmd(
|
||||
vec![Message(Optional)],
|
||||
"Send messages to everyone in your region of the world",
|
||||
None,
|
||||
),
|
||||
ChatCommand::Safezone => cmd(
|
||||
ServerChatCommand::Safezone => cmd(
|
||||
vec![Float("range", 100.0, Optional)],
|
||||
"Creates a safezone",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::Say => cmd(
|
||||
ServerChatCommand::Say => cmd(
|
||||
vec![Message(Optional)],
|
||||
"Send messages to everyone within shouting distance",
|
||||
None,
|
||||
),
|
||||
ChatCommand::ServerPhysics => cmd(
|
||||
ServerChatCommand::ServerPhysics => cmd(
|
||||
vec![
|
||||
PlayerName(Required),
|
||||
Boolean("enabled", "true".to_string(), Optional),
|
||||
@ -583,24 +588,24 @@ impl ChatCommand {
|
||||
"Set/unset server-authoritative physics for an account",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::SetMotd => cmd(
|
||||
ServerChatCommand::SetMotd => cmd(
|
||||
vec![Message(Optional)],
|
||||
"Set the server description",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Ship => cmd(
|
||||
ServerChatCommand::Ship => cmd(
|
||||
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
||||
"Spawns a ship",
|
||||
Some(Admin),
|
||||
),
|
||||
// Uses Message because site names can contain spaces,
|
||||
// which would be assumed to be separators otherwise
|
||||
ChatCommand::Site => cmd(
|
||||
ServerChatCommand::Site => cmd(
|
||||
vec![SiteName(Required)],
|
||||
"Teleport to a site",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::SkillPoint => cmd(
|
||||
ServerChatCommand::SkillPoint => cmd(
|
||||
vec![
|
||||
Enum("skill tree", SKILL_TREES.clone(), Required),
|
||||
Integer("amount", 1, Optional),
|
||||
@ -608,12 +613,12 @@ impl ChatCommand {
|
||||
"Give yourself skill points for a particular skill tree",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::SkillPreset => cmd(
|
||||
ServerChatCommand::SkillPreset => cmd(
|
||||
vec![Enum("preset_name", PRESET_LIST.to_vec(), Required)],
|
||||
"Gives your character desired skills.",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Spawn => cmd(
|
||||
ServerChatCommand::Spawn => cmd(
|
||||
vec![
|
||||
Enum("alignment", ALIGNMENTS.clone(), Required),
|
||||
Enum("entity", ENTITIES.clone(), Required),
|
||||
@ -623,58 +628,60 @@ impl ChatCommand {
|
||||
"Spawn a test entity",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Sudo => cmd(
|
||||
ServerChatCommand::Sudo => cmd(
|
||||
vec![PlayerName(Required), SubCommand],
|
||||
"Run command as if you were another player",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::Tell => cmd(
|
||||
ServerChatCommand::Tell => cmd(
|
||||
vec![PlayerName(Required), Message(Optional)],
|
||||
"Send a message to another player",
|
||||
None,
|
||||
),
|
||||
ChatCommand::Time => cmd(
|
||||
ServerChatCommand::Time => cmd(
|
||||
vec![Enum("time", TIMES.clone(), Optional)],
|
||||
"Set the time of day",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Tp => cmd(
|
||||
ServerChatCommand::Tp => cmd(
|
||||
vec![PlayerName(Optional)],
|
||||
"Teleport to another player",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::Unban => cmd(
|
||||
ServerChatCommand::Unban => cmd(
|
||||
vec![PlayerName(Required)],
|
||||
"Remove the ban for the given username",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::Version => cmd(vec![], "Prints server version", None),
|
||||
ChatCommand::Waypoint => cmd(
|
||||
ServerChatCommand::Version => cmd(vec![], "Prints server version", None),
|
||||
ServerChatCommand::Waypoint => cmd(
|
||||
vec![],
|
||||
"Set your waypoint to your current position",
|
||||
Some(Admin),
|
||||
),
|
||||
ChatCommand::Wiring => cmd(vec![], "Create wiring element", Some(Admin)),
|
||||
ChatCommand::Whitelist => cmd(
|
||||
ServerChatCommand::Wiring => cmd(vec![], "Create wiring element", Some(Admin)),
|
||||
ServerChatCommand::Whitelist => cmd(
|
||||
vec![Any("add/remove", Required), PlayerName(Required)],
|
||||
"Adds/removes username to whitelist",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::World => cmd(
|
||||
ServerChatCommand::World => cmd(
|
||||
vec![Message(Optional)],
|
||||
"Send messages to everyone on the server",
|
||||
None,
|
||||
),
|
||||
ChatCommand::MakeVolume => cmd(vec![], "Create a volume (experimental)", Some(Admin)),
|
||||
ChatCommand::Location => {
|
||||
ServerChatCommand::MakeVolume => {
|
||||
cmd(vec![], "Create a volume (experimental)", Some(Admin))
|
||||
},
|
||||
ServerChatCommand::Location => {
|
||||
cmd(vec![Any("name", Required)], "Teleport to a location", None)
|
||||
},
|
||||
ChatCommand::CreateLocation => cmd(
|
||||
ServerChatCommand::CreateLocation => cmd(
|
||||
vec![Any("name", Required)],
|
||||
"Create a location at the current position",
|
||||
Some(Moderator),
|
||||
),
|
||||
ChatCommand::DeleteLocation => cmd(
|
||||
ServerChatCommand::DeleteLocation => cmd(
|
||||
vec![Any("name", Required)],
|
||||
"Delete a location",
|
||||
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 {
|
||||
match self {
|
||||
ChatCommand::Adminify => "adminify",
|
||||
ChatCommand::Airship => "airship",
|
||||
ChatCommand::Alias => "alias",
|
||||
ChatCommand::ApplyBuff => "buff",
|
||||
ChatCommand::Ban => "ban",
|
||||
ChatCommand::BattleMode => "battlemode",
|
||||
ChatCommand::BattleModeForce => "battlemode_force",
|
||||
ChatCommand::Build => "build",
|
||||
ChatCommand::BuildAreaAdd => "build_area_add",
|
||||
ChatCommand::BuildAreaList => "build_area_list",
|
||||
ChatCommand::BuildAreaRemove => "build_area_remove",
|
||||
ChatCommand::Campfire => "campfire",
|
||||
ChatCommand::DebugColumn => "debug_column",
|
||||
ChatCommand::DisconnectAllPlayers => "disconnect_all_players",
|
||||
ChatCommand::DropAll => "dropall",
|
||||
ChatCommand::Dummy => "dummy",
|
||||
ChatCommand::Explosion => "explosion",
|
||||
ChatCommand::Faction => "faction",
|
||||
ChatCommand::GiveItem => "give_item",
|
||||
ChatCommand::Goto => "goto",
|
||||
ChatCommand::Group => "group",
|
||||
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",
|
||||
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",
|
||||
ChatCommand::MakeBlock => "make_block",
|
||||
ChatCommand::MakeNpc => "make_npc",
|
||||
ChatCommand::MakeSprite => "make_sprite",
|
||||
ChatCommand::Motd => "motd",
|
||||
ChatCommand::Object => "object",
|
||||
ChatCommand::PermitBuild => "permit_build",
|
||||
ChatCommand::Players => "players",
|
||||
ChatCommand::Region => "region",
|
||||
ChatCommand::ReloadChunks => "reload_chunks",
|
||||
ChatCommand::RemoveLights => "remove_lights",
|
||||
ChatCommand::RevokeBuild => "revoke_build",
|
||||
ChatCommand::RevokeBuildAll => "revoke_build_all",
|
||||
ChatCommand::Safezone => "safezone",
|
||||
ChatCommand::Say => "say",
|
||||
ChatCommand::ServerPhysics => "server_physics",
|
||||
ChatCommand::SetMotd => "set_motd",
|
||||
ChatCommand::Ship => "ship",
|
||||
ChatCommand::Site => "site",
|
||||
ChatCommand::SkillPoint => "skill_point",
|
||||
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",
|
||||
ChatCommand::Wiring => "wiring",
|
||||
ChatCommand::Whitelist => "whitelist",
|
||||
ChatCommand::World => "world",
|
||||
ChatCommand::MakeVolume => "make_volume",
|
||||
ChatCommand::Location => "location",
|
||||
ChatCommand::CreateLocation => "create_location",
|
||||
ChatCommand::DeleteLocation => "delete_location",
|
||||
ServerChatCommand::Adminify => "adminify",
|
||||
ServerChatCommand::Airship => "airship",
|
||||
ServerChatCommand::Alias => "alias",
|
||||
ServerChatCommand::ApplyBuff => "buff",
|
||||
ServerChatCommand::Ban => "ban",
|
||||
ServerChatCommand::BattleMode => "battlemode",
|
||||
ServerChatCommand::BattleModeForce => "battlemode_force",
|
||||
ServerChatCommand::Build => "build",
|
||||
ServerChatCommand::BuildAreaAdd => "build_area_add",
|
||||
ServerChatCommand::BuildAreaList => "build_area_list",
|
||||
ServerChatCommand::BuildAreaRemove => "build_area_remove",
|
||||
ServerChatCommand::Campfire => "campfire",
|
||||
ServerChatCommand::DebugColumn => "debug_column",
|
||||
ServerChatCommand::DisconnectAllPlayers => "disconnect_all_players",
|
||||
ServerChatCommand::DropAll => "dropall",
|
||||
ServerChatCommand::Dummy => "dummy",
|
||||
ServerChatCommand::Explosion => "explosion",
|
||||
ServerChatCommand::Faction => "faction",
|
||||
ServerChatCommand::GiveItem => "give_item",
|
||||
ServerChatCommand::Goto => "goto",
|
||||
ServerChatCommand::Group => "group",
|
||||
ServerChatCommand::GroupInvite => "group_invite",
|
||||
ServerChatCommand::GroupKick => "group_kick",
|
||||
ServerChatCommand::GroupPromote => "group_promote",
|
||||
ServerChatCommand::GroupLeave => "group_leave",
|
||||
ServerChatCommand::Health => "health",
|
||||
ServerChatCommand::JoinFaction => "join_faction",
|
||||
ServerChatCommand::Help => "help",
|
||||
ServerChatCommand::Home => "home",
|
||||
ServerChatCommand::Jump => "jump",
|
||||
ServerChatCommand::Kick => "kick",
|
||||
ServerChatCommand::Kill => "kill",
|
||||
ServerChatCommand::Kit => "kit",
|
||||
ServerChatCommand::KillNpcs => "kill_npcs",
|
||||
ServerChatCommand::Lantern => "lantern",
|
||||
ServerChatCommand::Light => "light",
|
||||
ServerChatCommand::MakeBlock => "make_block",
|
||||
ServerChatCommand::MakeNpc => "make_npc",
|
||||
ServerChatCommand::MakeSprite => "make_sprite",
|
||||
ServerChatCommand::Motd => "motd",
|
||||
ServerChatCommand::Object => "object",
|
||||
ServerChatCommand::PermitBuild => "permit_build",
|
||||
ServerChatCommand::Players => "players",
|
||||
ServerChatCommand::Region => "region",
|
||||
ServerChatCommand::ReloadChunks => "reload_chunks",
|
||||
ServerChatCommand::RemoveLights => "remove_lights",
|
||||
ServerChatCommand::RevokeBuild => "revoke_build",
|
||||
ServerChatCommand::RevokeBuildAll => "revoke_build_all",
|
||||
ServerChatCommand::Safezone => "safezone",
|
||||
ServerChatCommand::Say => "say",
|
||||
ServerChatCommand::ServerPhysics => "server_physics",
|
||||
ServerChatCommand::SetMotd => "set_motd",
|
||||
ServerChatCommand::Ship => "ship",
|
||||
ServerChatCommand::Site => "site",
|
||||
ServerChatCommand::SkillPoint => "skill_point",
|
||||
ServerChatCommand::SkillPreset => "skill_preset",
|
||||
ServerChatCommand::Spawn => "spawn",
|
||||
ServerChatCommand::Sudo => "sudo",
|
||||
ServerChatCommand::Tell => "tell",
|
||||
ServerChatCommand::Time => "time",
|
||||
ServerChatCommand::Tp => "tp",
|
||||
ServerChatCommand::Unban => "unban",
|
||||
ServerChatCommand::Version => "version",
|
||||
ServerChatCommand::Waypoint => "waypoint",
|
||||
ServerChatCommand::Wiring => "wiring",
|
||||
ServerChatCommand::Whitelist => "whitelist",
|
||||
ServerChatCommand::World => "world",
|
||||
ServerChatCommand::MakeVolume => "make_volume",
|
||||
ServerChatCommand::Location => "location",
|
||||
ServerChatCommand::CreateLocation => "create_location",
|
||||
ServerChatCommand::DeleteLocation => "delete_location",
|
||||
}
|
||||
}
|
||||
|
||||
@ -763,16 +770,19 @@ impl ChatCommand {
|
||||
/// 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",
|
||||
ServerChatCommand::Faction => "f",
|
||||
ServerChatCommand::Group => "g",
|
||||
ServerChatCommand::Region => "r",
|
||||
ServerChatCommand::Say => "s",
|
||||
ServerChatCommand::Tell => "t",
|
||||
ServerChatCommand::World => "w",
|
||||
_ => 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
|
||||
pub fn help_string(&self) -> String {
|
||||
let data = self.data();
|
||||
@ -783,9 +793,17 @@ impl ChatCommand {
|
||||
format!("{}: {}", usage, data.description)
|
||||
}
|
||||
|
||||
/// 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 }
|
||||
/// 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)))
|
||||
}
|
||||
|
||||
pub fn needs_role(&self) -> Option<comp::AdminRole> { self.data().needs_role }
|
||||
|
||||
/// Returns a format string for parsing arguments with scan_fmt
|
||||
pub fn arg_fmt(&self) -> String {
|
||||
@ -807,34 +825,22 @@ impl ChatCommand {
|
||||
.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 {
|
||||
impl Display for ServerChatCommand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.keyword())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ChatCommand {
|
||||
impl FromStr for ServerChatCommand {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(keyword: &str) -> Result<ChatCommand, ()> {
|
||||
let keyword = keyword.strip_prefix('/').unwrap_or(keyword);
|
||||
|
||||
Self::iter_with_keywords()
|
||||
fn from_str(keyword: &str) -> Result<ServerChatCommand, ()> {
|
||||
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)))
|
||||
// Find command with matching string as keyword
|
||||
.find_map(|(kwd, command)| (kwd == keyword).then(|| command))
|
||||
// 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)]
|
||||
mod tests {
|
||||
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(),
|
||||
}
|
||||
}),
|
||||
uuid: player.uuid(),
|
||||
}),
|
||||
);
|
||||
let remove_player_msg = ServerGeneral::PlayerListUpdate(
|
||||
|
@ -73,7 +73,7 @@ use common::{
|
||||
assets::AssetExt,
|
||||
calendar::Calendar,
|
||||
character::CharacterId,
|
||||
cmd::ChatCommand,
|
||||
cmd::ServerChatCommand,
|
||||
comp,
|
||||
event::{EventBus, ServerEvent},
|
||||
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>) {
|
||||
// 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);
|
||||
} else {
|
||||
#[cfg(feature = "plugins")]
|
||||
|
@ -88,6 +88,7 @@ impl<'a> System<'a> for Sys {
|
||||
character: stats.map(|stats| CharacterInfo {
|
||||
name: stats.name.clone(),
|
||||
}),
|
||||
uuid: player.uuid(),
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
@ -239,6 +240,7 @@ impl<'a> System<'a> for Sys {
|
||||
is_online: true,
|
||||
is_moderator: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
uuid: player.uuid(),
|
||||
}),
|
||||
)));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{AdminCommandState, EguiAction, EguiActions};
|
||||
use common::cmd::ChatCommand;
|
||||
use common::cmd::ServerChatCommand;
|
||||
use egui::{CollapsingHeader, CtxRef, Resize, Slider, Ui, Vec2, Window};
|
||||
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| {
|
||||
if ui.button("Give Kit").clicked() {
|
||||
egui_actions.actions.push(EguiAction::ChatCommand {
|
||||
cmd: ChatCommand::Kit,
|
||||
cmd: ServerChatCommand::Kit,
|
||||
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() {
|
||||
egui_actions.actions.push(EguiAction::ChatCommand {
|
||||
cmd: ChatCommand::GiveItem,
|
||||
cmd: ServerChatCommand::GiveItem,
|
||||
args: vec![
|
||||
format!(
|
||||
"common.items.{}",
|
||||
|
@ -10,6 +10,7 @@ mod widgets;
|
||||
|
||||
use client::{Client, Join, World, WorldExt};
|
||||
use common::{
|
||||
cmd::ServerChatCommand,
|
||||
comp,
|
||||
comp::{inventory::item::armor::Friction, Poise, PoiseState},
|
||||
};
|
||||
@ -24,10 +25,7 @@ use crate::{
|
||||
admin::draw_admin_commands_window, character_states::draw_char_state_group,
|
||||
experimental_shaders::draw_experimental_shaders_window, widgets::two_col_row,
|
||||
};
|
||||
use common::{
|
||||
cmd::ChatCommand,
|
||||
comp::{aura::AuraKind::Buff, Body, Fluid},
|
||||
};
|
||||
use common::comp::{aura::AuraKind::Buff, Body, Fluid};
|
||||
use egui_winit_platform::Platform;
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
@ -131,7 +129,10 @@ pub enum EguiDebugShapeAction {
|
||||
}
|
||||
|
||||
pub enum EguiAction {
|
||||
ChatCommand { cmd: ChatCommand, args: Vec<String> },
|
||||
ChatCommand {
|
||||
cmd: ServerChatCommand,
|
||||
args: Vec<String>,
|
||||
},
|
||||
DebugShape(EguiDebugShapeAction),
|
||||
SetExperimentalShader(String, bool),
|
||||
}
|
||||
@ -613,13 +614,20 @@ fn selected_entity_window(
|
||||
.spacing([40.0, 4.0])
|
||||
.max_col_width(100.0)
|
||||
.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");
|
||||
poise_state_label(ui, poise);
|
||||
ui.end_row();
|
||||
two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
|
||||
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,
|
||||
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 client::{cmd, Client};
|
||||
use crate::{cmd::complete, settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState};
|
||||
use client::Client;
|
||||
use common::comp::{
|
||||
chat::{KillSource, KillType},
|
||||
group::Role,
|
||||
@ -108,7 +108,11 @@ impl<'a> Chat<'a> {
|
||||
|
||||
pub fn prepare_tab_completion(mut self, input: String) -> Self {
|
||||
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 {
|
||||
None
|
||||
};
|
||||
@ -659,7 +663,7 @@ impl<'a> Widget for Chat<'a> {
|
||||
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) {
|
||||
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
|
||||
Err(err) => self.new_messages.push_back(ChatMsg {
|
||||
|
@ -53,6 +53,7 @@ use social::Social;
|
||||
use trade::Trade;
|
||||
|
||||
use crate::{
|
||||
cmd::get_player_uuid,
|
||||
ecs::{comp as vcomp, comp::HpFloaterList},
|
||||
game_input::GameInput,
|
||||
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
||||
@ -1789,6 +1790,21 @@ impl Hud {
|
||||
self.speech_bubbles
|
||||
.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
|
||||
for msg in self.new_messages.iter() {
|
||||
if let Some((bubble, uid)) = msg.to_bubble() {
|
||||
|
@ -18,6 +18,7 @@
|
||||
#[macro_use]
|
||||
pub mod ui;
|
||||
pub mod audio;
|
||||
pub mod cmd;
|
||||
pub mod controller;
|
||||
mod credits;
|
||||
mod ecs;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::hud;
|
||||
use common::character::CharacterId;
|
||||
use common::{character::CharacterId, uuid::Uuid};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@ -57,6 +57,7 @@ impl Default for ServerProfile {
|
||||
#[serde(default)]
|
||||
pub struct Profile {
|
||||
pub servers: HashMap<String, ServerProfile>,
|
||||
pub mutelist: HashMap<Uuid, String>,
|
||||
/// Temporary character profile, used when it should
|
||||
/// not be persisted to the disk.
|
||||
#[serde(skip)]
|
||||
|
@ -41,6 +41,7 @@ use common_net::{
|
||||
|
||||
use crate::{
|
||||
audio::sfx::SfxEvent,
|
||||
cmd::run_command,
|
||||
error::Error,
|
||||
game_input::GameInput,
|
||||
hud::{
|
||||
@ -1155,7 +1156,16 @@ impl PlayState for SessionState {
|
||||
self.client.borrow_mut().send_chat(msg);
|
||||
},
|
||||
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 => {
|
||||
self.client.borrow_mut().request_remove_character()
|
||||
|
@ -73,6 +73,7 @@ pub struct ChatSettings {
|
||||
pub chat_character_name: bool,
|
||||
pub chat_tabs: Vec<ChatTab>,
|
||||
pub chat_tab_index: Option<usize>,
|
||||
pub chat_cmd_prefix: char,
|
||||
}
|
||||
|
||||
impl Default for ChatSettings {
|
||||
@ -82,6 +83,7 @@ impl Default for ChatSettings {
|
||||
chat_character_name: true,
|
||||
chat_tabs: vec![ChatTab::default()],
|
||||
chat_tab_index: Some(0),
|
||||
chat_cmd_prefix: '/',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user