diff --git a/Cargo.lock b/Cargo.lock index c0706d615..bc97011ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2064,9 +2064,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2312,9 +2312,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2338,7 +2338,7 @@ dependencies = [ [[package]] name = "rand" version = "0.10.0-rc.7" -source = "git+https://github.com/rust-random/rand#6e80534233d4bb09ed0abc086edeacaae6f54dc0" +source = "git+https://github.com/rust-random/rand#7a81944044f3f1d2dc5c85a8ca9bd30429f46bd0" dependencies = [ "chacha20", "getrandom 0.4.0-rc.0", @@ -3302,9 +3302,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index fa7fac69d..68025883d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ futures = { version = "0.3", default-features = false, features = ["executor"] } rayon = "1.11" crossbeam = "0.8" -uuid = { version = "1.19", features = ["serde", "v3", "v4"] } +uuid = { version = "1.20", features = ["serde", "v3", "v4"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/pumpkin-util/src/text/README.md b/pumpkin-util/src/text/README.md deleted file mode 100644 index 2a827977e..000000000 --- a/pumpkin-util/src/text/README.md +++ /dev/null @@ -1,45 +0,0 @@ -### Pumpkin Text -Here we build Mojang's Textcomponent, Which is used across many places, Often where text is send to the player. - -### Features -- Colors - - [x] RBG - - [x] Black - - [x] Dark Blue - - [x] Dark Green - - [x] Dark Aqua - - [x] Dark Red - - [x] Dark Purple - - [x] Gold - - [x] Gray - - [x] Dark Gray - - [x] Blue - - [x] Green - - [x] Aqua - - [x] Red - - [x] Light Purple - - [x] Yellow - - [x] White -- Style - - [x] Bold - - [x] Italic - - [x] Underlined - - [x] Strikethrough - - [x] Obfuscated - - [x] Insertion - - [x] Shadow Color -- Click Event - - [x] Open URL - - [x] Run Command - - [x] Suggest Command - - [x] Change Pange (Book) - - [x] Copy to Clipboard -- Hover Event - - [x] Show Text - - [x] Show Item - - [x] ShowEntity -- Fonts - - [x] Default - - [x] Uniform (Unicode) - - [x] Alt - - [x] Illageralt diff --git a/pumpkin-util/src/text/color.rs b/pumpkin-util/src/text/color.rs index 3f04fbe42..75ee42f72 100644 --- a/pumpkin-util/src/text/color.rs +++ b/pumpkin-util/src/text/color.rs @@ -16,6 +16,25 @@ pub enum Color { Named(NamedColor), } +pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) { + let c = v * s; + let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); + let m = v - c; + let (r, g, b) = match (h as i32 / 60) % 6 { + 0 => (c, x, 0.0), + 1 => (x, c, 0.0), + 2 => (0.0, c, x), + 3 => (0.0, x, c), + 4 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + ( + ((r + m) * 255.0) as u8, + ((g + m) * 255.0) as u8, + ((b + m) * 255.0) as u8, + ) +} + impl<'de> Deserialize<'de> for Color { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; @@ -75,9 +94,9 @@ impl Color { #[derive(Debug, Deserialize, Clone, Copy, Eq, Hash, PartialEq)] pub struct RGBColor { - red: u8, - green: u8, - blue: u8, + pub red: u8, + pub green: u8, + pub blue: u8, } impl RGBColor { @@ -142,6 +161,29 @@ pub enum NamedColor { White, } +impl NamedColor { + pub fn to_rgb(&self) -> RGBColor { + match self { + NamedColor::Black => RGBColor::new(0, 0, 0), + NamedColor::DarkBlue => RGBColor::new(0, 0, 170), + NamedColor::DarkGreen => RGBColor::new(0, 170, 0), + NamedColor::DarkAqua => RGBColor::new(0, 170, 170), + NamedColor::DarkRed => RGBColor::new(170, 0, 0), + NamedColor::DarkPurple => RGBColor::new(170, 0, 170), + NamedColor::Gold => RGBColor::new(255, 170, 0), + NamedColor::Gray => RGBColor::new(170, 170, 170), + NamedColor::DarkGray => RGBColor::new(85, 85, 85), + NamedColor::Blue => RGBColor::new(85, 85, 255), + NamedColor::Green => RGBColor::new(85, 255, 85), + NamedColor::Aqua => RGBColor::new(85, 255, 255), + NamedColor::Red => RGBColor::new(255, 85, 85), + NamedColor::LightPurple => RGBColor::new(255, 85, 255), + NamedColor::Yellow => RGBColor::new(255, 255, 85), + NamedColor::White => RGBColor::new(255, 255, 255), + } + } +} + impl TryFrom<&str> for NamedColor { type Error = (); diff --git a/pumpkin-util/src/text/mod.rs b/pumpkin-util/src/text/mod.rs index 35becbeca..213bffb25 100644 --- a/pumpkin-util/src/text/mod.rs +++ b/pumpkin-util/src/text/mod.rs @@ -1,4 +1,4 @@ -use crate::text::color::ARGBColor; +use crate::text::color::{ARGBColor, hsv_to_rgb}; use crate::translation::{ Locale, get_translation, get_translation_text, reorder_substitutions, translation_to_pretty, }; @@ -352,6 +352,82 @@ impl TextComponent { self } + /// Applies a color gradient to the text + pub fn gradient_named(self, colors: &[color::NamedColor]) -> Self { + let rgb_colors: Vec = colors.iter().map(|c| c.to_rgb()).collect(); + self.gradient(&rgb_colors) + } + + /// Applies a color gradient to the text + pub fn gradient(self, colors: &[color::RGBColor]) -> Self { + if colors.len() < 2 { + return self; + } + + self.apply_color_effect(|i, len| { + if len <= 1 { + return colors[0]; + } + let total_segments = colors.len() - 1; + let position = i as f32 / (len - 1) as f32; + let segment_f = position * total_segments as f32; + let segment_index = (segment_f.floor() as usize).min(total_segments - 1); + + let local_t = segment_f - segment_index as f32; + let start = colors[segment_index]; + let end = colors[segment_index + 1]; + + // LERP logic + color::RGBColor::new( + (start.red as f32 + (end.red as f32 - start.red as f32) * local_t) as u8, + (start.green as f32 + (end.green as f32 - start.green as f32) * local_t) as u8, + (start.blue as f32 + (end.blue as f32 - start.blue as f32) * local_t) as u8, + ) + }) + } + + /// Applies a rainbow effect to the text + pub fn rainbow(self) -> Self { + self.apply_color_effect(|i, len| { + let hue = (i as f32 / len as f32) * 360.0; + let (r, g, b) = hsv_to_rgb(hue, 1.0, 1.0); + color::RGBColor::new(r, g, b) + }) + } + + fn apply_color_effect(mut self, color_gen: F) -> Self + where + F: Fn(usize, usize) -> color::RGBColor, + { + // TODO + let raw_text = self.0.clone().get_text(Locale::EnUs); + let chars: Vec = raw_text.chars().collect(); + let len = chars.len(); + + if len == 0 { + return self; + } + + let mut colored_extra = Vec::new(); + for (i, c) in chars.into_iter().enumerate() { + let rgb = color_gen(i, len); + + let mut char_base = TextComponentBase { + content: TextContent::Text { + text: Cow::Owned(c.to_string()), + }, + style: self.0.style.clone(), + extra: vec![], + }; + char_base.style.color = Some(Color::Rgb(rgb)); + colored_extra.push(char_base); + } + + self.0.content = TextContent::Text { text: "".into() }; + self.0.extra = colored_extra; + self + } + /// Makes the text bold pub fn bold(mut self) -> Self { self.0.style.bold = Some(true); diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 1925e282c..ba8ee7a8d 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -886,7 +886,6 @@ impl ProtoChunk { block_registry: &dyn BlockRegistryExt, random_config: &GlobalRandomConfig, ) { - // 1. Efficiently collect unique biomes from the flat map let (center_x, center_z, min_y, height, biomes_in_chunk) = { let chunk = cache.get_center_chunk(); let mut unique_biomes = Vec::with_capacity(4); // Usually few biomes per chunk @@ -911,13 +910,9 @@ impl ProtoChunk { let population_seed = Xoroshiro::get_population_seed(random_config.seed, start_block_x, start_block_z); - // Vanilla has 11 steps (0..10) for step in 0..11 { - // --- STEP A: STRUCTURE PIECES --- - // Structures are handled first because features (like trees) need to avoid clipping into them Self::generate_structure_step(cache, step, population_seed, random_config.seed as i64); - // --- STEP B: PLACED FEATURES --- let mut features_to_run = Vec::new(); for biome_id in &biomes_in_chunk { if let Some(biome) = Biome::from_id(*biome_id) @@ -930,7 +925,6 @@ impl ProtoChunk { } } - // CRITICAL: Determinism. Sort features so the index 'p' always refers to the same feature. features_to_run.sort_unstable(); features_to_run.dedup(); diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index 199ddbedc..6379ffe6a 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -24,7 +24,7 @@ pub type BlockStateId = u16; pub const CURRENT_MC_VERSION: &str = "1.21.11"; pub const LOWEST_SUPPRORTED_PROTOCOL_VERSION: u32 = 773; -pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.130"; +pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.132"; pub const CURRENT_BEDROCK_MC_PROTOCOL: u32 = 898; #[macro_export] diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index af6a33644..61f73145e 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -381,6 +381,8 @@ pub struct Player { pub keep_alive_id: AtomicI64, /// The last time we sent a keep alive packet. pub last_keep_alive_time: AtomicCell, + /// The ping in millis. + pub ping: AtomicU32, /// The amount of ticks since the player's last attack. pub last_attacked_ticks: AtomicU32, /// The player's last known experience level. @@ -486,6 +488,7 @@ impl Player { wait_for_keep_alive: AtomicBool::new(false), keep_alive_id: AtomicI64::new(0), last_keep_alive_time: AtomicCell::new(std::time::Instant::now()), + ping: AtomicU32::new(0), last_attacked_ticks: AtomicU32::new(0), client_loaded: AtomicBool::new(false), client_loaded_timeout: AtomicU32::new(60), @@ -1402,15 +1405,8 @@ impl Player { self.set_client_loaded(false); let uuid = self.gameprofile.id; - current_world.remove_player(self, false).await; - new_world.players.write().await.insert(uuid, Arc::new(Self::new( - self.client.clone(), - self.gameprofile.clone(), - self.config.read().await.clone(), - new_world.clone(), - self.gamemode.load(), - ) - .await)); + let player = current_world.remove_player(self, false).await.unwrap(); + new_world.players.write().await.insert(uuid, player); self.unload_watched_chunks(¤t_world).await; self.chunk_manager.lock().await.change_world(¤t_world.level, &new_world.level); diff --git a/pumpkin/src/lib.rs b/pumpkin/src/lib.rs index 162301d9c..5bf3baef9 100644 --- a/pumpkin/src/lib.rs +++ b/pumpkin/src/lib.rs @@ -2,18 +2,16 @@ #![allow(unused_labels)] use crate::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper}; -use crate::net::DisconnectReason; use crate::net::bedrock::BedrockClient; -use crate::net::java::JavaClient; +use crate::net::java::{JavaClient, PacketHandlerResult}; +use crate::net::{ClientPlatform, DisconnectReason}; use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer}; use crate::server::{Server, ticker::Ticker}; use log::{Level, LevelFilter}; -use net::authentication::fetch_mojang_public_keys; use plugin::PluginManager; use plugin::server::server_command::ServerCommandEvent; use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; use pumpkin_macros::send_cancellable; -use pumpkin_protocol::ConnectionState::Play; use pumpkin_util::permission::{PermissionManager, PermissionRegistry}; use pumpkin_util::text::TextComponent; use rustyline::Editor; @@ -279,13 +277,6 @@ impl PumpkinServer { tcp_listener = Some(listener); } - if server.basic_config.allow_chat_reports { - let mojang_public_keys = - fetch_mojang_public_keys(&server.advanced_config.networking.authentication) - .unwrap(); - *server.mojang_public_keys.lock().await = mojang_public_keys; - } - // Ticker { let ticker_server = server.clone(); @@ -405,33 +396,36 @@ impl PumpkinServer { format!("{client_addr}") }; log::debug!("Accepted connection from Java Edition: {formatted_address} (id {client_id})"); - - let mut java_client = JavaClient::new(connection, client_addr, client_id); - java_client.start_outgoing_packet_task(); - let java_client = Arc::new(java_client); - let server_clone = self.server.clone(); tasks.spawn(async move { - java_client.process_packets(&server_clone).await; - java_client.close(); - java_client.await_tasks().await; + let mut java_client = JavaClient::new(connection, client_addr, client_id); + java_client.start_outgoing_packet_task(); + let login_result = java_client.handle_login_sequence(&server_clone).await; - let player = java_client.player.lock().await; - if let Some(player) = player.as_ref() { - log::debug!("Cleaning up player for id {client_id}"); - - if let Err(e) = server_clone.player_data_storage - .handle_player_leave(player) - .await + match login_result { + PacketHandlerResult::Stop => { + java_client.close(); + java_client.await_tasks().await; + }, + PacketHandlerResult::ReadyToPlay(profile,config) => { + if let Some((player, world)) = server_clone + .add_player(ClientPlatform::Java(java_client), profile, Some(config)) + .await { - log::error!("Failed to save player data on disconnect: {e}"); + world + .spawn_java_player(&server_clone.basic_config, &player, &server_clone) + .await; + if let ClientPlatform::Java(client) = &player.client { + client.progress_player_packets(&player, &server_clone).await; + // Close when done + client.close(); + client.await_tasks().await; + } + player.remove().await; + server_clone.remove_player(&player).await; } - - player.remove().await; - server_clone.remove_player(player).await; - } else if java_client.connection_state.load() == Play { - log::error!("No player found for id {client_id}. This should not happen!"); + }, } }); } diff --git a/pumpkin/src/net/java/config.rs b/pumpkin/src/net/java/config.rs index 5a1808385..86b8a85f6 100644 --- a/pumpkin/src/net/java/config.rs +++ b/pumpkin/src/net/java/config.rs @@ -1,8 +1,11 @@ -use std::{num::NonZeroU8, sync::Arc}; +use std::num::NonZeroU8; use crate::{ entity::player::ChatMode, - net::{ClientPlatform, PlayerConfig, can_not_join, java::JavaClient}, + net::{ + PlayerConfig, can_not_join, + java::{JavaClient, PacketHandlerResult}, + }, server::Server, }; use core::str; @@ -189,7 +192,7 @@ impl JavaClient { self.send_packet_now(&CFinishConfig).await; } - pub async fn handle_config_acknowledged(self: &Arc, server: &Server) { + pub async fn handle_config_acknowledged(&self, server: &Server) -> PacketHandlerResult { log::debug!("Handling config acknowledgement"); self.connection_state.store(ConnectionState::Play); @@ -199,19 +202,10 @@ impl JavaClient { if let Some(reason) = can_not_join(&profile, &address, server).await { self.kick(reason).await; - return; + return PacketHandlerResult::Stop; } let config = self.config.lock().await; - - if let Some((player, world)) = server - .add_player(ClientPlatform::Java(self.clone()), profile, config.clone()) - .await - { - world - .spawn_java_player(&server.basic_config, player.clone(), server) - .await; - *self.player.lock().await = Some(player); - } + PacketHandlerResult::ReadyToPlay(profile, config.clone().unwrap_or_default()) } } diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index 6472d264a..0d1dcfb55 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -79,7 +79,6 @@ pub struct JavaClient { pub address: Mutex, /// The client's brand or modpack information, Optional. pub brand: Mutex>, - pub player: Mutex>>, /// A collection of tasks associated with this client. The tasks await completion when removing the client. tasks: TaskTracker, /// An notifier that is triggered when this client is closed. @@ -94,6 +93,12 @@ pub struct JavaClient { network_reader: Mutex>>, } +pub enum PacketHandlerResult { + Stop, + // Signal to spawn the player + ReadyToPlay(GameProfile, PlayerConfig), +} + impl JavaClient { #[must_use] pub fn new(tcp_stream: TcpStream, address: SocketAddr, id: u64) -> Self { @@ -114,7 +119,6 @@ impl JavaClient { network_writer: Arc::new(Mutex::new(TCPNetworkEncoder::new(BufWriter::new(write)))), network_reader: Mutex::new(TCPNetworkDecoder::new(BufReader::new(read))), brand: Mutex::new(None), - player: Mutex::new(None), } } pub async fn set_encryption( @@ -158,16 +162,45 @@ impl JavaClient { /// # Arguments /// /// * `server`: A reference to the `Server` instance. - pub async fn process_packets(self: &Arc, server: &Arc) { + pub async fn handle_login_sequence(&self, server: &Server) -> PacketHandlerResult { while let Some(packet) = self.get_packet().await { - if let Err(error) = self.handle_packet(server, &packet).await { - let text = format!("Error while reading incoming packet {error}"); - log::error!( - "Failed to read incoming packet with id {}: {}", - packet.id, - error - ); - self.kick(TextComponent::text(text)).await; + match self.handle_packet(server, &packet).await { + Ok(result) => { + if let Some(result) = result { + return result; + } + } + Err(error) => { + let text = format!("Error while reading incoming packet {error}"); + log::error!( + "Failed to read incoming packet with id {}: {}", + packet.id, + error + ); + self.kick(TextComponent::text(text)).await; + } + } + } + PacketHandlerResult::Stop + } + + pub async fn progress_player_packets(&self, player: &Arc, server: &Arc) { + while let Some(packet) = self.get_packet().await { + match self.handle_play_packet(player, server, &packet).await { + Ok(()) => {} + Err(e) => { + if e.is_kick() { + if let Some(kick_reason) = e.client_kick_reason() { + self.kick(TextComponent::text(kick_reason)).await; + } else { + self.kick(TextComponent::text(format!( + "Error while handling incoming packet {e}" + ))) + .await; + } + } + e.log(); + } } } } @@ -327,10 +360,10 @@ impl JavaClient { /// /// Returns a `DeserializerError` if an error occurs during packet deserialization. pub async fn handle_packet( - self: &Arc, - server: &Arc, + &self, + server: &Server, packet: &RawPacket, - ) -> Result<(), ReadingError> { + ) -> Result, ReadingError> { match self.connection_state.load() { ConnectionState::HandShake => self.handle_handshake_packet(packet).await, ConnectionState::Status => self.handle_status_packet(server, packet).await, @@ -339,37 +372,20 @@ impl JavaClient { self.handle_login_packet(server, packet).await } ConnectionState::Config => self.handle_config_packet(server, packet).await, - ConnectionState::Play => { - if let Some(player) = self.player.lock().await.as_ref() { - match self.handle_play_packet(player, server, packet).await { - Ok(()) => {} - Err(e) => { - if e.is_kick() { - if let Some(kick_reason) = e.client_kick_reason() { - self.kick(TextComponent::text(kick_reason)).await; - } else { - self.kick(TextComponent::text(format!( - "Error while handling incoming packet {e}" - ))) - .await; - } - } - e.log(); - } - } - } - Ok(()) - } + ConnectionState::Play => Ok(None), } } - async fn handle_handshake_packet(&self, packet: &RawPacket) -> Result<(), ReadingError> { + async fn handle_handshake_packet( + &self, + packet: &RawPacket, + ) -> Result, ReadingError> { log::debug!("Handling handshake group"); let payload = &packet.payload[..]; match packet.id { 0 => { self.handle_handshake(SHandShake::read(payload)?).await; - Ok(()) + Ok(None) } _ => Err(ReadingError::Message(format!( "Failed to handle packet id {} in Handshake State", @@ -382,18 +398,18 @@ impl JavaClient { &self, server: &Server, packet: &RawPacket, - ) -> Result<(), ReadingError> { + ) -> Result, ReadingError> { log::debug!("Handling status group"); let payload = &packet.payload[..]; match packet.id { SStatusRequest::PACKET_ID => { self.handle_status_request(server).await; - Ok(()) + Ok(None) } SStatusPingRequest::PACKET_ID => { self.handle_ping_request(SStatusPingRequest::read(payload)?) .await; - Ok(()) + Ok(None) } _ => Err(ReadingError::Message(format!( "Failed to handle java client packet id {} in Status State", @@ -456,7 +472,7 @@ impl JavaClient { &self, server: &Server, packet: &RawPacket, - ) -> Result<(), ReadingError> { + ) -> Result, ReadingError> { log::debug!("Handling login group for id"); let payload = &packet.payload[..]; match packet.id { @@ -485,14 +501,14 @@ impl JavaClient { ); } } - Ok(()) + Ok(None) } async fn handle_config_packet( - self: &Arc, + &self, server: &Server, packet: &RawPacket, - ) -> Result<(), ReadingError> { + ) -> Result, ReadingError> { log::debug!("Handling config group"); let payload = &packet.payload[..]; match packet.id { @@ -505,7 +521,7 @@ impl JavaClient { .await; } SAcknowledgeFinishConfig::PACKET_ID => { - self.handle_config_acknowledged(server).await; + return Ok(Some(self.handle_config_acknowledged(server).await)); } SKnownPacks::PACKET_ID => { self.handle_known_packs(SKnownPacks::read(payload)?).await; @@ -524,7 +540,7 @@ impl JavaClient { ); } } - Ok(()) + Ok(None) } #[expect(clippy::too_many_lines)] diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index 5dd7dc8fd..f8767a2f0 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -1407,6 +1407,12 @@ impl JavaClient { if player.wait_for_keep_alive.load(Ordering::Relaxed) && keep_alive.keep_alive_id == player.keep_alive_id.load(Ordering::Relaxed) { + let ping = player.last_keep_alive_time.load().elapsed(); + // Vanilla logic + player.ping.store( + (player.ping.load(Ordering::Relaxed) * 3 + ping.as_millis() as u32) / 4, + Ordering::Relaxed, + ); player.wait_for_keep_alive.store(false, Ordering::Relaxed); } else { self.kick(TextComponent::text( diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs index c9918a2dc..b068a6b4f 100644 --- a/pumpkin/src/net/mod.rs +++ b/pumpkin/src/net/mod.rs @@ -86,15 +86,10 @@ impl Default for PlayerConfig { } } -pub enum PacketHandlerState { - PacketReady, - Stop, -} - /// This is just a Wrapper for both Java & Bedrock connections -#[derive(Clone)] +#[expect(clippy::large_enum_variant)] pub enum ClientPlatform { - Java(Arc), + Java(JavaClient), Bedrock(Arc), } @@ -119,7 +114,7 @@ impl ClientPlatform { /// This function should only be used where you know that the client is java! #[inline] #[must_use] - pub fn java(&self) -> &Arc { + pub fn java(&self) -> &JavaClient { if let Self::Java(client) = self { return client; } diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 49b3ebafb..0a309a78a 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -4,6 +4,7 @@ use crate::command::commands::defaultgamemode::DefaultGamemode; use crate::data::player_server::ServerPlayerData; use crate::entity::{EntityBase, NBTStorage}; use crate::item::registry::ItemRegistry; +use crate::net::authentication::fetch_mojang_public_keys; use crate::net::{ClientPlatform, DisconnectReason, EncryptionError, GameProfile, PlayerConfig}; use crate::plugin::player::player_login::PlayerLoginEvent; use crate::plugin::server::server_broadcast::ServerBroadcastEvent; @@ -39,7 +40,7 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32}; use std::{future::Future, sync::atomic::Ordering, time::Duration}; use tokio::sync::{Mutex, OnceCell, RwLock}; -use tokio::task::JoinHandle; +use tokio::task::{JoinHandle, JoinSet}; use tokio_util::task::TaskTracker; mod connection_cache; @@ -172,6 +173,12 @@ impl Server { let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps)); + let mojang_public_keys = if basic_config.allow_chat_reports { + fetch_mojang_public_keys(&advanced_config.networking.authentication).unwrap() + } else { + Vec::new() + }; + let server = Self { basic_config, advanced_config, @@ -198,7 +205,7 @@ impl Server { tick_count: AtomicI32::new(0), tasks: TaskTracker::new(), server_guid: rand::random(), - mojang_public_keys: Mutex::new(Vec::new()), + mojang_public_keys: Mutex::new(mojang_public_keys), world_info_writer: Arc::new(AnvilLevelInfo), level_info: level_info.clone(), _locker: Arc::new(locker), @@ -697,21 +704,21 @@ impl Server { } /// Ticks the game logic for all worlds. This is the part that is affected by `/tick freeze`. pub async fn tick_worlds(self: &Arc) { - let worlds = self.worlds.read().await.clone(); - let mut handles = Vec::with_capacity(worlds.len()); - for world in &worlds { + let worlds = self.worlds.read().await; + let mut set = JoinSet::new(); + + for world in worlds.iter() { let world = world.clone(); let server = self.clone(); - handles.push(tokio::spawn(async move { + + set.spawn(async move { world.tick(&server).await; - })); - } - for handle in handles { - // Wait for all world ticks to complete - let _ = handle.await; + }); } - // Global periodic tasks + set.join_all().await; + + // Global tasks if let Err(e) = self.player_data_storage.tick(self).await { log::error!("Error ticking player data: {e}"); } diff --git a/pumpkin/src/world/border.rs b/pumpkin/src/world/border.rs index e402df494..93a483c0a 100644 --- a/pumpkin/src/world/border.rs +++ b/pumpkin/src/world/border.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use pumpkin_protocol::java::client::play::{ CInitializeWorldBorder, CSetBorderCenter, CSetBorderLerpSize, CSetBorderSize, CSetBorderWarningDelay, CSetBorderWarningDistance, @@ -46,7 +44,7 @@ impl Worldborder { } } - pub async fn init_client(&self, client: &Arc) { + pub async fn init_client(&self, client: &JavaClient) { client .enqueue_packet(&CInitializeWorldBorder::new( self.center_x, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index dada84cb3..60094cb3e 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1427,7 +1427,7 @@ impl World { pub async fn spawn_java_player( &self, base_config: &BasicConfiguration, - player: Arc, + player: &Arc, server: &Server, ) { let dimensions: Vec = server @@ -1478,10 +1478,7 @@ impl World { .await; // Send the current ticking state to the new player so they are in sync. - server - .tick_rate_manager - .update_joining_player(&player) - .await; + server.tick_rate_manager.update_joining_player(player).await; // Permissions, i.e. the commands a player may use. player.send_permission_lvl_update().await; @@ -1490,12 +1487,12 @@ impl World { player.send_difficulty_update().await; { let command_dispatcher = server.command_dispatcher.read().await; - client_suggestions::send_c_commands_packet(&player, &command_dispatcher).await; + client_suggestions::send_c_commands_packet(player, &command_dispatcher).await; }; // Spawn in initial chunks // This is made before the player teleport so that the player doesn't glitch out when spawning - chunker::update_position(&player).await; + chunker::update_position(player).await; // Teleport let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) { @@ -1775,7 +1772,7 @@ impl World { .await; player.send_active_effects().await; - self.send_player_equipment(&player).await; + self.send_player_equipment(player).await; } async fn send_player_equipment(&self, from: &Player) { @@ -2370,41 +2367,40 @@ impl World { /// /// - This function assumes `broadcast_packet_expect` and `remove_entity` are defined elsewhere. /// - The disconnect message sending is currently optional. Consider making it a configurable option. - pub async fn remove_player(&self, player: &Arc, fire_event: bool) { - if self - .players - .write() - .await - .remove(&player.gameprofile.id) - .is_none() - { - return; - } - let uuid = player.gameprofile.id; - self.broadcast_packet_all(&CRemovePlayerInfo::new(&[uuid])) - .await; - self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()])) - .await; + pub async fn remove_player( + &self, + player: &Arc, + fire_event: bool, + ) -> Option> { + let player = self.players.write().await.remove(&player.gameprofile.id); + if let Some(player) = player { + let uuid = player.gameprofile.id; + self.broadcast_packet_all(&CRemovePlayerInfo::new(&[uuid])) + .await; + self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()])) + .await; - if fire_event { - let msg_comp = TextComponent::translate( - "multiplayer.player.left", - [TextComponent::text(player.gameprofile.name.clone())], - ) - .color_named(NamedColor::Yellow); - let event = PlayerLeaveEvent::new(player.clone(), msg_comp); + if fire_event { + let msg_comp = TextComponent::translate( + "multiplayer.player.left", + [TextComponent::text(player.gameprofile.name.clone())], + ) + .color_named(NamedColor::Yellow); + let event = PlayerLeaveEvent::new(player.clone(), msg_comp); - let event = PLUGIN_MANAGER.fire(event).await; + let event = PLUGIN_MANAGER.fire(event).await; - if !event.cancelled { - let players = self.players.read().await; - for player in players.values() { - player.send_system_message(&event.leave_message).await; + if !event.cancelled { + let players = self.players.read().await; + for player in players.values() { + player.send_system_message(&event.leave_message).await; + } + drop(players); + log::info!("{}", event.leave_message.to_pretty_console()); } - drop(players); - log::info!("{}", event.leave_message.to_pretty_console()); } } + None } /// Adds an entity to the world.