diff --git a/Cargo.lock b/Cargo.lock index afe3d80245..11aba082cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1189,6 +1189,26 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "enum-iterator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.31", +] + [[package]] name = "error-chain" version = "0.12.2" @@ -4627,6 +4647,7 @@ dependencies = [ "criterion", "crossbeam", "dot_vox", + "enum-iterator", "find_folder", "hashbrown", "image", diff --git a/common/Cargo.toml b/common/Cargo.toml index 92d63c8045..60e7bc534c 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -31,6 +31,7 @@ indexmap = "1.3.0" sum_type = "0.2.0" authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" } slab = "0.4.2" +enum-iterator = "0.6" [dev-dependencies] criterion = "0.3" diff --git a/common/src/cmd.rs b/common/src/cmd.rs index e128901f46..7e3194c4a6 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -1,4 +1,4 @@ -use crate::{assets, comp, npc}; +use crate::{assets, comp, npc, terrain}; use lazy_static::lazy_static; use std::{ collections::HashMap, @@ -56,6 +56,7 @@ pub enum ChatCommand { KillNpcs, Lantern, Light, + MakeBlock, Motd, Object, Players, @@ -98,6 +99,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::KillNpcs, ChatCommand::Lantern, ChatCommand::Light, + ChatCommand::MakeBlock, ChatCommand::Motd, ChatCommand::Object, ChatCommand::Players, @@ -149,6 +151,11 @@ lazy_static! { .map(|s| s.to_string()) .collect(); + static ref BLOCK_KINDS: Vec = terrain::block::BLOCK_KINDS + .keys() + .cloned() + .collect(); + /// List of item specifiers. Useful for tab completing static ref ITEM_SPECS: Vec = { let path = assets::ASSETS_PATH.join("common").join("items"); @@ -281,6 +288,11 @@ impl ChatCommand { "Spawn entity with light", Admin, ), + ChatCommand::MakeBlock => cmd( + vec![Enum("block", BLOCK_KINDS.clone(), Required)], + "Make a block", + Admin, + ), ChatCommand::Motd => cmd( vec![Message(Optional)], "View the server description", @@ -386,6 +398,7 @@ impl ChatCommand { ChatCommand::KillNpcs => "kill_npcs", ChatCommand::Lantern => "lantern", ChatCommand::Light => "light", + ChatCommand::MakeBlock => "make_block", ChatCommand::Motd => "motd", ChatCommand::Object => "object", ChatCommand::Players => "players", diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index bf6886c0ca..383b4475fc 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,9 +1,16 @@ use crate::vol::Vox; use serde::{Deserialize, Serialize}; -use std::ops::Deref; +use std::{ + ops::Deref, + convert::TryFrom, + collections::HashMap, + fmt, +}; +use lazy_static::lazy_static; +use enum_iterator::IntoEnumIterator; use vek::*; -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, IntoEnumIterator)] #[repr(u8)] pub enum BlockKind { Air, @@ -88,6 +95,26 @@ pub enum BlockKind { ShinyGem, } +impl fmt::Display for BlockKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +lazy_static! { + pub static ref BLOCK_KINDS: HashMap = BlockKind::into_enum_iter() + .map(|bk| (bk.to_string(), bk)) + .collect(); +} + +impl<'a> TryFrom<&'a str> for BlockKind { + type Error = (); + + fn try_from(s: &'a str) -> Result { + BLOCK_KINDS.get(s).copied().ok_or(()) + } +} + impl BlockKind { pub const MAX_HEIGHT: f32 = 3.0; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 5ee23d8328..f1460ce1f1 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -13,15 +13,16 @@ use common::{ npc::{self, get_npc_name}, state::TimeOfDay, sync::{Uid, WorldSyncExt}, - terrain::TerrainChunkSize, + terrain::{TerrainChunkSize, Block, BlockKind}, util::Dir, - vol::RectVolSize, + vol::{RectVolSize, WriteVol}, LoadoutBuilder, }; use rand::Rng; use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use vek::*; use world::util::Sampler; +use std::convert::TryFrom; use scan_fmt::{scan_fmt, scan_fmt_some}; use tracing::error; @@ -83,6 +84,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler { ChatCommand::KillNpcs => handle_kill_npcs, ChatCommand::Lantern => handle_lantern, ChatCommand::Light => handle_light, + ChatCommand::MakeBlock => handle_make_block, ChatCommand::Motd => handle_motd, ChatCommand::Object => handle_object, ChatCommand::Players => handle_players, @@ -179,6 +181,39 @@ fn handle_give_item( } } +fn handle_make_block( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: String, + action: &ChatCommand, +) { + if let Some(block_name) = scan_fmt_some!(&args, &action.arg_fmt(), String) { + if let Ok(bk) = BlockKind::try_from(block_name.as_str()) { + match server.state.read_component_cloned::(target) { + Some(pos) => server.state.set_block( + pos.0.map(|e| e.floor() as i32), + Block::new(bk, Rgb::broadcast(255)), + ), + None => server.notify_client( + client, + ChatType::CommandError.server_msg(String::from("You have no position.")), + ), + } + } else { + server.notify_client( + client, + ChatType::CommandError.server_msg(format!("Invalid block kind: {}", block_name)), + ); + } + } else { + server.notify_client( + client, + ChatType::CommandError.server_msg(String::from(action.help_string())), + ); + } +} + fn handle_motd( server: &mut Server, client: EcsEntity,