diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index b7a3e63627..a14ca93cf2 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -61,6 +61,13 @@ lazy_static! { RwLock::new(HashMap::new()); } +const ASSETS_TMP: [&'static str; 1] = ["common/items/lantern/black_0"]; +pub fn iterate() -> impl Iterator { + // TODO FIXME implement this + //ASSETS.read().iter().flat_map(|e| e.keys()) + ASSETS_TMP.iter().map(|k| *k) +} + // TODO: Remove this function. It's only used in world/ in a really ugly way.To // do this properly assets should have all their necessary data in one file. A // ron file could be used to combine voxel data with positioning data for diff --git a/common/src/cmd.rs b/common/src/cmd.rs new file mode 100644 index 0000000000..2ab72f83a9 --- /dev/null +++ b/common/src/cmd.rs @@ -0,0 +1,156 @@ +use crate::{assets, comp::Player, state::State}; +use lazy_static::lazy_static; +use specs::prelude::{Join, WorldExt}; + + +/// Struct representing a command that a user can run from server chat. +pub struct ChatCommand { + /// The keyword used to invoke the command, omitting the leading '/'. + pub keyword: &'static str, + /// A format string for parsing arguments. + pub args: Vec, + /// A one-line message that explains what the command does + pub description: &'static str, + /// A boolean that is used to check whether the command requires + /// administrator permissions or not. + pub needs_admin: bool, +} + +impl ChatCommand { + pub fn new( + keyword: &'static str, + args: Vec, + description: &'static str, + needs_admin: bool, + ) -> Self { + Self { + keyword, + args, + description, + needs_admin, + } + } +} + +lazy_static! { + static ref CHAT_COMMANDS: Vec = { + use ArgumentSyntax::*; + vec![ + ChatCommand::new("help", vec![Command(true)], "Display information about commands", false), + ] + }; +} + +/// Representation for chat command arguments +pub enum ArgumentSyntax { + /// The argument refers to a player by alias + PlayerName(bool), + /// The argument refers to an item asset by path + ItemSpec(bool), + /// The argument is a float. The associated values are + /// * label + /// * default tab-completion + /// * whether it's optional + Float(&'static str, f32, bool), + /// The argument is a float. The associated values are + /// * label + /// * default tab-completion + /// * whether it's optional + Integer(&'static str, f32, bool), + /// The argument is a command name + Command(bool), + /// This is the final argument, consuming all characters until the end of input. + Message, + /// The argument is likely an enum. The associated values are + /// * label + /// * Predefined string completions + /// * Other completion types + /// * whether it's optional + OneOf(&'static str, &'static [&'static str], Vec>, bool), +} + +impl ArgumentSyntax { + pub fn help_string(arg: &ArgumentSyntax) -> String { + match arg { + ArgumentSyntax::PlayerName(optional) => { + if *optional { + "[player]".to_string() + } else { + "".to_string() + } + }, + ArgumentSyntax::ItemSpec(optional) => { + if *optional { + "[item]".to_string() + } else { + "".to_string() + } + }, + ArgumentSyntax::Float(label, _, optional) => { + if *optional { + format!("[{}]", label) + } else { + format!("<{}>", label) + } + }, + ArgumentSyntax::Integer(label, _, optional) => { + if *optional { + format!("[{}]", label) + } else { + format!("<{}>", label) + } + }, + ArgumentSyntax::Command(optional) => { + if *optional { + "[[/]command]".to_string() + } else { + "<[/]command>".to_string() + } + }, + ArgumentSyntax::Message => { + "".to_string() + }, + ArgumentSyntax::OneOf(label, _, _, optional) => { + if *optional { + format! {"[{}]", label} + } else { + format! {"<{}>", label} + } + }, + } + } + + pub fn complete(&self, state: &State, part: &String) -> Vec { + match self { + ArgumentSyntax::PlayerName(_) => (&state.ecs().read_storage::()) + .join() + .filter(|player| player.alias.starts_with(part)) + .map(|player| player.alias.clone()) + .collect(), + ArgumentSyntax::ItemSpec(_) => assets::iterate() + .filter(|asset| asset.starts_with(part)) + .map(|c| c.to_string()) + .collect(), + ArgumentSyntax::Float(_, x, _) => vec![format!("{}", x)], + ArgumentSyntax::Integer(_, x, _) => vec![format!("{}", x)], + ArgumentSyntax::Command(_) => CHAT_COMMANDS + .iter() + .map(|com| com.keyword.clone()) + .filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part)) + .map(|c| c.to_string()) + .collect(), + ArgumentSyntax::Message => vec![], + ArgumentSyntax::OneOf(_, strings, alts, _) => { + let string_completions = strings + .iter() + .filter(|string| string.starts_with(part)) + .map(|c| c.to_string()); + let alt_completions = alts + .iter() + .flat_map(|b| (*b).complete(&state, part)) + .map(|c| c.to_string()); + string_completions.chain(alt_completions).collect() + } + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 1ea3df4c51..12600ed3d4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,6 +16,7 @@ pub mod assets; pub mod astar; pub mod character; pub mod clock; +pub mod cmd; pub mod comp; pub mod effect; pub mod event;