chore: add uuid to world

This commit is contained in:
Alexander Medvedev
2026-01-31 15:24:11 +01:00
parent 67fc80439d
commit 452d8c413b
4 changed files with 107 additions and 11 deletions

View File

@@ -1,10 +1,16 @@
use std::fmt::Formatter;
use pumpkin_util::math::vector3::Vector3;
use crate::{
VarInt,
ser::{NetworkWriteExt, WritingError},
ser::{NetworkWriteExt, ReadingError, WritingError},
};
use serde::{
Deserializer, Serialize,
de::{self, Visitor},
ser::Serializer,
};
use serde::{Serialize, ser::Serializer};
#[derive(Clone, Copy)]
pub struct Velocity(pub Vector3<f64>);
@@ -58,6 +64,52 @@ impl Velocity {
Ok(())
}
pub fn read<R: std::io::Read>(reader: &mut R) -> Result<Self, ReadingError> {
let mut low_16 = [0u8; 2];
reader
.read_exact(&mut low_16)
.map_err(|e| ReadingError::Message(e.to_string()))?;
if low_16[0] == 0 && low_16[1] == 0 {
return Ok(Self(Vector3::new(0.0, 0.0, 0.0)));
}
let mut mid_32 = [0u8; 4];
reader
.read_exact(&mut mid_32)
.map_err(|e| ReadingError::Message(e.to_string()))?;
let low = u16::from_le_bytes(low_16) as i64;
let mid = i32::from_be_bytes(mid_32) as i64;
let packed_data = low | (mid << 16);
let header = packed_data & 0x07;
let is_extended = (header & 4) != 0;
let scale_factor = if is_extended {
let scale_tail = VarInt::decode(reader)?;
((scale_tail.0 as i64) << 2) | (header & 3)
} else {
header & 3
};
if scale_factor == 0 && !is_extended {
return Ok(Self(Vector3::new(0.0, 0.0, 0.0)));
}
let q_x = (packed_data >> 3) & 0x7FFF;
let q_y = (packed_data >> 18) & 0x7FFF;
let q_z = (packed_data >> 33) & 0x7FFF;
let scale = scale_factor as f64;
Ok(Self(Vector3::new(
from_long(q_x, scale),
from_long(q_y, scale),
from_long(q_z, scale),
)))
}
}
const MAX_VELOCITY_CLAMP: f64 = 1.717_986_918_3E10;
@@ -79,6 +131,12 @@ fn to_long(value: f64) -> i64 {
((value.mul_add(0.5, 0.5) * MAX_15_BIT_VALUE).round() as i64).clamp(0, 32766)
}
fn from_long(quantized: i64, scale: f64) -> f64 {
// Reverse: ((v * 0.5 + 0.5) * 32766) -> v
let normalized = (quantized as f64 / MAX_15_BIT_VALUE) - 0.5;
(normalized / 0.5) * scale
}
impl Serialize for Velocity {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buf = Vec::new();
@@ -86,3 +144,30 @@ impl Serialize for Velocity {
serializer.serialize_bytes(&buf)
}
}
impl<'de> de::Deserialize<'de> for Velocity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VelocityVisitor;
impl Visitor<'_> for VelocityVisitor {
type Value = Velocity;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a byte array representing bit-packed velocity")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut cursor = std::io::Cursor::new(v);
Velocity::read(&mut cursor).map_err(de::Error::custom)
}
}
deserializer.deserialize_bytes(VelocityVisitor)
}
}

View File

@@ -1,7 +1,7 @@
use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION;
use pumpkin_macros::java_packet;
use pumpkin_util::math::vector3::Vector3;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use crate::{VarInt, codec::velocity::Velocity};
@@ -9,7 +9,7 @@ use crate::{VarInt, codec::velocity::Velocity};
///
/// This packet informs the client of a sudden change in an entity's movement,
/// such as knockback from an attack, explosions, or being launched by a piston.
#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
#[java_packet(PLAY_SET_ENTITY_MOTION)]
pub struct CEntityVelocity {
/// The Entity ID of the entity whose velocity is being set

View File

@@ -2,7 +2,7 @@ use pumpkin_data::packet::clientbound::PLAY_LOGIN;
use pumpkin_util::{math::position::BlockPos, resource_location::ResourceLocation};
use pumpkin_macros::java_packet;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use crate::VarInt;
@@ -12,14 +12,14 @@ use crate::VarInt;
/// This is one of the largest and most important packets in the protocol. It
/// initializes the player's world view, dimension settings, and local game
/// rules. Once received, the client begins rendering the world.
#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
#[java_packet(PLAY_LOGIN)]
pub struct CLogin<'a> {
pub struct CLogin {
/// The unique ID assigned to the player for the current session.
pub entity_id: i32,
pub is_hardcore: bool,
/// A list of all dimensions present on the server (e.g., overworld, nether, end).
pub dimension_names: &'a [ResourceLocation],
pub dimension_names: Vec<ResourceLocation>,
pub max_players: VarInt,
/// The number of chunks the client will render in each direction.
pub view_distance: VarInt,
@@ -52,14 +52,14 @@ pub struct CLogin<'a> {
pub enforce_secure_chat: bool,
}
impl<'a> CLogin<'a> {
impl CLogin {
#[expect(clippy::too_many_arguments)]
#[expect(clippy::fn_params_excessive_bools)]
#[must_use]
pub const fn new(
entity_id: i32,
is_hardcore: bool,
dimension_names: &'a [ResourceLocation],
dimension_names: Vec<ResourceLocation>,
max_players: VarInt,
view_distance: VarInt,
simulated_distance: VarInt,

View File

@@ -165,6 +165,8 @@ impl PumpkinError for GetBlockError {
/// - Stores and tracks active `Player` entities within the world.
/// - Provides a central hub for interacting with the world's entities and environment.
pub struct World {
/// Represents the World's Unique Identifier
pub uuid: Uuid,
/// The underlying level, responsible for chunk management and terrain generation.
pub level: Arc<Level>,
pub level_info: Arc<ArcSwap<LevelData>>,
@@ -197,6 +199,14 @@ pub struct World {
pub portal_poi: Mutex<portal::PortalPoiStorage>,
}
impl PartialEq for World {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for World {}
impl World {
#[must_use]
pub fn load(
@@ -213,6 +223,7 @@ impl World {
let portal_poi = portal::PortalPoiStorage::new(&level.level_folder.root_folder);
Self {
uuid: Uuid::new_v4(),
level,
level_info,
players: ArcSwap::new(Arc::new(Vec::new())),
@@ -1445,7 +1456,7 @@ impl World {
.send_packet_now(&CLogin::new(
entity_id,
base_config.hardcore,
&dimensions,
dimensions,
base_config.max_players.try_into().unwrap(),
base_config.view_distance.get().into(), // TODO: view distance
base_config.simulation_distance.get().into(), // TODO: sim view dinstance