From 031e4b36d66f54e62b6ce8d6b6190a8b866f6760 Mon Sep 17 00:00:00 2001 From: Sebastian Venter Date: Tue, 16 Apr 2019 14:06:30 +0100 Subject: [PATCH 1/7] add chat commands, rustfmt Former-commit-id: 64941127598b53c64fe2e0e7c167f0ce3f358060 --- Cargo.lock | 11 ++ server/Cargo.toml | 2 + server/src/lib.rs | 346 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 268 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b178e912b6..8c48383a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,6 +1499,14 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scan_fmt" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -1811,6 +1819,8 @@ dependencies = [ name = "veloren-server" version = "0.2.0" dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2208,6 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rusttype 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "25951e85bb2647960969f72c559392245a5bd07446a589390bf427dda31cdc4a" "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" +"checksum scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b87497427f9fbe539ee6b9626f5a5e899331fdf1c1d62f14c637a462969db30" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" diff --git a/server/Cargo.toml b/server/Cargo.toml index 4189be7693..a3eb8aa8f1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,3 +11,5 @@ world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" threadpool = "1.7" +lazy_static = "1.3.0" +scan_fmt = "0.1.3" \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs index dd71df87cc..f3d455ebcc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -5,53 +5,80 @@ pub mod error; pub mod input; // Reexports -pub use crate::{ - error::Error, - input::Input, -}; +pub use crate::{error::Error, input::Input}; -use std::{ - time::Duration, - net::SocketAddr, - sync::mpsc, - collections::HashSet, -}; -use specs::{ - Entity as EcsEntity, - world::EntityBuilder as EcsEntityBuilder, - Builder, - join::Join, - saveload::MarkedBuilder, -}; -use vek::*; -use threadpool::ThreadPool; +use crate::client::{Client, ClientState, Clients}; use common::{ comp, - state::{State, Uid}, + msg::{ClientMsg, ServerMsg}, net::PostOffice, - msg::{ServerMsg, ClientMsg}, + state::State, terrain::TerrainChunk, }; -use world::World; -use crate::client::{ - ClientState, - Client, - Clients, +use specs::{ + join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder, + Entity as EcsEntity, }; +use std::{collections::HashSet, net::SocketAddr, sync::mpsc, time::Duration}; +use threadpool::ThreadPool; +use vek::*; +use world::World; + +use lazy_static::lazy_static; +use scan_fmt::scan_fmt; const CLIENT_TIMEOUT: f64 = 5.0; // Seconds +struct ChatCommand { + keyword: &'static str, + arg_fmt: &'static str, + help_string: &'static str, +} + +impl ChatCommand { + pub fn new(keyword: &'static str, arg_fmt: &'static str, help_string: &'static str) -> Self { + Self { + keyword, + arg_fmt, + help_string, + } + } +} + +lazy_static! { + static ref CHAT_COMMANDS: Vec = vec![ + ChatCommand::new( + "jump", + "{d} {d} {d}", + "jump: offset your current position by a vector\n + Usage: /jump [x] [y] [z]" + ), + ChatCommand::new( + "goto", + "{d} {d} {d}", + "goto: teleport to a given position\n + Usage: /goto [x] [y] [z]" + ), + ChatCommand::new( + "alias", + "{}", + "alias: change your player name (cannot contain spaces)\n + Usage: /alias [name]" + ), + ChatCommand::new( + "tp", + "{}", + "tp: teleport to a named player\n + Usage: /tp [name]" + ), + ChatCommand::new("help", "", "help: display this message") + ]; +} + pub enum Event { - ClientConnected { - entity: EcsEntity, - }, - ClientDisconnected { - entity: EcsEntity, - }, - Chat { - entity: EcsEntity, - msg: String, - }, + ClientConnected { entity: EcsEntity }, + ClientDisconnected { entity: EcsEntity }, + Chat { entity: EcsEntity, msg: String }, } pub struct Server { @@ -73,11 +100,8 @@ impl Server { pub fn new() -> Result { let (chunk_tx, chunk_rx) = mpsc::channel(); - let mut state = State::new(); - state.ecs_mut().internal_mut().register::(); - Ok(Self { - state, + state: State::new(), world: World::new(), postoffice: PostOffice::bind(SocketAddr::from(([0; 4], 59003)))?, @@ -94,17 +118,25 @@ impl Server { /// Get a reference to the server's game state. #[allow(dead_code)] - pub fn state(&self) -> &State { &self.state } + pub fn state(&self) -> &State { + &self.state + } /// Get a mutable reference to the server's game state. #[allow(dead_code)] - pub fn state_mut(&mut self) -> &mut State { &mut self.state } + pub fn state_mut(&mut self) -> &mut State { + &mut self.state + } /// Get a reference to the server's world. #[allow(dead_code)] - pub fn world(&self) -> &World { &self.world } + pub fn world(&self) -> &World { + &self.world + } /// Get a mutable reference to the server's world. #[allow(dead_code)] - pub fn world_mut(&mut self) -> &mut World { &mut self.world } + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } /// Execute a single server tick, handle input and update the game state by the given duration #[allow(dead_code)] @@ -147,8 +179,14 @@ impl Server { for (entity, player, pos) in ( &self.state.ecs().internal().entities(), &self.state.ecs().internal().read_storage::(), - &self.state.ecs().internal().read_storage::(), - ).join() { + &self + .state + .ecs() + .internal() + .read_storage::(), + ) + .join() + { // TODO: Distance check // if self.state.terrain().key_pos(key) @@ -182,23 +220,18 @@ impl Server { let mut frontend_events = Vec::new(); for mut postbox in self.postoffice.new_postboxes() { - let entity = self.state - .ecs_mut() - .create_entity_synced() - .build(); + let entity = self.state.ecs_mut().create_entity_synced().build(); - // Make sure the entity gets properly created - self.state.ecs_mut().internal_mut().maintain(); - - self.clients.add(entity, Client { - state: ClientState::Connecting, - postbox, - last_ping: self.state.get_time(), - }); - - frontend_events.push(Event::ClientConnected { + self.clients.add( entity, - }); + Client { + state: ClientState::Connecting, + postbox, + last_ping: self.state.get_time(), + }, + ); + + frontend_events.push(Event::ClientConnected { entity }); } Ok(frontend_events) @@ -236,7 +269,6 @@ impl Server { state.write_component(entity, character); } - state.write_component(entity, comp::phys::ForceUpdate); client.state = ClientState::Connected; @@ -249,34 +281,36 @@ impl Server { .unwrap() .into(), }); - }, + } _ => disconnect = true, }, ClientState::Connected => match msg { ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected ClientMsg::Disconnect => disconnect = true, ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong), - ClientMsg::Pong => {}, + ClientMsg::Pong => {} ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)), ClientMsg::PlayerAnimation(animation) => state.write_component(entity, animation), ClientMsg::PlayerPhysics { pos, vel, dir } => { state.write_component(entity, pos); state.write_component(entity, vel); state.write_component(entity, dir); - }, - ClientMsg::TerrainChunkRequest { key } => match state.terrain().get_key(key) { - Some(chunk) => {}, /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate { + } + ClientMsg::TerrainChunkRequest { key } => { + match state.terrain().get_key(key) { + Some(chunk) => {} /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate { key, chunk: Box::new(chunk.clone()), - }),*/ - None => requested_chunks.push(key), - }, + }),*/ + None => requested_chunks.push(key), + } + } }, } } - } else if - state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout - client.postbox.error().is_some() // Postbox error + } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout + client.postbox.error().is_some() + // Postbox error { disconnect = true; } else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 { @@ -294,29 +328,33 @@ impl Server { // Handle new chat messages for (entity, msg) in new_chat_msgs { - self.clients.notify_connected(ServerMsg::Chat(match state - .ecs() - .internal() - .read_storage::() - .get(entity) - { - Some(player) => format!("[{}] {}", &player.alias, msg), - None => format!("[] {}", msg), - })); + // Handle chat commands + if msg.starts_with("/") && msg.len() > 1 { + let argv = String::from(&msg[1..]); + self.process_chat_cmd(entity, argv); + } else { + self.clients.notify_connected(ServerMsg::Chat( + match self + .state + .ecs() + .internal() + .read_storage::() + .get(entity) + { + Some(player) => format!("[{}] {}", &player.alias, msg), + None => format!("[] {}", msg), + }, + )); - frontend_events.push(Event::Chat { - entity, - msg, - }); + frontend_events.push(Event::Chat { entity, msg }); + } } // Handle client disconnects for entity in disconnected_clients { - state.ecs_mut().delete_entity_synced(entity).unwrap(); + self.state.ecs_mut().delete_entity_synced(entity); - frontend_events.push(Event::ClientDisconnected { - entity, - }); + frontend_events.push(Event::ClientDisconnected { entity }); } // Generate requested chunks @@ -375,7 +413,133 @@ impl Server { pub fn generate_chunk(&mut self, key: Vec3) { if self.pending_chunks.insert(key) { let chunk_tx = self.chunk_tx.clone(); - self.thread_pool.execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap()); + self.thread_pool + .execute(move || chunk_tx.send((key, World::generate_chunk(key))).unwrap()); + } + } + + fn process_chat_cmd<'a>(&mut self, entity: EcsEntity, cmd: String) { + let sep = cmd.find(' '); + let (kwd, args) = match sep { + Some(i) => (cmd[..i].to_string(), cmd[(i + 1)..].to_string()), + None => (cmd, "".to_string()), + }; + let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd); + match action_opt { + Some(action) => match action.keyword { + "jump" => { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => { + if let Some(current_pos) = + self.state.read_component_cloned::(entity) + { + self.state.write_component( + entity, + comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z)), + ) + } else { + self.clients.notify( + entity, + ServerMsg::Chat(String::from( + "Command 'jump' invalid in current state", + )), + ) + } + + } + _ => self + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } + } + "goto" => { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => self + .state + .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))), + _ => self + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } + } + "alias" => { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => self + .state + .write_component(entity, comp::player::Player { alias }), + None => self + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } + } + "tp" => { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => { + let ecs = self.state.ecs().internal(); + let opt_player = + (&ecs.entities(), &ecs.read_storage::()) + .join() + .find(|(_, player)| player.alias == alias) + .map(|(entity, _)| entity); + match opt_player { + Some(player) => { + if let Some(pos) = + self.state.read_component_cloned::(player) + { + self.state.write_component(entity, pos); + } else { + self.clients.notify( + entity, + ServerMsg::Chat(format!( + "Unable to teleport to player '{}'", + alias + )), + ); + } + } + None => { + self.clients.notify( + entity, + ServerMsg::Chat(format!("Player '{}' not found!", alias)), + ); + self.clients.notify( + entity, + ServerMsg::Chat(String::from(action.help_string)), + ); + } + } + } + None => self + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } + } + "help" => { + for cmd in CHAT_COMMANDS.iter() { + self.clients + .notify(entity, ServerMsg::Chat(String::from(cmd.help_string))); + } + } + _ => {} + }, + // unknown command + None => { + self.clients.notify( + entity, + ServerMsg::Chat(format!( + "Unrecognised command: '/{}'\nAvailable commands:", + kwd + )), + ); + for cmd in CHAT_COMMANDS.iter() { + self.clients + .notify(entity, ServerMsg::Chat(String::from(cmd.keyword))); + } + } } } } From f8d75d900e9955fa846f0f7e18c45d5a76998886 Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 15:31:14 +0100 Subject: [PATCH 2/7] Clean up help message and if-let Former-commit-id: 450281467a92b2b8186df74d83d108457513277a --- server/src/lib.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index f3d455ebcc..e0f86e32a3 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -486,21 +486,20 @@ impl Server { .find(|(_, player)| player.alias == alias) .map(|(entity, _)| entity); match opt_player { - Some(player) => { - if let Some(pos) = - self.state.read_component_cloned::(player) - { - self.state.write_component(entity, pos); - } else { - self.clients.notify( - entity, - ServerMsg::Chat(format!( - "Unable to teleport to player '{}'", - alias - )), - ); - } - } + Some(player) => match self + .state + .read_component_cloned::(player) + { + Some(pos) => self.state.write_component(entity, pos), + None => self.clients.notify( + entity, + ServerMsg::Chat(format!( + "Unable to teleport to player '{}'", + alias + )), + ), + }, + None => { self.clients.notify( entity, @@ -531,7 +530,8 @@ impl Server { self.clients.notify( entity, ServerMsg::Chat(format!( - "Unrecognised command: '/{}'\nAvailable commands:", + "Unrecognised command: '/{}'\n + type '/help' for a list of available commands", kwd )), ); From ee47b598aa79d010ad45a0b00995fba379041eb7 Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 16:14:43 +0100 Subject: [PATCH 3/7] use handlers instead of hard-coding for commands Former-commit-id: 80d693254bfbfc7ae0ea4dcbdc76301dca067468 --- server/src/lib.rs | 224 +++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index e0f86e32a3..881215744f 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -33,14 +33,21 @@ struct ChatCommand { keyword: &'static str, arg_fmt: &'static str, help_string: &'static str, + handler: fn(&mut Server, EcsEntity, String, &ChatCommand), } impl ChatCommand { - pub fn new(keyword: &'static str, arg_fmt: &'static str, help_string: &'static str) -> Self { + pub fn new( + keyword: &'static str, + arg_fmt: &'static str, + help_string: &'static str, + handler: fn(&mut Server, EcsEntity, String, &ChatCommand), + ) -> Self { Self { keyword, arg_fmt, help_string, + handler, } } } @@ -51,30 +58,128 @@ lazy_static! { "jump", "{d} {d} {d}", "jump: offset your current position by a vector\n - Usage: /jump [x] [y] [z]" + Usage: /jump [x] [y] [z]", + handle_jump ), ChatCommand::new( "goto", "{d} {d} {d}", "goto: teleport to a given position\n - Usage: /goto [x] [y] [z]" + Usage: /goto [x] [y] [z]", + handle_goto ), ChatCommand::new( "alias", "{}", "alias: change your player name (cannot contain spaces)\n - Usage: /alias [name]" + Usage: /alias [name]", + handle_alias ), ChatCommand::new( "tp", "{}", "tp: teleport to a named player\n - Usage: /tp [name]" + Usage: /tp [name]", + handle_tp ), - ChatCommand::new("help", "", "help: display this message") + ChatCommand::new("help", "", "help: display this message", handle_help) ]; } +fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => { + if let Some(current_pos) = server + .state + .read_component_cloned::(entity) + { + server + .state + .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))) + } else { + server.clients.notify( + entity, + ServerMsg::Chat(String::from("Command 'jump' invalid in current state")), + ) + } + } + _ => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => server + .state + .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))), + _ => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => server + .state + .write_component(entity, comp::player::Player { alias }), + None => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => { + let ecs = server.state.ecs().internal(); + let opt_player = (&ecs.entities(), &ecs.read_storage::()) + .join() + .find(|(_, player)| player.alias == alias) + .map(|(entity, _)| entity); + match opt_player { + Some(player) => match server + .state + .read_component_cloned::(player) + { + Some(pos) => server.state.write_component(entity, pos), + None => server.clients.notify( + entity, + ServerMsg::Chat(format!("Unable to teleport to player '{}'", alias)), + ), + }, + + None => { + server.clients.notify( + entity, + ServerMsg::Chat(format!("Player '{}' not found!", alias)), + ); + server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))); + } + } + } + None => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { + for cmd in CHAT_COMMANDS.iter() { + server + .clients + .notify(entity, ServerMsg::Chat(String::from(cmd.help_string))); + } +} + pub enum Event { ClientConnected { entity: EcsEntity }, ClientDisconnected { entity: EcsEntity }, @@ -426,119 +531,16 @@ impl Server { }; let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd); match action_opt { - Some(action) => match action.keyword { - "jump" => { - let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); - match (opt_x, opt_y, opt_z) { - (Some(x), Some(y), Some(z)) => { - if let Some(current_pos) = - self.state.read_component_cloned::(entity) - { - self.state.write_component( - entity, - comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z)), - ) - } else { - self.clients.notify( - entity, - ServerMsg::Chat(String::from( - "Command 'jump' invalid in current state", - )), - ) - } - - } - _ => self - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } - } - "goto" => { - let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); - match (opt_x, opt_y, opt_z) { - (Some(x), Some(y), Some(z)) => self - .state - .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))), - _ => self - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } - } - "alias" => { - let opt_alias = scan_fmt!(&args, action.arg_fmt, String); - match opt_alias { - Some(alias) => self - .state - .write_component(entity, comp::player::Player { alias }), - None => self - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } - } - "tp" => { - let opt_alias = scan_fmt!(&args, action.arg_fmt, String); - match opt_alias { - Some(alias) => { - let ecs = self.state.ecs().internal(); - let opt_player = - (&ecs.entities(), &ecs.read_storage::()) - .join() - .find(|(_, player)| player.alias == alias) - .map(|(entity, _)| entity); - match opt_player { - Some(player) => match self - .state - .read_component_cloned::(player) - { - Some(pos) => self.state.write_component(entity, pos), - None => self.clients.notify( - entity, - ServerMsg::Chat(format!( - "Unable to teleport to player '{}'", - alias - )), - ), - }, - - None => { - self.clients.notify( - entity, - ServerMsg::Chat(format!("Player '{}' not found!", alias)), - ); - self.clients.notify( - entity, - ServerMsg::Chat(String::from(action.help_string)), - ); - } - } - } - None => self - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } - } - "help" => { - for cmd in CHAT_COMMANDS.iter() { - self.clients - .notify(entity, ServerMsg::Chat(String::from(cmd.help_string))); - } - } - _ => {} - }, + Some(action) => (action.handler)(self, entity, args, action), // unknown command None => { self.clients.notify( entity, ServerMsg::Chat(format!( - "Unrecognised command: '/{}'\n - type '/help' for a list of available commands", + "Unrecognised command: '/{}'\ntype '/help' for a list of available commands", kwd )), ); - for cmd in CHAT_COMMANDS.iter() { - self.clients - .notify(entity, ServerMsg::Chat(String::from(cmd.keyword))); - } } } } From cf3235d351bba4f44589ddfc8cf8d276bf4fcb0f Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 16:38:01 +0100 Subject: [PATCH 4/7] move chat command code to cmd.rs Former-commit-id: 25bbcceae51024439dbf6c143a49e3d5d55e7f4b --- server/src/cmd.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++ server/src/lib.rs | 164 ++-------------------------------------------- 2 files changed, 168 insertions(+), 157 deletions(-) create mode 100644 server/src/cmd.rs diff --git a/server/src/cmd.rs b/server/src/cmd.rs new file mode 100644 index 0000000000..1e1cfbfec3 --- /dev/null +++ b/server/src/cmd.rs @@ -0,0 +1,161 @@ +use crate::Server; +use common::{comp, msg::ServerMsg}; +use specs::{Entity as EcsEntity, join::Join}; +use vek::*; + +use scan_fmt::scan_fmt; +use lazy_static::lazy_static; + +pub struct ChatCommand { + pub keyword: &'static str, + arg_fmt: &'static str, + help_string: &'static str, + handler: fn(&mut Server, EcsEntity, String, &ChatCommand), +} + +impl ChatCommand { + pub fn new( + keyword: &'static str, + arg_fmt: &'static str, + help_string: &'static str, + handler: fn(&mut Server, EcsEntity, String, &ChatCommand), + ) -> Self { + Self { + keyword, + arg_fmt, + help_string, + handler, + } + } + pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) { + (self.handler)(server, entity, args, self); + } +} + +lazy_static! { + pub static ref CHAT_COMMANDS: Vec = vec![ + ChatCommand::new( + "jump", + "{d} {d} {d}", + "jump: offset your current position by a vector\n + Usage: /jump [x] [y] [z]", + handle_jump + ), + ChatCommand::new( + "goto", + "{d} {d} {d}", + "goto: teleport to a given position\n + Usage: /goto [x] [y] [z]", + handle_goto + ), + ChatCommand::new( + "alias", + "{}", + "alias: change your player name (cannot contain spaces)\n + Usage: /alias [name]", + handle_alias + ), + ChatCommand::new( + "tp", + "{}", + "tp: teleport to a named player\n + Usage: /tp [name]", + handle_tp + ), + ChatCommand::new("help", "", "help: display this message", handle_help) + ]; +} + +fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => { + if let Some(current_pos) = server + .state + .read_component_cloned::(entity) + { + server + .state + .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))) + } else { + server.clients.notify( + entity, + ServerMsg::Chat(String::from("Command 'jump' invalid in current state")), + ) + } + } + _ => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); + match (opt_x, opt_y, opt_z) { + (Some(x), Some(y), Some(z)) => server + .state + .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))), + _ => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => server + .state + .write_component(entity, comp::player::Player { alias }), + None => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + let opt_alias = scan_fmt!(&args, action.arg_fmt, String); + match opt_alias { + Some(alias) => { + let ecs = server.state.ecs().internal(); + let opt_player = (&ecs.entities(), &ecs.read_storage::()) + .join() + .find(|(_, player)| player.alias == alias) + .map(|(entity, _)| entity); + match opt_player { + Some(player) => match server + .state + .read_component_cloned::(player) + { + Some(pos) => server.state.write_component(entity, pos), + None => server.clients.notify( + entity, + ServerMsg::Chat(format!("Unable to teleport to player '{}'", alias)), + ), + }, + + None => { + server.clients.notify( + entity, + ServerMsg::Chat(format!("Player '{}' not found!", alias)), + ); + server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))); + } + } + } + None => server + .clients + .notify(entity, ServerMsg::Chat(String::from(action.help_string))), + } +} + +fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { + for cmd in CHAT_COMMANDS.iter() { + server + .clients + .notify(entity, ServerMsg::Chat(String::from(cmd.help_string))); + } +} \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs index 881215744f..a1eb2fa86c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -3,11 +3,12 @@ pub mod client; pub mod error; pub mod input; +pub mod cmd; // Reexports pub use crate::{error::Error, input::Input}; -use crate::client::{Client, ClientState, Clients}; +use crate::{client::{Client, ClientState, Clients}, cmd::CHAT_COMMANDS}; use common::{ comp, msg::{ClientMsg, ServerMsg}, @@ -24,162 +25,8 @@ use threadpool::ThreadPool; use vek::*; use world::World; -use lazy_static::lazy_static; -use scan_fmt::scan_fmt; - const CLIENT_TIMEOUT: f64 = 5.0; // Seconds -struct ChatCommand { - keyword: &'static str, - arg_fmt: &'static str, - help_string: &'static str, - handler: fn(&mut Server, EcsEntity, String, &ChatCommand), -} - -impl ChatCommand { - pub fn new( - keyword: &'static str, - arg_fmt: &'static str, - help_string: &'static str, - handler: fn(&mut Server, EcsEntity, String, &ChatCommand), - ) -> Self { - Self { - keyword, - arg_fmt, - help_string, - handler, - } - } -} - -lazy_static! { - static ref CHAT_COMMANDS: Vec = vec![ - ChatCommand::new( - "jump", - "{d} {d} {d}", - "jump: offset your current position by a vector\n - Usage: /jump [x] [y] [z]", - handle_jump - ), - ChatCommand::new( - "goto", - "{d} {d} {d}", - "goto: teleport to a given position\n - Usage: /goto [x] [y] [z]", - handle_goto - ), - ChatCommand::new( - "alias", - "{}", - "alias: change your player name (cannot contain spaces)\n - Usage: /alias [name]", - handle_alias - ), - ChatCommand::new( - "tp", - "{}", - "tp: teleport to a named player\n - Usage: /tp [name]", - handle_tp - ), - ChatCommand::new("help", "", "help: display this message", handle_help) - ]; -} - -fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); - match (opt_x, opt_y, opt_z) { - (Some(x), Some(y), Some(z)) => { - if let Some(current_pos) = server - .state - .read_component_cloned::(entity) - { - server - .state - .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))) - } else { - server.clients.notify( - entity, - ServerMsg::Chat(String::from("Command 'jump' invalid in current state")), - ) - } - } - _ => server - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } -} - -fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); - match (opt_x, opt_y, opt_z) { - (Some(x), Some(y), Some(z)) => server - .state - .write_component(entity, comp::phys::Pos(Vec3::new(x, y, z))), - _ => server - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } -} - -fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - let opt_alias = scan_fmt!(&args, action.arg_fmt, String); - match opt_alias { - Some(alias) => server - .state - .write_component(entity, comp::player::Player { alias }), - None => server - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } -} - -fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { - let opt_alias = scan_fmt!(&args, action.arg_fmt, String); - match opt_alias { - Some(alias) => { - let ecs = server.state.ecs().internal(); - let opt_player = (&ecs.entities(), &ecs.read_storage::()) - .join() - .find(|(_, player)| player.alias == alias) - .map(|(entity, _)| entity); - match opt_player { - Some(player) => match server - .state - .read_component_cloned::(player) - { - Some(pos) => server.state.write_component(entity, pos), - None => server.clients.notify( - entity, - ServerMsg::Chat(format!("Unable to teleport to player '{}'", alias)), - ), - }, - - None => { - server.clients.notify( - entity, - ServerMsg::Chat(format!("Player '{}' not found!", alias)), - ); - server - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))); - } - } - } - None => server - .clients - .notify(entity, ServerMsg::Chat(String::from(action.help_string))), - } -} - -fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { - for cmd in CHAT_COMMANDS.iter() { - server - .clients - .notify(entity, ServerMsg::Chat(String::from(cmd.help_string))); - } -} - pub enum Event { ClientConnected { entity: EcsEntity }, ClientDisconnected { entity: EcsEntity }, @@ -523,15 +370,18 @@ impl Server { } } - fn process_chat_cmd<'a>(&mut self, entity: EcsEntity, cmd: String) { + fn process_chat_cmd(&mut self, entity: EcsEntity, cmd: String) { + // separate string into keyword and arguments let sep = cmd.find(' '); let (kwd, args) = match sep { Some(i) => (cmd[..i].to_string(), cmd[(i + 1)..].to_string()), None => (cmd, "".to_string()), }; + + // find command object and run its handler let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd); match action_opt { - Some(action) => (action.handler)(self, entity, args, action), + Some(action) => action.execute(self, entity, args), // unknown command None => { self.clients.notify( From 447bf18a6377e264fa01924129cd27d9d4e7d173 Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 17:08:36 +0100 Subject: [PATCH 5/7] add documentation Former-commit-id: 5abbbaf9b24702dffc345466b1af60ee594bf01a --- server/src/cmd.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 1e1cfbfec3..73d0cc1362 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1,3 +1,7 @@ +//! #Implementing new commands +//! To implement a new command, add an instance of `ChatCommand` to `CHAT_COMMANDS` +//! and provide a handler function + use crate::Server; use common::{comp, msg::ServerMsg}; use specs::{Entity as EcsEntity, join::Join}; @@ -6,14 +10,20 @@ use vek::*; use scan_fmt::scan_fmt; use lazy_static::lazy_static; +/// Struct representing a command that a user can run from server chat pub struct ChatCommand { + /// The keyword used to invoke the function, omitting the leading '/' pub keyword: &'static str, + /// the format string used by `scan_fmt` to parse arguments arg_fmt: &'static str, + /// message to explain how the command is used help_string: &'static str, + /// handler function called when the command is executed handler: fn(&mut Server, EcsEntity, String, &ChatCommand), } impl ChatCommand { + /// Creates a new chat command pub fn new( keyword: &'static str, arg_fmt: &'static str, @@ -27,12 +37,14 @@ impl ChatCommand { handler, } } + /// Calls the contained handler function, passing `&self` as the last argument pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) { (self.handler)(server, entity, args, self); } } lazy_static! { + /// Static list of chat commands available to the server pub static ref CHAT_COMMANDS: Vec = vec![ ChatCommand::new( "jump", From 4e3c97862ede80e8a29f4e9432674e8ac51d5d56 Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 17:22:44 +0100 Subject: [PATCH 6/7] Update docs Former-commit-id: 381a759ebcefb4f0fad76fe66ca2720b572849b5 --- server/src/cmd.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 73d0cc1362..b090f38fc0 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1,6 +1,6 @@ -//! #Implementing new commands +//! # Implementing new commands //! To implement a new command, add an instance of `ChatCommand` to `CHAT_COMMANDS` -//! and provide a handler function +//! and provide a handler function. use crate::Server; use common::{comp, msg::ServerMsg}; @@ -12,13 +12,19 @@ use lazy_static::lazy_static; /// Struct representing a command that a user can run from server chat pub struct ChatCommand { - /// The keyword used to invoke the function, omitting the leading '/' + /// The keyword used to invoke the command, omitting the leading '/' pub keyword: &'static str, - /// the format string used by `scan_fmt` to parse arguments + /// A format string for parsing arguments arg_fmt: &'static str, /// message to explain how the command is used help_string: &'static str, - /// handler function called when the command is executed + /// Handler function called when the command is executed + /// # Arguments + /// * `&mut Server` - the `Server` instance executing the command + /// * `EcsEntity` - an `Entity` corresponding to the player that invoked the command + /// * `String` - a `String` containing the part 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) handler: fn(&mut Server, EcsEntity, String, &ChatCommand), } From dbccb345820aa1d0cbd34ffbca04a1f25b1cd9a7 Mon Sep 17 00:00:00 2001 From: sxv20_ Date: Tue, 16 Apr 2019 17:34:41 +0100 Subject: [PATCH 7/7] replace if-let with match Former-commit-id: ed39cbb1ccc69302cb65822ad4a4cfecd2bd8f86 --- server/src/cmd.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b090f38fc0..529b09f0a8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -4,12 +4,12 @@ use crate::Server; use common::{comp, msg::ServerMsg}; -use specs::{Entity as EcsEntity, join::Join}; +use specs::{join::Join, Entity as EcsEntity}; use vek::*; -use scan_fmt::scan_fmt; -use lazy_static::lazy_static; +use lazy_static::lazy_static; +use scan_fmt::scan_fmt; /// 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 '/' @@ -88,18 +88,17 @@ fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &Ch let (opt_x, opt_y, opt_z) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32); match (opt_x, opt_y, opt_z) { (Some(x), Some(y), Some(z)) => { - if let Some(current_pos) = server + match server .state .read_component_cloned::(entity) { - server + Some(current_pos) => server .state - .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))) - } else { - server.clients.notify( + .write_component(entity, comp::phys::Pos(current_pos.0 + Vec3::new(x, y, z))), + None => server.clients.notify( entity, ServerMsg::Chat(String::from("Command 'jump' invalid in current state")), - ) + ), } } _ => server