From c3c4aff91228b0084c34837f05be612b8cfeb0a4 Mon Sep 17 00:00:00 2001 From: Alvsch <94403567+Alvsch@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:00:34 +0100 Subject: [PATCH] Consistent registry values and player changes (#324) * Made registry values consistent * Port to own nbt crate --------- Co-authored-by: Snowiiii --- pumpkin-registry/Cargo.toml | 2 + pumpkin-registry/src/lib.rs | 47 +++++++--- pumpkin/src/client/player_packet.rs | 4 +- pumpkin/src/entity/living.rs | 8 +- pumpkin/src/entity/player.rs | 122 ++++---------------------- pumpkin/src/main.rs | 2 +- pumpkin/src/server/mod.rs | 21 +++-- pumpkin/src/world/mod.rs | 127 ++++++++++++++++++++++++++-- 8 files changed, 194 insertions(+), 139 deletions(-) diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index 7b6565326..929a7e8e6 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -8,6 +8,8 @@ pumpkin-protocol = { path = "../pumpkin-protocol" } pumpkin-nbt = { path = "../pumpkin-nbt" } pumpkin-core = { path = "../pumpkin-core" } +indexmap = { version = "2.6.0", features = ["serde"] } + serde.workspace = true serde_json.workspace = true diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index 4ee22412c..46c0726c1 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::LazyLock}; +use std::sync::LazyLock; use banner_pattern::BannerPattern; use biome::Biome; @@ -6,6 +6,7 @@ use chat_type::ChatType; use damage_type::DamageType; use dimension::Dimension; use enchantment::Enchantment; +use indexmap::IndexMap; use instrument::Instrument; use jukebox_song::JukeboxSong; use paint::Painting; @@ -47,29 +48,49 @@ pub struct Registry { #[derive(Serialize, Deserialize)] pub struct SyncedRegistry { #[serde(rename = "minecraft:worldgen/biome")] - biome: HashMap, + biome: IndexMap, #[serde(rename = "minecraft:chat_type")] - chat_type: HashMap, + chat_type: IndexMap, #[serde(rename = "minecraft:trim_pattern")] - trim_pattern: HashMap, + trim_pattern: IndexMap, #[serde(rename = "minecraft:trim_material")] - trim_material: HashMap, + trim_material: IndexMap, #[serde(rename = "minecraft:wolf_variant")] - wolf_variant: HashMap, + wolf_variant: IndexMap, #[serde(rename = "minecraft:painting_variant")] - painting_variant: HashMap, + painting_variant: IndexMap, #[serde(rename = "minecraft:dimension_type")] - dimension_type: HashMap, + dimension_type: IndexMap, #[serde(rename = "minecraft:damage_type")] - damage_type: HashMap, + damage_type: IndexMap, #[serde(rename = "minecraft:banner_pattern")] - banner_pattern: HashMap, + banner_pattern: IndexMap, #[serde(rename = "minecraft:enchantment")] - enchantment: HashMap, + enchantment: IndexMap, #[serde(rename = "minecraft:jukebox_song")] - jukebox_song: HashMap, + jukebox_song: IndexMap, #[serde(rename = "minecraft:instrument")] - instrument: HashMap, + instrument: IndexMap, +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum DimensionType { + Overworld, + OverworldCaves, + TheEnd, + TheNether, +} + +impl DimensionType { + pub fn name(&self) -> &str { + match self { + Self::Overworld => "minecraft:overworld", + Self::OverworldCaves => "minecraft:overworld_caves", + Self::TheEnd => "minecraft:the_end", + Self::TheNether => "minecraft:the_nether", + } + } } impl Registry { diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 297b7c499..9a3194c99 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -361,7 +361,7 @@ impl Player { Hand::Off => Animation::SwingOffhand, }; let id = self.entity_id(); - let world = &self.living_entity.entity.world; + let world = self.world(); world .broadcast_packet_except( &[self.gameprofile.id], @@ -449,7 +449,7 @@ impl Player { if self.living_entity.health.load() > 0.0 { return; } - self.respawn(false).await; + self.world().respawn_player(self, false).await; // TODO: hardcore set spectator } 1 => { diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 50e3ca33b..2980eaf5e 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -64,13 +64,13 @@ impl LivingEntity { .await; } - pub async fn damage(&self, amount: f32) { + // TODO add damage_type enum + pub async fn damage(&self, amount: f32, damage_type: u8) { self.entity .world .broadcast_packet_all(&CDamageEvent::new( self.entity.entity_id.into(), - // TODO add damage_type id - 0.into(), + damage_type.into(), None, None, None, @@ -130,7 +130,7 @@ impl LivingEntity { return; } - self.damage(damage).await; + self.damage(damage, 10).await; // Fall } else if y_diff < 0.0 { self.fall_distance.store(0.0); } else { diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 0d7201b6d..c6a4f9121 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -24,14 +24,13 @@ use pumpkin_core::{ use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_inventory::player::PlayerInventory; use pumpkin_macros::sound; -use pumpkin_protocol::client::play::{CSetEntityMetadata, Metadata}; use pumpkin_protocol::server::play::{SClickContainer, SKeepAlive}; use pumpkin_protocol::{ bytebuf::packet_id::Packet, client::play::{ CCombatDeath, CEntityStatus, CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect, - CPlayerAbilities, CPlayerInfoUpdate, CRespawn, CSetHealth, CSpawnEntity, - CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction, + CPlayerAbilities, CPlayerInfoUpdate, CSetHealth, CSyncPlayerPosition, CSystemChatMessage, + GameEvent, PlayerAction, }, server::play::{ SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd, @@ -53,7 +52,7 @@ use crate::{ Client, PlayerConfig, }, server::Server, - world::{player_chunker, World}, + world::World, }; use crate::{error::PumpkinError, world::player_chunker::get_view_distance}; @@ -225,7 +224,7 @@ impl Player { /// Removes the Player out of the current World #[allow(unused_variables)] pub async fn remove(&self) { - let world = &self.living_entity.entity.world; + let world = self.world(); // Abort pending chunks here too because we might clean up before chunk tasks are done self.abort_chunks("closed"); @@ -343,14 +342,14 @@ impl Player { "Removed player id {} ({}) ({} chunks remain cached)", self.gameprofile.name, self.client.id, - self.living_entity.entity.world.get_cached_chunk_len() + self.world().get_cached_chunk_len() ); - //self.living_entity.entity.world.level.list_cached(); + //self.world().level.list_cached(); } pub async fn attack(&self, victim: &Arc) { - let world = &self.living_entity.entity.world; + let world = self.world(); let victim_entity = &victim.living_entity.entity; let attacker_entity = &self.living_entity.entity; let config = &ADVANCED_CONFIG.pvp; @@ -392,7 +391,10 @@ impl Player { damage *= 1.5; } - victim.living_entity.damage(damage).await; + victim + .living_entity + .damage(damage, 34) // PlayerAttack + .await; let mut knockback_strength = 1.0; match attack_type { @@ -473,6 +475,10 @@ impl Player { self.living_entity.entity.entity_id } + pub const fn world(&self) -> &Arc { + &self.living_entity.entity.world + } + /// Updates the current abilities the Player has pub async fn send_abilties_update(&self) { let mut b = 0i8; @@ -520,104 +526,6 @@ impl Player { self.permission_lvl } - pub async fn respawn(self: &Arc, alive: bool) { - let last_pos = self.living_entity.last_pos.load(); - let death_location = WorldPosition(Vector3::new( - last_pos.x.round() as i32, - last_pos.y.round() as i32, - last_pos.z.round() as i32, - )); - - let data_kept = u8::from(alive); - - self.client - .send_packet(&CRespawn::new( - 0.into(), - "minecraft:overworld", - 0, // seed - self.gamemode.load() as u8, - self.gamemode.load() as i8, - false, - false, - Some(("minecraft:overworld", death_location)), - 0.into(), - 0.into(), - data_kept, - )) - .await; - - log::debug!("Sending player abilities to {}", self.gameprofile.name); - self.send_abilties_update().await; - - self.send_permission_lvl_update().await; - - let world = &self.living_entity.entity.world; - - // teleport - let x = 10.0; - let z = 10.0; - let top = world.get_top_block(Vector2::new(x as i32, z as i32)).await; - let position = Vector3::new(x, f64::from(top + 1), z); - let yaw = 10.0; - let pitch = 10.0; - - log::debug!("Sending player teleport to {}", self.gameprofile.name); - self.teleport(position, yaw, pitch).await; - - self.living_entity.last_pos.store(position); - - // TODO: difficulty, exp bar, status effect - - let world = &self.living_entity.entity.world; - world - .worldborder - .lock() - .await - .init_client(&self.client) - .await; - - // TODO: world spawn (compass stuff) - - self.client - .send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0)) - .await; - - let entity = &self.living_entity.entity; - let entity_id = entity.entity_id; - - let skin_parts = self.config.lock().await.skin_parts; - let entity_metadata_packet = - CSetEntityMetadata::new(entity_id.into(), Metadata::new(17, VarInt(0), &skin_parts)); - - world - .broadcast_packet_except( - &[self.gameprofile.id], - // TODO: add velo - &CSpawnEntity::new( - entity.entity_id.into(), - self.gameprofile.id, - (EntityType::Player as i32).into(), - position.x, - position.y, - position.z, - pitch, - yaw, - yaw, - 0.into(), - 0.0, - 0.0, - 0.0, - ), - ) - .await; - - player_chunker::player_join(world, self.clone()).await; - world.broadcast_packet_all(&entity_metadata_packet).await; - // update commands - - self.set_health(20.0, 20, 20.0).await; - } - /// yaw and pitch in degrees pub async fn teleport(&self, position: Vector3, yaw: f32, pitch: f32) { // this is the ultra special magic code used to create the teleport id diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 736363b98..a936f2e2c 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -301,7 +301,7 @@ async fn main() -> io::Result<()> { { let (player, world) = server.add_player(client).await; world - .spawn_player(&BASIC_CONFIG, player.clone(), &server.command_dispatcher) + .spawn_player(&BASIC_CONFIG, player.clone(), &server) .await; // poll Player diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 491aefc2b..184547a5d 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -7,7 +7,7 @@ use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; -use pumpkin_registry::Registry; +use pumpkin_registry::{DimensionType, Registry}; use pumpkin_world::dimension::Dimension; use rand::prelude::SliceRandom; use std::collections::HashMap; @@ -46,6 +46,8 @@ pub struct Server { pub command_dispatcher: Arc>, /// Manages multiple worlds within the server. pub worlds: Vec>, + // All the dimensions that exists on the server, + pub dimensions: Vec, /// Caches game registries for efficient access. pub cached_registry: Vec, /// Tracks open containers used for item interactions. @@ -77,10 +79,13 @@ impl Server { // First register default command, after that plugins can put in their own let command_dispatcher = default_dispatcher(); - let world = World::load(Dimension::OverWorld.into_level( - // TODO: load form config - "./world".parse().unwrap(), - )); + let world = World::load( + Dimension::OverWorld.into_level( + // TODO: load form config + "./world".parse().unwrap(), + ), + DimensionType::Overworld, + ); Self { cached_registry: Registry::get_synced(), open_containers: RwLock::new(HashMap::new()), @@ -88,6 +93,12 @@ impl Server { // 0 is invalid entity_id: 2.into(), worlds: vec![Arc::new(world)], + dimensions: vec![ + DimensionType::Overworld, + DimensionType::OverworldCaves, + DimensionType::TheNether, + DimensionType::TheEnd, + ], command_dispatcher, auth_client, key_store: KeyStore::new(), diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b7772b649..a6e326f90 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -6,20 +6,22 @@ use std::{ pub mod player_chunker; use crate::{ - command::{client_cmd_suggestions, dispatcher::CommandDispatcher}, + command::client_cmd_suggestions, entity::{ player::{ChunkHandleWrapper, Player}, Entity, }, error::PumpkinError, + server::Server, }; +use itertools::Itertools; use pumpkin_config::BasicConfiguration; use pumpkin_core::math::vector2::Vector2; use pumpkin_core::math::{position::WorldPosition, vector3::Vector3}; use pumpkin_core::text::{color::NamedColor, TextComponent}; use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_protocol::{ - client::play::{CBlockUpdate, CSoundEffect, CWorldEvent}, + client::play::{CBlockUpdate, CRespawn, CSoundEffect, CWorldEvent}, SoundCategory, }; use pumpkin_protocol::{ @@ -29,6 +31,7 @@ use pumpkin_protocol::{ }, ClientPacket, VarInt, }; +use pumpkin_registry::DimensionType; use pumpkin_world::chunk::ChunkData; use pumpkin_world::level::Level; use pumpkin_world::{ @@ -99,17 +102,20 @@ pub struct World { pub scoreboard: Mutex, /// The world's worldborder, defining the playable area and controlling its expansion or contraction. pub worldborder: Mutex, + /// The type of dimension the world is in + pub dimension_type: DimensionType, // TODO: entities } impl World { #[must_use] - pub fn load(level: Level) -> Self { + pub fn load(level: Level, dimension_type: DimensionType) -> Self { Self { level: Arc::new(level), current_players: Arc::new(Mutex::new(HashMap::new())), scoreboard: Mutex::new(Scoreboard::new()), worldborder: Mutex::new(Worldborder::new(0.0, 0.0, 29_999_984.0, 0, 0, 0)), + dimension_type, } } @@ -190,8 +196,15 @@ impl World { &self, base_config: &BasicConfiguration, player: Arc, - command_dispatcher: &CommandDispatcher<'_>, + server: &Server, ) { + let command_dispatcher = &server.command_dispatcher; + let dimensions = &server + .dimensions + .iter() + .map(DimensionType::name) + .collect_vec(); + // This code follows the vanilla packet order let entity_id = player.entity_id(); let gamemode = player.gamemode.load(); @@ -207,15 +220,15 @@ impl World { .send_packet(&CLogin::new( entity_id, base_config.hardcore, - &["minecraft:overworld"], + dimensions, base_config.max_players.into(), base_config.view_distance.into(), // TODO: view distance base_config.simulation_distance.into(), // TODO: sim view dinstance false, true, false, - 0.into(), - "minecraft:overworld", + (self.dimension_type as u8).into(), + self.dimension_type.name(), 0, // seed gamemode as u8, base_config.default_gamemode as i8, @@ -376,6 +389,106 @@ impl World { player_chunker::player_join(self, player.clone()).await; } + pub async fn respawn_player(&self, player: &Arc, alive: bool) { + let last_pos = player.living_entity.last_pos.load(); + let death_dimension = player.world().dimension_type.name(); + let death_location = WorldPosition(Vector3::new( + last_pos.x.round() as i32, + last_pos.y.round() as i32, + last_pos.z.round() as i32, + )); + + let data_kept = u8::from(alive); + + // TODO: switch world in player entity to new world + + player + .client + .send_packet(&CRespawn::new( + (self.dimension_type as u8).into(), + self.dimension_type.name(), + 0, // seed + player.gamemode.load() as u8, + player.gamemode.load() as i8, + false, + false, + Some((death_dimension, death_location)), + 0.into(), + 0.into(), + data_kept, + )) + .await; + + log::debug!("Sending player abilities to {}", player.gameprofile.name); + player.send_abilties_update().await; + + player.send_permission_lvl_update().await; + + // teleport + let mut position = Vector3::new(10.0, 120.0, 10.0); + let yaw = 10.0; + let pitch = 10.0; + + let top = self + .get_top_block(Vector2::new(position.x as i32, position.z as i32)) + .await; + position.y = f64::from(top + 1); + + log::debug!("Sending player teleport to {}", player.gameprofile.name); + player.teleport(position, yaw, pitch).await; + + player.living_entity.last_pos.store(position); + + // TODO: difficulty, exp bar, status effect + + self.worldborder + .lock() + .await + .init_client(&player.client) + .await; + + // TODO: world spawn (compass stuff) + + player + .client + .send_packet(&CGameEvent::new(GameEvent::StartWaitingChunks, 0.0)) + .await; + + let entity = &player.living_entity.entity; + let entity_id = entity.entity_id; + + let skin_parts = player.config.lock().await.skin_parts; + let entity_metadata_packet = + CSetEntityMetadata::new(entity_id.into(), Metadata::new(17, VarInt(0), &skin_parts)); + + self.broadcast_packet_except( + &[player.gameprofile.id], + // TODO: add velo + &CSpawnEntity::new( + entity.entity_id.into(), + player.gameprofile.id, + (EntityType::Player as i32).into(), + position.x, + position.y, + position.z, + pitch, + yaw, + yaw, + 0.into(), + 0.0, + 0.0, + 0.0, + ), + ) + .await; + + player_chunker::player_join(self, player.clone()).await; + self.broadcast_packet_all(&entity_metadata_packet).await; + // update commands + + player.set_health(20.0, 20, 20.0).await; + } + pub fn mark_chunks_as_not_watched(&self, chunks: &[Vector2]) -> Vec> { self.level.mark_chunks_as_not_watched(chunks) }