Properly fixed command parsing

This commit is contained in:
Joshua Barretto 2021-07-23 17:33:31 +01:00
parent e9f6ae6b37
commit 0cb524d8d6
7 changed files with 206 additions and 182 deletions

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ maps
screenshots
todo.txt
userdata
heaptrack.*
# Export data
*.csv

View File

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bald hairstyles for humans and danari
- AI for sceptre wielders and sceptre cultists in Tier 5 dungeons
- HUD debug info now displays current biome and site
- Quotes and escape codes can be used in command arguments
### Changed

24
Cargo.lock generated
View File

@ -564,6 +564,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "chumsky"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6144914dd4928dc86677ef8824a77cf1a5df915590af7befa0ac8d9815cd740"
[[package]]
name = "clang-sys"
version = "1.2.0"
@ -716,12 +722,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "comma"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96677551532ffe910f470bd767a9a7daf9ba53b1f5532e0891dba6c735f692e5"
[[package]]
name = "concurrent-queue"
version = "1.2.2"
@ -4660,15 +4660,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scan_fmt"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248"
dependencies = [
"regex",
]
[[package]]
name = "schannel"
version = "0.1.19"
@ -6139,7 +6130,6 @@ dependencies = [
"refinery",
"ron",
"rusqlite",
"scan_fmt",
"serde",
"serde_json",
"slab",
@ -6191,7 +6181,7 @@ dependencies = [
"bincode",
"bytemuck",
"chrono",
"comma",
"chumsky",
"conrod_core",
"conrod_winit",
"copy_dir",

View File

@ -38,7 +38,6 @@ chrono = { version = "0.4.9", features = ["serde"] }
humantime = "2.1.0"
itertools = "0.10"
lazy_static = "1.4.0"
scan_fmt = "0.2.6"
ron = { version = "0.6", default-features = false }
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.50"

View File

@ -49,7 +49,6 @@ use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
use world::util::Sampler;
use crate::{client::Client, login_provider::LoginProvider, wiring};
use scan_fmt::{scan_fmt, scan_fmt_some};
use tracing::{error, info, warn};
pub trait ChatCommandExt {
@ -57,10 +56,6 @@ pub trait ChatCommandExt {
}
impl ChatCommandExt for ChatCommand {
fn execute(&self, server: &mut Server, entity: EcsEntity, args: Vec<String>) {
// TODO: Pass arguments to commands as Vec<String>, not String, to support
// proper parsing.
let args = args.join(" ");
if let Err(err) = do_command(server, entity, entity, args, self) {
server.notify_client(
entity,
@ -78,24 +73,25 @@ type CmdResult<T> = Result<T, String>;
/// command.
/// * `EcsEntity` - an `Entity` for the player on whom the command is invoked.
/// This differs from the previous argument when using /sudo
/// * `String` - a `String` containing the part of the command after the
/// keyword.
/// * `Vec<String>` - a `Vec<String>` containing the arguments of the command
/// after the keyword.
/// * `&ChatCommand` - the command to execute with the above arguments.
/// Handler functions must parse arguments from the the given `String`
/// (`scan_fmt!` is included for this purpose).
/// (`parse_args!` exists for this purpose).
///
/// # Returns
///
/// A `Result` that is `Ok` if the command went smoothly, and `Err` if it
/// failed; on failure, the string is sent to the client who initiated the
/// command.
type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand) -> CmdResult<()>;
type CommandHandler =
fn(&mut Server, EcsEntity, EcsEntity, Vec<String>, &ChatCommand) -> CmdResult<()>;
fn do_command(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
cmd: &ChatCommand,
) -> CmdResult<()> {
// Make sure your role is at least high enough to execute this command.
@ -396,11 +392,25 @@ fn edit_setting_feedback<S: EditableSetting>(
}
}
/// Parse a series of command arguments into values, including collecting all
/// trailing arguments.
macro_rules! parse_args {
($args:expr, $($t:ty),* $(, ..$tail:ty)? $(,)?) => {
{
let mut args = $args.into_iter();
(
$(args.next().and_then(|s| s.parse::<$t>().ok())),*
$(, args.map(|s| s.to_string()).collect::<$tail>())?
)
}
};
}
fn handle_drop_all(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
@ -443,12 +453,10 @@ fn handle_give_item(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(item_name), give_amount_opt) =
scan_fmt_some!(&args, &action.arg_fmt(), String, u32)
{
if let (Some(item_name), give_amount_opt) = parse_args!(args, String, u32) {
let give_amount = give_amount_opt.unwrap_or(1);
if let Ok(item) = Item::new_from_asset(&item_name.replace('/', ".").replace("\\", ".")) {
let mut item: Item = item;
@ -510,10 +518,10 @@ fn handle_make_block(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(block_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(block_name) = parse_args!(args, String) {
if let Ok(bk) = BlockKind::from_str(block_name.as_str()) {
let pos = position(server, target, "target")?;
server.state.set_block(
@ -533,10 +541,10 @@ fn handle_make_sprite(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(sprite_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(sprite_name) = parse_args!(args, String) {
if let Ok(sk) = SpriteKind::try_from(sprite_name.as_str()) {
let pos = position(server, target, "target")?;
let pos = pos.0.map(|e| e.floor() as i32);
@ -560,7 +568,7 @@ fn handle_motd(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
server.notify_client(
@ -577,16 +585,16 @@ fn handle_set_motd(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let data_dir = server.data_dir();
let client_uuid = uuid(server, client, "client")?;
// Ensure the person setting this has a real role in the settings file, since
// it's persistent.
let _client_real_role = real_role(server, client_uuid, "client")?;
match scan_fmt!(&args, &action.arg_fmt(), String) {
Ok(msg) => {
match parse_args!(args, String) {
Some(msg) => {
let edit =
server
.editable_settings_mut()
@ -601,7 +609,7 @@ fn handle_set_motd(
unreachable!("edit always returns Some")
})
},
Err(_) => {
None => {
let edit =
server
.editable_settings_mut()
@ -622,10 +630,10 @@ fn handle_jump(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
if let (Some(x), Some(y), Some(z)) = parse_args!(args, f32, f32, f32) {
position_mut(server, target, "target", |current_pos| {
current_pos.0 += Vec3::new(x, y, z)
})?;
@ -639,10 +647,10 @@ fn handle_goto(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
if let (Some(x), Some(y), Some(z)) = parse_args!(args, f32, f32, f32) {
position_mut(server, target, "target", |current_pos| {
current_pos.0 = Vec3::new(x, y, z)
})?;
@ -658,11 +666,11 @@ fn handle_site(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
#[cfg(feature = "worldgen")]
if let Ok(dest_name) = scan_fmt!(&args, &action.arg_fmt(), String) {
if let Some(dest_name) = parse_args!(args, String) {
let site = server
.world
.civs()
@ -695,7 +703,7 @@ fn handle_home(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let home_pos = server.state.mut_resource::<SpawnPoint>().0;
@ -717,7 +725,7 @@ fn handle_kill(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let reason = if client == target {
@ -743,8 +751,8 @@ fn handle_time(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
const DAY: u64 = 86400;
@ -763,7 +771,7 @@ fn handle_time(
}
};
let time = scan_fmt_some!(&args, &action.arg_fmt(), String);
let time = parse_args!(args, String);
let new_time = match time.as_deref() {
Some("midnight") => {
next_cycle(NaiveTime::from_hms(0, 0, 0).num_seconds_from_midnight() as f64)
@ -889,10 +897,10 @@ fn handle_health(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) {
if let Some(hp) = parse_args!(args, u32) {
if let Some(mut health) = server
.state
.ecs()
@ -913,10 +921,10 @@ fn handle_alias(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
if let Some(alias) = parse_args!(args, String) {
// Prevent silly aliases
comp::Player::alias_validate(&alias).map_err(|e| e.to_string())?;
@ -965,10 +973,10 @@ fn handle_tp(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
let player = if let Some(alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
let player = if let Some(alias) = parse_args!(args, String) {
find_alias(server.state.ecs(), &alias)?.0
} else if client != target {
client
@ -986,10 +994,10 @@ fn handle_spawn(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
match scan_fmt_some!(&args, &action.arg_fmt(), String, npc::NpcBody, u32, bool) {
match parse_args!(args, String, npc::NpcBody, u32, bool) {
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
let uid = uid(server, target, "target")?;
let alignment = parse_alignment(uid, &opt_align)?;
@ -1099,7 +1107,7 @@ fn handle_spawn_training_dummy(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
@ -1142,10 +1150,10 @@ fn handle_spawn_airship(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let angle = scan_fmt!(&args, &action.arg_fmt(), f32).ok();
let angle = parse_args!(args, f32);
let mut pos = position(server, target, "target")?;
pos.0.z += 50.0;
const DESTINATION_RADIUS: f32 = 2000.0;
@ -1189,7 +1197,7 @@ fn handle_spawn_campfire(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
@ -1240,10 +1248,10 @@ fn handle_safezone(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let range = scan_fmt_some!(&args, &action.arg_fmt(), f32);
let range = parse_args!(args, f32);
let pos = position(server, target, "target")?;
server.state.create_safezone(range, pos).build();
@ -1258,10 +1266,10 @@ fn handle_permit_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(area_name) = parse_args!(args, String) {
let bb_id = area(server, &area_name)?;
let mut can_build = server.state.ecs().write_storage::<comp::CanBuild>();
let entry = can_build
@ -1299,10 +1307,10 @@ fn handle_revoke_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(area_name) = parse_args!(args, String) {
let bb_id = area(server, &area_name)?;
let mut can_build = server.state.ecs_mut().write_storage::<comp::CanBuild>();
if let Some(mut comp_can_build) = can_build.get_mut(target) {
@ -1337,7 +1345,7 @@ fn handle_revoke_build_all(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let ecs = server.state.ecs();
@ -1363,7 +1371,7 @@ fn handle_players(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let ecs = server.state.ecs();
@ -1391,7 +1399,7 @@ fn handle_build(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
if let Some(mut can_build) = server
@ -1420,20 +1428,12 @@ fn handle_build_area_add(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) = scan_fmt_some!(
&args,
&action.arg_fmt(),
String,
i32,
i32,
i32,
i32,
i32,
i32
) {
if let (Some(area_name), Some(xlo), Some(xhi), Some(ylo), Some(yhi), Some(zlo), Some(zhi)) =
parse_args!(args, String, i32, i32, i32, i32, i32, i32)
{
let build_areas = server.state.mut_resource::<BuildAreas>();
let msg = ServerGeneral::server_msg(
ChatType::CommandInfo,
@ -1456,7 +1456,7 @@ fn handle_build_area_list(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let build_areas = server.state.mut_resource::<BuildAreas>();
@ -1482,10 +1482,10 @@ fn handle_build_area_remove(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(area_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(area_name) = parse_args!(args, String) {
let build_areas = server.state.mut_resource::<BuildAreas>();
build_areas.remove(&area_name).map_err(|err| match err {
@ -1512,10 +1512,10 @@ fn handle_help(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
if let Some(cmd) = scan_fmt_some!(&args, &action.arg_fmt(), ChatCommand) {
if let Some(cmd) = parse_args!(args, ChatCommand) {
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, cmd.help_string()),
@ -1560,7 +1560,7 @@ fn handle_kill_npcs(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let ecs = server.state.ecs();
@ -1588,11 +1588,10 @@ fn handle_kit(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
let kit_name = scan_fmt!(&args, &action.arg_fmt(), String);
if let Ok(name) = kit_name {
if let Some(name) = parse_args!(args, String) {
if let Ok(kits) = common::cmd::KitManifest::load("server.manifests.kits") {
let kits = kits.read();
if let Some(kit) = kits.0.get(&name) {
@ -1638,10 +1637,10 @@ fn handle_object(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let obj_type = scan_fmt!(&args, &action.arg_fmt(), String);
let obj_type = parse_args!(args, String);
let pos = position(server, target, "target")?;
let ori = server
@ -1654,10 +1653,10 @@ fn handle_object(
/*let builder = server.state
.create_object(pos, ori, obj_type)
.with(ori);*/
let obj_str_res = obj_type.as_ref().map(String::as_str);
let obj_str_res = obj_type.as_deref();
if let Some(obj_type) = comp::object::ALL_OBJECTS
.iter()
.find(|o| Ok(o.to_string()) == obj_str_res)
.find(|o| Some(o.to_string()) == obj_str_res)
{
server
.state
@ -1697,11 +1696,11 @@ fn handle_light(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let (opt_r, opt_g, opt_b, opt_x, opt_y, opt_z, opt_s) =
scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32, f32, f32, f32);
parse_args!(args, f32, f32, f32, f32, f32, f32, f32);
let mut light_emitter = comp::LightEmitter::default();
let mut light_offset_opt = None;
@ -1750,10 +1749,10 @@ fn handle_lantern(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(s), r, g, b) = scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32) {
if let (Some(s), r, g, b) = parse_args!(args, f32, f32, f32, f32) {
if let Some(mut light) = server
.state
.ecs()
@ -1797,10 +1796,10 @@ fn handle_explosion(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let power = scan_fmt!(&args, &action.arg_fmt(), f32).unwrap_or(8.0);
let power = parse_args!(args, f32).unwrap_or(8.0);
const MIN_POWER: f32 = 0.0;
const MAX_POWER: f32 = 512.0;
@ -1850,7 +1849,7 @@ fn handle_waypoint(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
@ -1876,7 +1875,7 @@ fn handle_spawn_wiring(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
// Obviously it is a WIP - use it for debug
@ -2045,10 +2044,10 @@ fn handle_adminify(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(alias), desired_role) = scan_fmt_some!(&args, &action.arg_fmt(), String, String) {
if let (Some(alias), desired_role) = parse_args!(args, String, String) {
let desired_role = if let Some(mut desired_role) = desired_role {
desired_role.make_ascii_lowercase();
Some(match &*desired_role {
@ -2155,12 +2154,12 @@ fn handle_tell(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
if let (Some(alias), message_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, String) {
if let (Some(alias), message_opt) = parse_args!(args, String, ..Vec<String>) {
let ecs = server.state.ecs();
let player = find_alias(ecs, &alias)?.0;
@ -2171,7 +2170,11 @@ fn handle_tell(
let player_uid = uid(server, player, "player")?;
let mode = comp::ChatMode::Tell(player_uid);
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = message_opt.unwrap_or_else(|| format!("{} wants to talk to you.", alias));
let msg = if message_opt.is_empty() {
format!("{} wants to talk to you.", alias)
} else {
message_opt.join(" ")
};
server.state.send_chat(mode.new_message(target_uid, msg));
server.notify_client(target, ServerGeneral::ChatMode(mode));
Ok(())
@ -2184,7 +2187,7 @@ fn handle_faction(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
msg: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
@ -2194,6 +2197,7 @@ fn handle_faction(
let mode = comp::ChatMode::Faction(faction.to_string());
drop(factions);
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = args.join(" ");
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(target) {
server.state.send_chat(mode.new_message(*uid, msg));
@ -2210,7 +2214,7 @@ fn handle_group(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
msg: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
@ -2220,6 +2224,7 @@ fn handle_group(
let mode = comp::ChatMode::Group(*group);
drop(groups);
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = args.join(" ");
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(target) {
server.state.send_chat(mode.new_message(*uid, msg));
@ -2236,10 +2241,10 @@ fn handle_group_invite(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(target_alias) = parse_args!(args, String) {
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
let uid = uid(server, target_player, "player")?;
@ -2275,11 +2280,11 @@ fn handle_group_kick(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
// Checking if leader is already done in group_manip
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(target_alias) = parse_args!(args, String) {
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
let uid = uid(server, target_player, "player")?;
@ -2297,7 +2302,7 @@ fn handle_group_leave(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
server
@ -2311,11 +2316,11 @@ fn handle_group_promote(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
// Checking if leader is already done in group_manip
if let Some(target_alias) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(target_alias) = parse_args!(args, String) {
let target_player = find_alias(server.state.ecs(), &target_alias)?.0;
let uid = uid(server, target_player, "player")?;
@ -2336,13 +2341,14 @@ fn handle_region(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
msg: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
let mode = comp::ChatMode::Region;
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = args.join(" ");
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(target) {
server.state.send_chat(mode.new_message(*uid, msg));
@ -2356,13 +2362,14 @@ fn handle_say(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
msg: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
let mode = comp::ChatMode::Say;
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = args.join(" ");
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(target) {
server.state.send_chat(mode.new_message(*uid, msg));
@ -2376,13 +2383,14 @@ fn handle_world(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
msg: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
no_sudo(client, target)?;
let mode = comp::ChatMode::World;
insert_or_replace_component(server, target, mode.clone(), "target")?;
let msg = args.join(" ");
if !msg.is_empty() {
if let Some(uid) = server.state.ecs().read_storage().get(target) {
server.state.send_chat(mode.new_message(*uid, msg));
@ -2396,14 +2404,13 @@ fn handle_join_faction(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let players = server.state.ecs().read_storage::<comp::Player>();
if let Some(alias) = players.get(target).map(|player| player.alias.clone()) {
drop(players);
let (faction_leave, mode) = if let Ok(faction) = scan_fmt!(&args, &action.arg_fmt(), String)
{
let (faction_leave, mode) = if let Some(faction) = parse_args!(args, String) {
let mode = comp::ChatMode::Faction(faction.clone());
insert_or_replace_component(server, target, mode.clone(), "target")?;
let faction_join = server
@ -2448,7 +2455,7 @@ fn handle_debug_column(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
Err("Unsupported without worldgen enabled".into())
@ -2459,12 +2466,12 @@ fn handle_debug_column(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let sim = server.world.sim();
let sampler = server.world.sample_columns();
let wpos = if let Ok((x, y)) = scan_fmt!(&args, &action.arg_fmt(), i32, i32) {
let wpos = if let (Some(x), Some(y)) = parse_args!(args, i32, i32) {
Vec2::new(x, y)
} else {
let pos = position(server, target, "target")?;
@ -2534,14 +2541,14 @@ fn handle_disconnect_all_players(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let client_uuid = uuid(server, client, "client")?;
// Make sure temporary mods/admins can't run this command.
let _role = real_role(server, client_uuid, "role")?;
if args != *"confirm" {
if parse_args!(args, String).as_deref() != Some("confirm") {
return Err(
"Please run the command again with the second argument of \"confirm\" to confirm that \
you really want to disconnect all players from the server"
@ -2578,12 +2585,10 @@ fn handle_skill_point(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(a_skill_tree), Some(sp), a_alias) =
scan_fmt_some!(&args, &action.arg_fmt(), String, u16, String)
{
if let (Some(a_skill_tree), Some(sp), a_alias) = parse_args!(args, String, u16, String) {
let skill_tree = parse_skill_tree(&a_skill_tree)?;
let player = a_alias
.map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target))
@ -2624,10 +2629,10 @@ fn handle_remove_lights(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: String,
action: &ChatCommand,
args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
let opt_radius = parse_args!(args, f32);
let player_pos = position(server, target, "target")?;
let mut to_delete = vec![];
@ -2668,13 +2673,12 @@ fn handle_sudo(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(player_alias), Some(cmd), cmd_args) =
scan_fmt_some!(&args, &action.arg_fmt(), String, String, String)
parse_args!(args, String, String, ..Vec<String>)
{
let cmd_args = cmd_args.unwrap_or_else(|| String::from(""));
if let Ok(action) = cmd.parse() {
let (player, player_uuid) = find_alias(server.state.ecs(), &player_alias)?;
let client_uuid = uuid(server, client, "client")?;
@ -2701,7 +2705,7 @@ fn handle_version(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
_args: String,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
server.notify_client(
@ -2722,12 +2726,12 @@ fn handle_whitelist(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
let now = Utc::now();
if let Ok((whitelist_action, username)) = scan_fmt!(&args, &action.arg_fmt(), String, String) {
if let (Some(whitelist_action), Some(username)) = parse_args!(args, String, String) {
let client_uuid = uuid(server, client, "client")?;
let client_username = uuid_to_username(server, client, client_uuid)?;
let client_role = real_role(server, client_uuid, "client")?;
@ -2820,12 +2824,10 @@ fn handle_kick(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(target_alias), reason_opt) =
scan_fmt_some!(&args, &action.arg_fmt(), String, String)
{
if let (Some(target_alias), reason_opt) = parse_args!(args, String, String) {
let client_uuid = uuid(server, client, "client")?;
let reason = reason_opt.unwrap_or_default();
let ecs = server.state.ecs();
@ -2852,17 +2854,12 @@ fn handle_ban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(username), overwrite, parse_duration, reason_opt) = scan_fmt_some!(
&args,
&action.arg_fmt(),
String,
bool,
HumanDuration,
String
) {
if let (Some(username), overwrite, parse_duration, reason_opt) =
parse_args!(args, String, bool, HumanDuration, String)
{
let reason = reason_opt.unwrap_or_default();
let overwrite = overwrite.unwrap_or(false);
@ -2935,10 +2932,10 @@ fn handle_unban(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Ok(username) = scan_fmt!(&args, &action.arg_fmt(), String) {
if let Some(username) = parse_args!(args, String) {
let player_uuid = find_username(server, &username)?;
let client_uuid = uuid(server, client, "client")?;
@ -2980,10 +2977,10 @@ fn handle_server_physics(
server: &mut Server,
client: EcsEntity,
_target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(username), enabled_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, bool) {
if let (Some(username), enabled_opt) = parse_args!(args, String, bool) {
let uuid = find_username(server, &username)?;
let server_force = enabled_opt.unwrap_or(true);
@ -3012,12 +3009,10 @@ fn handle_apply_buff(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let (Some(buff), strength, duration) =
scan_fmt_some!(&args, &action.arg_fmt(), String, f32, f64)
{
if let (Some(buff), strength, duration) = parse_args!(args, String, f32, f64) {
let strength = strength.unwrap_or(0.01);
let duration = Duration::from_secs_f64(duration.unwrap_or(1.0));
let buffdata = BuffData::new(strength, Some(duration));
@ -3053,10 +3048,10 @@ fn handle_skill_preset(
server: &mut Server,
_client: EcsEntity,
target: EcsEntity,
args: String,
args: Vec<String>,
action: &ChatCommand,
) -> CmdResult<()> {
if let Some(preset) = scan_fmt_some!(&args, &action.arg_fmt(), String) {
if let Some(preset) = parse_args!(args, String) {
if let Some(mut skill_set) = server
.state
.ecs_mut()

View File

@ -88,8 +88,8 @@ server = {package = "veloren-server", path = "../server", optional = true}
backtrace = "0.3.40"
bincode = "1.3.1"
chrono = { version = "0.4.9", features = ["serde"] }
chumsky = "0.3.2"
cpal = "0.13"
comma = "0.1"
copy_dir = "0.1.2"
crossbeam-utils = "0.8.1"
crossbeam-channel = "0.5"

View File

@ -659,11 +659,11 @@ impl<'a> Widget for Chat<'a> {
}
});
if let Some(msg) = msg.strip_prefix('/') {
match msg.parse::<comma::Command>() {
Ok(cmd) => events.push(Event::SendCommand(cmd.name, cmd.arguments)),
match parse_cmd(msg) {
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
Err(err) => self.new_messages.push_back(ChatMsg {
chat_type: ChatType::CommandError,
message: err.to_string(),
message: err,
}),
}
} else {
@ -814,3 +814,41 @@ fn get_chat_template_key(chat_type: &ChatType<String>) -> Option<&str> {
_ => return None,
})
}
fn parse_cmd(msg: &str) -> Result<(String, Vec<String>), String> {
use chumsky::prelude::*;
let escape = just::<_, Simple<char>>('\\').padding_for(
just('\\')
.or(just('/'))
.or(just('"'))
.or(just('b').to('\x08'))
.or(just('f').to('\x0C'))
.or(just('n').to('\n'))
.or(just('r').to('\r'))
.or(just('t').to('\t')),
);
let string = just('"')
.padding_for(filter(|c| *c != '\\' && *c != '"').or(escape).repeated())
.padded_by(just('"'))
.labelled("quoted argument");
let arg = string
.or(filter(|c: &char| !c.is_whitespace() && *c != '"')
.repeated_at_least(1)
.labelled("argument"))
.collect::<String>();
let cmd = text::ident()
.collect::<String>()
.then(arg.padded().repeated())
.padded_by(end());
cmd.parse(msg).map_err(|errs| {
errs.into_iter()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join(", ")
})
}