Skip to content

Commit

Permalink
Add /setblock command (#263)
Browse files Browse the repository at this point in the history
* add /setblock command

* fix particles, block state id, block coordinate rounding, WoldPosition formatting

* fix block state/block mixup

---------

Co-authored-by: user622628252416 <nnew8715@gmail.com>
  • Loading branch information
user622628252416 and user622628252416 authored Nov 14, 2024
1 parent 91d2eee commit 29d203c
Show file tree
Hide file tree
Showing 16 changed files with 507 additions and 100 deletions.
2 changes: 1 addition & 1 deletion pumpkin-core/src/math/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
20 changes: 20 additions & 0 deletions pumpkin-world/src/block/block_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
10 changes: 5 additions & 5 deletions pumpkin/src/client/player_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,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 => {
Expand All @@ -514,10 +513,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))
Expand Down Expand Up @@ -599,7 +597,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
Expand Down
84 changes: 84 additions & 0 deletions pumpkin/src/command/args/arg_block.rs
Original file line number Diff line number Diff line change
@@ -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<ProtoCmdArgSuggestionType> {
None
}
}

#[async_trait]
impl ArgumentConsumer for BlockArgumentConsumer {
async fn consume<'a>(
&self,
_sender: &CommandSender<'a>,
_server: &'a Server,
args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> {
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<Option<Vec<CommandSuggestion<'a>>>, 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<Self::Data, CommandError> {
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()))),
}
}
}
10 changes: 8 additions & 2 deletions pumpkin/src/command/args/arg_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<Self::Data, CommandError> {
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()))),
}
}
Expand Down
38 changes: 24 additions & 14 deletions pumpkin/src/command/args/arg_position_2d.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand All @@ -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
Expand All @@ -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<Arg<'a>> {
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::<f64>().ok()?;
let mut z = z_str.parse::<f64>().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>(
Expand All @@ -62,6 +54,24 @@ impl ArgumentConsumer for Position2DArgumentConsumer {
}
}

struct MaybeRelativePosition2D(
MaybeRelativeCoordinate<false>,
MaybeRelativeCoordinate<false>,
);

impl MaybeRelativePosition2D {
fn try_new(x: &str, z: &str) -> Option<Self> {
Some(Self(x.try_into().ok()?, z.try_into().ok()?))
}

fn try_to_abolute(self, origin: Option<Vector3<f64>>) -> Option<Vector2<f64>> {
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"
Expand Down
57 changes: 13 additions & 44 deletions pumpkin/src/command/args/arg_position_3d.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::num::ParseFloatError;

use async_trait::async_trait;
use pumpkin_core::math::vector3::Vector3;
use pumpkin_protocol::client::play::{
Expand All @@ -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
Expand All @@ -35,9 +34,9 @@ impl ArgumentConsumer for Position3DArgumentConsumer {
_server: &'a Server,
args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> {
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))
}
Expand All @@ -52,9 +51,13 @@ impl ArgumentConsumer for Position3DArgumentConsumer {
}
}

struct Position3D(Coordinate<false>, Coordinate<true>, Coordinate<false>);
struct MaybeRelativePosition3D(
MaybeRelativeCoordinate<false>,
MaybeRelativeCoordinate<true>,
MaybeRelativeCoordinate<false>,
);

impl Position3D {
impl MaybeRelativePosition3D {
fn try_new(x: &str, y: &str, z: &str) -> Option<Self> {
Some(Self(
x.try_into().ok()?,
Expand All @@ -63,11 +66,11 @@ impl Position3D {
))
}

fn try_get_values(self, origin: Option<Vector3<f64>>) -> Option<Vector3<f64>> {
fn try_to_absolute(self, origin: Option<Vector3<f64>>) -> Option<Vector3<f64>> {
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))?,
))
}
}
Expand All @@ -82,40 +85,6 @@ impl DefaultNameArgConsumer for Position3DArgumentConsumer {
}
}

enum Coordinate<const IS_Y: bool> {
Absolute(f64),
Relative(f64),
}

impl<const IS_Y: bool> TryFrom<&str> for Coordinate<IS_Y> {
type Error = ParseFloatError;

fn try_from(s: &str) -> Result<Self, Self::Error> {
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<const IS_Y: bool> Coordinate<IS_Y> {
fn value(self, origin: Option<f64>) -> Option<f64> {
match self {
Self::Absolute(v) => Some(v),
Self::Relative(offset) => Some(origin? + offset),
}
}
}

impl<'a> FindArg<'a> for Position3DArgumentConsumer {
type Data = Vector3<f64>;

Expand Down
Loading

0 comments on commit 29d203c

Please sign in to comment.