216 lines
7.8 KiB
Rust
216 lines
7.8 KiB
Rust
use pumpkin_data::packet::clientbound::PLAY_LOGIN;
|
|
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion};
|
|
|
|
use pumpkin_macros::java_packet;
|
|
|
|
use crate::{
|
|
ClientPacket, VarInt,
|
|
java::client::play::player_spawn_data::PlayerSpawnData,
|
|
ser::{NetworkWriteExt, WritingError},
|
|
};
|
|
|
|
/// The "Join Game" packet that transitions the client from the Configuration state
|
|
/// to the Play state.
|
|
///
|
|
/// 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.
|
|
#[java_packet(PLAY_LOGIN)]
|
|
pub struct CLogin<'a> {
|
|
/// 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 max_players: VarInt,
|
|
/// The number of chunks the client will render in each direction.
|
|
pub view_distance: VarInt,
|
|
/// The distance at which entities and world ticks are processed.
|
|
pub simulated_distance: VarInt,
|
|
/// If true, hides coordinates and other info from the F3 screen.
|
|
pub reduced_debug_info: bool,
|
|
pub enabled_respawn_screen: bool,
|
|
pub limited_crafting: bool,
|
|
// Spawn info
|
|
pub spawn_data: PlayerSpawnData,
|
|
pub online_mode: bool,
|
|
/// If true, the client will warn the player if they send unsigned chat messages.
|
|
pub enforce_secure_chat: bool,
|
|
}
|
|
|
|
impl<'a> CLogin<'a> {
|
|
#[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],
|
|
max_players: VarInt,
|
|
view_distance: VarInt,
|
|
simulated_distance: VarInt,
|
|
reduced_debug_info: bool,
|
|
enabled_respawn_screen: bool,
|
|
limited_crafting: bool,
|
|
spawn_data: PlayerSpawnData,
|
|
online_mode: bool,
|
|
enforce_secure_chat: bool,
|
|
) -> Self {
|
|
Self {
|
|
entity_id,
|
|
is_hardcore,
|
|
dimension_names,
|
|
max_players,
|
|
view_distance,
|
|
simulated_distance,
|
|
reduced_debug_info,
|
|
enabled_respawn_screen,
|
|
limited_crafting,
|
|
spawn_data,
|
|
online_mode,
|
|
enforce_secure_chat,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn build_v1_20_registry_codec(
|
|
version: JavaMinecraftVersion,
|
|
) -> pumpkin_nbt::compound::NbtCompound {
|
|
use pumpkin_nbt::compound::NbtCompound;
|
|
use pumpkin_nbt::deserializer::NbtReadHelperJava;
|
|
use pumpkin_nbt::tag::NbtTag;
|
|
use std::io::Cursor;
|
|
|
|
let mut root = NbtCompound::new();
|
|
let synced = pumpkin_data::registry::Registry::get_synced(version);
|
|
|
|
for reg in synced {
|
|
let reg_name = if let Some(suffix) = reg.registry_id.strip_prefix("minecraft:") {
|
|
suffix
|
|
} else {
|
|
®.registry_id
|
|
};
|
|
if version < JavaMinecraftVersion::V_1_20_2
|
|
&& !matches!(
|
|
reg_name,
|
|
"dimension_type"
|
|
| "worldgen/biome"
|
|
| "chat_type"
|
|
| "damage_type"
|
|
| "trim_pattern"
|
|
| "trim_material"
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let mut reg_compound = NbtCompound::new();
|
|
let reg_type = if reg.registry_id.contains(':') {
|
|
reg.registry_id.clone()
|
|
} else {
|
|
format!("minecraft:{}", reg.registry_id)
|
|
};
|
|
reg_compound.put("type", NbtTag::String(reg_type.clone().into()));
|
|
|
|
let mut values_list = Vec::new();
|
|
for (i, entry) in reg.registry_entries.iter().enumerate() {
|
|
let mut entry_compound = NbtCompound::new();
|
|
entry_compound.put("name", NbtTag::String(entry.entry_id.clone().into()));
|
|
entry_compound.put("id", NbtTag::Int(i as i32));
|
|
|
|
if let Some(ref data) = entry.data {
|
|
let mut cursor = Cursor::new(&data[..]);
|
|
let mut reader = NbtReadHelperJava::new(&mut cursor);
|
|
if let Ok(element_nbt) = pumpkin_nbt::Nbt::read_unnamed(&mut reader) {
|
|
entry_compound.put("element", NbtTag::Compound(element_nbt.root_tag));
|
|
}
|
|
}
|
|
values_list.push(NbtTag::Compound(entry_compound));
|
|
}
|
|
|
|
reg_compound.put("value", NbtTag::List(values_list));
|
|
root.put(®_type, NbtTag::Compound(reg_compound));
|
|
}
|
|
|
|
root
|
|
}
|
|
|
|
impl ClientPacket for CLogin<'_> {
|
|
fn write_packet_data(
|
|
&self,
|
|
mut write: impl std::io::Write,
|
|
version: &JavaMinecraftVersion,
|
|
) -> Result<(), WritingError> {
|
|
if version < &JavaMinecraftVersion::V_1_20_2 {
|
|
write.write_i32_be(self.entity_id)?;
|
|
write.write_bool(self.is_hardcore)?;
|
|
write.write_u8(self.spawn_data.game_mode)?;
|
|
write.write_i8(self.spawn_data.previous_gamemode)?;
|
|
write.write_list(self.dimension_names, |write, dim| write.write_string(dim))?;
|
|
|
|
let registry_codec = build_v1_20_registry_codec(*version);
|
|
let nbt_bytes = pumpkin_nbt::Nbt::new(String::new(), registry_codec).write();
|
|
write.write_all(&nbt_bytes)?;
|
|
|
|
write.write_string(self.spawn_data.dimension.minecraft_name)?;
|
|
write.write_string(self.spawn_data.dimension.minecraft_name)?;
|
|
write.write_i64_be(self.spawn_data.hashed_seed)?;
|
|
write.write_var_int(&self.max_players)?;
|
|
write.write_var_int(&self.view_distance)?;
|
|
write.write_var_int(&self.simulated_distance)?;
|
|
write.write_bool(self.reduced_debug_info)?;
|
|
write.write_bool(self.enabled_respawn_screen)?;
|
|
write.write_bool(self.limited_crafting)?;
|
|
write.write_bool(self.spawn_data.debug)?;
|
|
write.write_bool(self.spawn_data.is_flat)?;
|
|
write.write_option(
|
|
&self.spawn_data.death_dimension_name,
|
|
|write, (dim, pos)| {
|
|
write.write_string(dim)?;
|
|
write.write_block_pos(pos)?;
|
|
Ok(())
|
|
},
|
|
)?;
|
|
if version >= &JavaMinecraftVersion::V_1_20 {
|
|
write.write_var_int(&self.spawn_data.portal_cooldown)?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
write.write_i32_be(self.entity_id)?;
|
|
write.write_bool(self.is_hardcore)?;
|
|
if version >= &JavaMinecraftVersion::V_1_16 {
|
|
write.write_list(self.dimension_names, |write, dim| write.write_string(dim))?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_16 {
|
|
write.write_var_int(&self.max_players)?;
|
|
} else {
|
|
write.write_u8(self.max_players.0 as u8)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_14 {
|
|
write.write_var_int(&self.view_distance)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_18 {
|
|
write.write_var_int(&self.simulated_distance)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_8 {
|
|
write.write_bool(self.reduced_debug_info)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_15 {
|
|
write.write_bool(self.enabled_respawn_screen)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_19_3 {
|
|
write.write_bool(self.limited_crafting)?;
|
|
}
|
|
self.spawn_data.write_packet_data(&mut write, version)?;
|
|
if version >= &JavaMinecraftVersion::V_26_2 {
|
|
write.write_bool(self.online_mode)?;
|
|
}
|
|
if version >= &JavaMinecraftVersion::V_1_19_1 {
|
|
write.write_bool(self.enforce_secure_chat)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|