diff --git a/pumpkin-protocol/src/bytebuf/packet_id.rs b/pumpkin-protocol/src/bytebuf/packet_id.rs index c896b0870..c8d17c695 100644 --- a/pumpkin-protocol/src/bytebuf/packet_id.rs +++ b/pumpkin-protocol/src/bytebuf/packet_id.rs @@ -4,7 +4,7 @@ use serde::{ Deserialize, Deserializer, Serialize, Serializer, }; -use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType}; +use crate::{BitSet, ClientPacket, ServerPacket, VarInt, VarIntType, VarLong}; use super::{deserializer, serializer, ByteBuffer, DeserializerError}; @@ -74,6 +74,62 @@ impl<'de> Deserialize<'de> for VarInt { } } +impl Serialize for VarLong { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut value = self.0 as u64; + let mut buf = Vec::new(); + + while value > 0x7F { + buf.put_u8(value as u8 | 0x80); + value >>= 7; + } + + buf.put_u8(value as u8); + + serializer.serialize_bytes(&buf) + } +} + +impl<'de> Deserialize<'de> for VarLong { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VarLongVisitor; + + impl<'de> Visitor<'de> for VarLongVisitor { + type Value = VarLong; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut val = 0; + for i in 0..VarLong::MAX_SIZE { + if let Some(byte) = seq.next_element::()? { + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } else { + break; + } + } + Err(de::Error::custom("VarInt was too large")) + } + } + + deserializer.deserialize_seq(VarLongVisitor) + } +} + pub trait ClientPacketID { const PACKET_ID: VarIntType; } diff --git a/pumpkin-protocol/src/client/play/c_initialize_world_border.rs b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs new file mode 100644 index 000000000..f8518552c --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_initialize_world_border.rs @@ -0,0 +1,44 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::{VarInt, VarLong}; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::InitializeWorldBorder as i32)] +pub struct CInitializeWorldBorder { + x: f64, + z: f64, + old_diameter: f64, + new_diameter: f64, + speed: VarLong, + portal_teleport_boundary: VarInt, + warning_blocks: VarInt, + warning_time: VarInt, +} + +impl CInitializeWorldBorder { + #[expect(clippy::too_many_arguments)] + pub fn new( + x: f64, + z: f64, + old_diameter: f64, + new_diameter: f64, + speed: VarLong, + portal_teleport_boundary: VarInt, + warning_blocks: VarInt, + warning_time: VarInt, + ) -> Self { + Self { + x, + z, + old_diameter, + new_diameter, + speed, + portal_teleport_boundary, + warning_blocks, + warning_time, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_border_center.rs b/pumpkin-protocol/src/client/play/c_set_border_center.rs new file mode 100644 index 000000000..12a6d9604 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_border_center.rs @@ -0,0 +1,17 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::WorldBorderCenter as i32)] +pub struct CSetBorderCenter { + x: f64, + z: f64, +} + +impl CSetBorderCenter { + pub fn new(x: f64, z: f64) -> Self { + Self { x, z } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs new file mode 100644 index 000000000..222618c6e --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_border_lerp_size.rs @@ -0,0 +1,24 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::VarLong; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::WorldBorderLerpSize as i32)] +pub struct CSetBorderLerpSize { + old_diameter: f64, + new_diameter: f64, + speed: VarLong, +} + +impl CSetBorderLerpSize { + pub fn new(old_diameter: f64, new_diameter: f64, speed: VarLong) -> Self { + Self { + old_diameter, + new_diameter, + speed, + } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_border_size.rs b/pumpkin-protocol/src/client/play/c_set_border_size.rs new file mode 100644 index 000000000..d4733ce01 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_border_size.rs @@ -0,0 +1,16 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::WorldBorderSize as i32)] +pub struct CSetBorderSize { + diameter: f64, +} + +impl CSetBorderSize { + pub fn new(diameter: f64) -> Self { + Self { diameter } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_border_warning_delay.rs b/pumpkin-protocol/src/client/play/c_set_border_warning_delay.rs new file mode 100644 index 000000000..bc3073570 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_border_warning_delay.rs @@ -0,0 +1,18 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::VarInt; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::WorldBorderWarningDelay as i32)] +pub struct CSetBorderWarningDelay { + warning_time: VarInt, +} + +impl CSetBorderWarningDelay { + pub fn new(warning_time: VarInt) -> Self { + Self { warning_time } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_border_warning_distance.rs b/pumpkin-protocol/src/client/play/c_set_border_warning_distance.rs new file mode 100644 index 000000000..2f265e2d0 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_border_warning_distance.rs @@ -0,0 +1,18 @@ +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::VarInt; + +use super::ClientboundPlayPackets; + +#[derive(Serialize)] +#[client_packet(ClientboundPlayPackets::WorldBorderWarningReach as i32)] +pub struct CSetBorderWarningDistance { + warning_blocks: VarInt, +} + +impl CSetBorderWarningDistance { + pub fn new(warning_blocks: VarInt) -> Self { + Self { warning_blocks } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 0693f69f0..6091ce4d7 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -16,6 +16,7 @@ mod c_entity_velocity; mod c_game_event; mod c_head_rot; mod c_hurt_animation; +mod c_initialize_world_border; mod c_keep_alive; mod c_login; mod c_open_screen; @@ -28,6 +29,11 @@ mod c_player_info_update; mod c_player_remove; mod c_remove_entities; mod c_reset_score; +mod c_set_border_center; +mod c_set_border_lerp_size; +mod c_set_border_size; +mod c_set_border_warning_delay; +mod c_set_border_warning_distance; mod c_set_container_content; mod c_set_container_property; mod c_set_container_slot; @@ -67,6 +73,7 @@ pub use c_entity_velocity::*; pub use c_game_event::*; pub use c_head_rot::*; pub use c_hurt_animation::*; +pub use c_initialize_world_border::*; pub use c_keep_alive::*; pub use c_login::*; pub use c_open_screen::*; @@ -79,6 +86,11 @@ pub use c_player_info_update::*; pub use c_player_remove::*; pub use c_remove_entities::*; pub use c_reset_score::*; +pub use c_set_border_center::*; +pub use c_set_border_lerp_size::*; +pub use c_set_border_size::*; +pub use c_set_border_warning_delay::*; +pub use c_set_border_warning_distance::*; pub use c_set_container_content::*; pub use c_set_container_property::*; pub use c_set_container_slot::*; diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index b95ef21c4..0ef2bc252 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -1,8 +1,6 @@ use bytebuf::{packet_id::ClientPacketID, ByteBuffer, DeserializerError}; -use bytes::Buf; use pumpkin_core::text::{style::Style, TextComponent}; use serde::{Deserialize, Serialize}; -use std::io::{self, Write}; use thiserror::Error; pub mod bytebuf; @@ -12,6 +10,12 @@ pub mod packet_encoder; pub mod server; pub mod slot; +mod var_int; +pub use var_int::*; + +mod var_long; +pub use var_long::*; + /// To current Minecraft protocol /// Don't forget to change this when porting pub const CURRENT_MC_PROTOCOL: u32 = 768; @@ -26,107 +30,6 @@ pub type FixedBitSet = bytes::Bytes; pub struct BitSet<'a>(pub VarInt, pub &'a [i64]); -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct VarInt(pub VarIntType); - -impl VarInt { - /// The maximum number of bytes a `VarInt` could occupy when read from and - /// written to the Minecraft protocol. - pub const MAX_SIZE: usize = 5; - - /// Returns the exact number of bytes this varint will write when - /// [`Encode::encode`] is called, assuming no error occurs. - pub const fn written_size(self) -> usize { - match self.0 { - 0 => 1, - n => (31 - n.leading_zeros() as usize) / 7 + 1, - } - } - - pub fn decode_partial(r: &mut &[u8]) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarIntDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(val); - } - } - - Err(VarIntDecodeError::TooLarge) - } - - pub fn encode(&self, mut w: impl Write) -> Result<(), io::Error> { - let mut x = self.0 as u64; - loop { - let byte = (x & 0x7F) as u8; - x >>= 7; - if x == 0 { - w.write_all(&[byte])?; - break; - } - w.write_all(&[byte | 0x80])?; - } - Ok(()) - } - - pub fn decode(r: &mut &[u8]) -> Result { - let mut val = 0; - for i in 0..Self::MAX_SIZE { - if !r.has_remaining() { - return Err(VarIntDecodeError::Incomplete); - } - let byte = r.get_u8(); - val |= (i32::from(byte) & 0b01111111) << (i * 7); - if byte & 0b10000000 == 0 { - return Ok(VarInt(val)); - } - } - Err(VarIntDecodeError::TooLarge) - } -} - -impl From for VarInt { - fn from(value: i32) -> Self { - VarInt(value) - } -} - -impl From for VarInt { - fn from(value: u32) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: u8) -> Self { - VarInt(value as i32) - } -} - -impl From for VarInt { - fn from(value: usize) -> Self { - VarInt(value as i32) - } -} - -impl From for i32 { - fn from(value: VarInt) -> Self { - value.0 - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] -pub enum VarIntDecodeError { - #[error("incomplete VarInt decode")] - Incomplete, - #[error("VarInt is too large")] - TooLarge, -} - #[derive(Error, Debug)] pub enum PacketError { #[error("failed to decode packet ID")] diff --git a/pumpkin-protocol/src/var_int.rs b/pumpkin-protocol/src/var_int.rs new file mode 100644 index 000000000..61f596caf --- /dev/null +++ b/pumpkin-protocol/src/var_int.rs @@ -0,0 +1,107 @@ +use std::io::{self, Write}; + +use bytes::Buf; +use thiserror::Error; + +use crate::VarIntType; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarInt(pub VarIntType); + +impl VarInt { + /// The maximum number of bytes a `VarInt` could occupy when read from and + /// written to the Minecraft protocol. + pub const MAX_SIZE: usize = 5; + + /// Returns the exact number of bytes this varint will write when + /// [`Encode::encode`] is called, assuming no error occurs. + pub const fn written_size(self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + pub fn decode_partial(r: &mut &[u8]) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE { + if !r.has_remaining() { + return Err(VarIntDecodeError::Incomplete); + } + let byte = r.get_u8(); + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(val); + } + } + + Err(VarIntDecodeError::TooLarge) + } + + pub fn encode(&self, mut w: impl Write) -> Result<(), io::Error> { + let mut x = self.0 as u64; + loop { + let byte = (x & 0x7F) as u8; + x >>= 7; + if x == 0 { + w.write_all(&[byte])?; + break; + } + w.write_all(&[byte | 0x80])?; + } + Ok(()) + } + + pub fn decode(r: &mut &[u8]) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE { + if !r.has_remaining() { + return Err(VarIntDecodeError::Incomplete); + } + let byte = r.get_u8(); + val |= (i32::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarInt(val)); + } + } + Err(VarIntDecodeError::TooLarge) + } +} + +impl From for VarInt { + fn from(value: i32) -> Self { + VarInt(value) + } +} + +impl From for VarInt { + fn from(value: u32) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: u8) -> Self { + VarInt(value as i32) + } +} + +impl From for VarInt { + fn from(value: usize) -> Self { + VarInt(value as i32) + } +} + +impl From for i32 { + fn from(value: VarInt) -> Self { + value.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] +pub enum VarIntDecodeError { + #[error("incomplete VarInt decode")] + Incomplete, + #[error("VarInt is too large")] + TooLarge, +} diff --git a/pumpkin-protocol/src/var_long.rs b/pumpkin-protocol/src/var_long.rs new file mode 100644 index 000000000..91a3bbc2a --- /dev/null +++ b/pumpkin-protocol/src/var_long.rs @@ -0,0 +1,91 @@ +use std::io::{self, Write}; + +use bytes::Buf; +use thiserror::Error; + +use crate::VarLongType; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VarLong(pub VarLongType); + +impl VarLong { + /// The maximum number of bytes a `VarInt` could occupy when read from and + /// written to the Minecraft protocol. + pub const MAX_SIZE: usize = 10; + + /// Returns the exact number of bytes this varint will write when + /// [`Encode::encode`] is called, assuming no error occurs. + pub const fn written_size(self) -> usize { + match self.0 { + 0 => 1, + n => (31 - n.leading_zeros() as usize) / 7 + 1, + } + } + + pub fn encode(&self, mut w: impl Write) -> Result<(), io::Error> { + let mut x = self.0 as u64; + loop { + let byte = (x & 0x7F) as u8; + x >>= 7; + if x == 0 { + w.write_all(&[byte])?; + break; + } + w.write_all(&[byte | 0x80])?; + } + Ok(()) + } + + pub fn decode(r: &mut &[u8]) -> Result { + let mut val = 0; + for i in 0..Self::MAX_SIZE { + if !r.has_remaining() { + return Err(VarLongDecodeError::Incomplete); + } + let byte = r.get_u8(); + val |= (i64::from(byte) & 0b01111111) << (i * 7); + if byte & 0b10000000 == 0 { + return Ok(VarLong(val)); + } + } + Err(VarLongDecodeError::TooLarge) + } +} + +impl From for VarLong { + fn from(value: i64) -> Self { + VarLong(value) + } +} + +impl From for VarLong { + fn from(value: u32) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: u8) -> Self { + VarLong(value as i64) + } +} + +impl From for VarLong { + fn from(value: usize) -> Self { + VarLong(value as i64) + } +} + +impl From for i64 { + fn from(value: VarLong) -> Self { + value.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)] +pub enum VarLongDecodeError { + #[error("incomplete VarLong decode")] + Incomplete, + #[error("VarLong is too large")] + TooLarge, +} diff --git a/pumpkin/src/commands/arg_position.rs b/pumpkin/src/commands/arg_position.rs new file mode 100644 index 000000000..98431f1b8 --- /dev/null +++ b/pumpkin/src/commands/arg_position.rs @@ -0,0 +1,45 @@ +use async_trait::async_trait; + +use crate::commands::dispatcher::InvalidTreeError; +use crate::commands::tree::{ConsumedArgs, RawArgs}; +use crate::commands::CommandSender; +use crate::server::Server; + +use super::tree::ArgumentConsumer; + +/// TODO: Seperate base functionality of these two methods into single method + +/// todo: implement (so far only own name + @s/@p is implemented) +pub(crate) struct PositionArgumentConsumer; + +#[async_trait] +impl ArgumentConsumer for PositionArgumentConsumer { + async fn consume<'a>( + &self, + _src: &CommandSender<'a>, + _server: &Server, + args: &mut RawArgs<'a>, + ) -> Result> { + let Some(arg) = args.pop() else { + return Err(None); + }; + + // TODO implement ~ ^ notations + let value = arg.parse::().map_err(|err| Some(err.to_string()))?; + Ok(value.to_string()) + } +} + +pub fn parse_arg_position( + arg_name: &str, + consumed_args: &ConsumedArgs<'_>, +) -> Result { + let s = consumed_args + .get(arg_name) + .ok_or(InvalidTreeError::InvalidConsumptionError(None))?; + + let value = s + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + Ok(value) +} diff --git a/pumpkin/src/commands/arg_simple.rs b/pumpkin/src/commands/arg_simple.rs new file mode 100644 index 000000000..2a5d38ad9 --- /dev/null +++ b/pumpkin/src/commands/arg_simple.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; + +use crate::server::Server; + +use super::{ + tree::{ArgumentConsumer, RawArgs}, + CommandSender, +}; + +/// Should never be a permanent solution +pub(crate) struct SimpleArgConsumer; + +#[async_trait] +impl ArgumentConsumer for SimpleArgConsumer { + async fn consume<'a>( + &self, + _sender: &CommandSender<'a>, + _server: &Server, + args: &mut RawArgs<'a>, + ) -> Result> { + args.pop().ok_or(None).map(ToString::to_string) + } +} diff --git a/pumpkin/src/commands/cmd_worldborder.rs b/pumpkin/src/commands/cmd_worldborder.rs new file mode 100644 index 000000000..b3f2f894d --- /dev/null +++ b/pumpkin/src/commands/cmd_worldborder.rs @@ -0,0 +1,510 @@ +use async_trait::async_trait; +use pumpkin_core::text::{ + color::{Color, NamedColor}, + TextComponent, +}; + +use crate::server::Server; + +use super::{ + arg_position::{parse_arg_position, PositionArgumentConsumer}, + arg_simple::SimpleArgConsumer, + dispatcher::InvalidTreeError, + tree::{CommandTree, ConsumedArgs}, + tree_builder::{argument, literal}, + CommandExecutor, CommandSender, +}; + +const NAMES: [&str; 1] = ["worldborder"]; + +const DESCRIPTION: &str = "Worldborder command."; + +struct WorldborderGetExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderGetExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + _args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let border = world.worldborder.lock().await; + + let diameter = border.new_diameter.round() as i32; + sender + .send_message(TextComponent::text(&format!( + "The world border is currently {diameter} block(s) wide" + ))) + .await; + Ok(()) + } +} + +struct WorldborderSetExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderSetExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let distance = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if (distance - border.new_diameter).abs() < f64::EPSILON { + sender + .send_message( + TextComponent::text("Nothing changed. The world border is already that size") + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + sender + .send_message(TextComponent::text(&format!( + "Set the world border to {distance:.1} block(s) wide" + ))) + .await; + border.set_diameter(world, distance, None).await; + Ok(()) + } +} + +struct WorldborderSetTimeExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderSetTimeExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let distance = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let time = args + .get("time") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + match distance.total_cmp(&border.new_diameter) { + std::cmp::Ordering::Equal => { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border is already that size", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + std::cmp::Ordering::Less => { + sender.send_message(TextComponent::text(&format!("Shrinking the world border to {distance:.2} blocks wide over {time} second(s)"))).await; + } + std::cmp::Ordering::Greater => { + sender + .send_message(TextComponent::text(&format!( + "Growing the world border to {distance:.2} blocks wide over {time} seconds" + ))) + .await; + } + } + + border + .set_diameter(world, distance, Some(i64::from(time) * 1000)) + .await; + Ok(()) + } +} + +struct WorldborderAddExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderAddExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let distance = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if distance == 0.0 { + sender + .send_message( + TextComponent::text("Nothing changed. The world border is already that size") + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + let distance = border.new_diameter + distance; + + sender + .send_message(TextComponent::text(&format!( + "Set the world border to {distance:.1} block(s) wide" + ))) + .await; + border.set_diameter(world, distance, None).await; + Ok(()) + } +} + +struct WorldborderAddTimeExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderAddTimeExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let distance = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + let time = args + .get("time") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + let distance = distance + border.new_diameter; + + match distance.total_cmp(&border.new_diameter) { + std::cmp::Ordering::Equal => { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border is already that size", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + std::cmp::Ordering::Less => { + sender.send_message(TextComponent::text(&format!("Shrinking the world border to {distance:.2} blocks wide over {time} second(s)"))).await; + } + std::cmp::Ordering::Greater => { + sender + .send_message(TextComponent::text(&format!( + "Growing the world border to {distance:.2} blocks wide over {time} seconds" + ))) + .await; + } + } + + border + .set_diameter(world, distance, Some(i64::from(time) * 1000)) + .await; + Ok(()) + } +} + +struct WorldborderCenterExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderCenterExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let x = parse_arg_position("x", args)?; + let z = parse_arg_position("z", args)?; + + sender + .send_message(TextComponent::text(&format!( + "Set the center of world border to {x:.2}, {z:.2}" + ))) + .await; + border.set_center(world, x, z).await; + Ok(()) + } +} + +struct WorldborderDamageAmountExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderDamageAmountExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let damage_per_block = args + .get("damage_per_block") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border damage is already that amount", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + sender + .send_message(TextComponent::text(&format!( + "Set the world border damage to {damage_per_block:.2} per block each second" + ))) + .await; + border.damage_per_block = damage_per_block; + Ok(()) + } +} + +struct WorldborderDamageBufferExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderDamageBufferExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let buffer = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if (buffer - border.buffer).abs() < f32::EPSILON { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border damage buffer is already that distance", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + sender + .send_message(TextComponent::text(&format!( + "Set the world border damage buffer to {buffer:.2} block(s)" + ))) + .await; + border.buffer = buffer; + Ok(()) + } +} + +struct WorldborderWarningDistanceExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderWarningDistanceExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let distance = args + .get("distance") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if distance == border.warning_blocks { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border warning is already that distance", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + sender + .send_message(TextComponent::text(&format!( + "Set the world border warning distance to {distance} block(s)" + ))) + .await; + border.set_warning_distance(world, distance).await; + Ok(()) + } +} + +struct WorldborderWarningTimeExecutor; + +#[async_trait] +impl CommandExecutor for WorldborderWarningTimeExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), InvalidTreeError> { + let world = server + .worlds + .first() + .expect("There should always be atleast one world"); + let mut border = world.worldborder.lock().await; + + let time = args + .get("time") + .ok_or(InvalidTreeError::InvalidConsumptionError(None))? + .parse::() + .map_err(|err| InvalidTreeError::InvalidConsumptionError(Some(err.to_string())))?; + + if time == border.warning_time { + sender + .send_message( + TextComponent::text( + "Nothing changed. The world border warning is already that amount of time", + ) + .color(Color::Named(NamedColor::Red)), + ) + .await; + return Ok(()); + } + + sender + .send_message(TextComponent::text(&format!( + "Set the world border warning time to {time} second(s)" + ))) + .await; + border.set_warning_delay(world, time).await; + Ok(()) + } +} + +pub fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION) + .with_child( + literal("add").with_child( + argument("distance", &SimpleArgConsumer) + .execute(&WorldborderAddExecutor) + .with_child( + argument("time", &SimpleArgConsumer).execute(&WorldborderAddTimeExecutor), + ), + ), + ) + .with_child(literal("center").with_child( + argument("x", &PositionArgumentConsumer).with_child( + argument("z", &PositionArgumentConsumer).execute(&WorldborderCenterExecutor), + ), + )) + .with_child( + literal("damage") + .with_child( + literal("amount").with_child( + argument("damage_per_block", &SimpleArgConsumer) + .execute(&WorldborderDamageAmountExecutor), + ), + ) + .with_child( + literal("buffer").with_child( + argument("distance", &SimpleArgConsumer) + .execute(&WorldborderDamageBufferExecutor), + ), + ), + ) + .with_child(literal("get").execute(&WorldborderGetExecutor)) + .with_child( + literal("set").with_child( + argument("distance", &SimpleArgConsumer) + .execute(&WorldborderSetExecutor) + .with_child( + argument("time", &SimpleArgConsumer).execute(&WorldborderSetTimeExecutor), + ), + ), + ) + .with_child( + literal("warning") + .with_child( + literal("distance").with_child( + argument("distance", &SimpleArgConsumer) + .execute(&WorldborderWarningDistanceExecutor), + ), + ) + .with_child(literal("time").with_child( + argument("time", &SimpleArgConsumer).execute(&WorldborderWarningTimeExecutor), + )), + ) +} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index f4ce90931..e3b8c4183 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -8,7 +8,11 @@ use tree::ConsumedArgs; use crate::commands::dispatcher::CommandDispatcher; use crate::entity::player::Player; use crate::server::Server; + mod arg_player; +mod arg_position; +mod arg_simple; + mod cmd_echest; mod cmd_gamemode; mod cmd_help; @@ -16,6 +20,8 @@ mod cmd_kick; mod cmd_kill; mod cmd_pumpkin; mod cmd_stop; +mod cmd_worldborder; + pub mod dispatcher; mod tree; mod tree_builder; @@ -71,6 +77,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_echest::init_command_tree()); dispatcher.register(cmd_kill::init_command_tree()); dispatcher.register(cmd_kick::init_command_tree()); + dispatcher.register(cmd_worldborder::init_command_tree()); Arc::new(dispatcher) } diff --git a/pumpkin/src/commands/tree_builder.rs b/pumpkin/src/commands/tree_builder.rs index 7efa787b6..4463eddd4 100644 --- a/pumpkin/src/commands/tree_builder.rs +++ b/pumpkin/src/commands/tree_builder.rs @@ -123,7 +123,6 @@ impl<'a> NonLeafNodeBuilder<'a> { } /// Matches a sting literal. -#[expect(dead_code)] // todo: remove (so far no commands requiring this are implemented) pub const fn literal(string: &str) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Literal { string }, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 88165e432..74fd7ab0a 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -36,8 +36,10 @@ use tokio::{ sync::{mpsc, RwLock}, task::JoinHandle, }; +use worldborder::Worldborder; pub mod scoreboard; +pub mod worldborder; type ChunkReceiver = ( Vec<(Vector2, JoinHandle<()>)>, @@ -59,6 +61,7 @@ pub struct World { /// A map of active players within the world, keyed by their unique token. pub current_players: Arc>>>, pub scoreboard: Mutex, + pub worldborder: Mutex, // TODO: entities } @@ -69,6 +72,7 @@ impl World { level: Arc::new(level), current_players: Arc::new(Mutex::new(HashMap::new())), scoreboard: Mutex::new(Scoreboard::new()), + worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)), } } @@ -306,6 +310,12 @@ impl World { .send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0)) .await; + self.worldborder + .lock() + .await + .init_client(&player.client) + .await; + // Spawn in initial chunks player_chunker::player_join(self, player.clone()).await; } diff --git a/pumpkin/src/world/worldborder.rs b/pumpkin/src/world/worldborder.rs new file mode 100644 index 000000000..6402a3f92 --- /dev/null +++ b/pumpkin/src/world/worldborder.rs @@ -0,0 +1,113 @@ +use pumpkin_protocol::client::play::{ + CInitializeWorldBorder, CSetBorderCenter, CSetBorderLerpSize, CSetBorderSize, + CSetBorderWarningDelay, CSetBorderWarningDistance, +}; + +use crate::client::Client; + +use super::World; + +pub struct Worldborder { + pub center_x: f64, + pub center_z: f64, + pub old_diameter: f64, + pub new_diameter: f64, + pub speed: i64, + pub portal_teleport_boundary: i32, + pub warning_blocks: i32, + pub warning_time: i32, + pub damage_per_block: f32, + pub buffer: f32, +} + +impl Worldborder { + #[must_use] + pub fn new( + x: f64, + z: f64, + diameter: f64, + speed: i64, + warning_blocks: i32, + warning_time: i32, + ) -> Self { + Self { + center_x: x, + center_z: z, + old_diameter: diameter, + new_diameter: diameter, + speed, + portal_teleport_boundary: 29_999_984, + warning_blocks, + warning_time, + damage_per_block: 0.0, + buffer: 0.0, + } + } + + pub async fn init_client(&self, client: &Client) { + client + .send_packet(&CInitializeWorldBorder::new( + self.center_x, + self.center_z, + self.old_diameter, + self.new_diameter, + self.speed.into(), + self.portal_teleport_boundary.into(), + self.warning_blocks.into(), + self.warning_time.into(), + )) + .await; + } + + pub async fn set_center(&mut self, world: &World, x: f64, z: f64) { + self.center_x = x; + self.center_z = z; + + world + .broadcast_packet_all(&CSetBorderCenter::new(self.center_x, self.center_z)) + .await; + } + + pub async fn set_diameter(&mut self, world: &World, diameter: f64, speed: Option) { + self.old_diameter = self.new_diameter; + self.new_diameter = diameter; + + match speed { + Some(speed) => { + world + .broadcast_packet_all(&CSetBorderLerpSize::new( + self.old_diameter, + self.new_diameter, + speed.into(), + )) + .await; + } + None => { + world + .broadcast_packet_all(&CSetBorderSize::new(self.new_diameter)) + .await; + } + } + } + + pub async fn add_diameter(&mut self, world: &World, offset: f64, speed: Option) { + self.set_diameter(world, self.new_diameter + offset, speed) + .await; + } + + pub async fn set_warning_delay(&mut self, world: &World, delay: i32) { + self.warning_time = delay; + + world + .broadcast_packet_all(&CSetBorderWarningDelay::new(self.warning_time.into())) + .await; + } + + pub async fn set_warning_distance(&mut self, world: &World, distance: i32) { + self.warning_blocks = distance; + + world + .broadcast_packet_all(&CSetBorderWarningDistance::new(self.warning_blocks.into())) + .await; + } +}