chore: added gradient and rainbow to TextComponent
ofc did also a bunch of other things, the JavaClient implementation should be cleaner now and does not lock the player anymore
This commit is contained in:
parent
2853a1e741
commit
622d0599df
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
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 = ();
|
||||
|
||||
|
||||
@ -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<color::RGBColor> = 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<F>(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<char> = 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);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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<Instant>,
|
||||
/// 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);
|
||||
|
||||
@ -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;
|
||||
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;
|
||||
|
||||
match login_result {
|
||||
PacketHandlerResult::Stop => {
|
||||
java_client.close();
|
||||
java_client.await_tasks().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)
|
||||
},
|
||||
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;
|
||||
} else if java_client.connection_state.load() == Play {
|
||||
log::error!("No player found for id {client_id}. This should not happen!");
|
||||
server_clone.remove_player(&player).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<Self>, 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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,6 @@ pub struct JavaClient {
|
||||
pub address: Mutex<SocketAddr>,
|
||||
/// The client's brand or modpack information, Optional.
|
||||
pub brand: Mutex<Option<String>>,
|
||||
pub player: Mutex<Option<Arc<Player>>>,
|
||||
/// 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<TCPNetworkDecoder<BufReader<OwnedReadHalf>>>,
|
||||
}
|
||||
|
||||
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,9 +162,15 @@ impl JavaClient {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `server`: A reference to the `Server` instance.
|
||||
pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) {
|
||||
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 {
|
||||
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 {}: {}",
|
||||
@ -171,6 +181,29 @@ impl JavaClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
PacketHandlerResult::Stop
|
||||
}
|
||||
|
||||
pub async fn progress_player_packets(&self, player: &Arc<Player>, server: &Arc<Server>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn await_tasks(&self) {
|
||||
self.tasks.close();
|
||||
@ -327,10 +360,10 @@ impl JavaClient {
|
||||
///
|
||||
/// Returns a `DeserializerError` if an error occurs during packet deserialization.
|
||||
pub async fn handle_packet(
|
||||
self: &Arc<Self>,
|
||||
server: &Arc<Server>,
|
||||
&self,
|
||||
server: &Server,
|
||||
packet: &RawPacket,
|
||||
) -> Result<(), ReadingError> {
|
||||
) -> Result<Option<PacketHandlerResult>, 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<Option<PacketHandlerResult>, 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<Option<PacketHandlerResult>, 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<Option<PacketHandlerResult>, 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>,
|
||||
&self,
|
||||
server: &Server,
|
||||
packet: &RawPacket,
|
||||
) -> Result<(), ReadingError> {
|
||||
) -> Result<Option<PacketHandlerResult>, 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)]
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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<JavaClient>),
|
||||
Java(JavaClient),
|
||||
Bedrock(Arc<BedrockClient>),
|
||||
}
|
||||
|
||||
@ -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<JavaClient> {
|
||||
pub fn java(&self) -> &JavaClient {
|
||||
if let Self::Java(client) = self {
|
||||
return client;
|
||||
}
|
||||
|
||||
@ -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<Self>) {
|
||||
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}");
|
||||
}
|
||||
|
||||
@ -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<JavaClient>) {
|
||||
pub async fn init_client(&self, client: &JavaClient) {
|
||||
client
|
||||
.enqueue_packet(&CInitializeWorldBorder::new(
|
||||
self.center_x,
|
||||
|
||||
@ -1427,7 +1427,7 @@ impl World {
|
||||
pub async fn spawn_java_player(
|
||||
&self,
|
||||
base_config: &BasicConfiguration,
|
||||
player: Arc<Player>,
|
||||
player: &Arc<Player>,
|
||||
server: &Server,
|
||||
) {
|
||||
let dimensions: Vec<ResourceLocation> = 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,16 +2367,13 @@ 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<Player>, fire_event: bool) {
|
||||
if self
|
||||
.players
|
||||
.write()
|
||||
.await
|
||||
.remove(&player.gameprofile.id)
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
pub async fn remove_player(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
fire_event: bool,
|
||||
) -> Option<Arc<Player>> {
|
||||
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;
|
||||
@ -2406,6 +2400,8 @@ impl World {
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Adds an entity to the world.
|
||||
pub async fn spawn_entity(&self, entity: Arc<dyn EntityBase>) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user