From a0339728c7058a40fe55840a235d517f64405313 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 26 Dec 2024 20:59:25 +0100 Subject: [PATCH 01/11] pumpkin-protocol: remove pumpkin-config --- pumpkin-protocol/Cargo.toml | 1 - pumpkin-protocol/src/lib.rs | 15 ++++ pumpkin-protocol/src/packet_decoder.rs | 17 ++-- pumpkin-protocol/src/packet_encoder.rs | 111 +++++++++++++------------ pumpkin/src/net/mod.rs | 11 ++- 5 files changed, 89 insertions(+), 66 deletions(-) diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index 674b290e..c167df95 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -12,7 +12,6 @@ query = [] [dependencies] pumpkin-nbt = { path = "../pumpkin-nbt" } -pumpkin-config = { path = "../pumpkin-config" } pumpkin-macros = { path = "../pumpkin-macros" } pumpkin-world = { path = "../pumpkin-world" } pumpkin-core = { path = "../pumpkin-core" } diff --git a/pumpkin-protocol/src/lib.rs b/pumpkin-protocol/src/lib.rs index 8ef37f7a..fec62014 100644 --- a/pumpkin-protocol/src/lib.rs +++ b/pumpkin-protocol/src/lib.rs @@ -25,6 +25,21 @@ pub const MAX_PACKET_SIZE: i32 = 2097152; pub type FixedBitSet = bytes::Bytes; +/// Represents a compression threshold. +/// +/// The threshold determines the minimum size of data that should be compressed. +/// Data smaller than the threshold will not be compressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CompressionThreshold(pub u32); + +/// Represents a compression level. +/// +/// The level controls the amount of compression applied to the data. +/// Higher levels generally result in higher compression ratios but also +/// increase CPU usage. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CompressionLevel(pub u32); + #[derive(Debug, PartialEq, Clone, Copy)] pub enum ConnectionState { HandShake, diff --git a/pumpkin-protocol/src/packet_decoder.rs b/pumpkin-protocol/src/packet_decoder.rs index 5059a2c5..89ec4f42 100644 --- a/pumpkin-protocol/src/packet_decoder.rs +++ b/pumpkin-protocol/src/packet_decoder.rs @@ -17,8 +17,7 @@ pub struct PacketDecoder { buf: BytesMut, decompress_buf: BytesMut, cipher: Option, - compression: bool, - decompressor: Decompressor, + decompressor: Option, } // Manual implementation of Default trait for PacketDecoder @@ -29,8 +28,7 @@ impl Default for PacketDecoder { buf: BytesMut::new(), decompress_buf: BytesMut::new(), cipher: None, - compression: false, - decompressor: Decompressor::new(), + decompressor: None, } } } @@ -58,7 +56,7 @@ impl PacketDecoder { let packet_len_len = VarInt(packet_len).written_size(); let mut data; - if self.compression { + if let Some(decompressor) = &mut self.decompressor { r = &r[..packet_len as usize]; let data_len = VarInt::decode(&mut r) @@ -77,8 +75,7 @@ impl PacketDecoder { self.decompress_buf.resize(data_len as usize, 0); // Perform decompression using libdeflater - let decompressed_size = self - .decompressor + let decompressed_size = decompressor .zlib_decompress(r, &mut self.decompress_buf) .map_err(PacketDecodeError::from)?; @@ -135,7 +132,11 @@ impl PacketDecoder { /// Sets ZLib Decompression pub fn set_compression(&mut self, compression: bool) { - self.compression = compression; + if compression { + self.decompressor = Some(Decompressor::new()); + } else { + self.decompressor = None + } } fn decrypt_bytes(cipher: &mut Cipher, bytes: &mut [u8]) { diff --git a/pumpkin-protocol/src/packet_encoder.rs b/pumpkin-protocol/src/packet_encoder.rs index 1636d56e..006e1a99 100644 --- a/pumpkin-protocol/src/packet_encoder.rs +++ b/pumpkin-protocol/src/packet_encoder.rs @@ -1,11 +1,12 @@ use aes::cipher::{generic_array::GenericArray, BlockEncryptMut, BlockSizeUser, KeyIvInit}; use bytes::{BufMut, BytesMut}; -use pumpkin_config::compression::CompressionInfo; use thiserror::Error; use libdeflater::{CompressionLvl, Compressor}; -use crate::{codec::Codec, ClientPacket, VarInt, MAX_PACKET_SIZE}; +use crate::{ + codec::Codec, ClientPacket, CompressionLevel, CompressionThreshold, VarInt, MAX_PACKET_SIZE, +}; type Cipher = cfb8::Encryptor; @@ -16,8 +17,8 @@ pub struct PacketEncoder { buf: BytesMut, compress_buf: Vec, cipher: Option, - compression_threshold: Option, - compressor: Compressor, // Reuse the compressor for all packets + // compression and compression threshold + compression: Option<(Compressor, CompressionThreshold)>, } // Manual implementation of Default trait for PacketEncoder @@ -28,8 +29,7 @@ impl Default for PacketEncoder { buf: BytesMut::with_capacity(1024), compress_buf: Vec::with_capacity(1024), cipher: None, - compression_threshold: None, - compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with fastest compression level + compression: None, // init compressor with fastest compression level } } } @@ -73,8 +73,8 @@ impl PacketEncoder { packet.write(&mut self.buf); let data_len = self.buf.len() - start_len; - if let Some(compression_threshold) = self.compression_threshold { - if data_len > compression_threshold as usize { + if let Some((compressor, compression_threshold)) = &mut self.compression { + if data_len > compression_threshold.0 as usize { // Get the data to compress let data_to_compress = &self.buf[start_len..]; @@ -82,15 +82,13 @@ impl PacketEncoder { self.compress_buf.clear(); // Compute the maximum size of compressed data - let max_compressed_size = - self.compressor.zlib_compress_bound(data_to_compress.len()); + let max_compressed_size = compressor.zlib_compress_bound(data_to_compress.len()); // Ensure compress_buf has enough capacity self.compress_buf.resize(max_compressed_size, 0); // Compress the data - let compressed_size = self - .compressor + let compressed_size = compressor .zlib_compress(data_to_compress, &mut self.compress_buf) .map_err(|e| PacketEncodeError::CompressionFailed(e.to_string()))?; @@ -166,26 +164,30 @@ impl PacketEncoder { } } - /// Enables or disables Zlib compression with the given options. + /// Enables or disables Zlib compression. /// - /// If `compression` is `Some`, compression is enabled with the provided - /// options. If `compression` is `None`, compression is disabled. - pub fn set_compression(&mut self, compression: Option) { - // Reset the compressor with the new compression level - if let Some(compression) = &compression { - self.compression_threshold = Some(compression.threshold); - let compression_level = compression.level as i32; - let level = match CompressionLvl::new(compression_level) { - Ok(level) => level, - Err(error) => { - log::error!("Invalid compression level {:?}", error); - return; - } - }; - self.compressor = Compressor::new(level); - } else { - self.compression_threshold = None; + /// If `compression` is `Some`, compression is enabled with the given `threshold` + /// for triggering compression and the specified `level`. If `compression` is + /// `None`, compression is disabled. + /// + /// # Errors + /// + /// Returns an `CompressionLevelError` if an invalid compression level is provided. + pub fn set_compression( + &mut self, + compression: Option<(CompressionThreshold, CompressionLevel)>, + ) -> Result<(), CompressionLevelError> { + match compression { + Some((threshold, level)) => { + let level = + CompressionLvl::new(level.0 as i32).map_err(|_| CompressionLevelError)?; + self.compression = Some((Compressor::new(level), threshold)); + } + None => { + self.compression = None; + } } + Ok(()) } /// Encrypts the data in the internal buffer and returns it as a `BytesMut`. @@ -209,6 +211,10 @@ impl PacketEncoder { } } +#[derive(Error, Debug)] +#[error("Invalid compression Level")] +pub struct CompressionLevelError; + /// Errors that can occur during packet encoding. #[derive(Error, Debug)] pub enum PacketEncodeError { @@ -268,15 +274,15 @@ mod tests { /// Helper function to build a packet with optional compression and encryption fn build_packet_with_encoder( packet: &T, - compression_info: Option, + compression_info: Option<(CompressionThreshold, CompressionLevel)>, key: Option<&[u8; 16]>, ) -> BytesMut { let mut encoder = PacketEncoder::default(); if let Some(compression) = compression_info { - encoder.set_compression(Some(compression)); + encoder.set_compression(Some(compression)).unwrap(); } else { - encoder.set_compression(None); + encoder.set_compression(None).unwrap(); } if let Some(key) = key { @@ -328,14 +334,12 @@ mod tests { // Create a CStatusResponse packet let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}"); - // Compression threshold is set to 0 to force compression - let compression_info = CompressionInfo { - threshold: 0, - level: 6, // Standard compression level - }; - // Build the packet with compression enabled - let packet_bytes = build_packet_with_encoder(&packet, Some(compression_info), None); + let packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(0), CompressionLevel(6))), + None, + ); // Decode the packet manually to verify correctness let mut buffer = &packet_bytes[..]; @@ -418,18 +422,16 @@ mod tests { // Create a CStatusResponse packet let packet = CStatusResponse::new("{\"description\": \"A Minecraft Server\"}"); - // Compression threshold is set to 0 to force compression - let compression_info = CompressionInfo { - threshold: 0, - level: 6, // Standard compression level - }; - // Encryption key and IV (IV is the same as key in this case) let key = [0x01u8; 16]; // Example key // Build the packet with both compression and encryption enabled - let mut packet_bytes = - build_packet_with_encoder(&packet, Some(compression_info), Some(&key)); + // Compression threshold is set to 0 to force compression + let mut packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(0), CompressionLevel(6))), + Some(&key), + ); // Decrypt the packet decrypt_aes128(&mut packet_bytes, &key, &key); @@ -567,14 +569,13 @@ mod tests { // Create a CStatusResponse packet with small payload let packet = CStatusResponse::new("Hi"); - // Compression threshold is set to a value higher than payload length - let compression_info = CompressionInfo { - threshold: 10, - level: 6, // Standard compression level - }; - // Build the packet with compression enabled - let packet_bytes = build_packet_with_encoder(&packet, Some(compression_info), None); + // Compression threshold is set to a value higher than payload length + let packet_bytes = build_packet_with_encoder( + &packet, + Some((CompressionThreshold(10), CompressionLevel(6))), + None, + ); // Decode the packet manually to verify that it was not compressed let mut buffer = &packet_bytes[..]; diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index 0e5f9e9c..b43cb1cb 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -33,7 +33,8 @@ use pumpkin_protocol::{ }, status::{SStatusPingRequest, SStatusRequest}, }, - ClientPacket, ConnectionState, Property, RawPacket, ServerPacket, + ClientPacket, CompressionLevel, CompressionThreshold, ConnectionState, Property, RawPacket, + ServerPacket, }; use serde::Deserialize; use sha1::Digest; @@ -224,7 +225,13 @@ impl Client { /// * `compression`: An optional `CompressionInfo` struct containing the compression threshold and compression level. pub async fn set_compression(&self, compression: Option) { self.dec.lock().await.set_compression(compression.is_some()); - self.enc.lock().await.set_compression(compression); + self.enc + .lock() + .await + .set_compression( + compression.map(|s| (CompressionThreshold(s.threshold), CompressionLevel(s.level))), + ) + .unwrap_or_else(|_| log::warn!("invalid compression level")); } /// Sends a clientbound packet to the connected client. From 29ed502aeaa892ae06497f8dbf941b28bc715b6b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 26 Dec 2024 22:10:12 +0100 Subject: [PATCH 02/11] extractor: extract entities --- assets/entities.json | 1 + extractor/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- extractor/gradlew | 5 +- extractor/gradlew.bat | 2 +- .../kotlin/de/snowii/extractor/Extractor.kt | 2 +- .../snowii/extractor/extractors/Entities.kt | 42 ++++ .../de/snowii/extractor/extractors/Tests.kt | 217 +++++++++--------- 8 files changed, 155 insertions(+), 118 deletions(-) create mode 100644 assets/entities.json create mode 100644 extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt diff --git a/assets/entities.json b/assets/entities.json new file mode 100644 index 00000000..ac4995f5 --- /dev/null +++ b/assets/entities.json @@ -0,0 +1 @@ +{"acacia_boat":{"id":0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"acacia_chest_boat":{"id":1,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"allay":{"id":2,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.35,0.6]},"area_effect_cloud":{"id":3,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[6.0,0.5]},"armadillo":{"id":4,"max_health":12.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.65]},"armor_stand":{"id":5,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,1.975]},"arrow":{"id":6,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"axolotl":{"id":7,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.75,0.42]},"bamboo_chest_raft":{"id":8,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"bamboo_raft":{"id":9,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"bat":{"id":10,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.9]},"bee":{"id":11,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.6]},"birch_boat":{"id":12,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"birch_chest_boat":{"id":13,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"blaze":{"id":14,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.6,1.8]},"block_display":{"id":15,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"bogged":{"id":16,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"breeze":{"id":17,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.77]},"breeze_wind_charge":{"id":18,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"camel":{"id":19,"max_health":32.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.7,2.375]},"cat":{"id":20,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"cave_spider":{"id":21,"max_health":12.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.5]},"cherry_boat":{"id":22,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"cherry_chest_boat":{"id":23,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"chest_minecart":{"id":24,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"chicken":{"id":25,"max_health":4.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.7]},"cod":{"id":26,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.3]},"command_block_minecart":{"id":27,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"cow":{"id":28,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.4]},"creaking":{"id":29,"max_health":1.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,2.7]},"creeper":{"id":30,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.7]},"dark_oak_boat":{"id":31,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"dark_oak_chest_boat":{"id":32,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"dolphin":{"id":33,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.6]},"donkey":{"id":34,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.5]},"dragon_fireball":{"id":35,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.0,1.0]},"drowned":{"id":36,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"egg":{"id":37,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"elder_guardian":{"id":38,"max_health":80.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.9975,1.9975]},"enderman":{"id":39,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,2.9]},"endermite":{"id":40,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"ender_dragon":{"id":41,"max_health":200.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[16.0,8.0]},"ender_pearl":{"id":42,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"end_crystal":{"id":43,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[2.0,2.0]},"evoker":{"id":44,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"evoker_fangs":{"id":45,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.8]},"experience_bottle":{"id":46,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"experience_orb":{"id":47,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"eye_of_ender":{"id":48,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"falling_block":{"id":49,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.98,0.98]},"fireball":{"id":50,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.0,1.0]},"firework_rocket":{"id":51,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"fox":{"id":52,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"frog":{"id":53,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"furnace_minecart":{"id":54,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"ghast":{"id":55,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[4.0,4.0]},"giant":{"id":56,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[3.6,12.0]},"glow_item_frame":{"id":57,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"glow_squid":{"id":58,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.8,0.8]},"goat":{"id":59,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.3]},"guardian":{"id":60,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.85,0.85]},"hoglin":{"id":61,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.4]},"hopper_minecart":{"id":62,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"horse":{"id":63,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"husk":{"id":64,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"illusioner":{"id":65,"max_health":32.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"interaction":{"id":66,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"iron_golem":{"id":67,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,2.7]},"item":{"id":68,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"item_display":{"id":69,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"item_frame":{"id":70,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"jungle_boat":{"id":71,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"jungle_chest_boat":{"id":72,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"leash_knot":{"id":73,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.375,0.5]},"lightning_bolt":{"id":74,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"llama":{"id":75,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.87]},"llama_spit":{"id":76,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"magma_cube":{"id":77,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.52,0.52]},"mangrove_boat":{"id":78,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"mangrove_chest_boat":{"id":79,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"marker":{"id":80,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"minecart":{"id":81,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"mooshroom":{"id":82,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.4]},"mule":{"id":83,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"oak_boat":{"id":84,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"oak_chest_boat":{"id":85,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"ocelot":{"id":86,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.7]},"ominous_item_spawner":{"id":87,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"painting":{"id":88,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"pale_oak_boat":{"id":89,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"pale_oak_chest_boat":{"id":90,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"panda":{"id":91,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3,1.25]},"parrot":{"id":92,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.9]},"phantom":{"id":93,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.5]},"pig":{"id":94,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,0.9]},"piglin":{"id":95,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"piglin_brute":{"id":96,"max_health":50.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"pillager":{"id":97,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"polar_bear":{"id":98,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,1.4]},"potion":{"id":99,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"pufferfish":{"id":100,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.7]},"rabbit":{"id":101,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.5]},"ravager":{"id":102,"max_health":100.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.95,2.2]},"salmon":{"id":103,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,0.4]},"sheep":{"id":104,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.3]},"shulker":{"id":105,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[1.0,1.0]},"shulker_bullet":{"id":106,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"silverfish":{"id":107,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"skeleton":{"id":108,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"skeleton_horse":{"id":109,"max_health":15.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"slime":{"id":110,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.52,0.52]},"small_fireball":{"id":111,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"sniffer":{"id":112,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.9,1.75]},"snowball":{"id":113,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.25,0.25]},"snow_golem":{"id":114,"max_health":4.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.7,1.9]},"spawner_minecart":{"id":115,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"spectral_arrow":{"id":116,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"spider":{"id":117,"max_health":16.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.4,0.9]},"spruce_boat":{"id":118,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"spruce_chest_boat":{"id":119,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.375,0.5625]},"squid":{"id":120,"max_health":10.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.8,0.8]},"stray":{"id":121,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.99]},"strider":{"id":122,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,1.7]},"tadpole":{"id":123,"max_health":6.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.4,0.3]},"text_display":{"id":124,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.0,0.0]},"tnt":{"id":125,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.98,0.98]},"tnt_minecart":{"id":126,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.98,0.7]},"trader_llama":{"id":127,"max_health":53.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.9,1.87]},"trident":{"id":128,"attackable":false,"summonable":true,"fire_immune":false,"dimension":[0.5,0.5]},"tropical_fish":{"id":129,"max_health":3.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.5,0.4]},"turtle":{"id":130,"max_health":30.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.2,0.4]},"vex":{"id":131,"max_health":14.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.4,0.8]},"villager":{"id":132,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"vindicator":{"id":133,"max_health":24.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"wandering_trader":{"id":134,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"warden":{"id":135,"max_health":500.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,2.9]},"wind_charge":{"id":136,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"witch":{"id":137,"max_health":26.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"wither":{"id":138,"max_health":300.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.9,3.5]},"wither_skeleton":{"id":139,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.7,2.4]},"wither_skull":{"id":140,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.3125,0.3125]},"wolf":{"id":141,"max_health":8.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,0.85]},"zoglin":{"id":142,"max_health":40.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[1.3964844,1.4]},"zombie":{"id":143,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"zombie_horse":{"id":144,"max_health":15.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[1.3964844,1.6]},"zombie_villager":{"id":145,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":false,"dimension":[0.6,1.95]},"zombified_piglin":{"id":146,"max_health":20.0,"attackable":true,"summonable":true,"fire_immune":true,"dimension":[0.6,1.95]},"fishing_bobber":{"id":148,"attackable":true,"summonable":false,"fire_immune":false,"dimension":[0.25,0.25]}} \ No newline at end of file diff --git a/extractor/gradle.properties b/extractor/gradle.properties index 5b4def5d..880c9772 100644 --- a/extractor/gradle.properties +++ b/extractor/gradle.properties @@ -11,4 +11,4 @@ kotlin_loader_version=1.13.0+kotlin.2.1.0 mod_version=1.0-SNAPSHOT maven_group=de.snowii archives_base_name=extractor -fabric_version=0.112.1+1.21.4 +fabric_version=0.113.0+1.21.4 diff --git a/extractor/gradle/wrapper/gradle-wrapper.properties b/extractor/gradle/wrapper/gradle-wrapper.properties index e2847c82..cea7a793 100644 --- a/extractor/gradle/wrapper/gradle-wrapper.properties +++ b/extractor/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/extractor/gradlew b/extractor/gradlew index f5feea6d..057afac5 100755 --- a/extractor/gradlew +++ b/extractor/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -203,7 +202,7 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, diff --git a/extractor/gradlew.bat b/extractor/gradlew.bat index 9b42019c..6a90cee9 100644 --- a/extractor/gradlew.bat +++ b/extractor/gradlew.bat @@ -36,7 +36,7 @@ set APP_HOME=%DIRNAME% for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt b/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt index d16e89b6..2f47fe63 100644 --- a/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt +++ b/extractor/src/main/kotlin/de/snowii/extractor/Extractor.kt @@ -1,6 +1,5 @@ package de.snowii.extractor -import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonElement import de.snowii.extractor.extractors.* @@ -31,6 +30,7 @@ class Extractor : ModInitializer { Packets(), Screens(), Tags(), + Entities(), Items(), Blocks(), Tests(), diff --git a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt new file mode 100644 index 00000000..47504711 --- /dev/null +++ b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Entities.kt @@ -0,0 +1,42 @@ +package de.snowii.extractor.extractors + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import de.snowii.extractor.Extractor +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.SpawnReason +import net.minecraft.registry.Registries +import net.minecraft.server.MinecraftServer + +class Entities : Extractor.Extractor { + override fun fileName(): String { + return "entities.json" + } + + override fun extract(server: MinecraftServer): JsonElement { + val entitiesJson = JsonObject() + for (entityType in Registries.ENTITY_TYPE) { + val entity = entityType.create(server.overworld!!, SpawnReason.NATURAL) ?: continue + val entityJson = JsonObject() + entityJson.addProperty("id", Registries.ENTITY_TYPE.getRawId(entityType)) + if (entity is LivingEntity) { + entityJson.addProperty("max_health", entity.maxHealth) + + } + entityJson.addProperty("attackable", entity.isAttackable) + entityJson.addProperty("summonable", entityType.isSummonable) + entityJson.addProperty("fire_immune", entityType.isFireImmune) + val dimension = JsonArray() + dimension.add(entityType.dimensions.width) + dimension.add(entityType.dimensions.height) + entityJson.add("dimension", dimension) + + entitiesJson.add( + Registries.ENTITY_TYPE.getId(entityType).path, entityJson + ) + } + + return entitiesJson + } +} diff --git a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt index ac81ec76..e75a1797 100644 --- a/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt +++ b/extractor/src/main/kotlin/de/snowii/extractor/extractors/Tests.kt @@ -1,8 +1,5 @@ - package de.snowii.extractor.extractors -import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonElement import de.snowii.extractor.Extractor @@ -11,129 +8,127 @@ import net.minecraft.block.BlockState import net.minecraft.block.Blocks import net.minecraft.registry.BuiltinRegistries import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.RegistryWrapper -import net.minecraft.registry.RegistryWrapper.WrapperLookup -import net.minecraft.registry.entry.RegistryEntry.Reference import net.minecraft.server.MinecraftServer -import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters import net.minecraft.util.math.ChunkPos -import net.minecraft.world.gen.chunk.AquiferSampler -import net.minecraft.world.gen.chunk.Blender -import net.minecraft.world.gen.chunk.ChunkGeneratorSettings -import net.minecraft.world.gen.chunk.ChunkNoiseSampler -import net.minecraft.world.gen.chunk.GenerationShapeConfig -import net.minecraft.world.gen.densityfunction.DensityFunction.NoisePos; -import net.minecraft.world.gen.densityfunction.DensityFunction.EachApplier; +import net.minecraft.world.gen.chunk.* +import net.minecraft.world.gen.densityfunction.DensityFunction.EachApplier +import net.minecraft.world.gen.densityfunction.DensityFunction.NoisePos import net.minecraft.world.gen.densityfunction.DensityFunctionTypes import net.minecraft.world.gen.noise.NoiseConfig - -import java.lang.reflect.Method -import java.util.Arrays -import kotlin.reflect.full.createType -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.jvm.javaMethod import kotlin.reflect.KFunction +import kotlin.reflect.full.declaredFunctions class Tests : Extractor.Extractor { override fun fileName(): String = "chunk.json" - private fun createFluidLevelSampler(settings: ChunkGeneratorSettings): AquiferSampler.FluidLevelSampler { - val fluidLevel = AquiferSampler.FluidLevel(-54, Blocks.LAVA.getDefaultState()); - val i = settings.seaLevel(); - val fluidLevel2 = AquiferSampler.FluidLevel(i, settings.defaultFluid()); - return AquiferSampler.FluidLevelSampler {_, y, _ -> if (y < Math.min(-54, i)) fluidLevel else fluidLevel2}; - } - - private fun get_index(config: GenerationShapeConfig, x: Int, y: Int, z: Int): Int { - if (x < 0 || y < 0 || z < 0) { - System.err.println("Bad local pos"); - System.exit(1); - } - return config.height() * 16 * x + 16 * y + z - } - - // This is basically just what NoiseChunkGenerator is doing - private fun populate_noise(start_x: Int, start_z: Int, sampler: ChunkNoiseSampler, config: GenerationShapeConfig, settings: ChunkGeneratorSettings): IntArray? { - val result = IntArray(16 * 16 * config.height()) - - for (method: KFunction<*> in sampler::class.declaredFunctions) { - if (method.name.equals("sampleBlockState")) { - sampler.sampleStartDensity() - val k = config.horizontalCellBlockCount() - val l = config.verticalCellBlockCount() - - val m = 16 / k - val n = 16 / k + private fun createFluidLevelSampler(settings: ChunkGeneratorSettings): AquiferSampler.FluidLevelSampler { + val fluidLevel = AquiferSampler.FluidLevel(-54, Blocks.LAVA.defaultState) + val i = settings.seaLevel() + val fluidLevel2 = AquiferSampler.FluidLevel(i, settings.defaultFluid()) + return AquiferSampler.FluidLevelSampler { _, y, _ -> if (y < Math.min(-54, i)) fluidLevel else fluidLevel2 } + } - val cellHeight = config.height() / l - val minimumCellY = config.minimumY() / l + private fun get_index(config: GenerationShapeConfig, x: Int, y: Int, z: Int): Int { + if (x < 0 || y < 0 || z < 0) { + System.err.println("Bad local pos") + System.exit(1) + } + return config.height() * 16 * x + 16 * y + z + } - for (o in 0.. in sampler::class.declaredFunctions) { + if (method.name.equals("sampleBlockState")) { + sampler.sampleStartDensity() + val k = config.horizontalCellBlockCount() + val l = config.verticalCellBlockCount() + + val m = 16 / k + val n = 16 / k + + val cellHeight = config.height() / l + val minimumCellY = config.minimumY() / l + + for (o in 0.. + val seed = 0L + val chunk_pos = ChunkPos(7, 4) + + val lookup = BuiltinRegistries.createWrapperLookup() + val wrapper = lookup.getOrThrow(RegistryKeys.CHUNK_GENERATOR_SETTINGS) + val noise_params = lookup.getOrThrow(RegistryKeys.NOISE_PARAMETERS) + + val ref = wrapper.getOrThrow(ChunkGeneratorSettings.OVERWORLD) + val settings = ref.value() + val config = NoiseConfig.create(settings, noise_params, seed) + + // Overworld shape config + val shape = GenerationShapeConfig(-64, 384, 1, 2) + val test_sampler = + ChunkNoiseSampler( + 16 / shape.horizontalCellBlockCount(), config, chunk_pos.startX, chunk_pos.startZ, + shape, object : DensityFunctionTypes.Beardifying { + override fun maxValue(): Double = 0.0 + override fun minValue(): Double = 0.0 + override fun sample(pos: NoisePos): Double = 0.0 + override fun fill(densities: DoubleArray, applier: EachApplier) { + densities.fill(0.0) + } + }, settings, createFluidLevelSampler(settings), Blender.getNoBlending() + ) + + val data = populate_noise(chunk_pos.startX, chunk_pos.startZ, test_sampler, shape, settings) + data?.forEach { state -> topLevelJson.add(state) } From 25f48342366ef2782675283e85eb12781c192223 Mon Sep 17 00:00:00 2001 From: urisinger <60300761+urisinger@users.noreply.github.com> Date: Fri, 27 Dec 2024 00:15:13 +0200 Subject: [PATCH 03/11] Remove unnecessary lifetimes in CommandDispatcher and Command (#419) * make command dispatcher use owned strings and not take any lifetimes * remove get_argument_consumer * fix clippy * fix elided lifetimes --- .../src/client/play/c_command_suggestions.rs | 6 +- pumpkin/src/command/args/arg_block.rs | 14 +-- pumpkin/src/command/args/arg_bool.rs | 6 +- pumpkin/src/command/args/arg_bossbar_color.rs | 16 +-- pumpkin/src/command/args/arg_bossbar_style.rs | 16 +-- pumpkin/src/command/args/arg_bounded_num.rs | 12 +- pumpkin/src/command/args/arg_command.rs | 20 ++-- pumpkin/src/command/args/arg_entities.rs | 14 +-- pumpkin/src/command/args/arg_entity.rs | 14 +-- pumpkin/src/command/args/arg_gamemode.rs | 14 +-- pumpkin/src/command/args/arg_item.rs | 14 +-- pumpkin/src/command/args/arg_message.rs | 14 +-- pumpkin/src/command/args/arg_players.rs | 14 +-- pumpkin/src/command/args/arg_position_2d.rs | 14 +-- pumpkin/src/command/args/arg_position_3d.rs | 14 +-- .../src/command/args/arg_position_block.rs | 14 +-- .../src/command/args/arg_resource_location.rs | 14 +-- pumpkin/src/command/args/arg_rotation.rs | 14 +-- pumpkin/src/command/args/arg_simple.rs | 6 +- pumpkin/src/command/args/mod.rs | 19 ++- pumpkin/src/command/client_cmd_suggestions.rs | 9 +- pumpkin/src/command/commands/cmd_bossbar.rs | 89 +++++++------- pumpkin/src/command/commands/cmd_clear.rs | 6 +- pumpkin/src/command/commands/cmd_fill.rs | 28 ++--- pumpkin/src/command/commands/cmd_gamemode.rs | 10 +- pumpkin/src/command/commands/cmd_give.rs | 24 ++-- pumpkin/src/command/commands/cmd_help.rs | 23 ++-- pumpkin/src/command/commands/cmd_kick.rs | 4 +- pumpkin/src/command/commands/cmd_kill.rs | 6 +- pumpkin/src/command/commands/cmd_list.rs | 4 +- pumpkin/src/command/commands/cmd_pumpkin.rs | 4 +- pumpkin/src/command/commands/cmd_say.rs | 6 +- pumpkin/src/command/commands/cmd_seed.rs | 6 +- pumpkin/src/command/commands/cmd_setblock.rs | 22 ++-- pumpkin/src/command/commands/cmd_stop.rs | 4 +- pumpkin/src/command/commands/cmd_teleport.rs | 30 ++--- pumpkin/src/command/commands/cmd_time.rs | 28 ++--- pumpkin/src/command/commands/cmd_transfer.rs | 30 ++--- .../src/command/commands/cmd_worldborder.rs | 111 +++++++++--------- pumpkin/src/command/dispatcher.rs | 46 ++++---- pumpkin/src/command/mod.rs | 2 +- pumpkin/src/command/tree.rs | 42 +++---- pumpkin/src/command/tree_builder.rs | 90 ++++++++------ pumpkin/src/command/tree_format.rs | 10 +- pumpkin/src/server/mod.rs | 2 +- 45 files changed, 429 insertions(+), 476 deletions(-) diff --git a/pumpkin-protocol/src/client/play/c_command_suggestions.rs b/pumpkin-protocol/src/client/play/c_command_suggestions.rs index 25ae6683..c137b9ad 100644 --- a/pumpkin-protocol/src/client/play/c_command_suggestions.rs +++ b/pumpkin-protocol/src/client/play/c_command_suggestions.rs @@ -35,7 +35,7 @@ impl ClientPacket for CCommandSuggestions<'_> { bytebuf.put_var_int(&self.length); bytebuf.put_list(&self.matches, |bytebuf, suggestion| { - bytebuf.put_string(suggestion.suggestion); + bytebuf.put_string(&suggestion.suggestion); bytebuf.put_bool(suggestion.tooltip.is_some()); if let Some(tooltip) = &suggestion.tooltip { bytebuf.put_slice(&tooltip.encode()); @@ -46,12 +46,12 @@ impl ClientPacket for CCommandSuggestions<'_> { #[derive(PartialEq, Eq, Hash, Debug)] pub struct CommandSuggestion<'a> { - pub suggestion: &'a str, + pub suggestion: String, pub tooltip: Option>, } impl<'a> CommandSuggestion<'a> { - pub fn new(suggestion: &'a str, tooltip: Option>) -> Self { + pub fn new(suggestion: String, tooltip: Option>) -> Self { Self { suggestion, tooltip, diff --git a/pumpkin/src/command/args/arg_block.rs b/pumpkin/src/command/args/arg_block.rs index fe11228f..77962552 100644 --- a/pumpkin/src/command/args/arg_block.rs +++ b/pumpkin/src/command/args/arg_block.rs @@ -31,7 +31,7 @@ impl GetClientSideArgParser for BlockArgumentConsumer { #[async_trait] impl ArgumentConsumer for BlockArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -41,7 +41,7 @@ impl ArgumentConsumer for BlockArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -51,19 +51,15 @@ impl ArgumentConsumer for BlockArgumentConsumer { } impl DefaultNameArgConsumer for BlockArgumentConsumer { - fn default_name(&self) -> &'static str { - "block" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "block".to_string() } } impl<'a> FindArg<'a> for BlockArgumentConsumer { type Data = &'a Block; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Block(name)) => block_registry::get_block(name).map_or_else( || { diff --git a/pumpkin/src/command/args/arg_bool.rs b/pumpkin/src/command/args/arg_bool.rs index fc47f590..39b69c89 100644 --- a/pumpkin/src/command/args/arg_bool.rs +++ b/pumpkin/src/command/args/arg_bool.rs @@ -23,7 +23,7 @@ impl GetClientSideArgParser for BoolArgConsumer { #[async_trait] impl ArgumentConsumer for BoolArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -38,7 +38,7 @@ impl ArgumentConsumer for BoolArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -50,7 +50,7 @@ impl ArgumentConsumer for BoolArgConsumer { impl<'a> FindArg<'a> for BoolArgConsumer { type Data = bool; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Bool(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bossbar_color.rs b/pumpkin/src/command/args/arg_bossbar_color.rs index 94df5a4c..664264ea 100644 --- a/pumpkin/src/command/args/arg_bossbar_color.rs +++ b/pumpkin/src/command/args/arg_bossbar_color.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for BossbarColorArgumentConsumer { #[async_trait] impl ArgumentConsumer for BossbarColorArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -49,7 +49,7 @@ impl ArgumentConsumer for BossbarColorArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -57,26 +57,22 @@ impl ArgumentConsumer for BossbarColorArgumentConsumer { let colors = ["blue", "green", "pink", "purple", "red", "white", "yellow"]; let suggestions: Vec = colors .iter() - .map(|color| CommandSuggestion::new(color, None)) + .map(|color| CommandSuggestion::new((*color).to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for BossbarColorArgumentConsumer { - fn default_name(&self) -> &'static str { - "color" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "color".to_string() } } impl<'a> FindArg<'a> for BossbarColorArgumentConsumer { type Data = &'a BossbarColor; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::BossbarColor(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bossbar_style.rs b/pumpkin/src/command/args/arg_bossbar_style.rs index 6111688b..6d3b2d44 100644 --- a/pumpkin/src/command/args/arg_bossbar_style.rs +++ b/pumpkin/src/command/args/arg_bossbar_style.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for BossbarStyleArgumentConsumer { #[async_trait] impl ArgumentConsumer for BossbarStyleArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -47,7 +47,7 @@ impl ArgumentConsumer for BossbarStyleArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -61,26 +61,22 @@ impl ArgumentConsumer for BossbarStyleArgumentConsumer { ]; let suggestions: Vec = styles .iter() - .map(|style| CommandSuggestion::new(style, None)) + .map(|style| CommandSuggestion::new((*style).to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for BossbarStyleArgumentConsumer { - fn default_name(&self) -> &'static str { - "style" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "style".to_string() } } impl<'a> FindArg<'a> for BossbarStyleArgumentConsumer { type Data = &'a BossbarDivisions; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::BossbarStyle(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_bounded_num.rs b/pumpkin/src/command/args/arg_bounded_num.rs index 41410367..0cd20af2 100644 --- a/pumpkin/src/command/args/arg_bounded_num.rs +++ b/pumpkin/src/command/args/arg_bounded_num.rs @@ -25,7 +25,7 @@ where Self: GetClientSideArgParser, { async fn consume<'a>( - &self, + &'a self, _src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -48,7 +48,7 @@ where } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -238,12 +238,8 @@ impl DefaultNameArgConsumer for BoundedNumArgumentConsumer where Self: ArgumentConsumer, { - fn default_name(&self) -> &'static str { + fn default_name(&self) -> String { // setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion - self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.") - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.").to_string() } } diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index 9055fde1..a8ea8234 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -30,7 +30,7 @@ impl GetClientSideArgParser for CommandTreeArgumentConsumer { #[async_trait] impl ArgumentConsumer for CommandTreeArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -40,11 +40,11 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { let dispatcher = server.command_dispatcher.read().await; dispatcher .get_tree(s) - .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))) + .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree.clone()))) } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, server: &'a Server, input: &'a str, @@ -58,26 +58,22 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { .commands .keys() .filter(|suggestion| suggestion.starts_with(input)) - .map(|suggestion| CommandSuggestion::new(suggestion, None)) + .map(|suggestion| CommandSuggestion::new(suggestion.to_string(), None)) .collect(); Ok(Some(suggestions)) } } impl DefaultNameArgConsumer for CommandTreeArgumentConsumer { - fn default_name(&self) -> &'static str { - "cmd" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "cmd".to_string() } } impl<'a> FindArg<'a> for CommandTreeArgumentConsumer { - type Data = &'a CommandTree<'a>; + type Data = &'a CommandTree; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::CommandTree(tree)) => Ok(tree), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_entities.rs b/pumpkin/src/command/args/arg_entities.rs index 102a177b..0c202ac7 100644 --- a/pumpkin/src/command/args/arg_entities.rs +++ b/pumpkin/src/command/args/arg_entities.rs @@ -34,7 +34,7 @@ impl GetClientSideArgParser for EntitiesArgumentConsumer { #[async_trait] impl ArgumentConsumer for EntitiesArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -47,7 +47,7 @@ impl ArgumentConsumer for EntitiesArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -57,19 +57,15 @@ impl ArgumentConsumer for EntitiesArgumentConsumer { } impl DefaultNameArgConsumer for EntitiesArgumentConsumer { - fn default_name(&self) -> &'static str { - "targets" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "targets".to_string() } } impl<'a> FindArg<'a> for EntitiesArgumentConsumer { type Data = &'a [Arc]; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Entities(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_entity.rs b/pumpkin/src/command/args/arg_entity.rs index 37e4dac0..bbbed190 100644 --- a/pumpkin/src/command/args/arg_entity.rs +++ b/pumpkin/src/command/args/arg_entity.rs @@ -37,7 +37,7 @@ impl GetClientSideArgParser for EntityArgumentConsumer { #[async_trait] impl ArgumentConsumer for EntityArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -69,7 +69,7 @@ impl ArgumentConsumer for EntityArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -79,19 +79,15 @@ impl ArgumentConsumer for EntityArgumentConsumer { } impl DefaultNameArgConsumer for EntityArgumentConsumer { - fn default_name(&self) -> &'static str { - "target" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "target".to_string() } } impl<'a> FindArg<'a> for EntityArgumentConsumer { type Data = Arc; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Entity(data)) => Ok(data.clone()), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_gamemode.rs b/pumpkin/src/command/args/arg_gamemode.rs index d520b25c..fee1264d 100644 --- a/pumpkin/src/command/args/arg_gamemode.rs +++ b/pumpkin/src/command/args/arg_gamemode.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for GamemodeArgumentConsumer { #[async_trait] impl ArgumentConsumer for GamemodeArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -50,7 +50,7 @@ impl ArgumentConsumer for GamemodeArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -60,19 +60,15 @@ impl ArgumentConsumer for GamemodeArgumentConsumer { } impl DefaultNameArgConsumer for GamemodeArgumentConsumer { - fn default_name(&self) -> &'static str { - "gamemode" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "gamemode".to_string() } } impl<'a> FindArg<'a> for GamemodeArgumentConsumer { type Data = GameMode; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::GameMode(data)) => Ok(*data), _ => 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 481feb7d..bf4254b9 100644 --- a/pumpkin/src/command/args/arg_item.rs +++ b/pumpkin/src/command/args/arg_item.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for ItemArgumentConsumer { #[async_trait] impl ArgumentConsumer for ItemArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -39,7 +39,7 @@ impl ArgumentConsumer for ItemArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -49,19 +49,15 @@ impl ArgumentConsumer for ItemArgumentConsumer { } impl DefaultNameArgConsumer for ItemArgumentConsumer { - fn default_name(&self) -> &'static str { - "item" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "item".to_string() } } impl<'a> FindArg<'a> for ItemArgumentConsumer { type Data = (&'a str, &'a Item); - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Item(name)) => item_registry::get_item(name).map_or_else( || { diff --git a/pumpkin/src/command/args/arg_message.rs b/pumpkin/src/command/args/arg_message.rs index 00af2d87..8d4081d6 100644 --- a/pumpkin/src/command/args/arg_message.rs +++ b/pumpkin/src/command/args/arg_message.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for MsgArgConsumer { #[async_trait] impl ArgumentConsumer for MsgArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -45,7 +45,7 @@ impl ArgumentConsumer for MsgArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -55,19 +55,15 @@ impl ArgumentConsumer for MsgArgConsumer { } impl DefaultNameArgConsumer for MsgArgConsumer { - fn default_name(&self) -> &'static str { - "msg" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "msg".to_string() } } impl<'a> FindArg<'a> for MsgArgConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Msg(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_players.rs b/pumpkin/src/command/args/arg_players.rs index 0e8876a7..c7fdc70a 100644 --- a/pumpkin/src/command/args/arg_players.rs +++ b/pumpkin/src/command/args/arg_players.rs @@ -33,7 +33,7 @@ impl GetClientSideArgParser for PlayersArgumentConsumer { #[async_trait] impl ArgumentConsumer for PlayersArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, @@ -63,7 +63,7 @@ impl ArgumentConsumer for PlayersArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -73,19 +73,15 @@ impl ArgumentConsumer for PlayersArgumentConsumer { } impl DefaultNameArgConsumer for PlayersArgumentConsumer { - fn default_name(&self) -> &'static str { - "player" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "player".to_string() } } impl<'a> FindArg<'a> for PlayersArgumentConsumer { type Data = &'a [Arc]; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Players(data)) => Ok(data), _ => 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 107ea432..9a9c5b56 100644 --- a/pumpkin/src/command/args/arg_position_2d.rs +++ b/pumpkin/src/command/args/arg_position_2d.rs @@ -32,7 +32,7 @@ impl GetClientSideArgParser for Position2DArgumentConsumer { #[async_trait] impl ArgumentConsumer for Position2DArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -45,7 +45,7 @@ impl ArgumentConsumer for Position2DArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -73,19 +73,15 @@ impl MaybeRelativePosition2D { } impl DefaultNameArgConsumer for Position2DArgumentConsumer { - fn default_name(&self) -> &'static str { - "pos2d" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "pos2d".to_string() } } impl<'a> FindArg<'a> for Position2DArgumentConsumer { type Data = Vector2; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Pos2D(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_position_3d.rs b/pumpkin/src/command/args/arg_position_3d.rs index 1e58ad81..8df9761c 100644 --- a/pumpkin/src/command/args/arg_position_3d.rs +++ b/pumpkin/src/command/args/arg_position_3d.rs @@ -29,7 +29,7 @@ impl GetClientSideArgParser for Position3DArgumentConsumer { #[async_trait] impl ArgumentConsumer for Position3DArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -42,7 +42,7 @@ impl ArgumentConsumer for Position3DArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -76,19 +76,15 @@ impl MaybeRelativePosition3D { } impl DefaultNameArgConsumer for Position3DArgumentConsumer { - fn default_name(&self) -> &'static str { - "pos" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "pos".to_string() } } impl<'a> FindArg<'a> for Position3DArgumentConsumer { type Data = Vector3; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Pos3D(data)) => Ok(*data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_position_block.rs b/pumpkin/src/command/args/arg_position_block.rs index 861eec12..4f5b3a40 100644 --- a/pumpkin/src/command/args/arg_position_block.rs +++ b/pumpkin/src/command/args/arg_position_block.rs @@ -30,7 +30,7 @@ impl GetClientSideArgParser for BlockPosArgumentConsumer { #[async_trait] impl ArgumentConsumer for BlockPosArgumentConsumer { async fn consume<'a>( - &self, + &'a self, src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -43,7 +43,7 @@ impl ArgumentConsumer for BlockPosArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -77,19 +77,15 @@ impl MaybeRelativeBlockPos { } impl DefaultNameArgConsumer for BlockPosArgumentConsumer { - fn default_name(&self) -> &'static str { - "block_pos" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "block_pos".to_string() } } impl<'a> FindArg<'a> for BlockPosArgumentConsumer { type Data = WorldPosition; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &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/arg_resource_location.rs b/pumpkin/src/command/args/arg_resource_location.rs index 02781593..c156a57f 100644 --- a/pumpkin/src/command/args/arg_resource_location.rs +++ b/pumpkin/src/command/args/arg_resource_location.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for ResourceLocationArgumentConsumer { #[async_trait] impl ArgumentConsumer for ResourceLocationArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -36,7 +36,7 @@ impl ArgumentConsumer for ResourceLocationArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -60,19 +60,15 @@ impl ArgumentConsumer for ResourceLocationArgumentConsumer { } impl DefaultNameArgConsumer for ResourceLocationArgumentConsumer { - fn default_name(&self) -> &'static str { - "id" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - self + fn default_name(&self) -> String { + "id".to_string() } } impl<'a> FindArg<'a> for ResourceLocationArgumentConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::ResourceLocation(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_rotation.rs b/pumpkin/src/command/args/arg_rotation.rs index 6e53f2f9..91b3363b 100644 --- a/pumpkin/src/command/args/arg_rotation.rs +++ b/pumpkin/src/command/args/arg_rotation.rs @@ -27,7 +27,7 @@ impl GetClientSideArgParser for RotationArgumentConsumer { #[async_trait] impl ArgumentConsumer for RotationArgumentConsumer { async fn consume<'a>( - &self, + &'a self, _src: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -51,7 +51,7 @@ impl ArgumentConsumer for RotationArgumentConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -61,19 +61,15 @@ impl ArgumentConsumer for RotationArgumentConsumer { } impl DefaultNameArgConsumer for RotationArgumentConsumer { - fn default_name(&self) -> &'static str { - "rotation" - } - - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer { - &Self + fn default_name(&self) -> String { + "rotation".to_string() } } impl<'a> FindArg<'a> for RotationArgumentConsumer { type Data = (f32, f32); - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Rotation(yaw, pitch)) => Ok((*yaw, *pitch)), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/arg_simple.rs b/pumpkin/src/command/args/arg_simple.rs index 1412d63e..67207fb8 100644 --- a/pumpkin/src/command/args/arg_simple.rs +++ b/pumpkin/src/command/args/arg_simple.rs @@ -30,7 +30,7 @@ impl GetClientSideArgParser for SimpleArgConsumer { #[async_trait] impl ArgumentConsumer for SimpleArgConsumer { async fn consume<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, args: &mut RawArgs<'a>, @@ -39,7 +39,7 @@ impl ArgumentConsumer for SimpleArgConsumer { } async fn suggest<'a>( - &self, + &'a self, _sender: &CommandSender<'a>, _server: &'a Server, _input: &'a str, @@ -51,7 +51,7 @@ impl ArgumentConsumer for SimpleArgConsumer { impl<'a> FindArg<'a> for SimpleArgConsumer { type Data = &'a str; - fn find_arg(args: &'a super::ConsumedArgs, name: &'a str) -> Result { + fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result { match args.get(name) { Some(Arg::Simple(data)) => Ok(data), _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index 8342386b..f1fa8f85 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -42,21 +42,21 @@ mod coordinate; #[async_trait] pub(crate) trait ArgumentConsumer: Sync + GetClientSideArgParser { async fn consume<'a>( - &self, + &'a self, sender: &CommandSender<'a>, server: &'a Server, args: &mut RawArgs<'a>, - ) -> Option>; + ) -> Option; /// Used for tab completion (but only if argument suggestion type is "minecraft:ask_server"!). /// /// NOTE: This is called after this consumer's [`ArgumentConsumer::consume`] method returned None, so if args is used here, make sure [`ArgumentConsumer::consume`] never returns None after mutating args. async fn suggest<'a>( - &self, + &'a self, sender: &CommandSender<'a>, server: &'a Server, input: &'a str, - ) -> Result>>, CommandError>; + ) -> Result>, CommandError>; } pub(crate) trait GetClientSideArgParser { @@ -67,10 +67,7 @@ pub(crate) trait GetClientSideArgParser { } pub(crate) trait DefaultNameArgConsumer: ArgumentConsumer { - fn default_name(&self) -> &'static str; - - /// needed because trait upcasting is not stable - fn get_argument_consumer(&self) -> &dyn ArgumentConsumer; + fn default_name(&self) -> String; } #[derive(Clone)] @@ -83,7 +80,7 @@ pub(crate) enum Arg<'a> { Pos2D(Vector2), Rotation(f32, f32), GameMode(GameMode), - CommandTree(CommandTree<'a>), + CommandTree(CommandTree), Item(&'a str), ResourceLocation(&'a str), Block(&'a str), @@ -112,7 +109,7 @@ impl GetCloned for HashMap { pub(crate) trait FindArg<'a> { type Data; - fn find_arg(args: &'a ConsumedArgs, name: &'a str) -> Result; + fn find_arg(args: &'a ConsumedArgs, name: &str) -> Result; } pub(crate) trait FindArgDefaultName<'a, T> { @@ -121,7 +118,7 @@ pub(crate) trait FindArgDefaultName<'a, T> { impl<'a, T, C: FindArg<'a, Data = T> + DefaultNameArgConsumer> FindArgDefaultName<'a, T> for C { fn find_arg_default_name(&self, args: &'a ConsumedArgs) -> Result { - C::find_arg(args, self.default_name()) + C::find_arg(args, &self.default_name()) } } diff --git a/pumpkin/src/command/client_cmd_suggestions.rs b/pumpkin/src/command/client_cmd_suggestions.rs index 7c8c7b2f..707dfcf5 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -10,10 +10,7 @@ use super::{ tree::{Node, NodeType}, }; -pub async fn send_c_commands_packet<'a>( - player: &Arc, - dispatcher: &RwLock>, -) { +pub async fn send_c_commands_packet(player: &Arc, dispatcher: &RwLock) { let cmd_src = super::CommandSender::Player(player.clone()); let mut first_level = Vec::new(); @@ -74,7 +71,7 @@ impl<'a> ProtoNodeBuilder<'a> { fn nodes_to_proto_node_builders<'a>( cmd_src: &super::CommandSender, - nodes: &[Node<'a>], + nodes: &'a [Node], children: &[usize], ) -> (bool, Vec>) { let mut child_nodes = Vec::new(); @@ -82,7 +79,7 @@ fn nodes_to_proto_node_builders<'a>( for i in children { let node = &nodes[*i]; - match node.node_type { + match &node.node_type { NodeType::Argument { name, consumer } => { let (node_is_executable, node_children) = nodes_to_proto_node_builders(cmd_src, nodes, &node.children); diff --git a/pumpkin/src/command/commands/cmd_bossbar.rs b/pumpkin/src/command/commands/cmd_bossbar.rs index 9caaea21..25e449a7 100644 --- a/pumpkin/src/command/commands/cmd_bossbar.rs +++ b/pumpkin/src/command/commands/cmd_bossbar.rs @@ -28,10 +28,12 @@ const ARG_NAME: &str = "name"; const ARG_VISIBLE: &str = "visible"; -const AUTOCOMPLETE_CONSUMER: ResourceLocationArgumentConsumer = - ResourceLocationArgumentConsumer::new(true); -const NON_AUTOCOMPLETE_CONSUMER: ResourceLocationArgumentConsumer = - ResourceLocationArgumentConsumer::new(false); +const fn autocomplete_consumer() -> ResourceLocationArgumentConsumer { + ResourceLocationArgumentConsumer::new(true) +} +const fn non_autocomplete_consumer() -> ResourceLocationArgumentConsumer { + ResourceLocationArgumentConsumer::new(false) +} enum CommandValueGet { Max, @@ -61,7 +63,7 @@ impl CommandExecutor for BossbarAddExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = NON_AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = non_autocomplete_consumer().find_arg_default_name(args)?; let Some(Arg::Simple(name)) = args.get(ARG_NAME) else { return Err(InvalidConsumption(Some(ARG_NAME.into()))); }; @@ -96,7 +98,7 @@ impl CommandExecutor for BossbarGetExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else { send_error_message( @@ -197,7 +199,7 @@ impl CommandExecutor for BossbarRemoveExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; if !server.bossbars.lock().await.has_bossbar(namespace) { send_error_message( @@ -237,7 +239,7 @@ impl CommandExecutor for BossbarSetExecuter { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let namespace = AUTOCOMPLETE_CONSUMER.find_arg_default_name(args)?; + let namespace = autocomplete_consumer().find_arg_default_name(args)?; let Some(bossbar) = server.bossbars.lock().await.get_bossbar(namespace) else { handle_bossbar_error( @@ -275,10 +277,10 @@ impl CommandExecutor for BossbarSetExecuter { Ok(()) } CommandValueSet::Max => { - let Ok(max_value) = MAX_VALUE_CONSUMER.find_arg_default_name(args)? else { + let Ok(max_value) = max_value_consumer().find_arg_default_name(args)? else { send_error_message( sender, - format!("{} is out of bounds.", MAX_VALUE_CONSUMER.default_name()), + format!("{} is out of bounds.", max_value_consumer().default_name()), ) .await; return Ok(()); @@ -423,10 +425,10 @@ impl CommandExecutor for BossbarSetExecuter { Ok(()) } CommandValueSet::Value => { - let Ok(value) = VALUE_CONSUMER.find_arg_default_name(args)? else { + let Ok(value) = value_consumer().find_arg_default_name(args)? else { send_error_message( sender, - format!("{} is out of bounds.", VALUE_CONSUMER.default_name()), + format!("{} is out of bounds.", value_consumer().default_name()), ) .await; return Ok(()); @@ -488,85 +490,86 @@ impl CommandExecutor for BossbarSetExecuter { } } -static MAX_VALUE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("max"); +fn max_value_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("max") +} -static VALUE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("value"); +fn value_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("value") +} -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .with_child( literal("add").with_child( - argument_default_name(&NON_AUTOCOMPLETE_CONSUMER).with_child( - argument(ARG_NAME, &SimpleArgConsumer).execute(&BossbarAddExecuter), - ), + argument_default_name(non_autocomplete_consumer()) + .with_child(argument(ARG_NAME, SimpleArgConsumer).execute(BossbarAddExecuter)), ), ) .with_child( literal("get").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER) - .with_child(literal("max").execute(&BossbarGetExecuter(CommandValueGet::Max))) + argument_default_name(autocomplete_consumer()) + .with_child(literal("max").execute(BossbarGetExecuter(CommandValueGet::Max))) .with_child( - literal("players").execute(&BossbarGetExecuter(CommandValueGet::Players)), + literal("players").execute(BossbarGetExecuter(CommandValueGet::Players)), ) .with_child( - literal("value").execute(&BossbarGetExecuter(CommandValueGet::Value)), + literal("value").execute(BossbarGetExecuter(CommandValueGet::Value)), ) .with_child( - literal("visible").execute(&BossbarGetExecuter(CommandValueGet::Visible)), + literal("visible").execute(BossbarGetExecuter(CommandValueGet::Visible)), ), ), ) - .with_child(literal("list").execute(&BossbarListExecuter)) + .with_child(literal("list").execute(BossbarListExecuter)) .with_child(literal("remove").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER).execute(&BossbarRemoveExecuter), + argument_default_name(autocomplete_consumer()).execute(BossbarRemoveExecuter), )) .with_child( literal("set").with_child( - argument_default_name(&AUTOCOMPLETE_CONSUMER) + argument_default_name(autocomplete_consumer()) .with_child( literal("color").with_child( - argument_default_name(&BossbarColorArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Color)), + argument_default_name(BossbarColorArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Color)), ), ) .with_child( literal("max").with_child( - argument_default_name(&MAX_VALUE_CONSUMER) - .execute(&BossbarSetExecuter(CommandValueSet::Max)), + argument_default_name(max_value_consumer()) + .execute(BossbarSetExecuter(CommandValueSet::Max)), ), ) .with_child( literal("name").with_child( - argument(ARG_NAME, &SimpleArgConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Name)), + argument(ARG_NAME, SimpleArgConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Name)), ), ) .with_child( literal("players") .with_child( - argument_default_name(&PlayersArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Players(true))), + argument_default_name(PlayersArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Players(true))), ) - .execute(&BossbarSetExecuter(CommandValueSet::Players(false))), + .execute(BossbarSetExecuter(CommandValueSet::Players(false))), ) .with_child( literal("style").with_child( - argument_default_name(&BossbarStyleArgumentConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Style)), + argument_default_name(BossbarStyleArgumentConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Style)), ), ) .with_child( literal("value").with_child( - argument_default_name(&VALUE_CONSUMER) - .execute(&BossbarSetExecuter(CommandValueSet::Value)), + argument_default_name(value_consumer()) + .execute(BossbarSetExecuter(CommandValueSet::Value)), ), ) .with_child( literal("visible").with_child( - argument(ARG_VISIBLE, &BoolArgConsumer) - .execute(&BossbarSetExecuter(CommandValueSet::Visible)), + argument(ARG_VISIBLE, BoolArgConsumer) + .execute(BossbarSetExecuter(CommandValueSet::Visible)), ), ), ), diff --git a/pumpkin/src/command/commands/cmd_clear.rs b/pumpkin/src/command/commands/cmd_clear.rs index 07379587..b948d0df 100644 --- a/pumpkin/src/command/commands/cmd_clear.rs +++ b/pumpkin/src/command/commands/cmd_clear.rs @@ -107,8 +107,8 @@ impl CommandExecutor for ClearSelfExecutor { } #[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &EntitiesArgumentConsumer).execute(&ClearExecutor)) - .with_child(require(&|sender| sender.is_player()).execute(&ClearSelfExecutor)) + .with_child(argument(ARG_TARGET, EntitiesArgumentConsumer).execute(ClearExecutor)) + .with_child(require(|sender| sender.is_player()).execute(ClearSelfExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_fill.rs b/pumpkin/src/command/commands/cmd_fill.rs index 1a01fb15..140e8729 100644 --- a/pumpkin/src/command/commands/cmd_fill.rs +++ b/pumpkin/src/command/commands/cmd_fill.rs @@ -154,23 +154,21 @@ impl CommandExecutor for SetblockExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| { - sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some() - }) - .with_child( - argument(ARG_FROM, &BlockPosArgumentConsumer).with_child( - argument(ARG_TO, &BlockPosArgumentConsumer).with_child( - argument(ARG_BLOCK, &BlockArgumentConsumer) - .with_child(literal("destroy").execute(&SetblockExecutor(Mode::Destroy))) - .with_child(literal("hollow").execute(&SetblockExecutor(Mode::Hollow))) - .with_child(literal("keep").execute(&SetblockExecutor(Mode::Keep))) - .with_child(literal("outline").execute(&SetblockExecutor(Mode::Outline))) - .with_child(literal("replace").execute(&SetblockExecutor(Mode::Replace))) - .execute(&SetblockExecutor(Mode::Replace)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two) && sender.world().is_some()) + .with_child( + argument(ARG_FROM, BlockPosArgumentConsumer).with_child( + argument(ARG_TO, BlockPosArgumentConsumer).with_child( + argument(ARG_BLOCK, BlockArgumentConsumer) + .with_child(literal("destroy").execute(SetblockExecutor(Mode::Destroy))) + .with_child(literal("hollow").execute(SetblockExecutor(Mode::Hollow))) + .with_child(literal("keep").execute(SetblockExecutor(Mode::Keep))) + .with_child(literal("outline").execute(SetblockExecutor(Mode::Outline))) + .with_child(literal("replace").execute(SetblockExecutor(Mode::Replace))) + .execute(SetblockExecutor(Mode::Replace)), + ), ), ), - ), ) } diff --git a/pumpkin/src/command/commands/cmd_gamemode.rs b/pumpkin/src/command/commands/cmd_gamemode.rs index faab9537..2b627862 100644 --- a/pumpkin/src/command/commands/cmd_gamemode.rs +++ b/pumpkin/src/command/commands/cmd_gamemode.rs @@ -107,13 +107,13 @@ impl CommandExecutor for GamemodeTargetPlayer { } #[allow(clippy::redundant_closure_for_method_calls)] -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( - argument(ARG_GAMEMODE, &GamemodeArgumentConsumer) - .with_child(require(&|sender| sender.is_player()).execute(&GamemodeTargetSelf)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( + argument(ARG_GAMEMODE, GamemodeArgumentConsumer) + .with_child(require(|sender| sender.is_player()).execute(GamemodeTargetSelf)) .with_child( - argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&GamemodeTargetPlayer), + argument(ARG_TARGET, PlayersArgumentConsumer).execute(GamemodeTargetPlayer), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs index 36edb640..cdc3334b 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -17,10 +17,12 @@ const DESCRIPTION: &str = "Give items to player(s)."; const ARG_ITEM: &str = "item"; -static ITEM_COUNT_CONSUMER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("count") - .min(0) - .max(6400); +fn item_count_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("count") + .min(0) + .max(6400) +} struct GiveExecutor; @@ -36,7 +38,7 @@ impl CommandExecutor for GiveExecutor { let (item_name, item) = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?; - let item_count = match ITEM_COUNT_CONSUMER.find_arg_default_name(args) { + let item_count = match item_count_consumer().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -72,13 +74,13 @@ impl CommandExecutor for GiveExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( - argument_default_name(&PlayersArgumentConsumer).with_child( - argument(ARG_ITEM, &ItemArgumentConsumer) - .execute(&GiveExecutor) - .with_child(argument_default_name(&ITEM_COUNT_CONSUMER).execute(&GiveExecutor)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)).with_child( + argument_default_name(PlayersArgumentConsumer).with_child( + argument(ARG_ITEM, ItemArgumentConsumer) + .execute(GiveExecutor) + .with_child(argument_default_name(item_count_consumer()).execute(GiveExecutor)), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_help.rs b/pumpkin/src/command/commands/cmd_help.rs index e382febf..c0ab3eb7 100644 --- a/pumpkin/src/command/commands/cmd_help.rs +++ b/pumpkin/src/command/commands/cmd_help.rs @@ -22,8 +22,9 @@ const ARG_COMMAND: &str = "command"; const COMMANDS_PER_PAGE: i32 = 7; -static PAGE_NUMBER_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().name("page").min(1); +fn page_number_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().name("page").min(1) +} struct CommandHelpExecutor; @@ -41,7 +42,7 @@ impl CommandExecutor for CommandHelpExecutor { let command_names = tree.names.join(", /"); let usage = format!("{tree}"); - let description = tree.description; + let description = &tree.description; let header_text = format!(" Help - /{} ", tree.names[0]); @@ -107,7 +108,7 @@ impl CommandExecutor for BaseHelpExecutor { server: &Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let page_number = match PAGE_NUMBER_CONSUMER.find_arg_default_name(args) { + let page_number = match page_number_consumer().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(number)) => number, Ok(Err(())) => { @@ -131,7 +132,7 @@ impl CommandExecutor for BaseHelpExecutor { }) .collect(); - commands.sort_by(|a, b| a.names[0].cmp(b.names[0])); + commands.sort_by(|a, b| a.names[0].cmp(&b.names[0])); let total_pages = (commands.len().to_i32().unwrap() + COMMANDS_PER_PAGE - 1) / COMMANDS_PER_PAGE; @@ -183,7 +184,7 @@ impl CommandExecutor for BaseHelpExecutor { .color_named(NamedColor::Gold) .add_child(TextComponent::text(" - ").color_named(NamedColor::Yellow)) .add_child( - TextComponent::text_string(tree.description.to_owned() + "\n") + TextComponent::text_string(tree.description.clone() + "\n") .color_named(NamedColor::White), ) .add_child(TextComponent::text(" Usage: ").color_named(NamedColor::Yellow)) @@ -220,11 +221,9 @@ impl CommandExecutor for BaseHelpExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child( - argument(ARG_COMMAND, &CommandTreeArgumentConsumer).execute(&CommandHelpExecutor), - ) - .with_child(argument_default_name(&PAGE_NUMBER_CONSUMER).execute(&BaseHelpExecutor)) - .execute(&BaseHelpExecutor) + .with_child(argument(ARG_COMMAND, CommandTreeArgumentConsumer).execute(CommandHelpExecutor)) + .with_child(argument_default_name(page_number_consumer()).execute(BaseHelpExecutor)) + .execute(BaseHelpExecutor) } diff --git a/pumpkin/src/command/commands/cmd_kick.rs b/pumpkin/src/command/commands/cmd_kick.rs index d0a8eec9..8d77bf3d 100644 --- a/pumpkin/src/command/commands/cmd_kick.rs +++ b/pumpkin/src/command/commands/cmd_kick.rs @@ -49,7 +49,7 @@ impl CommandExecutor for KickExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &PlayersArgumentConsumer).execute(&KickExecutor)) + .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(KickExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_kill.rs b/pumpkin/src/command/commands/cmd_kill.rs index d763054b..2b36b0c4 100644 --- a/pumpkin/src/command/commands/cmd_kill.rs +++ b/pumpkin/src/command/commands/cmd_kill.rs @@ -65,8 +65,8 @@ impl CommandExecutor for KillSelfExecutor { } #[allow(clippy::redundant_closure_for_method_calls)] // causes lifetime issues -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(argument(ARG_TARGET, &EntitiesArgumentConsumer).execute(&KillExecutor)) - .with_child(require(&|sender| sender.is_player()).execute(&KillSelfExecutor)) + .with_child(argument(ARG_TARGET, EntitiesArgumentConsumer).execute(KillExecutor)) + .with_child(require(|sender| sender.is_player()).execute(KillSelfExecutor)) } diff --git a/pumpkin/src/command/commands/cmd_list.rs b/pumpkin/src/command/commands/cmd_list.rs index 5a81f8f9..bbcc3641 100644 --- a/pumpkin/src/command/commands/cmd_list.rs +++ b/pumpkin/src/command/commands/cmd_list.rs @@ -55,6 +55,6 @@ fn get_player_names(players: Vec>) -> String { names } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&ListExecutor) +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(ListExecutor) } diff --git a/pumpkin/src/command/commands/cmd_pumpkin.rs b/pumpkin/src/command/commands/cmd_pumpkin.rs index 3dad98ec..a76a1be9 100644 --- a/pumpkin/src/command/commands/cmd_pumpkin.rs +++ b/pumpkin/src/command/commands/cmd_pumpkin.rs @@ -92,6 +92,6 @@ impl CommandExecutor for PumpkinExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { - CommandTree::new(NAMES, DESCRIPTION).execute(&PumpkinExecutor) +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).execute(PumpkinExecutor) } diff --git a/pumpkin/src/command/commands/cmd_say.rs b/pumpkin/src/command/commands/cmd_say.rs index 66b74338..fad4b64b 100644 --- a/pumpkin/src/command/commands/cmd_say.rs +++ b/pumpkin/src/command/commands/cmd_say.rs @@ -43,9 +43,9 @@ impl CommandExecutor for SayExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) - .with_child(argument(ARG_MESSAGE, &MsgArgConsumer).execute(&SayExecutor)), + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) + .with_child(argument(ARG_MESSAGE, MsgArgConsumer).execute(SayExecutor)), ) } diff --git a/pumpkin/src/command/commands/cmd_seed.rs b/pumpkin/src/command/commands/cmd_seed.rs index fb93acb0..c4ffc375 100644 --- a/pumpkin/src/command/commands/cmd_seed.rs +++ b/pumpkin/src/command/commands/cmd_seed.rs @@ -54,10 +54,10 @@ impl CommandExecutor for PumpkinExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child(require(&|sender| { + .with_child(require(|sender| { sender.has_permission_lvl(PermissionLvl::Two) })) - .execute(&PumpkinExecutor) + .execute(PumpkinExecutor) } diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs index 0dd6ec9f..8b7cf973 100644 --- a/pumpkin/src/command/commands/cmd_setblock.rs +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -79,19 +79,17 @@ impl CommandExecutor for SetblockExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { 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)), + 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/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs index de7803b1..9e3ada0b 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -37,8 +37,8 @@ impl CommandExecutor for StopExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Four)).execute(&StopExecutor), + require(|sender| sender.has_permission_lvl(PermissionLvl::Four)).execute(StopExecutor), ) } diff --git a/pumpkin/src/command/commands/cmd_teleport.rs b/pumpkin/src/command/commands/cmd_teleport.rs index 3bc4cf08..d9468147 100644 --- a/pumpkin/src/command/commands/cmd_teleport.rs +++ b/pumpkin/src/command/commands/cmd_teleport.rs @@ -237,41 +237,41 @@ impl CommandExecutor for TpSelfToPosExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) .with_child( - argument(ARG_LOCATION, &Position3DArgumentConsumer).execute(&TpSelfToPosExecutor), + argument(ARG_LOCATION, Position3DArgumentConsumer).execute(TpSelfToPosExecutor), ) .with_child( - argument(ARG_DESTINATION, &EntityArgumentConsumer).execute(&TpSelfToEntityExecutor), + argument(ARG_DESTINATION, EntityArgumentConsumer).execute(TpSelfToEntityExecutor), ) .with_child( - argument(ARG_TARGETS, &EntitiesArgumentConsumer) + argument(ARG_TARGETS, EntitiesArgumentConsumer) .with_child( - argument(ARG_LOCATION, &Position3DArgumentConsumer) - .execute(&TpEntitiesToPosExecutor) + argument(ARG_LOCATION, Position3DArgumentConsumer) + .execute(TpEntitiesToPosExecutor) .with_child( - argument(ARG_ROTATION, &RotationArgumentConsumer) - .execute(&TpEntitiesToPosWithRotationExecutor), + argument(ARG_ROTATION, RotationArgumentConsumer) + .execute(TpEntitiesToPosWithRotationExecutor), ) .with_child( literal("facing") .with_child( literal("entity").with_child( - argument(ARG_FACING_ENTITY, &EntityArgumentConsumer) - .execute(&TpEntitiesToPosFacingEntityExecutor), + argument(ARG_FACING_ENTITY, EntityArgumentConsumer) + .execute(TpEntitiesToPosFacingEntityExecutor), ), ) .with_child( - argument(ARG_FACING_LOCATION, &Position3DArgumentConsumer) - .execute(&TpEntitiesToPosFacingPosExecutor), + argument(ARG_FACING_LOCATION, Position3DArgumentConsumer) + .execute(TpEntitiesToPosFacingPosExecutor), ), ), ) .with_child( - argument(ARG_DESTINATION, &EntityArgumentConsumer) - .execute(&TpEntitiesToEntityExecutor), + argument(ARG_DESTINATION, EntityArgumentConsumer) + .execute(TpEntitiesToEntityExecutor), ), ), ) diff --git a/pumpkin/src/command/commands/cmd_time.rs b/pumpkin/src/command/commands/cmd_time.rs index ad289401..669a3294 100644 --- a/pumpkin/src/command/commands/cmd_time.rs +++ b/pumpkin/src/command/commands/cmd_time.rs @@ -18,10 +18,12 @@ const NAMES: [&str; 1] = ["time"]; const DESCRIPTION: &str = "Query the world time."; // TODO: This should be either higher or not bounded -static ARG_NUMBER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("time") - .min(0) - .max(24000); +fn arg_number() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("time") + .min(0) + .max(24000) +} #[derive(Clone, Copy)] enum Mode { @@ -83,7 +85,7 @@ impl CommandExecutor for TimeChangeExecutor { server: &crate::server::Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let time_count = match ARG_NUMBER.find_arg_default_name(args) { + let time_count = match arg_number().find_arg_default_name(args) { Err(_) => 1, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -124,22 +126,20 @@ impl CommandExecutor for TimeChangeExecutor { } } -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Two)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Two)) .with_child(literal("add").with_child( - argument_default_name(&ARG_NUMBER).execute(&TimeChangeExecutor(Mode::Add)), + argument_default_name(arg_number()).execute(TimeChangeExecutor(Mode::Add)), )) .with_child( literal("query") - .with_child(literal("daytime").execute(&TimeQueryExecutor(QueryMode::DayTime))) - .with_child( - literal("gametime").execute(&TimeQueryExecutor(QueryMode::GameTime)), - ) - .with_child(literal("day").execute(&TimeQueryExecutor(QueryMode::Day))), + .with_child(literal("daytime").execute(TimeQueryExecutor(QueryMode::DayTime))) + .with_child(literal("gametime").execute(TimeQueryExecutor(QueryMode::GameTime))) + .with_child(literal("day").execute(TimeQueryExecutor(QueryMode::Day))), ) .with_child(literal("set").with_child( - argument_default_name(&ARG_NUMBER).execute(&TimeChangeExecutor(Mode::Set)), + argument_default_name(arg_number()).execute(TimeChangeExecutor(Mode::Set)), )), ) } diff --git a/pumpkin/src/command/commands/cmd_transfer.rs b/pumpkin/src/command/commands/cmd_transfer.rs index 7660b808..e35b0329 100644 --- a/pumpkin/src/command/commands/cmd_transfer.rs +++ b/pumpkin/src/command/commands/cmd_transfer.rs @@ -23,10 +23,12 @@ const ARG_HOSTNAME: &str = "hostname"; const ARG_PLAYERS: &str = "players"; -static PORT_CONSUMER: BoundedNumArgumentConsumer = BoundedNumArgumentConsumer::new() - .name("port") - .min(1) - .max(65535); +fn port_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .name("port") + .min(1) + .max(65535) +} struct TransferTargetSelf; @@ -42,7 +44,7 @@ impl CommandExecutor for TransferTargetSelf { return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); }; - let port = match PORT_CONSUMER.find_arg_default_name(args) { + let port = match port_consumer().find_arg_default_name(args) { Err(_) => 25565, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -84,7 +86,7 @@ impl CommandExecutor for TransferTargetPlayer { return Err(InvalidConsumption(Some(ARG_HOSTNAME.into()))); }; - let port = match PORT_CONSUMER.find_arg_default_name(args) { + let port = match port_consumer().find_arg_default_name(args) { Err(_) => 25565, Ok(Ok(count)) => count, Ok(Err(())) => { @@ -117,19 +119,19 @@ impl CommandExecutor for TransferTargetPlayer { } #[allow(clippy::redundant_closure_for_method_calls)] -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION).with_child( - require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)).with_child( - argument(ARG_HOSTNAME, &SimpleArgConsumer) - .with_child(require(&|sender| sender.is_player()).execute(&TransferTargetSelf)) + require(|sender| sender.has_permission_lvl(PermissionLvl::Three)).with_child( + argument(ARG_HOSTNAME, SimpleArgConsumer) + .with_child(require(|sender| sender.is_player()).execute(TransferTargetSelf)) .with_child( - argument_default_name(&PORT_CONSUMER) + argument_default_name(port_consumer()) .with_child( - require(&|sender| sender.is_player()).execute(&TransferTargetSelf), + require(|sender| sender.is_player()).execute(TransferTargetSelf), ) .with_child( - argument(ARG_PLAYERS, &PlayersArgumentConsumer) - .execute(&TransferTargetPlayer), + argument(ARG_PLAYERS, PlayersArgumentConsumer) + .execute(TransferTargetPlayer), ), ), ), diff --git a/pumpkin/src/command/commands/cmd_worldborder.rs b/pumpkin/src/command/commands/cmd_worldborder.rs index fd9ae1a2..b52ac6b5 100644 --- a/pumpkin/src/command/commands/cmd_worldborder.rs +++ b/pumpkin/src/command/commands/cmd_worldborder.rs @@ -25,6 +25,28 @@ const NAMES: [&str; 1] = ["worldborder"]; const DESCRIPTION: &str = "Worldborder command."; +fn distance_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0.0).name("distance") +} + +fn time_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("time") +} + +fn damage_per_block_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new() + .min(0.0) + .name("damage_per_block") +} + +fn damage_buffer_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0.0).name("buffer") +} + +fn warning_distance_consumer() -> BoundedNumArgumentConsumer { + BoundedNumArgumentConsumer::new().min(0).name("distance") +} + struct WorldborderGetExecutor; #[async_trait] @@ -67,12 +89,12 @@ impl CommandExecutor for WorldborderSetExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -116,24 +138,24 @@ impl CommandExecutor for WorldborderSetTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) .await; return Ok(()); }; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -188,12 +210,12 @@ impl CommandExecutor for WorldborderAddExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -239,24 +261,24 @@ impl CommandExecutor for WorldborderAddTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DISTANCE_CONSUMER.default_name() + distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) .await; return Ok(()); }; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -341,12 +363,12 @@ impl CommandExecutor for WorldborderDamageAmountExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(damage_per_block) = DAMAGE_PER_BLOCK_CONSUMER.find_arg_default_name(args)? else { + let Ok(damage_per_block) = damage_per_block_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DAMAGE_PER_BLOCK_CONSUMER.default_name() + damage_per_block_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -392,12 +414,12 @@ impl CommandExecutor for WorldborderDamageBufferExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(buffer) = DAMAGE_BUFFER_CONSUMER.find_arg_default_name(args)? else { + let Ok(buffer) = damage_buffer_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - DAMAGE_BUFFER_CONSUMER.default_name() + damage_buffer_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -443,12 +465,12 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(distance) = WARNING_DISTANCE_CONSUMER.find_arg_default_name(args)? else { + let Ok(distance) = warning_distance_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - WARNING_DISTANCE_CONSUMER.default_name() + warning_distance_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -494,12 +516,12 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { .expect("There should always be atleast one world"); let mut border = world.worldborder.lock().await; - let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else { + let Ok(time) = time_consumer().find_arg_default_name(args)? else { sender .send_message( TextComponent::text_string(format!( "{} is out of bounds.", - TIME_CONSUMER.default_name() + time_consumer().default_name() )) .color(Color::Named(NamedColor::Red)), ) @@ -529,59 +551,42 @@ impl CommandExecutor for WorldborderWarningTimeExecutor { } } -static DISTANCE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0.0).name("distance"); - -static TIME_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("time"); - -static DAMAGE_PER_BLOCK_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new() - .min(0.0) - .name("damage_per_block"); - -static DAMAGE_BUFFER_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0.0).name("buffer"); - -static WARNING_DISTANCE_CONSUMER: BoundedNumArgumentConsumer = - BoundedNumArgumentConsumer::new().min(0).name("distance"); - -pub fn init_command_tree<'a>() -> CommandTree<'a> { +pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .with_child( literal("add").with_child( - argument_default_name(&DISTANCE_CONSUMER) - .execute(&WorldborderAddExecutor) + argument_default_name(distance_consumer()) + .execute(WorldborderAddExecutor) .with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderAddTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderAddTimeExecutor), ), ), ) .with_child(literal("center").with_child( - argument_default_name(&Position2DArgumentConsumer).execute(&WorldborderCenterExecutor), + argument_default_name(Position2DArgumentConsumer).execute(WorldborderCenterExecutor), )) .with_child( literal("damage") .with_child( literal("amount").with_child( - argument_default_name(&DAMAGE_PER_BLOCK_CONSUMER) - .execute(&WorldborderDamageAmountExecutor), + argument_default_name(damage_per_block_consumer()) + .execute(WorldborderDamageAmountExecutor), ), ) .with_child( literal("buffer").with_child( - argument_default_name(&DAMAGE_BUFFER_CONSUMER) - .execute(&WorldborderDamageBufferExecutor), + argument_default_name(damage_buffer_consumer()) + .execute(WorldborderDamageBufferExecutor), ), ), ) - .with_child(literal("get").execute(&WorldborderGetExecutor)) + .with_child(literal("get").execute(WorldborderGetExecutor)) .with_child( literal("set").with_child( - argument_default_name(&DISTANCE_CONSUMER) - .execute(&WorldborderSetExecutor) + argument_default_name(distance_consumer()) + .execute(WorldborderSetExecutor) .with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderSetTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderSetTimeExecutor), ), ), ) @@ -589,12 +594,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> { literal("warning") .with_child( literal("distance").with_child( - argument_default_name(&WARNING_DISTANCE_CONSUMER) - .execute(&WorldborderWarningDistanceExecutor), + argument_default_name(warning_distance_consumer()) + .execute(WorldborderWarningDistanceExecutor), ), ) .with_child(literal("time").with_child( - argument_default_name(&TIME_CONSUMER).execute(&WorldborderWarningTimeExecutor), + argument_default_name(time_consumer()).execute(WorldborderWarningTimeExecutor), )), ) } diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index b2dc27ae..4d1bb943 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -46,13 +46,13 @@ impl CommandError { } #[derive(Default)] -pub struct CommandDispatcher<'a> { - pub(crate) commands: HashMap<&'a str, Command<'a>>, +pub struct CommandDispatcher { + pub(crate) commands: HashMap, } /// Stores registered [`CommandTree`]s and dispatches commands to them. -impl<'a> CommandDispatcher<'a> { - pub async fn handle_command( +impl CommandDispatcher { + pub async fn handle_command<'a>( &'a self, sender: &mut CommandSender<'a>, server: &'a Server, @@ -81,7 +81,7 @@ impl<'a> CommandDispatcher<'a> { /// # todo /// - make this less ugly /// - do not query suggestions for the same consumer multiple times just because they are on different paths through the tree - pub(crate) async fn find_suggestions( + pub(crate) async fn find_suggestions<'a>( &'a self, src: &mut CommandSender<'a>, server: &'a Server, @@ -102,7 +102,7 @@ impl<'a> CommandDispatcher<'a> { // try paths and collect the nodes that fail // todo: make this more fine-grained for path in tree.iter_paths() { - match Self::try_find_suggestions_on_path(src, server, &path, &tree, &mut raw_args, cmd) + match Self::try_find_suggestions_on_path(src, server, &path, tree, &mut raw_args, cmd) .await { Err(InvalidConsumption(s)) => { @@ -129,12 +129,12 @@ impl<'a> CommandDispatcher<'a> { } let mut suggestions = Vec::from_iter(suggestions); - suggestions.sort_by(|a, b| a.suggestion.cmp(b.suggestion)); + suggestions.sort_by(|a, b| a.suggestion.cmp(&b.suggestion)); suggestions } /// Execute a command using its corresponding [`CommandTree`]. - pub(crate) async fn dispatch( + pub(crate) async fn dispatch<'a>( &'a self, src: &mut CommandSender<'a>, server: &'a Server, @@ -151,7 +151,7 @@ impl<'a> CommandDispatcher<'a> { // try paths until fitting path is found for path in tree.iter_paths() { - if Self::try_is_fitting_path(src, server, &path, &tree, &mut raw_args.clone()).await? { + if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args.clone()).await? { return Ok(()); } } @@ -160,14 +160,14 @@ impl<'a> CommandDispatcher<'a> { ))) } - pub(crate) fn get_tree(&self, key: &str) -> Result, CommandError> { + pub(crate) fn get_tree(&self, key: &str) -> Result<&CommandTree, CommandError> { let command = self .commands .get(key) .ok_or(GeneralCommandIssue("Command not found".to_string()))?; match command { - Command::Tree(tree) => Ok(tree.clone()), + Command::Tree(tree) => Ok(tree), Command::Alias(target) => { let Some(Command::Tree(tree)) = self.commands.get(target) else { log::error!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree"); @@ -175,22 +175,22 @@ impl<'a> CommandDispatcher<'a> { "Internal Error (See logs for details)".into(), )); }; - Ok(tree.clone()) + Ok(tree) } } } - async fn try_is_fitting_path( + async fn try_is_fitting_path<'a>( src: &mut CommandSender<'a>, server: &'a Server, path: &[usize], - tree: &CommandTree<'a>, + tree: &'a CommandTree, raw_args: &mut RawArgs<'a>, ) -> Result { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { - match node.node_type { + match &node.node_type { NodeType::ExecuteLeaf { executor } => { return if raw_args.is_empty() { executor.execute(src, server, &parsed_args).await?; @@ -223,18 +223,18 @@ impl<'a> CommandDispatcher<'a> { Ok(false) } - async fn try_find_suggestions_on_path( + async fn try_find_suggestions_on_path<'a>( src: &mut CommandSender<'a>, server: &'a Server, path: &[usize], - tree: &CommandTree<'a>, + tree: &'a CommandTree, raw_args: &mut RawArgs<'a>, input: &'a str, ) -> Result>>, CommandError> { let mut parsed_args: ConsumedArgs = HashMap::new(); for node in path.iter().map(|&i| &tree.nodes[i]) { - match node.node_type { + match &node.node_type { NodeType::ExecuteLeaf { .. } => { return Ok(None); } @@ -270,16 +270,18 @@ impl<'a> CommandDispatcher<'a> { } /// Register a command with the dispatcher. - pub(crate) fn register(&mut self, tree: CommandTree<'a>) { + pub(crate) fn register(&mut self, tree: CommandTree) { let mut names = tree.names.iter(); let primary_name = names.next().expect("at least one name must be provided"); - for &name in names { - self.commands.insert(name, Command::Alias(primary_name)); + for name in names { + self.commands + .insert(name.to_string(), Command::Alias(primary_name.to_string())); } - self.commands.insert(primary_name, Command::Tree(tree)); + self.commands + .insert(primary_name.to_string(), Command::Tree(tree)); } } diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 9ab33038..11eea197 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -107,7 +107,7 @@ impl<'a> CommandSender<'a> { } #[must_use] -pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { +pub fn default_dispatcher() -> CommandDispatcher { let mut dispatcher = CommandDispatcher::default(); dispatcher.register(cmd_pumpkin::init_command_tree()); diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 9ea18e35..8f54f949 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -1,34 +1,34 @@ use super::{args::ArgumentConsumer, CommandExecutor}; use crate::command::CommandSender; -use std::{collections::VecDeque, fmt::Debug}; +use std::{collections::VecDeque, fmt::Debug, sync::Arc}; /// see [`crate::commands::tree_builder::argument`] pub type RawArgs<'a> = Vec<&'a str>; #[derive(Debug, Clone)] -pub struct Node<'a> { +pub struct Node { pub(crate) children: Vec, - pub(crate) node_type: NodeType<'a>, + pub(crate) node_type: NodeType, } #[derive(Clone)] -pub enum NodeType<'a> { +pub enum NodeType { ExecuteLeaf { - executor: &'a dyn CommandExecutor, + executor: Arc, }, Literal { - string: &'a str, + string: String, }, Argument { - name: &'a str, - consumer: &'a dyn ArgumentConsumer, + name: String, + consumer: Arc, }, Require { - predicate: &'a (dyn Fn(&CommandSender) -> bool + Sync), + predicate: Arc bool + Send + Sync>, }, } -impl Debug for NodeType<'_> { +impl Debug for NodeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ExecuteLeaf { .. } => f @@ -46,28 +46,28 @@ impl Debug for NodeType<'_> { } } -pub enum Command<'a> { - Tree(CommandTree<'a>), - Alias(&'a str), +pub enum Command { + Tree(CommandTree), + Alias(String), } #[derive(Debug, Clone)] -pub struct CommandTree<'a> { - pub(crate) nodes: Vec>, +pub struct CommandTree { + pub(crate) nodes: Vec, pub(crate) children: Vec, - pub(crate) names: Vec<&'a str>, - pub(crate) description: &'a str, + pub(crate) names: Vec, + pub(crate) description: String, } -impl<'a> CommandTree<'a> { +impl CommandTree { /// iterate over all possible paths that end in a [`NodeType::ExecuteLeaf`] - pub(crate) fn iter_paths(&'a self) -> impl Iterator> + 'a { + pub(crate) fn iter_paths(&self) -> impl Iterator> + use<'_> { let mut todo = VecDeque::<(usize, usize)>::new(); // add root's children todo.extend(self.children.iter().map(|&i| (0, i))); - TraverseAllPathsIter::<'a> { + TraverseAllPathsIter { tree: self, path: Vec::::new(), todo, @@ -76,7 +76,7 @@ impl<'a> CommandTree<'a> { } struct TraverseAllPathsIter<'a> { - tree: &'a CommandTree<'a>, + tree: &'a CommandTree, path: Vec, /// (depth, i) todo: VecDeque<(usize, usize)>, diff --git a/pumpkin/src/command/tree_builder.rs b/pumpkin/src/command/tree_builder.rs index e750ea0b..92f65d9e 100644 --- a/pumpkin/src/command/tree_builder.rs +++ b/pumpkin/src/command/tree_builder.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::command::args::ArgumentConsumer; use crate::command::tree::{CommandTree, Node, NodeType}; use crate::command::CommandSender; @@ -5,9 +7,9 @@ use crate::command::CommandSender; use super::args::DefaultNameArgConsumer; use super::CommandExecutor; -impl<'a> CommandTree<'a> { +impl CommandTree { /// Add a child [Node] to the root of this [`CommandTree`]. - pub fn with_child(mut self, child: impl NodeBuilder<'a>) -> Self { + pub fn with_child(mut self, child: impl NodeBuilder) -> Self { let node = child.build(&mut self); self.children.push(self.nodes.len()); self.nodes.push(node); @@ -15,23 +17,17 @@ impl<'a> CommandTree<'a> { } /// provide at least one name - pub fn new( - names: [&'a str; NAME_COUNT], - description: &'a str, + pub fn new( + names: impl IntoIterator>, + description: impl Into, ) -> Self { - assert!(NAME_COUNT > 0); - - let mut names_vec = Vec::with_capacity(NAME_COUNT); - - for name in names { - names_vec.push(name); - } + let names_vec = names.into_iter().map(Into::into).collect(); Self { nodes: Vec::new(), children: Vec::new(), names: names_vec, - description, + description: description.into(), } } @@ -42,9 +38,11 @@ impl<'a> CommandTree<'a> { /// desired type. /// /// Also see [`NonLeafNodeBuilder::execute`]. - pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { + pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { let node = Node { - node_type: NodeType::ExecuteLeaf { executor }, + node_type: NodeType::ExecuteLeaf { + executor: Arc::new(executor), + }, children: Vec::new(), }; @@ -55,16 +53,16 @@ impl<'a> CommandTree<'a> { } } -pub trait NodeBuilder<'a> { - fn build(self, tree: &mut CommandTree<'a>) -> Node<'a>; +pub trait NodeBuilder { + fn build(self, tree: &mut CommandTree) -> Node; } -struct LeafNodeBuilder<'a> { - node_type: NodeType<'a>, +struct LeafNodeBuilder { + node_type: NodeType, } -impl<'a> NodeBuilder<'a> for LeafNodeBuilder<'a> { - fn build(self, _tree: &mut CommandTree<'a>) -> Node<'a> { +impl NodeBuilder for LeafNodeBuilder { + fn build(self, _tree: &mut CommandTree) -> Node { Node { children: Vec::new(), node_type: self.node_type, @@ -72,14 +70,14 @@ impl<'a> NodeBuilder<'a> for LeafNodeBuilder<'a> { } } -pub struct NonLeafNodeBuilder<'a> { - node_type: NodeType<'a>, - child_nodes: Vec>, - leaf_nodes: Vec>, +pub struct NonLeafNodeBuilder { + node_type: NodeType, + child_nodes: Vec, + leaf_nodes: Vec, } -impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { - fn build(self, tree: &mut CommandTree<'a>) -> Node<'a> { +impl NodeBuilder for NonLeafNodeBuilder { + fn build(self, tree: &mut CommandTree) -> Node { let mut child_indices = Vec::new(); for node_builder in self.child_nodes { @@ -101,7 +99,7 @@ impl<'a> NodeBuilder<'a> for NonLeafNodeBuilder<'a> { } } -impl<'a> NonLeafNodeBuilder<'a> { +impl NonLeafNodeBuilder { /// Add a child [Node] to this one. pub fn with_child(mut self, child: Self) -> Self { self.child_nodes.push(child); @@ -115,9 +113,11 @@ impl<'a> NonLeafNodeBuilder<'a> { /// desired type. /// /// Also see [`CommandTree::execute`]. - pub fn execute(mut self, executor: &'a dyn CommandExecutor) -> Self { + pub fn execute(mut self, executor: impl CommandExecutor + 'static + Send) -> Self { self.leaf_nodes.push(LeafNodeBuilder { - node_type: NodeType::ExecuteLeaf { executor }, + node_type: NodeType::ExecuteLeaf { + executor: Arc::new(executor), + }, }); self @@ -125,9 +125,11 @@ impl<'a> NonLeafNodeBuilder<'a> { } /// Matches a sting literal. -pub const fn literal(string: &str) -> NonLeafNodeBuilder { +pub fn literal(string: impl Into) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Literal { string }, + node_type: NodeType::Literal { + string: string.into(), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } @@ -141,20 +143,28 @@ pub const fn literal(string: &str) -> NonLeafNodeBuilder { /// [`NonLeafNodeBuilder::execute`] nodes in a [`ConsumedArgs`] instance. It must remove consumed arg(s) /// from [`RawArgs`] and return them. It must return None if [`RawArgs`] are invalid. [`RawArgs`] is /// reversed, so [`Vec::pop`] can be used to obtain args in ltr order. -pub fn argument<'a>(name: &'a str, consumer: &'a dyn ArgumentConsumer) -> NonLeafNodeBuilder<'a> { +pub fn argument( + name: impl Into, + consumer: impl ArgumentConsumer + 'static + Send, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Argument { name, consumer }, + node_type: NodeType::Argument { + name: name.into(), + consumer: Arc::new(consumer), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } } /// same as [`crate::command::tree_builder::argument`], but uses default arg name of consumer -pub fn argument_default_name(consumer: &dyn DefaultNameArgConsumer) -> NonLeafNodeBuilder<'_> { +pub fn argument_default_name( + consumer: impl DefaultNameArgConsumer + 'static + Send, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { node_type: NodeType::Argument { name: consumer.default_name(), - consumer: consumer.get_argument_consumer(), + consumer: Arc::new(consumer), }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), @@ -163,9 +173,13 @@ pub fn argument_default_name(consumer: &dyn DefaultNameArgConsumer) -> NonLeafNo /// ```predicate``` should return ```false``` if requirement for reaching following [Node]s is not /// met. -pub fn require(predicate: &(dyn Fn(&CommandSender) -> bool + Sync)) -> NonLeafNodeBuilder { +pub fn require( + predicate: impl Fn(&CommandSender) -> bool + Send + Sync + 'static, +) -> NonLeafNodeBuilder { NonLeafNodeBuilder { - node_type: NodeType::Require { predicate }, + node_type: NodeType::Require { + predicate: Arc::new(predicate), + }, child_nodes: Vec::new(), leaf_nodes: Vec::new(), } diff --git a/pumpkin/src/command/tree_format.rs b/pumpkin/src/command/tree_format.rs index 406d95f2..597aece0 100644 --- a/pumpkin/src/command/tree_format.rs +++ b/pumpkin/src/command/tree_format.rs @@ -7,7 +7,7 @@ trait IsVisible { fn is_visible(&self) -> bool; } -impl IsVisible for Node<'_> { +impl IsVisible for Node { fn is_visible(&self) -> bool { matches!( self.node_type, @@ -16,9 +16,9 @@ impl IsVisible for Node<'_> { } } -impl Display for Node<'_> { +impl Display for Node { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.node_type { + match &self.node_type { NodeType::Literal { string } => { f.write_str(string)?; } @@ -50,10 +50,10 @@ fn flatten_require_nodes(nodes: &[Node], children: &[usize]) -> Vec { new_children } -impl Display for CommandTree<'_> { +impl Display for CommandTree { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_char('/')?; - f.write_str(self.names[0])?; + f.write_str(&self.names[0])?; let mut todo = VecDeque::<&[usize]>::with_capacity(self.children.len()); todo.push_back(&self.children); diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index cb5c57b1..2ae5804f 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -47,7 +47,7 @@ pub struct Server { /// Saves server branding information. server_branding: CachedBranding, /// Saves and Dispatches commands to appropriate handlers. - pub command_dispatcher: RwLock>, + pub command_dispatcher: RwLock, /// Saves and calls blocks blocks pub block_manager: Arc, /// Manages multiple worlds within the server. From 7161491005f4308eff7cce646df910c3c9cc5993 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 27 Dec 2024 00:27:24 +0100 Subject: [PATCH 04/11] add some simple test to our nbt crate --- pumpkin-core/src/text/click.rs | 2 + pumpkin-entity/src/entity_type.rs | 2 +- pumpkin-nbt/src/deserializer.rs | 12 +++--- pumpkin-nbt/src/lib.rs | 62 +++++++++++++++++++++++++++++++ pumpkin-protocol/Cargo.toml | 2 +- pumpkin/Cargo.toml | 2 +- 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/pumpkin-core/src/text/click.rs b/pumpkin-core/src/text/click.rs index 0e917e15..caa78f61 100644 --- a/pumpkin-core/src/text/click.rs +++ b/pumpkin-core/src/text/click.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; pub enum ClickEvent<'a> { /// Opens a URL OpenUrl(Cow<'a, str>), + /// Opens a File + OpenFile(Cow<'a, str>), /// Works in signs, but only on the root text component RunCommand(Cow<'a, str>), /// Replaces the contents of the chat box with the text, not necessarily a diff --git a/pumpkin-entity/src/entity_type.rs b/pumpkin-entity/src/entity_type.rs index 282ac129..e065722f 100644 --- a/pumpkin-entity/src/entity_type.rs +++ b/pumpkin-entity/src/entity_type.rs @@ -1,4 +1,4 @@ -// TODO +// TODO make this dynamic #[derive(Clone)] #[repr(i32)] pub enum EntityType { diff --git a/pumpkin-nbt/src/deserializer.rs b/pumpkin-nbt/src/deserializer.rs index ae017b82..80e3b96f 100644 --- a/pumpkin-nbt/src/deserializer.rs +++ b/pumpkin-nbt/src/deserializer.rs @@ -23,7 +23,7 @@ impl<'de, T: Buf> Deserializer<'de, T> { } } -/// Deserializes struct using Serde Deserializer from unnamed (network) NBT +/// Deserializes struct using Serde Deserializer from normal NBT pub fn from_bytes<'a, T>(s: &'a mut impl Buf) -> Result where T: Deserialize<'a>, @@ -32,20 +32,20 @@ where T::deserialize(&mut deserializer) } -pub fn from_cursor<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result +/// Deserializes struct using Serde Deserializer from normal NBT +pub fn from_bytes_unnamed<'a, T>(s: &'a mut impl Buf) -> Result where T: Deserialize<'a>, { - let mut deserializer = Deserializer::new(cursor, true); + let mut deserializer = Deserializer::new(s, false); T::deserialize(&mut deserializer) } -/// Deserializes struct using Serde Deserializer from normal NBT -pub fn from_bytes_unnamed<'a, T>(s: &'a mut impl Buf) -> Result +pub fn from_cursor<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result where T: Deserialize<'a>, { - let mut deserializer = Deserializer::new(s, false); + let mut deserializer = Deserializer::new(cursor, true); T::deserialize(&mut deserializer) } diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index b2be0d65..acee723f 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -200,3 +200,65 @@ macro_rules! impl_array { impl_array!(IntArray, "int"); impl_array!(LongArray, "long"); impl_array!(BytesArray, "byte"); + +#[cfg(test)] +mod test { + use serde::{Deserialize, Serialize}; + + use crate::BytesArray; + use crate::IntArray; + use crate::LongArray; + use crate::{ + deserializer::from_bytes_unnamed, + serializer::to_bytes_unnamed, + }; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Test { + byte: i8, + short: i16, + int: i32, + long: i64, + float: f32, + string: String, + } + + #[test] + fn test_simple_ser_de_unamed() { + let test = Test { + byte: 123, + short: 1342, + int: 4313, + long: 34, + float: 1.00, + string: "Hello test".to_string(), + }; + let mut bytes = to_bytes_unnamed(&test).unwrap(); + let recreated_struct: Test = from_bytes_unnamed(&mut bytes).unwrap(); + + assert_eq!(test, recreated_struct); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestArray { + #[serde(with = "BytesArray")] + byte_array: Vec, + #[serde(with = "IntArray")] + int_array: Vec, + #[serde(with = "LongArray")] + long_array: Vec, + } + + #[test] + fn test_simple_ser_de_array() { + let test = TestArray { + byte_array: vec![0, 3, 2], + int_array: vec![13, 1321, 2], + long_array: vec![1, 0, 200301, 1], + }; + let mut bytes = to_bytes_unnamed(&test).unwrap(); + let recreated_struct: TestArray = from_bytes_unnamed(&mut bytes).unwrap(); + + assert_eq!(test, recreated_struct); + } +} diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index c167df95..f04d7731 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -30,4 +30,4 @@ aes = "0.8.4" cfb8 = "0.8.1" # decryption -libdeflater = "1.22.0" \ No newline at end of file +libdeflater = "1.23.0" \ No newline at end of file diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index a3f4a14e..87b03020 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -46,7 +46,7 @@ rsa = "0.9.7" rsa-der = "0.3.0" # authentication -reqwest = { version = "0.12.9", default-features = false, features = [ +reqwest = { version = "0.12.10", default-features = false, features = [ "http2", "json", "macos-system-configuration", From af54cb428969a2861f239bdd1f60969758d15f5b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 27 Dec 2024 13:15:45 +0100 Subject: [PATCH 05/11] fix: clippy fmt --- pumpkin-nbt/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pumpkin-nbt/src/lib.rs b/pumpkin-nbt/src/lib.rs index acee723f..5ed48da9 100644 --- a/pumpkin-nbt/src/lib.rs +++ b/pumpkin-nbt/src/lib.rs @@ -208,10 +208,7 @@ mod test { use crate::BytesArray; use crate::IntArray; use crate::LongArray; - use crate::{ - deserializer::from_bytes_unnamed, - serializer::to_bytes_unnamed, - }; + use crate::{deserializer::from_bytes_unnamed, serializer::to_bytes_unnamed}; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Test { From 37372986b029985e839dedcc615932187dd9d3d8 Mon Sep 17 00:00:00 2001 From: LieOnLion Date: Fri, 27 Dec 2024 12:18:43 +0000 Subject: [PATCH 06/11] Fixed Pumpkin website links (#423) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e4df0ef..f7113930 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ -[Pumpkin](https://snowiiii.github.io/Pumpkin/) is a Minecraft server built entirely in Rust, offering a fast, efficient, +[Pumpkin](https://snowiiii.github.io/Pumpkin-Website/) is a Minecraft server built entirely in Rust, offering a fast, efficient, and customizable experience. It prioritizes performance and player enjoyment while adhering to the core mechanics of the game. ![image](https://github.com/user-attachments/assets/7e2e865e-b150-4675-a2d5-b52f9900378e) @@ -87,7 +87,7 @@ Check out our [Github Project](https://github.com/users/Snowiiii/projects/12/vie ## How to run -See our [Quick Start](https://snowiiii.github.io/Pumpkin/about/quick-start.html) Guide to get Pumpkin running +See our [Quick Start](https://snowiiii.github.io/Pumpkin-Website/about/quick-start.html) Guide to get Pumpkin running ## Contributions @@ -95,7 +95,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) ## Docs -The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin/ +The Documentation of Pumpkin can be found at https://snowiiii.github.io/Pumpkin-Website/ ## Communication From 98d140664639cffc1b48167adb2dff3d24c12624 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 27 Dec 2024 16:04:40 +0100 Subject: [PATCH 07/11] Implement session.lock Lock session.lock so other processes know that we currently read and write to the level. This is vanilla behavior. Test: Tested to start the a new server while on was locking the file, and got `Failed to lock level: AlreadyLocked("session.lock")` as expected --- pumpkin-world/Cargo.toml | 2 ++ pumpkin-world/src/level.rs | 9 +++++++++ pumpkin-world/src/lib.rs | 1 + pumpkin-world/src/lock/anvil.rs | 32 ++++++++++++++++++++++++++++++++ pumpkin-world/src/lock/mod.rs | 18 ++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 pumpkin-world/src/lock/anvil.rs create mode 100644 pumpkin-world/src/lock/mod.rs diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 495ee3ff..1c7eacd8 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -25,6 +25,8 @@ dashmap = "6.1.0" flate2 = "1.0" lz4 = "1.28.0" +file-guard = "0.2.0" + enum_dispatch = "0.3.13" fastnbt = { git = "https://github.com/owengage/fastnbt.git" } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 930363ec..ede2d616 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -14,6 +14,7 @@ use crate::{ anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError, }, generation::{get_world_gen, Seed, WorldGenerator}, + lock::{anvil::AnvilLevelLocker, LevelLocker}, world_info::{anvil::AnvilLevelInfo, LevelData, WorldInfoReader, WorldInfoWriter}, }; @@ -35,6 +36,9 @@ pub struct Level { chunk_watchers: Arc, usize>>, chunk_reader: Arc, world_gen: Arc, + // Gets unlocked when dropped + // TODO: Make this a trait + _locker: Arc, } #[derive(Clone)] @@ -55,6 +59,10 @@ impl Level { region_folder, }; + // if we fail to lock, lets crash ???. maybe not the best soultion when we have a large server with many worlds and one is locked. + // So TODO + let locker = AnvilLevelLocker::look(&level_folder).expect("Failed to lock level"); + // TODO: Load info correctly based on world format type let level_info = AnvilLevelInfo .read_world_info(&level_folder) @@ -71,6 +79,7 @@ impl Level { loaded_chunks: Arc::new(DashMap::new()), chunk_watchers: Arc::new(DashMap::new()), level_info, + _locker: Arc::new(locker), } } diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index 8e26b106..ec23b151 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -16,6 +16,7 @@ pub mod dimension; mod generation; pub mod item; pub mod level; +mod lock; pub mod world_info; pub const WORLD_HEIGHT: usize = 384; pub const WORLD_LOWEST_Y: i16 = -64; diff --git a/pumpkin-world/src/lock/anvil.rs b/pumpkin-world/src/lock/anvil.rs new file mode 100644 index 00000000..7864cf71 --- /dev/null +++ b/pumpkin-world/src/lock/anvil.rs @@ -0,0 +1,32 @@ +use file_guard::{FileGuard, Lock}; + +use super::{LevelLocker, LockError}; + +use std::{fs::File, io::Write, sync::Arc}; + +pub struct AnvilLevelLocker { + _lock: Option>>, +} + +const SESSION_LOCK_FILE_NAME: &str = "session.lock"; + +const SNOWMAN: &[u8] = "☃".as_bytes(); + +impl LevelLocker for AnvilLevelLocker { + fn look(folder: &crate::level::LevelFolder) -> Result { + let file_path = folder.root_folder.join(SESSION_LOCK_FILE_NAME); + let mut file = File::options() + .create(true) + .truncate(false) + .write(true) + .open(file_path) + .unwrap(); + // im not joking, mojang writes a snowman into the lock file + file.write_all(SNOWMAN) + .map_err(|_| LockError::FailedWrite)?; + let file_arc = Arc::new(file); + let lock = file_guard::try_lock(file_arc, Lock::Exclusive, 0, 1) + .map_err(|_| LockError::AlreadyLocked(SESSION_LOCK_FILE_NAME.to_string()))?; + Ok(Self { _lock: Some(lock) }) + } +} diff --git a/pumpkin-world/src/lock/mod.rs b/pumpkin-world/src/lock/mod.rs new file mode 100644 index 00000000..bf4b262f --- /dev/null +++ b/pumpkin-world/src/lock/mod.rs @@ -0,0 +1,18 @@ +use thiserror::Error; + +use crate::level::LevelFolder; + +pub mod anvil; + +// Gets unlocked when dropped +pub trait LevelLocker: Send + Sync { + fn look(folder: &LevelFolder) -> Result; +} + +#[derive(Error, Debug)] +pub enum LockError { + #[error("Oh no, Level is already locked by {0}")] + AlreadyLocked(String), + #[error("Failed to write into lock file")] + FailedWrite, +} From c521ef8bb0be0c6c8cb1887c087a16c6730ca2fd Mon Sep 17 00:00:00 2001 From: Kris <37947442+OfficialKris@users.noreply.github.com> Date: Fri, 27 Dec 2024 10:24:23 -0500 Subject: [PATCH 08/11] Correct Interactive Block Behavior (#388) * Added chest and furnace * Corrected interactive block functionality * Fixed merge * Added standard functions and player remove on destroy * Added unique block standard functions * Fix clippy * Fix deadlock in standard function --- pumpkin-core/src/math/position.rs | 2 +- pumpkin-inventory/src/lib.rs | 7 ++ pumpkin-inventory/src/open_container.rs | 22 +++++ pumpkin/src/block/block_manager.rs | 18 +++- pumpkin/src/block/blocks/chest.rs | 65 +++++++----- pumpkin/src/block/blocks/crafting_table.rs | 75 +++++++++----- pumpkin/src/block/blocks/furnace.rs | 55 ++++++----- pumpkin/src/block/blocks/jukebox.rs | 18 +++- pumpkin/src/block/blocks/mod.rs | 110 +++++++++++++++++++++ pumpkin/src/block/pumpkin_block.rs | 40 +++++++- pumpkin/src/net/container.rs | 1 + pumpkin/src/net/packet/play.rs | 16 +-- pumpkin/src/server/mod.rs | 57 +++++++++++ 13 files changed, 400 insertions(+), 86 deletions(-) diff --git a/pumpkin-core/src/math/position.rs b/pumpkin-core/src/math/position.rs index 83989064..a3b82cbe 100644 --- a/pumpkin-core/src/math/position.rs +++ b/pumpkin-core/src/math/position.rs @@ -5,7 +5,7 @@ use crate::math::vector2::Vector2; use num_traits::Euclid; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] /// Aka Block Position pub struct WorldPosition(pub Vector3); diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index bf56dca6..f1273b05 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -94,6 +94,13 @@ pub trait Container: Sync + Send { fn all_slots_ref(&self) -> Vec>; + fn clear_all_slots(&mut self) { + let all_slots = self.all_slots(); + for stack in all_slots { + *stack = None; + } + } + fn all_combinable_slots(&self) -> Vec> { self.all_slots_ref() } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index ec68578f..3025ed92 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -5,8 +5,10 @@ use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::ItemStack; use std::sync::Arc; use tokio::sync::Mutex; + pub struct OpenContainer { // TODO: unique id should be here + // TODO: should this be uuid? players: Vec, container: Arc>>, location: Option, @@ -54,6 +56,22 @@ impl OpenContainer { } } + pub fn is_location(&self, try_position: WorldPosition) -> bool { + if let Some(location) = self.location { + location == try_position + } else { + false + } + } + + pub async fn clear_all_slots(&self) { + self.container.lock().await.clear_all_slots(); + } + + pub fn clear_all_players(&mut self) { + self.players = vec![]; + } + pub fn all_player_ids(&self) -> Vec { self.players.clone() } @@ -62,6 +80,10 @@ impl OpenContainer { self.location } + pub async fn set_location(&mut self, location: Option) { + self.location = location; + } + pub fn get_block(&self) -> Option { self.block.clone() } diff --git a/pumpkin/src/block/block_manager.rs b/pumpkin/src/block/block_manager.rs index 92395bb8..0fb26ede 100644 --- a/pumpkin/src/block/block_manager.rs +++ b/pumpkin/src/block/block_manager.rs @@ -2,6 +2,7 @@ use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::entity::player::Player; use crate::server::Server; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use std::collections::HashMap; @@ -33,7 +34,7 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_use(player, location, server).await; + pumpkin_block.on_use(block, player, location, server).await; } } @@ -48,7 +49,7 @@ impl BlockManager { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { return pumpkin_block - .on_use_with_item(player, location, item, server) + .on_use_with_item(block, player, location, item, server) .await; } BlockActionResult::Continue @@ -63,7 +64,9 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_placed(player, location, server).await; + pumpkin_block + .on_placed(block, player, location, server) + .await; } } @@ -76,7 +79,9 @@ impl BlockManager { ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_broken(player, location, server).await; + pumpkin_block + .on_broken(block, player, location, server) + .await; } } @@ -86,10 +91,13 @@ impl BlockManager { player: &Player, location: WorldPosition, server: &Server, + container: &mut OpenContainer, ) { let pumpkin_block = self.get_pumpkin_block(block); if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.on_close(player, location, server).await; + pumpkin_block + .on_close(block, player, location, server, container) + .await; } } diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 1600bb09..313d9a5b 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{Chest, OpenContainer, WindowType}; use pumpkin_macros::{pumpkin_block, sound}; -use pumpkin_world::{block::block_registry::get_block, item::item_registry::Item}; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; use crate::{ block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, @@ -15,8 +15,15 @@ pub struct ChestBlock; #[async_trait] impl PumpkinBlock for ChestBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_chest_block(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_chest_block(block, player, _location, server) + .await; player .world() .play_block_sound(sound!("block.chest.open"), _location) @@ -25,46 +32,60 @@ impl PumpkinBlock for ChestBlock { async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_chest_block(player, _location, server).await; + self.open_chest_block(block, player, _location, server) + .await; BlockActionResult::Consume } - async fn on_close<'a>(&self, player: &Player, _location: WorldPosition, _server: &Server) { + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_on_broken_with_container(block, player, location, server).await; + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { player .world() .play_block_sound(sound!("block.chest.close"), _location) .await; + // TODO: send entity updates close } } impl ChestBlock { pub async fn open_chest_block( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - let entity_id = player.entity_id(); - // TODO: This should be a unique identifier for the each block container - player.open_container.store(Some(2)); - { - let mut open_containers = server.open_containers.write().await; - if let Some(chest) = open_containers.get_mut(&2) { - chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:chest").cloned(), - ); - open_containers.insert(2, open_container); - } - } - player.open_container(server, WindowType::Generic9x3).await; + // TODO: shouldn't Chest and window type be constrained together to avoid errors? + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Generic9x3, + ) + .await; + // TODO: send entity updates open } } diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 962d5662..01ad4179 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -6,54 +6,83 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{CraftingTable, OpenContainer, WindowType}; use pumpkin_macros::pumpkin_block; -use pumpkin_world::{block::block_registry::get_block, item::item_registry::Item}; +use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; #[pumpkin_block("minecraft:crafting_table")] pub struct CraftingTableBlock; #[async_trait] impl PumpkinBlock for CraftingTableBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_crafting_screen(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_crafting_screen(block, player, _location, server) + .await; } async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_crafting_screen(player, _location, server).await; + self.open_crafting_screen(block, player, _location, server) + .await; BlockActionResult::Consume } -} -impl CraftingTableBlock { - pub async fn open_crafting_screen( + async fn on_broken<'a>( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - //TODO: Adjust /craft command to real crafting table + super::standard_on_broken_with_container(block, player, location, server).await; + } + + async fn on_close<'a>( + &self, + _block: &Block, + player: &Player, + _location: WorldPosition, + _server: &Server, + container: &OpenContainer, + ) { let entity_id = player.entity_id(); - player.open_container.store(Some(1)); - { - let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&1) { - ender_chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:crafting_table").cloned(), - ); - open_containers.insert(1, open_container); + for player_id in container.all_player_ids() { + if entity_id == player_id { + container.clear_all_slots().await; } } - player - .open_container(server, WindowType::CraftingTable) - .await; + + // TODO: items should be re-added to player inventory or dropped dependending on if they are in movement. + // TODO: unique containers should be implemented as a separate stack internally (optimizes large player servers for example) + // TODO: ephemeral containers (crafting tables) might need to be separate data structure than stored (ender chest) + } +} + +impl CraftingTableBlock { + pub async fn open_crafting_screen( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_open_container_unique::( + block, + player, + location, + server, + WindowType::CraftingTable, + ) + .await; } } diff --git a/pumpkin/src/block/blocks/furnace.rs b/pumpkin/src/block/blocks/furnace.rs index 8fc47949..1ecdf995 100644 --- a/pumpkin/src/block/blocks/furnace.rs +++ b/pumpkin/src/block/blocks/furnace.rs @@ -3,9 +3,9 @@ use crate::entity::player::Player; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::Furnace; -use pumpkin_inventory::{OpenContainer, WindowType}; +use pumpkin_inventory::WindowType; use pumpkin_macros::pumpkin_block; -use pumpkin_world::block::block_registry::get_block; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; use crate::{block::pumpkin_block::PumpkinBlock, server::Server}; @@ -15,45 +15,56 @@ pub struct FurnaceBlock; #[async_trait] impl PumpkinBlock for FurnaceBlock { - async fn on_use<'a>(&self, player: &Player, _location: WorldPosition, server: &Server) { - self.open_furnace_screen(player, _location, server).await; + async fn on_use<'a>( + &self, + block: &Block, + player: &Player, + _location: WorldPosition, + server: &Server, + ) { + self.open_furnace_screen(block, player, _location, server) + .await; } async fn on_use_with_item<'a>( &self, + block: &Block, player: &Player, _location: WorldPosition, _item: &Item, server: &Server, ) -> BlockActionResult { - self.open_furnace_screen(player, _location, server).await; + self.open_furnace_screen(block, player, _location, server) + .await; BlockActionResult::Consume } + + async fn on_broken<'a>( + &self, + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + ) { + super::standard_on_broken_with_container(block, player, location, server).await; + } } impl FurnaceBlock { pub async fn open_furnace_screen( &self, + block: &Block, player: &Player, location: WorldPosition, server: &Server, ) { - //TODO: Adjust /craft command to real crafting table - let entity_id = player.entity_id(); - player.open_container.store(Some(3)); - { - let mut open_containers = server.open_containers.write().await; - if let Some(ender_chest) = open_containers.get_mut(&3) { - ender_chest.add_player(entity_id); - } else { - let open_container = OpenContainer::new_empty_container::( - entity_id, - Some(location), - get_block("minecraft:furnace").cloned(), - ); - open_containers.insert(3, open_container); - } - } - player.open_container(server, WindowType::Furnace).await; + super::standard_open_container::( + block, + player, + location, + server, + WindowType::Furnace, + ) + .await; } } diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 3c52395b..4836c4be 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_macros::pumpkin_block; use pumpkin_registry::SYNCED_REGISTRIES; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; #[pumpkin_block("minecraft:jukebox")] @@ -13,7 +14,13 @@ pub struct JukeboxBlock; #[async_trait] impl PumpkinBlock for JukeboxBlock { - async fn on_use<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_use<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; @@ -22,6 +29,7 @@ impl PumpkinBlock for JukeboxBlock { async fn on_use_with_item<'a>( &self, + _block: &Block, player: &Player, location: WorldPosition, item: &Item, @@ -49,7 +57,13 @@ impl PumpkinBlock for JukeboxBlock { BlockActionResult::Consume } - async fn on_broken<'a>(&self, player: &Player, location: WorldPosition, _server: &Server) { + async fn on_broken<'a>( + &self, + _block: &Block, + player: &Player, + location: WorldPosition, + _server: &Server, + ) { // For now just stop the music at this position let world = &player.living_entity.entity.world; diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 4bdfa819..b0b12802 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -1,4 +1,114 @@ +use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::{Container, OpenContainer, WindowType}; +use pumpkin_world::block::block_registry::Block; + +use crate::{entity::player::Player, server::Server}; + pub(crate) mod chest; pub(crate) mod crafting_table; pub(crate) mod furnace; pub(crate) mod jukebox; + +/// The standard destroy with container removes the player forcibly from the container, +/// drops items to the floor, and back to the player's inventory if the item stack is in movement. +pub async fn standard_on_broken_with_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, +) { + // TODO: drop all items and back to players inventory if in motion + if let Some(all_container_ids) = server.get_all_container_ids(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + for individual_id in all_container_ids { + if let Some(container) = open_containers.get_mut(&u64::from(individual_id)) { + container.clear_all_slots().await; + player.open_container.store(None); + close_all_in_container(player, container).await; + container.clear_all_players(); + } + } + } +} + +/// The standard open container creates a new container if a container of the same block +/// type does not exist at the selected block location. If a container of the same type exists, the player +/// is added to the currently connected players to that container. +pub async fn standard_open_container( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + // If container exists, add player to container, otherwise create new container + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let mut open_containers = server.open_containers.write().await; + log::debug!("Using previous standard container ID: {}", container_id); + if let Some(container) = open_containers.get_mut(&u64::from(container_id)) { + container.add_player(entity_id); + player.open_container.store(Some(container_id.into())); + } + } else { + let mut open_containers = server.open_containers.write().await; + let new_id = server.new_container_id(); + log::debug!("Creating new standard container ID: {}", new_id); + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + open_containers.insert(new_id.into(), open_container); + player.open_container.store(Some(new_id.into())); + } + player.open_container(server, window_type).await; +} + +pub async fn standard_open_container_unique( + block: &Block, + player: &Player, + location: WorldPosition, + server: &Server, + window_type: WindowType, +) { + let entity_id = player.entity_id(); + let mut open_containers = server.open_containers.write().await; + let mut id_to_use = -1; + + // TODO: we can do better than brute force + for (id, container) in open_containers.iter() { + if let Some(a_block) = container.get_block() { + if a_block.id == block.id && container.all_player_ids().is_empty() { + id_to_use = *id as i64; + } + } + } + + if id_to_use == -1 { + let new_id = server.new_container_id(); + log::debug!("Creating new unqiue container ID: {}", new_id); + let open_container = + OpenContainer::new_empty_container::(entity_id, Some(location), Some(block.clone())); + + open_containers.insert(new_id.into(), open_container); + + player.open_container.store(Some(new_id.into())); + } else { + log::debug!("Using previous unqiue container ID: {}", id_to_use); + if let Some(unique_container) = open_containers.get_mut(&(id_to_use as u64)) { + unique_container.set_location(Some(location)).await; + unique_container.add_player(entity_id); + player + .open_container + .store(Some(id_to_use.try_into().unwrap())); + } + } + drop(open_containers); + player.open_container(server, window_type).await; +} + +pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { + for id in container.all_player_ids() { + if let Some(remote_player) = player.world().get_player_by_entityid(id).await { + remote_player.close_container().await; + } + } +} diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index 1849ea5a..c0a65c31 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -3,6 +3,8 @@ use crate::entity::player::Player; use crate::server::Server; use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; +use pumpkin_inventory::OpenContainer; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::item::item_registry::Item; pub trait BlockMetadata { @@ -15,9 +17,17 @@ pub trait BlockMetadata { #[async_trait] pub trait PumpkinBlock: Send + Sync { - async fn on_use<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_use<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } async fn on_use_with_item<'a>( &self, + _block: &Block, _player: &Player, _location: WorldPosition, _item: &Item, @@ -26,9 +36,31 @@ pub trait PumpkinBlock: Send + Sync { BlockActionResult::Continue } - async fn on_placed<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_placed<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } - async fn on_broken<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_broken<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + ) { + } - async fn on_close<'a>(&self, _player: &Player, _location: WorldPosition, _server: &Server) {} + async fn on_close<'a>( + &self, + _block: &Block, + _player: &Player, + _location: WorldPosition, + _server: &Server, + _container: &OpenContainer, + ) { + } } diff --git a/pumpkin/src/net/container.rs b/pumpkin/src/net/container.rs index 9e06a46d..c157b5e2 100644 --- a/pumpkin/src/net/container.rs +++ b/pumpkin/src/net/container.rs @@ -81,6 +81,7 @@ impl Player { } /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent + // TODO: is this just bc ids are not synced? pub async fn close_container(&self) { let mut inventory = self.inventory().lock().await; inventory.total_opened_containers += 1; diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index ec01ee04..baf90465 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -18,7 +18,7 @@ use pumpkin_core::{ text::TextComponent, GameMode, }; -use pumpkin_inventory::{InventoryError, WindowType}; +use pumpkin_inventory::InventoryError; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse; use pumpkin_protocol::{ @@ -838,11 +838,13 @@ impl Player { // TODO: // This function will in the future be used to keep track of if the client is in a valid state. // But this is not possible yet - pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) { - let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { - self.kick(TextComponent::text("Invalid window ID")).await; - return; - }; + pub async fn handle_close_container(&self, server: &Server, _packet: SCloseContainer) { + // TODO: This should check if player sent this packet before + // let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else { + // log::info!("Closed ID: {}", packet.window_id.0); + // self.kick(TextComponent::text("Invalid window ID")).await; + // return; + // }; // window_id 0 represents both 9x1 Generic AND inventory here let mut inventory = self.inventory().lock().await; @@ -856,7 +858,7 @@ impl Player { if let Some(block) = container.get_block() { server .block_manager - .on_close(&block, self, pos, server) //block, self, location, server) + .on_close(&block, self, pos, server, container) //block, self, location, server) .await; } } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 2ae5804f..96090ae8 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -1,6 +1,7 @@ use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use pumpkin_config::BASIC_CONFIG; +use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::GameMode; use pumpkin_entity::EntityId; @@ -9,9 +10,11 @@ use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::{DimensionType, Registry}; +use pumpkin_world::block::block_registry::Block; use pumpkin_world::dimension::Dimension; use rand::prelude::SliceRandom; use std::collections::HashMap; +use std::sync::atomic::AtomicU32; use std::{ sync::{ atomic::{AtomicI32, Ordering}, @@ -57,10 +60,13 @@ pub struct Server { /// Caches game registries for efficient access. pub cached_registry: Vec, /// Tracks open containers used for item interactions. + // TODO: should have per player open_containers pub open_containers: RwLock>, pub drag_handler: DragHandler, /// Assigns unique IDs to entities. entity_id: AtomicI32, + /// Assigns unique IDs to containers. + container_id: AtomicU32, /// Manages authentication with a authentication server, if enabled. pub auth_client: Option, /// The server's custom bossbars @@ -102,6 +108,7 @@ impl Server { drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), + container_id: 0.into(), worlds: vec![Arc::new(world)], dimensions: vec![ DimensionType::Overworld, @@ -192,6 +199,51 @@ impl Server { .cloned() } + /// Returns the first id with a matching location and block type. If this is used with unique + /// blocks, the output will return a random result. + pub async fn get_container_id(&self, location: WorldPosition, block: Block) -> Option { + let open_containers = self.open_containers.read().await; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found container id: {}", id); + return Some(*id as u32); + } + } + } + } + + drop(open_containers); + + None + } + + pub async fn get_all_container_ids( + &self, + location: WorldPosition, + block: Block, + ) -> Option> { + let open_containers = self.open_containers.read().await; + let mut matching_container_ids: Vec = vec![]; + // TODO: do better than brute force + for (id, container) in open_containers.iter() { + if container.is_location(location) { + if let Some(container_block) = container.get_block() { + if container_block.id == block.id { + log::debug!("Found matching container id: {}", id); + matching_container_ids.push(*id as u32); + } + } + } + } + + drop(open_containers); + + Some(matching_container_ids) + } + /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. @@ -303,6 +355,11 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } + /// Generates a new container id + pub fn new_container_id(&self) -> u32 { + self.container_id.fetch_add(1, Ordering::SeqCst) + } + pub fn get_branding(&self) -> CPluginMessage<'_> { self.server_branding.get_branding() } From 6a5add8de54de6c285cd61e6a81f6cfa26fc14d1 Mon Sep 17 00:00:00 2001 From: Kyle Davis Date: Fri, 27 Dec 2024 11:22:19 -0500 Subject: [PATCH 09/11] Added native operator permission management (#348) * Starting work on Operator permission system This commit adds the ops.json configuration found in vanilla servers, and updates the basic configuration to handle the default permission level. Server now uses ops file and defaults players to op level 0 The /op command has been added but needs players to rejoin for now. * Clippy Fix + need to Rejoin after /op removed I found the source of the DeadLock. Updated set permission function. Fix cargo formatting issues and clippy issues. * Remove temp warn message * Move OperatorConfig to server As Snowiii pointed out, OperatorConfig is runtime data. Revert most changes in pumpkin-config. op_permission_level must say in the basic configuration for parity with Minecraft. * cargo fmt + cargo clippy * Sync permission change with client * Fixs issues @Commandcracker found in review - Move PermissionLvl to core and removed OpLevel - Move ops.json to /data/ops.json - Fix permissions issue with /op command - Shorten /op command description * refactor into `data` folder * create data dir when needed * add to readme * fix: conflicts --------- Co-authored-by: Alexander Medvedev --- README.md | 1 + docker-compose.yml | 4 +- pumpkin-config/Cargo.toml | 1 + pumpkin-config/src/lib.rs | 6 +- pumpkin-config/src/op.rs | 27 ++++++ pumpkin-core/src/lib.rs | 2 + pumpkin-core/src/permission.rs | 56 +++++++++++++ pumpkin/src/command/commands/cmd_fill.rs | 3 +- pumpkin/src/command/commands/cmd_gamemode.rs | 2 +- pumpkin/src/command/commands/cmd_give.rs | 2 +- pumpkin/src/command/commands/cmd_kick.rs | 1 + pumpkin/src/command/commands/cmd_op.rs | 80 ++++++++++++++++++ pumpkin/src/command/commands/cmd_say.rs | 14 ++-- pumpkin/src/command/commands/cmd_seed.rs | 2 +- pumpkin/src/command/commands/cmd_setblock.rs | 2 +- pumpkin/src/command/commands/cmd_stop.rs | 2 +- pumpkin/src/command/commands/cmd_teleport.rs | 2 +- pumpkin/src/command/commands/cmd_time.rs | 10 +-- pumpkin/src/command/commands/cmd_transfer.rs | 2 +- pumpkin/src/command/commands/mod.rs | 1 + pumpkin/src/command/mod.rs | 9 +- pumpkin/src/data/mod.rs | 88 ++++++++++++++++++++ pumpkin/src/data/op_data.rs | 26 ++++++ pumpkin/src/entity/player.rs | 62 +++++++------- pumpkin/src/main.rs | 1 + 25 files changed, 346 insertions(+), 60 deletions(-) create mode 100644 pumpkin-config/src/op.rs create mode 100644 pumpkin-core/src/permission.rs create mode 100644 pumpkin/src/command/commands/cmd_op.rs create mode 100644 pumpkin/src/data/mod.rs create mode 100644 pumpkin/src/data/op_data.rs diff --git a/README.md b/README.md index f7113930..e6259515 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ and customizable experience. It prioritizes performance and player enjoyment whi - [x] Particles - [x] Chat - [x] Commands + - [x] OP Permission - Proxy - [x] Bungeecord - [x] Velocity diff --git a/docker-compose.yml b/docker-compose.yml index a8c019a0..418bdac0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,8 @@ services: pumpkin: build: . ports: - - 25565:25565 + - "25565:25565" volumes: - ./data:/pumpkin + stdin_open: true + tty: true diff --git a/pumpkin-config/Cargo.toml b/pumpkin-config/Cargo.toml index d3ce22e4..c9bf9ba8 100644 --- a/pumpkin-config/Cargo.toml +++ b/pumpkin-config/Cargo.toml @@ -7,5 +7,6 @@ edition.workspace = true pumpkin-core = { path = "../pumpkin-core" } serde.workspace = true log.workspace = true +uuid.workspace = true toml = "0.8" diff --git a/pumpkin-config/src/lib.rs b/pumpkin-config/src/lib.rs index 5e82ae33..94feda0b 100644 --- a/pumpkin-config/src/lib.rs +++ b/pumpkin-config/src/lib.rs @@ -1,6 +1,6 @@ use log::warn; use logging::LoggingConfig; -use pumpkin_core::{Difficulty, GameMode}; +use pumpkin_core::{Difficulty, GameMode, PermissionLvl}; use query::QueryConfig; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -29,6 +29,7 @@ pub use server_links::ServerLinksConfig; mod commands; pub mod compression; mod lan_broadcast; +pub mod op; mod pvp; mod rcon; mod server_links; @@ -79,6 +80,8 @@ pub struct BasicConfiguration { pub simulation_distance: NonZeroU8, /// The default game difficulty. pub default_difficulty: Difficulty, + /// The op level assign by the /op command + pub op_permission_level: PermissionLvl, /// Whether the Nether dimension is enabled. pub allow_nether: bool, /// Whether the server is in hardcore mode. @@ -109,6 +112,7 @@ impl Default for BasicConfiguration { view_distance: NonZeroU8::new(10).unwrap(), simulation_distance: NonZeroU8::new(10).unwrap(), default_difficulty: Difficulty::Normal, + op_permission_level: PermissionLvl::Four, allow_nether: true, hardcore: false, online_mode: true, diff --git a/pumpkin-config/src/op.rs b/pumpkin-config/src/op.rs new file mode 100644 index 00000000..edbd71bc --- /dev/null +++ b/pumpkin-config/src/op.rs @@ -0,0 +1,27 @@ +use pumpkin_core::permission::PermissionLvl; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct Op { + pub uuid: Uuid, + pub name: String, + pub level: PermissionLvl, + pub bypasses_player_limit: bool, +} + +impl Op { + pub fn new( + uuid: Uuid, + name: String, + level: PermissionLvl, + bypasses_player_limit: bool, + ) -> Self { + Self { + uuid, + name, + level, + bypasses_player_limit, + } + } +} diff --git a/pumpkin-core/src/lib.rs b/pumpkin-core/src/lib.rs index 8ffd25b2..8116211b 100644 --- a/pumpkin-core/src/lib.rs +++ b/pumpkin-core/src/lib.rs @@ -1,9 +1,11 @@ pub mod gamemode; pub mod math; +pub mod permission; pub mod random; pub mod text; pub use gamemode::GameMode; +pub use permission::PermissionLvl; use serde::{Deserialize, Serialize}; diff --git a/pumpkin-core/src/permission.rs b/pumpkin-core/src/permission.rs new file mode 100644 index 00000000..7229cbcb --- /dev/null +++ b/pumpkin-core/src/permission.rs @@ -0,0 +1,56 @@ +use num_derive::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Represents the player's permission level +/// +/// Permission levels determine the player's access to commands and server operations. +/// Each numeric level corresponds to a specific role: +/// - `Zero`: `normal`: Player can use basic commands. +/// - `One`: `moderator`: Player can bypass spawn protection. +/// - `Two`: `gamemaster`: Player or executor can use more commands and player can use command blocks. +/// - `Three`: `admin`: Player or executor can use commands related to multiplayer management. +/// - `Four`: `owner`: Player or executor can use all of the commands, including commands related to server management. +#[derive(FromPrimitive, ToPrimitive, Clone, Copy, Default, PartialEq, Eq)] +#[repr(i8)] +pub enum PermissionLvl { + #[default] + Zero = 0, + One = 1, + Two = 2, + Three = 3, + Four = 4, +} + +impl PartialOrd for PermissionLvl { + fn partial_cmp(&self, other: &Self) -> Option { + (*self as u8).partial_cmp(&(*other as u8)) + } +} + +impl Serialize for PermissionLvl { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_u8(*self as u8) + } +} + +impl<'de> Deserialize<'de> for PermissionLvl { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = u8::deserialize(deserializer)?; + match value { + 0 => Ok(PermissionLvl::Zero), + 2 => Ok(PermissionLvl::Two), + 3 => Ok(PermissionLvl::Three), + 4 => Ok(PermissionLvl::Four), + _ => Err(serde::de::Error::custom(format!( + "Invalid value for OpLevel: {}", + value + ))), + } + } +} diff --git a/pumpkin/src/command/commands/cmd_fill.rs b/pumpkin/src/command/commands/cmd_fill.rs index 140e8729..4f5e3d06 100644 --- a/pumpkin/src/command/commands/cmd_fill.rs +++ b/pumpkin/src/command/commands/cmd_fill.rs @@ -4,10 +4,11 @@ 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; + use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::vector3::Vector3; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::TextComponent; const NAMES: [&str; 1] = ["fill"]; diff --git a/pumpkin/src/command/commands/cmd_gamemode.rs b/pumpkin/src/command/commands/cmd_gamemode.rs index 2b627862..1b586130 100644 --- a/pumpkin/src/command/commands/cmd_gamemode.rs +++ b/pumpkin/src/command/commands/cmd_gamemode.rs @@ -3,8 +3,8 @@ use async_trait::async_trait; use crate::command::args::arg_gamemode::GamemodeArgumentConsumer; use crate::command::args::GetCloned; -use crate::entity::player::PermissionLvl; use crate::TextComponent; +use pumpkin_core::permission::PermissionLvl; use crate::command::args::arg_players::PlayersArgumentConsumer; diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs index cdc3334b..d3f91baf 100644 --- a/pumpkin/src/command/commands/cmd_give.rs +++ b/pumpkin/src/command/commands/cmd_give.rs @@ -9,7 +9,7 @@ use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName}; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, argument_default_name, require}; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["give"]; diff --git a/pumpkin/src/command/commands/cmd_kick.rs b/pumpkin/src/command/commands/cmd_kick.rs index 8d77bf3d..51832258 100644 --- a/pumpkin/src/command/commands/cmd_kick.rs +++ b/pumpkin/src/command/commands/cmd_kick.rs @@ -49,6 +49,7 @@ impl CommandExecutor for KickExecutor { } } +// TODO: Permission pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(KickExecutor)) diff --git a/pumpkin/src/command/commands/cmd_op.rs b/pumpkin/src/command/commands/cmd_op.rs new file mode 100644 index 00000000..614ffcc7 --- /dev/null +++ b/pumpkin/src/command/commands/cmd_op.rs @@ -0,0 +1,80 @@ +use crate::{ + command::{ + args::{arg_players::PlayersArgumentConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, require}, + CommandError, CommandExecutor, CommandSender, + }, + data::{op_data::OPERATOR_CONFIG, SaveJSONConfiguration}, +}; +use async_trait::async_trait; +use pumpkin_config::{op::Op, BASIC_CONFIG}; +use pumpkin_core::permission::PermissionLvl; +use pumpkin_core::text::TextComponent; +use CommandError::InvalidConsumption; + +const NAMES: [&str; 1] = ["op"]; +const DESCRIPTION: &str = "Grants operator status to a player."; +const ARG_TARGET: &str = "player"; + +struct OpExecutor; + +#[async_trait] +impl CommandExecutor for OpExecutor { + async fn execute<'a>( + &self, + sender: &mut CommandSender<'a>, + server: &crate::server::Server, + args: &ConsumedArgs<'a>, + ) -> Result<(), CommandError> { + let mut config = OPERATOR_CONFIG.write().await; + + let Some(Arg::Players(targets)) = args.get(&ARG_TARGET) else { + return Err(InvalidConsumption(Some(ARG_TARGET.into()))); + }; + + // log each player to the console. + for player in targets { + let new_level = if BASIC_CONFIG.op_permission_level > sender.permission_lvl() { + sender.permission_lvl() + } else { + BASIC_CONFIG.op_permission_level + }; + + let op_entry = Op::new( + player.gameprofile.id, + player.gameprofile.name.clone(), + new_level, + false, + ); + if let Some(op) = config + .ops + .iter_mut() + .find(|o| o.uuid == player.gameprofile.id) + { + op.level = new_level; + } else { + config.ops.push(op_entry); + } + config.save(); + + player + .set_permission_lvl(new_level, &server.command_dispatcher) + .await; + + let player_name = player.gameprofile.name.clone(); + let message = format!("Made {player_name} a server operator."); + let msg = TextComponent::text(&message); + sender.send_message(msg).await; + } + + Ok(()) + } +} + +pub fn init_command_tree() -> CommandTree { + CommandTree::new(NAMES, DESCRIPTION).with_child( + require(|sender| sender.has_permission_lvl(PermissionLvl::Three)) + .with_child(argument(ARG_TARGET, PlayersArgumentConsumer).execute(OpExecutor)), + ) +} diff --git a/pumpkin/src/command/commands/cmd_say.rs b/pumpkin/src/command/commands/cmd_say.rs index fad4b64b..ffeb9c88 100644 --- a/pumpkin/src/command/commands/cmd_say.rs +++ b/pumpkin/src/command/commands/cmd_say.rs @@ -2,15 +2,13 @@ use async_trait::async_trait; use pumpkin_core::text::TextComponent; use pumpkin_protocol::client::play::CSystemChatMessage; -use crate::{ - command::{ - args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs}, - tree::CommandTree, - tree_builder::{argument, require}, - CommandError, CommandExecutor, CommandSender, - }, - entity::player::PermissionLvl, +use crate::command::{ + args::{arg_message::MsgArgConsumer, Arg, ConsumedArgs}, + tree::CommandTree, + tree_builder::{argument, require}, + CommandError, CommandExecutor, CommandSender, }; +use pumpkin_core::permission::PermissionLvl; use CommandError::InvalidConsumption; const NAMES: [&str; 1] = ["say"]; diff --git a/pumpkin/src/command/commands/cmd_seed.rs b/pumpkin/src/command/commands/cmd_seed.rs index c4ffc375..50fe6ecd 100644 --- a/pumpkin/src/command/commands/cmd_seed.rs +++ b/pumpkin/src/command/commands/cmd_seed.rs @@ -2,8 +2,8 @@ use crate::command::tree_builder::require; use crate::command::{ args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, }; -use crate::entity::player::PermissionLvl; use async_trait::async_trait; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::click::ClickEvent; use pumpkin_core::text::hover::HoverEvent; use pumpkin_core::text::{color::NamedColor, TextComponent}; diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs index 8b7cf973..82e81392 100644 --- a/pumpkin/src/command/commands/cmd_setblock.rs +++ b/pumpkin/src/command/commands/cmd_setblock.rs @@ -8,7 +8,7 @@ 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; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["setblock"]; diff --git a/pumpkin/src/command/commands/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs index 9e3ada0b..6368988c 100644 --- a/pumpkin/src/command/commands/cmd_stop.rs +++ b/pumpkin/src/command/commands/cmd_stop.rs @@ -6,7 +6,7 @@ use crate::command::args::ConsumedArgs; use crate::command::tree::CommandTree; use crate::command::tree_builder::require; use crate::command::{CommandError, CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["stop"]; diff --git a/pumpkin/src/command/commands/cmd_teleport.rs b/pumpkin/src/command/commands/cmd_teleport.rs index d9468147..59ccc960 100644 --- a/pumpkin/src/command/commands/cmd_teleport.rs +++ b/pumpkin/src/command/commands/cmd_teleport.rs @@ -12,7 +12,7 @@ use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, literal, require}; use crate::command::CommandError; use crate::command::{CommandExecutor, CommandSender}; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 2] = ["teleport", "tp"]; const DESCRIPTION: &str = "Teleports entities, including players."; // todo diff --git a/pumpkin/src/command/commands/cmd_time.rs b/pumpkin/src/command/commands/cmd_time.rs index 669a3294..9cc45ec6 100644 --- a/pumpkin/src/command/commands/cmd_time.rs +++ b/pumpkin/src/command/commands/cmd_time.rs @@ -5,13 +5,11 @@ use pumpkin_core::text::TextComponent; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::FindArgDefaultName; use crate::command::tree_builder::{argument_default_name, literal}; -use crate::{ - command::{ - tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender, - ConsumedArgs, - }, - entity::player::PermissionLvl, +use crate::command::{ + tree::CommandTree, tree_builder::require, CommandError, CommandExecutor, CommandSender, + ConsumedArgs, }; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["time"]; diff --git a/pumpkin/src/command/commands/cmd_transfer.rs b/pumpkin/src/command/commands/cmd_transfer.rs index e35b0329..8aa41251 100644 --- a/pumpkin/src/command/commands/cmd_transfer.rs +++ b/pumpkin/src/command/commands/cmd_transfer.rs @@ -13,7 +13,7 @@ use crate::command::tree_builder::{argument, argument_default_name, require}; use crate::command::{ args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender, }; -use crate::entity::player::PermissionLvl; +use pumpkin_core::permission::PermissionLvl; const NAMES: [&str; 1] = ["transfer"]; diff --git a/pumpkin/src/command/commands/mod.rs b/pumpkin/src/command/commands/mod.rs index 6ea8c01a..5b463593 100644 --- a/pumpkin/src/command/commands/mod.rs +++ b/pumpkin/src/command/commands/mod.rs @@ -7,6 +7,7 @@ pub mod cmd_help; pub mod cmd_kick; pub mod cmd_kill; pub mod cmd_list; +pub mod cmd_op; pub mod cmd_pumpkin; pub mod cmd_say; pub mod cmd_seed; diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 11eea197..c05017ed 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -4,17 +4,19 @@ use std::sync::Arc; use crate::command::commands::cmd_seed; use crate::command::commands::{cmd_bossbar, cmd_transfer}; use crate::command::dispatcher::CommandDispatcher; -use crate::entity::player::{PermissionLvl, Player}; +use crate::entity::player::Player; use crate::server::Server; use crate::world::World; use args::ConsumedArgs; use async_trait::async_trait; +use commands::cmd_op; use commands::{ cmd_clear, cmd_fill, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, cmd_pumpkin, cmd_say, cmd_setblock, cmd_stop, cmd_teleport, cmd_time, cmd_worldborder, }; use dispatcher::CommandError; use pumpkin_core::math::vector3::Vector3; +use pumpkin_core::permission::PermissionLvl; use pumpkin_core::text::TextComponent; pub mod args; @@ -76,7 +78,7 @@ impl<'a> CommandSender<'a> { pub fn permission_lvl(&self) -> PermissionLvl { match self { CommandSender::Console | CommandSender::Rcon(_) => PermissionLvl::Four, - CommandSender::Player(p) => p.permission_lvl(), + CommandSender::Player(p) => p.permission_lvl.load(), } } @@ -84,7 +86,7 @@ impl<'a> CommandSender<'a> { pub fn has_permission_lvl(&self, lvl: PermissionLvl) -> bool { match self { CommandSender::Console | CommandSender::Rcon(_) => true, - CommandSender::Player(p) => (p.permission_lvl() as i8) >= (lvl as i8), + CommandSender::Player(p) => p.permission_lvl.load().ge(&lvl), } } @@ -128,6 +130,7 @@ pub fn default_dispatcher() -> CommandDispatcher { dispatcher.register(cmd_seed::init_command_tree()); dispatcher.register(cmd_transfer::init_command_tree()); dispatcher.register(cmd_fill::init_command_tree()); + dispatcher.register(cmd_op::init_command_tree()); dispatcher } diff --git a/pumpkin/src/data/mod.rs b/pumpkin/src/data/mod.rs new file mode 100644 index 00000000..7faf2d16 --- /dev/null +++ b/pumpkin/src/data/mod.rs @@ -0,0 +1,88 @@ +use std::{env, fs, path::Path}; + +use serde::{Deserialize, Serialize}; + +const DATA_FOLDER: &str = "data/"; + +pub mod op_data; + +pub trait LoadJSONConfiguration { + #[must_use] + fn load() -> Self + where + Self: Sized + Default + Serialize + for<'de> Deserialize<'de>, + { + let exe_dir = env::current_dir().unwrap(); + let data_dir = exe_dir.join(DATA_FOLDER); + if !data_dir.exists() { + log::debug!("creating new data root folder"); + fs::create_dir(&data_dir).expect("Failed to create data root folder"); + } + let path = data_dir.join(Self::get_path()); + + let config = if path.exists() { + let file_content = fs::read_to_string(&path) + .unwrap_or_else(|_| panic!("Couldn't read configuration file at {path:?}")); + + serde_json::from_str(&file_content).unwrap_or_else(|err| { + panic!( + "Couldn't parse data config at {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.", + ) + }) + } else { + let content = Self::default(); + + if let Err(err) = fs::write(&path, serde_json::to_string_pretty(&content).unwrap()) { + log::error!( + "Couldn't write default data config to {path:?}. Reason: {err}. This is probably caused by a config update. Just delete the old data config and restart.", + ); + } + + content + }; + + config.validate(); + config + } + + fn get_path() -> &'static Path; + + fn validate(&self); +} + +pub trait SaveJSONConfiguration: LoadJSONConfiguration { + // suppress clippy warning + + fn save(&self) + where + Self: Sized + Default + Serialize + for<'de> Deserialize<'de>, + { + let exe_dir = env::current_dir().unwrap(); + let data_dir = exe_dir.join(DATA_FOLDER); + if !data_dir.exists() { + log::debug!("creating new data root folder"); + fs::create_dir(&data_dir).expect("Failed to create data root folder"); + } + let path = data_dir.join(Self::get_path()); + + let content = match serde_json::to_string_pretty(self) { + Ok(content) => content, + Err(err) => { + log::warn!( + "Couldn't serialize operator data config to {:?}. Reason: {}", + path, + err + ); + return; + } + }; + + if let Err(err) = std::fs::write(&path, content) { + log::warn!( + "Couldn't write operator config to {:?}. Reason: {}", + path, + err + ); + } + } +} diff --git a/pumpkin/src/data/op_data.rs b/pumpkin/src/data/op_data.rs new file mode 100644 index 00000000..c1dada05 --- /dev/null +++ b/pumpkin/src/data/op_data.rs @@ -0,0 +1,26 @@ +use std::{path::Path, sync::LazyLock}; + +use pumpkin_config::op; +use serde::{Deserialize, Serialize}; + +use super::{LoadJSONConfiguration, SaveJSONConfiguration}; + +pub static OPERATOR_CONFIG: LazyLock> = + LazyLock::new(|| tokio::sync::RwLock::new(OperatorConfig::load())); + +#[derive(Deserialize, Serialize, Default)] +#[serde(transparent)] +pub struct OperatorConfig { + pub ops: Vec, +} + +impl LoadJSONConfiguration for OperatorConfig { + fn get_path() -> &'static Path { + Path::new("ops.json") + } + fn validate(&self) { + // TODO: Validate the operator configuration + } +} + +impl SaveJSONConfiguration for OperatorConfig {} diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index dbc84e74..eae83b3e 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -8,7 +8,7 @@ use std::{ }; use crossbeam::atomic::AtomicCell; -use num_derive::{FromPrimitive, ToPrimitive}; +use num_derive::FromPrimitive; use num_traits::Pow; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_core::{ @@ -18,6 +18,7 @@ use pumpkin_core::{ vector2::Vector2, vector3::Vector3, }, + permission::PermissionLvl, text::TextComponent, GameMode, }; @@ -54,11 +55,12 @@ use pumpkin_world::{ ItemStack, }, }; -use tokio::sync::{Mutex, Notify}; +use tokio::sync::{Mutex, Notify, RwLock}; use super::Entity; -use crate::{error::PumpkinError, net::GameProfile}; use crate::{ + command::{client_cmd_suggestions, dispatcher::CommandDispatcher}, + data::op_data::OPERATOR_CONFIG, net::{ combat::{self, player_attack_sound, AttackType}, Client, PlayerConfig, @@ -66,6 +68,7 @@ use crate::{ server::Server, world::World, }; +use crate::{error::PumpkinError, net::GameProfile}; use super::living::LivingEntity; @@ -116,12 +119,10 @@ pub struct Player { pub last_keep_alive_time: AtomicCell, /// Amount of ticks since last attack pub last_attacked_ticks: AtomicU32, - + /// The players op permission level + pub permission_lvl: AtomicCell, /// Tell tasks to stop if we are closing cancel_tasks: Notify, - - /// the players op permission level - permission_lvl: PermissionLvl, } impl Player { @@ -143,6 +144,8 @@ impl Player { }, |profile| profile, ); + + let gameprofile_clone = gameprofile.clone(); let config = client.config.lock().await.clone().unwrap_or_default(); let bounding_box_size = BoundingBoxSize { width: 0.6, @@ -186,8 +189,17 @@ impl Player { last_keep_alive_time: AtomicCell::new(std::time::Instant::now()), last_attacked_ticks: AtomicU32::new(0), cancel_tasks: Notify::new(), - // TODO: change this - permission_lvl: PermissionLvl::Four, + // Minecraft has no why to change the default permission level of new players. + // Minecrafts default permission level is 0 + permission_lvl: OPERATOR_CONFIG + .read() + .await + .ops + .iter() + .find(|op| op.uuid == gameprofile_clone.id) + .map_or(AtomicCell::new(PermissionLvl::Zero), |op| { + AtomicCell::new(op.level) + }), } } @@ -431,20 +443,20 @@ impl Player { self.client .send_packet(&CEntityStatus::new( self.entity_id(), - 24 + self.permission_lvl as i8, + 24 + self.permission_lvl.load() as i8, )) .await; } /// sets the players permission level and syncs it with the client - pub async fn set_permission_lvl(&mut self, lvl: PermissionLvl) { - self.permission_lvl = lvl; + pub async fn set_permission_lvl( + self: &Arc, + lvl: PermissionLvl, + command_dispatcher: &RwLock, + ) { + self.permission_lvl.store(lvl); self.send_permission_lvl_update().await; - } - - /// get the players permission level - pub fn permission_lvl(&self) -> PermissionLvl { - self.permission_lvl + client_cmd_suggestions::send_c_commands_packet(self, command_dispatcher).await; } /// Sends the world time to just the player. @@ -821,19 +833,3 @@ pub enum ChatMode { /// All messages should be hidden Hidden, } - -/// the player's permission level -#[derive(Debug, FromPrimitive, ToPrimitive, Clone, Copy)] -#[repr(i8)] -pub enum PermissionLvl { - /// `normal`: Player can use basic commands. - Zero = 0, - /// `moderator`: Player can bypass spawn protection. - One = 1, - /// `gamemaster`: Player or executor can use more commands and player can use command blocks. - Two = 2, - /// `admin`: Player or executor can use commands related to multiplayer management. - Three = 3, - /// `owner`: Player or executor can use all of the commands, including commands related to server management. - Four = 4, -} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 5009e96f..d0dbe401 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -55,6 +55,7 @@ use std::time::Instant; pub mod block; pub mod command; +pub mod data; pub mod entity; pub mod error; pub mod net; From 205191b1942c73550de0f81571e02645b3c6f6a4 Mon Sep 17 00:00:00 2001 From: Kris <37947442+OfficialKris@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:46:25 -0500 Subject: [PATCH 10/11] Added default op level (#426) --- pumpkin-config/src/commands.rs | 4 ++++ pumpkin/src/entity/player.rs | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pumpkin-config/src/commands.rs b/pumpkin-config/src/commands.rs index 23aa2711..bf686b0f 100644 --- a/pumpkin-config/src/commands.rs +++ b/pumpkin-config/src/commands.rs @@ -1,3 +1,4 @@ +use pumpkin_core::PermissionLvl; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] @@ -7,6 +8,8 @@ pub struct CommandsConfig { pub use_console: bool, /// Should be commands from players be logged in console? pub log_console: bool, // TODO: commands... + /// The op permission level of everyone that is not in the ops file + pub default_op_level: PermissionLvl, } impl Default for CommandsConfig { @@ -14,6 +17,7 @@ impl Default for CommandsConfig { Self { use_console: true, log_console: true, + default_op_level: PermissionLvl::Zero, } } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index eae83b3e..c197f58f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -197,9 +197,10 @@ impl Player { .ops .iter() .find(|op| op.uuid == gameprofile_clone.id) - .map_or(AtomicCell::new(PermissionLvl::Zero), |op| { - AtomicCell::new(op.level) - }), + .map_or( + AtomicCell::new(ADVANCED_CONFIG.commands.default_op_level), + |op| AtomicCell::new(op.level), + ), } } From ad2fd3c9a52a18986e6c9d6ee5399f21da1d39b7 Mon Sep 17 00:00:00 2001 From: Kris <37947442+OfficialKris@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:02:32 -0500 Subject: [PATCH 11/11] Added chest opening and closing animation (#421) * Added basic chest opening and closing * Added number tracking * Added play chest action function --- pumpkin-inventory/src/open_container.rs | 4 ++ .../src/client/play/c_block_event.rs | 31 ++++++++ pumpkin-protocol/src/client/play/mod.rs | 2 + pumpkin/src/block/blocks/chest.rs | 71 +++++++++++++++---- pumpkin/src/block/blocks/crafting_table.rs | 4 +- pumpkin/src/block/pumpkin_block.rs | 2 +- 6 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 pumpkin-protocol/src/client/play/c_block_event.rs diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 3025ed92..c563ee47 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -76,6 +76,10 @@ impl OpenContainer { self.players.clone() } + pub fn get_number_of_players(&self) -> usize { + self.players.len() + } + pub fn get_location(&self) -> Option { self.location } diff --git a/pumpkin-protocol/src/client/play/c_block_event.rs b/pumpkin-protocol/src/client/play/c_block_event.rs new file mode 100644 index 00000000..c1b2e6e0 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_block_event.rs @@ -0,0 +1,31 @@ +use pumpkin_core::math::position::WorldPosition; + +use pumpkin_macros::client_packet; +use serde::Serialize; + +use crate::VarInt; + +#[derive(Serialize)] +#[client_packet("play:block_event")] +pub struct CBlockAction<'a> { + location: &'a WorldPosition, + action_id: u8, + action_parameter: u8, + block_type: VarInt, +} + +impl<'a> CBlockAction<'a> { + pub fn new( + location: &'a WorldPosition, + action_id: u8, + action_parameter: u8, + block_type: VarInt, + ) -> Self { + Self { + location, + action_id, + action_parameter, + block_type, + } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 79f7e3ea..68a7cc57 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -2,6 +2,7 @@ mod bossevent_action; mod c_acknowledge_block; mod c_actionbar; mod c_block_destroy_stage; +mod c_block_event; mod c_block_update; mod c_boss_event; mod c_center_chunk; @@ -72,6 +73,7 @@ pub use bossevent_action::*; pub use c_acknowledge_block::*; pub use c_actionbar::*; pub use c_block_destroy_stage::*; +pub use c_block_event::*; pub use c_block_update::*; pub use c_boss_event::*; pub use c_center_chunk::*; diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index 313d9a5b..5a83570e 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -2,7 +2,11 @@ use async_trait::async_trait; use pumpkin_core::math::position::WorldPosition; use pumpkin_inventory::{Chest, OpenContainer, WindowType}; use pumpkin_macros::{pumpkin_block, sound}; -use pumpkin_world::{block::block_registry::Block, item::item_registry::Item}; +use pumpkin_protocol::{client::play::CBlockAction, codec::var_int::VarInt}; +use pumpkin_world::{ + block::block_registry::{get_block, Block}, + item::item_registry::Item, +}; use crate::{ block::{block_manager::BlockActionResult, pumpkin_block::PumpkinBlock}, @@ -10,6 +14,12 @@ use crate::{ server::Server, }; +#[derive(PartialEq)] +pub enum ChestState { + IsOpened, + IsClosed, +} + #[pumpkin_block("minecraft:chest")] pub struct ChestBlock; @@ -24,10 +34,6 @@ impl PumpkinBlock for ChestBlock { ) { self.open_chest_block(block, player, _location, server) .await; - player - .world() - .play_block_sound(sound!("block.chest.open"), _location) - .await; } async fn on_use_with_item<'a>( @@ -57,15 +63,14 @@ impl PumpkinBlock for ChestBlock { &self, _block: &Block, player: &Player, - _location: WorldPosition, - _server: &Server, - _container: &OpenContainer, + location: WorldPosition, + server: &Server, + container: &mut OpenContainer, ) { - player - .world() - .play_block_sound(sound!("block.chest.close"), _location) + container.remove_player(player.entity_id()); + + self.play_chest_action(container, player, location, server, ChestState::IsClosed) .await; - // TODO: send entity updates close } } @@ -86,6 +91,46 @@ impl ChestBlock { WindowType::Generic9x3, ) .await; - // TODO: send entity updates open + + if let Some(container_id) = server.get_container_id(location, block.clone()).await { + let open_containers = server.open_containers.read().await; + if let Some(container) = open_containers.get(&u64::from(container_id)) { + self.play_chest_action(container, player, location, server, ChestState::IsOpened) + .await; + } + } + } + + pub async fn play_chest_action( + &self, + container: &OpenContainer, + player: &Player, + location: WorldPosition, + server: &Server, + state: ChestState, + ) { + let num_players = container.get_number_of_players() as u8; + if state == ChestState::IsClosed && num_players == 0 { + player + .world() + .play_block_sound(sound!("block.chest.close"), location) + .await; + } else if state == ChestState::IsOpened && num_players == 1 { + player + .world() + .play_block_sound(sound!("block.chest.open"), location) + .await; + } + + if let Some(e) = get_block("minecraft:chest").cloned() { + server + .broadcast_packet_all(&CBlockAction::new( + &location, + 1, + num_players, + VarInt(e.id.into()), + )) + .await; + } } } diff --git a/pumpkin/src/block/blocks/crafting_table.rs b/pumpkin/src/block/blocks/crafting_table.rs index 01ad4179..c879f060 100644 --- a/pumpkin/src/block/blocks/crafting_table.rs +++ b/pumpkin/src/block/blocks/crafting_table.rs @@ -53,7 +53,7 @@ impl PumpkinBlock for CraftingTableBlock { player: &Player, _location: WorldPosition, _server: &Server, - container: &OpenContainer, + container: &mut OpenContainer, ) { let entity_id = player.entity_id(); for player_id in container.all_player_ids() { @@ -62,6 +62,8 @@ impl PumpkinBlock for CraftingTableBlock { } } + container.remove_player(entity_id); + // TODO: items should be re-added to player inventory or dropped dependending on if they are in movement. // TODO: unique containers should be implemented as a separate stack internally (optimizes large player servers for example) // TODO: ephemeral containers (crafting tables) might need to be separate data structure than stored (ender chest) diff --git a/pumpkin/src/block/pumpkin_block.rs b/pumpkin/src/block/pumpkin_block.rs index c0a65c31..90de5718 100644 --- a/pumpkin/src/block/pumpkin_block.rs +++ b/pumpkin/src/block/pumpkin_block.rs @@ -60,7 +60,7 @@ pub trait PumpkinBlock: Send + Sync { _player: &Player, _location: WorldPosition, _server: &Server, - _container: &OpenContainer, + _container: &mut OpenContainer, ) { } }