diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index 7c51dff45..478f12706 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -68,6 +68,6 @@ impl<'de> Deserialize<'de> for WorldPosition { impl std::fmt::Display for WorldPosition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}, {},{})", self.0.x, self.0.y, self.0.z) + write!(f, "{}, {}, {}", self.0.x, self.0.y, self.0.z) } } diff --git a/pumpkin-world/src/block/block_registry.rs b/pumpkin-world/src/block/block_registry.rs index f885c11e4..1ceca5a17 100644 --- a/pumpkin-world/src/block/block_registry.rs +++ b/pumpkin-world/src/block/block_registry.rs @@ -18,6 +18,26 @@ pub fn get_block_by_id<'a>(id: u16) -> Option<&'a Block> { BLOCKS.blocks.iter().find(|&block| block.id == id) } +pub fn get_state_by_state_id<'a>(id: u16) -> Option<&'a State> { + get_block_and_state_by_state_id(id).map(|(_, state)| state) +} + +pub fn get_block_by_state_id<'a>(id: u16) -> Option<&'a Block> { + get_block_and_state_by_state_id(id).map(|(block, _)| block) +} + +pub fn get_block_and_state_by_state_id<'a>(id: u16) -> Option<(&'a Block, &'a State)> { + for block in &BLOCKS.blocks { + for state in &block.states { + if state.id == id { + return Some((block, state)); + } + } + } + + None +} + pub fn get_block_by_item<'a>(item_id: u16) -> Option<&'a Block> { BLOCKS.blocks.iter().find(|&block| block.item_id == item_id) } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 06b2583e8..ccb788600 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -475,10 +475,9 @@ impl Player { if self.gamemode.load() == GameMode::Creative { let location = player_action.location; // Block break & block break sound - // TODO: currently this is always dirt replace it let entity = &self.living_entity.entity; let world = &entity.world; - world.break_block(location).await; + world.break_block(location, Some(self)).await; } } Status::CancelledDigging => { @@ -505,10 +504,9 @@ impl Player { return; } // Block break & block break sound - // TODO: currently this is always dirt replace it let entity = &self.living_entity.entity; let world = &entity.world; - world.break_block(location).await; + world.break_block(location, Some(self)).await; // TODO: Send this every tick self.client .send_packet(&CAcknowledgeBlockChange::new(player_action.sequence)) @@ -590,7 +588,9 @@ impl Player { let bounding_box = entity.bounding_box.load(); //TODO: Make this check for every entity in that posistion if !bounding_box.intersects(&block_bounding_box) { - world.set_block(world_pos, block.default_state_id).await; + world + .set_block_state(world_pos, block.default_state_id) + .await; } } self.client diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs new file mode 100644 index 000000000..6aa80f3ce --- /dev/null +++ b/pumpkin/src/command/args/arg_block.rs @@ -0,0 +1,84 @@ +use async_trait::async_trait; +use pumpkin_protocol::client::play::{ + CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, +}; +use pumpkin_world::block::block_registry::{self, Block}; + +use crate::{command::dispatcher::CommandError, server::Server}; + +use super::{ + super::{ + args::{ArgumentConsumer, RawArgs}, + CommandSender, + }, + Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser, +}; + +pub(crate) struct BlockArgumentConsumer; + +impl GetClientSideArgParser for BlockArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Resource { + identifier: "block", + } + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + +#[async_trait] +impl ArgumentConsumer for BlockArgumentConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + + let name = if s.contains(':') { + s.to_string() + } else { + format!("minecraft:{s}") + }; + + Some(Arg::Block(name)) + } + + async fn suggest<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + _input: &'a str, + ) -> Result>>, CommandError> { + Ok(None) + } +} + +impl DefaultNameArgConsumer for BlockArgumentConsumer { + fn default_name(&self) -> &'static str { + "block" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + self + } +} + +impl<'a> FindArg<'a> for BlockArgumentConsumer { + type Data = &'a Block; + + fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + match args.get(name) { + Some(Arg::Block(name)) => match block_registry::get_block(name) { + Some(block) => Ok(block), + None => Err(CommandError::GeneralCommandIssue(format!( + "Block {name} does not exist." + ))), + }, + _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), + } + } +} diff --git a/pumpkin/src/command/args/arg_item.rs b/pumpkin/src/command/args/arg_item.rs index 909c23d77..09d758346 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use pumpkin_protocol::client::play::{ CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, }; +use pumpkin_world::item::item_registry::{self, Item}; use crate::{command::dispatcher::CommandError, server::Server}; @@ -66,11 +67,16 @@ impl DefaultNameArgConsumer for ItemArgumentConsumer { } impl<'a> FindArg<'a> for ItemArgumentConsumer { - type Data = &'a str; + type Data = &'a Item; fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { match args.get(name) { - Some(Arg::Item(name)) => Ok(name), + Some(Arg::Item(name)) => match item_registry::get_item(name) { + Some(item) => Ok(item), + None => Err(CommandError::GeneralCommandIssue(format!( + "Item {name} does not exist." + ))), + }, _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), } } diff --git a/pumpkin/src/command/args/arg_position_2d.rs b/pumpkin/src/command/args/arg_position_2d.rs index 048e28ead..b855348ec 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use pumpkin_core::math::vector2::Vector2; +use pumpkin_core::math::vector3::Vector3; use pumpkin_protocol::client::play::{ CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, }; @@ -10,6 +11,7 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; +use super::coordinate::MaybeRelativeCoordinate; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x and z coordinates only @@ -31,25 +33,15 @@ impl GetClientSideArgParser for Position2DArgumentConsumer { impl ArgumentConsumer for Position2DArgumentConsumer { async fn consume<'a>( &self, - _src: &CommandSender<'a>, + src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, ) -> Option> { - let x_str = args.pop()?; - let z_str = args.pop()?; + let pos = MaybeRelativePosition2D::try_new(args.pop()?, args.pop()?)?; - let mut x = x_str.parse::().ok()?; - let mut z = z_str.parse::().ok()?; + let vec2 = pos.try_to_abolute(src.position())?; - // set position to block center if no decimal place is given - if !x_str.contains('.') { - x += 0.5; - } - if !z_str.contains('.') { - z += 0.5; - } - - Some(Arg::Pos2D(Vector2::new(x, z))) + Some(Arg::Pos2D(vec2)) } async fn suggest<'a>( @@ -62,6 +54,24 @@ impl ArgumentConsumer for Position2DArgumentConsumer { } } +struct MaybeRelativePosition2D( + MaybeRelativeCoordinate, + MaybeRelativeCoordinate, +); + +impl MaybeRelativePosition2D { + fn try_new(x: &str, z: &str) -> Option { + Some(Self(x.try_into().ok()?, z.try_into().ok()?)) + } + + fn try_to_abolute(self, origin: Option>) -> Option> { + Some(Vector2::new( + self.0.into_absolute(origin.map(|o| o.x))?, + self.1.into_absolute(origin.map(|o| o.z))?, + )) + } +} + impl DefaultNameArgConsumer for Position2DArgumentConsumer { fn default_name(&self) -> &'static str { "pos2d" diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 6d538d10f..4940b4dbc 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -1,5 +1,3 @@ -use std::num::ParseFloatError; - use async_trait::async_trait; use pumpkin_core::math::vector3::Vector3; use pumpkin_protocol::client::play::{ @@ -12,6 +10,7 @@ use crate::command::CommandSender; use crate::server::Server; use super::super::args::ArgumentConsumer; +use super::coordinate::MaybeRelativeCoordinate; use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; /// x, y and z coordinates @@ -35,9 +34,9 @@ impl ArgumentConsumer for Position3DArgumentConsumer { _server: &'a Server, args: &mut RawArgs<'a>, ) -> Option> { - let pos = Position3D::try_new(args.pop()?, args.pop()?, args.pop()?)?; + let pos = MaybeRelativePosition3D::try_new(args.pop()?, args.pop()?, args.pop()?)?; - let vec3 = pos.try_get_values(src.position())?; + let vec3 = pos.try_to_absolute(src.position())?; Some(Arg::Pos3D(vec3)) } @@ -52,9 +51,13 @@ impl ArgumentConsumer for Position3DArgumentConsumer { } } -struct Position3D(Coordinate, Coordinate, Coordinate); +struct MaybeRelativePosition3D( + MaybeRelativeCoordinate, + MaybeRelativeCoordinate, + MaybeRelativeCoordinate, +); -impl Position3D { +impl MaybeRelativePosition3D { fn try_new(x: &str, y: &str, z: &str) -> Option { Some(Self( x.try_into().ok()?, @@ -63,11 +66,11 @@ impl Position3D { )) } - fn try_get_values(self, origin: Option>) -> Option> { + fn try_to_absolute(self, origin: Option>) -> Option> { Some(Vector3::new( - self.0.value(origin.map(|o| o.x))?, - self.1.value(origin.map(|o| o.y))?, - self.2.value(origin.map(|o| o.z))?, + self.0.into_absolute(origin.map(|o| o.x))?, + self.1.into_absolute(origin.map(|o| o.y))?, + self.2.into_absolute(origin.map(|o| o.z))?, )) } } @@ -82,40 +85,6 @@ impl DefaultNameArgConsumer for Position3DArgumentConsumer { } } -enum Coordinate { - Absolute(f64), - Relative(f64), -} - -impl TryFrom<&str> for Coordinate { - type Error = ParseFloatError; - - fn try_from(s: &str) -> Result { - if let Some(s) = s.strip_prefix('~') { - let offset = if s.is_empty() { 0.0 } else { s.parse()? }; - Ok(Self::Relative(offset)) - } else { - let mut v = s.parse()?; - - // set position to block center if no decimal place is given - if !IS_Y && !s.contains('.') { - v += 0.5; - } - - Ok(Self::Absolute(v)) - } - } -} - -impl Coordinate { - fn value(self, origin: Option) -> Option { - match self { - Self::Absolute(v) => Some(v), - Self::Relative(offset) => Some(origin? + offset), - } - } -} - impl<'a> FindArg<'a> for Position3DArgumentConsumer { type Data = Vector3; diff --git a/pumpkin/src/command/args/arg_postition_block.rs b/pumpkin/src/command/args/arg_postition_block.rs new file mode 100644 index 000000000..b8ae25dba --- /dev/null +++ b/pumpkin/src/command/args/arg_postition_block.rs @@ -0,0 +1,98 @@ +use async_trait::async_trait; +use pumpkin_core::math::position::WorldPosition; +use pumpkin_core::math::vector3::Vector3; +use pumpkin_protocol::client::play::{ + CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, +}; + +use crate::command::dispatcher::CommandError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; + +use super::super::args::ArgumentConsumer; +use super::coordinate::MaybeRelativeBlockCoordinate; +use super::{Arg, DefaultNameArgConsumer, FindArg, GetClientSideArgParser}; + +/// x, y and z coordinates +pub(crate) struct BlockPosArgumentConsumer; + +impl GetClientSideArgParser for BlockPosArgumentConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::BlockPos + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + +#[async_trait] +impl ArgumentConsumer for BlockPosArgumentConsumer { + async fn consume<'a>( + &self, + src: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let pos = MaybeRelativeBlockPos::try_new(args.pop()?, args.pop()?, args.pop()?)?; + + let vec3 = pos.try_to_absolute(src.position())?; + + Some(Arg::BlockPos(vec3)) + } + + async fn suggest<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &'a Server, + _input: &'a str, + ) -> Result>>, CommandError> { + Ok(None) + } +} + +struct MaybeRelativeBlockPos( + MaybeRelativeBlockCoordinate, + MaybeRelativeBlockCoordinate, + MaybeRelativeBlockCoordinate, +); + +impl MaybeRelativeBlockPos { + fn try_new(x: &str, y: &str, z: &str) -> Option { + Some(Self( + x.try_into().ok()?, + y.try_into().ok()?, + z.try_into().ok()?, + )) + } + + fn try_to_absolute(self, origin: Option>) -> Option { + Some(WorldPosition(Vector3::new( + self.0.into_absolute(origin.map(|o| o.x))?, + self.1.into_absolute(origin.map(|o| o.y))?, + self.2.into_absolute(origin.map(|o| o.z))?, + ))) + } +} + +impl DefaultNameArgConsumer for BlockPosArgumentConsumer { + fn default_name(&self) -> &'static str { + "block_pos" + } + + fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { + &BlockPosArgumentConsumer + } +} + +impl<'a> FindArg<'a> for BlockPosArgumentConsumer { + type Data = WorldPosition; + + fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + match args.get(name) { + Some(Arg::BlockPos(data)) => Ok(*data), + _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), + } + } +} diff --git a/pumpkin/src/command/args/coordinate.rs b/pumpkin/src/command/args/coordinate.rs new file mode 100644 index 000000000..036ad5b0a --- /dev/null +++ b/pumpkin/src/command/args/coordinate.rs @@ -0,0 +1,71 @@ +use std::str::FromStr; + +use pumpkin_world::{WORLD_LOWEST_Y, WORLD_MAX_Y}; + +pub enum MaybeRelativeCoordinate { + Absolute(f64), + Relative(f64), +} + +impl TryFrom<&str> for MaybeRelativeCoordinate { + type Error = ::Err; + + fn try_from(s: &str) -> Result { + if let Some(s) = s.strip_prefix('~') { + let offset = if s.is_empty() { 0.0 } else { s.parse()? }; + Ok(Self::Relative(offset)) + } else { + let mut v = s.parse()?; + + // set position to block center if no decimal place is given + if !IS_Y && !s.contains('.') { + v += 0.5; + } + + Ok(Self::Absolute(v)) + } + } +} + +impl MaybeRelativeCoordinate { + pub fn into_absolute(self, origin: Option) -> Option { + match self { + Self::Absolute(v) => Some(v), + Self::Relative(offset) => Some(origin? + offset), + } + } +} + +#[derive(Debug)] +pub enum MaybeRelativeBlockCoordinate { + Absolute(i32), + Relative(i32), +} + +impl TryFrom<&str> for MaybeRelativeBlockCoordinate { + type Error = ::Err; + + fn try_from(s: &str) -> Result { + if let Some(s) = s.strip_prefix('~') { + let offset = if s.is_empty() { 0 } else { s.parse()? }; + Ok(Self::Relative(offset)) + } else { + Ok(Self::Absolute(s.parse()?)) + } + } +} + +impl MaybeRelativeBlockCoordinate { + pub fn into_absolute(self, origin: Option) -> Option { + let abs = match self { + Self::Absolute(v) => v, + Self::Relative(offset) => origin?.floor() as i32 + offset, + }; + + if IS_Y && (abs < WORLD_LOWEST_Y.into() || abs >= WORLD_MAX_Y.into()) { + return None; + } + + Some(abs) + } +} diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index d543814af..8b41c20d3 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, hash::Hash, sync::Arc}; use arg_bounded_num::{NotInBounds, Number}; use async_trait::async_trait; use pumpkin_core::{ - math::{vector2::Vector2, vector3::Vector3}, + math::{position::WorldPosition, vector2::Vector2, vector3::Vector3}, GameMode, }; use pumpkin_protocol::client::play::{ @@ -18,6 +18,7 @@ use super::{ CommandSender, }; +pub(crate) mod arg_block; pub(crate) mod arg_bounded_num; pub(crate) mod arg_command; pub(crate) mod arg_entities; @@ -28,8 +29,10 @@ pub(crate) mod arg_message; pub(crate) mod arg_players; pub(crate) mod arg_position_2d; pub(crate) mod arg_position_3d; +pub(crate) mod arg_postition_block; pub(crate) mod arg_rotation; pub(crate) mod arg_simple; +mod coordinate; /// see [`crate::commands::tree_builder::argument`] #[async_trait] @@ -71,12 +74,14 @@ pub(crate) enum Arg<'a> { Entities(Vec>), Entity(Arc), Players(Vec>), + BlockPos(WorldPosition), Pos3D(Vector3), Pos2D(Vector2), Rotation(f32, f32), GameMode(GameMode), CommandTree(&'a CommandTree<'a>), Item(String), + Block(String), Msg(String), Num(Result), #[allow(unused)] diff --git a/pumpkin/src/command/commands/cmd_clear.rs b/pumpkin/src/command/commands/cmd_clear.rs index 37e6f1b98..fee70f35e 100644 --- a/pumpkin/src/command/commands/cmd_clear.rs +++ b/pumpkin/src/command/commands/cmd_clear.rs @@ -97,7 +97,7 @@ impl CommandExecutor for ClearSelfExecutor { let item_count = clear_player(&target).await; - let hold_target = vec![target]; + let hold_target = [target]; let msg = clear_command_text_output(item_count, &hold_target); sender.send_message(msg).await; diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs index a245ff58f..5f62233e2 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -1,7 +1,6 @@ use async_trait::async_trait; use pumpkin_core::text::color::{Color, NamedColor}; use pumpkin_core::text::TextComponent; -use pumpkin_world::item::item_registry; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::arg_item::ItemArgumentConsumer; @@ -35,17 +34,7 @@ impl CommandExecutor for GiveExecutor { ) -> Result<(), CommandError> { let targets = PlayersArgumentConsumer.find_arg_default_name(args)?; - let item_name = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?; - - let Some(item) = item_registry::get_item(item_name) else { - sender - .send_message( - TextComponent::text_string(format!("Item {item_name} does not exist.")) - .color(Color::Named(NamedColor::Red)), - ) - .await; - return Ok(()); - }; + let item = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?; let item_count = match ITEM_COUNT_CONSUMER.find_arg_default_name(args) { Err(_) => 1, @@ -68,10 +57,14 @@ impl CommandExecutor for GiveExecutor { sender .send_message(TextComponent::text_string(match targets { [target] => format!( - "Gave {item_count} {item_name} to {}", - target.gameprofile.name + "Gave {item_count} {} to {}", + item.name, target.gameprofile.name + ), + _ => format!( + "Gave {item_count} {} to {} players", + item.name, + targets.len() ), - _ => format!("Gave {item_count} {item_name} to {} players", targets.len()), })) .await; diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs new file mode 100644 index 000000000..e2a92400d --- /dev/null +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -0,0 +1,95 @@ +use async_trait::async_trait; +use pumpkin_core::text::color::NamedColor; +use pumpkin_core::text::TextComponent; + +use crate::command::args::arg_block::BlockArgumentConsumer; +use crate::command::args::arg_postition_block::BlockPosArgumentConsumer; +use crate::command::args::{ConsumedArgs, FindArg}; +use crate::command::tree::CommandTree; +use crate::command::tree_builder::{argument, literal, require}; +use crate::command::{CommandError, CommandExecutor, CommandSender}; +use crate::entity::player::PermissionLvl; + +const NAMES: [&str; 1] = ["setblock"]; + +const DESCRIPTION: &str = "Place a block."; + +const ARG_BLOCK: &str = "block"; +const ARG_BLOCK_POS: &str = "position"; + +#[derive(Clone, Copy)] +enum Mode { + /// with particles + item drops + Destroy, + + /// only replaces air + Keep, + + /// default; without particles + Replace, +} + +struct SetblockExecutor(Mode); + +#[async_trait] +impl CommandExecutor for SetblockExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + _server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let block = BlockArgumentConsumer::find_arg(args, ARG_BLOCK)?; + let block_state_id = block.default_state_id; + let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?; + let mode = self.0; + let world = sender.world().ok_or(CommandError::InvalidRequirement)?; + + let success = match mode { + Mode::Destroy => { + world.break_block(pos, None).await; + world.set_block_state(pos, block_state_id).await; + true + } + Mode::Replace => { + world.set_block_state(pos, block_state_id).await; + true + } + Mode::Keep => match world.get_block_state(pos).await { + Some(old_state) if old_state.air => { + world.set_block_state(pos, block_state_id).await; + true + } + _ => false, + }, + }; + + sender + .send_message(if success { + TextComponent::text_string(format!("Placed block {} at {pos}", block.name,)) + } else { + TextComponent::text_string(format!("Kept block at {pos}")) + .color_named(NamedColor::Red) + }) + .await; + + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(&|sender| { + sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() + }) + .with_child( + argument(ARG_BLOCK_POS, &BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, &BlockArgumentConsumer) + .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) + .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) + .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) + .execute(&SetblockExecutor(Mode::Replace)), + ), + ), + ) +} diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index a69674e46..2fadce1a9 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -9,6 +9,7 @@ pub mod cmd_kill; pub mod cmd_list; pub mod cmd_pumpkin; pub mod cmd_say; +pub mod cmd_setblock; pub mod cmd_stop; pub mod cmd_teleport; pub mod cmd_worldborder; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 247cb923f..f5ffae7dd 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -4,7 +4,7 @@ use args::ConsumedArgs; use async_trait::async_trait; use commands::{ cmd_clear, cmd_craft, cmd_echest, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, - cmd_list, cmd_pumpkin, cmd_say, cmd_stop, cmd_teleport, cmd_worldborder, + cmd_list, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_worldborder, }; use dispatcher::CommandError; use pumpkin_core::math::vector3::Vector3; @@ -13,6 +13,7 @@ use pumpkin_core::text::TextComponent; use crate::command::dispatcher::CommandDispatcher; use crate::entity::player::{PermissionLvl, Player}; use crate::server::Server; +use crate::world::World; pub mod args; pub mod client_cmd_suggestions; @@ -78,6 +79,14 @@ impl<'a> CommandSender<'a> { CommandSender::Player(p) => Some(p.living_entity.entity.pos.load()), } } + + #[must_use] + pub fn world(&self) -> Option<&World> { + match self { + CommandSender::Console | CommandSender::Rcon(..) => None, + CommandSender::Player(p) => Some(&p.living_entity.entity.world), + } + } } #[must_use] @@ -98,6 +107,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_give::init_command_tree()); dispatcher.register(cmd_list::init_command_tree()); dispatcher.register(cmd_clear::init_command_tree()); + dispatcher.register(cmd_setblock::init_command_tree()); Arc::new(dispatcher) } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 2ecc5bd03..cfe7e4be8 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -28,9 +28,14 @@ use pumpkin_protocol::{ }, ClientPacket, VarInt, }; -use pumpkin_world::coordinates::ChunkRelativeBlockCoordinates; +use pumpkin_world::chunk::ChunkData; use pumpkin_world::level::Level; -use pumpkin_world::{block::block_registry::get_block_by_id, chunk::ChunkData}; +use pumpkin_world::{ + block::block_registry::{ + get_block_and_state_by_state_id, get_block_by_state_id, get_state_by_state_id, + }, + coordinates::ChunkRelativeBlockCoordinates, +}; use rand::{thread_rng, Rng}; use scoreboard::Scoreboard; use tokio::sync::{mpsc::Receiver, Mutex}; @@ -141,9 +146,9 @@ impl World { pub async fn get_top_block(&self, position: Vector2) -> i32 { for y in (-64..=319).rev() { let pos = WorldPosition(Vector3::new(position.x, y, position.z)); - let block = self.get_block(pos).await; + let block = self.get_block_state(pos).await; if let Some(block) = block { - if block.states[0].air { + if block.air { continue; } } @@ -606,17 +611,28 @@ impl World { self.broadcast_packet_all(&CRemoveEntities::new(&[entity.entity_id.into()])) .await; } - pub async fn set_block(&self, position: WorldPosition, block_id: u16) { + + /// Sets a block + pub async fn set_block_state(&self, position: WorldPosition, block_state_id: u16) -> u16 { let (chunk_coordinate, relative_coordinates) = position.chunk_and_chunk_relative_position(); // Since we divide by 16 remnant can never exceed u8 let relative = ChunkRelativeBlockCoordinates::from(relative_coordinates); let chunk = self.receive_chunk(chunk_coordinate).await; - chunk.write().await.blocks.set_block(relative, block_id); + let replaced_block_state_id = chunk + .write() + .await + .blocks + .set_block(relative, block_state_id); - self.broadcast_packet_all(&CBlockUpdate::new(&position, i32::from(block_id).into())) - .await; + self.broadcast_packet_all(&CBlockUpdate::new( + &position, + i32::from(block_state_id).into(), + )) + .await; + + replaced_block_state_id } // Stream the chunks (don't collect them and then do stuff with them) @@ -634,18 +650,26 @@ impl World { .expect("Channel closed for unknown reason") } - pub async fn break_block(&self, position: WorldPosition) { - self.set_block(position, 0).await; + pub async fn break_block(&self, position: WorldPosition, cause: Option<&Player>) { + let broken_block_state_id = self.set_block_state(position, 0).await; - self.broadcast_packet_all(&CWorldEvent::new(2001, &position, 11, false)) - .await; + let particles_packet = + CWorldEvent::new(2001, &position, broken_block_state_id.into(), false); + + match cause { + Some(player) => { + self.broadcast_packet_except(&[player.gameprofile.id], &particles_packet) + .await; + } + None => self.broadcast_packet_all(&particles_packet).await, + } } - pub async fn get_block_id(&self, position: WorldPosition) -> u16 { + pub async fn get_block_state_id(&self, position: WorldPosition) -> u16 { let (chunk, relative) = position.chunk_and_chunk_relative_position(); let relative = ChunkRelativeBlockCoordinates::from(relative); let chunk = self.receive_chunk(chunk).await; - let chunk = chunk.read().await; + let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; chunk.blocks.get_block(relative) } @@ -654,7 +678,28 @@ impl World { &self, position: WorldPosition, ) -> Option<&pumpkin_world::block::block_registry::Block> { - let block_id = self.get_block_id(position).await; - get_block_by_id(block_id) + let id = self.get_block_state_id(position).await; + get_block_by_state_id(id) + } + + /// Gets the Block state from the Block Registry, Returns None if the Block state has not been found + pub async fn get_block_state( + &self, + position: WorldPosition, + ) -> Option<&pumpkin_world::block::block_registry::State> { + let id = self.get_block_state_id(position).await; + get_state_by_state_id(id) + } + + /// Gets the Block + Block state from the Block Registry, Returns None if the Block state has not been found + pub async fn get_block_and_block_state( + &self, + position: WorldPosition, + ) -> Option<( + &pumpkin_world::block::block_registry::Block, + &pumpkin_world::block::block_registry::State, + )> { + let id = self.get_block_state_id(position).await; + get_block_and_state_by_state_id(id) } }