From af0573d9b84c37bbe68c03a8bf017476253576fc Mon Sep 17 00:00:00 2001 From: vyPal Date: Tue, 11 Feb 2025 17:08:11 +0100 Subject: [PATCH] Support Teleport to different world (#532) * make entity world RwLock * Add teleport function * Mark client as not loaded before change dim * clippy * Post-merge issues * FIx post merge issues * Make world.remove_player fire event conditionally * Remove extra StartWaitingChunks packet * Add world.send_world_info() --- pumpkin/src/block/blocks/chest.rs | 2 + pumpkin/src/block/blocks/jukebox.rs | 6 +- pumpkin/src/block/blocks/mod.rs | 4 +- pumpkin/src/command/commands/fill.rs | 5 +- pumpkin/src/command/commands/seed.rs | 4 +- pumpkin/src/command/commands/setblock.rs | 5 +- pumpkin/src/command/commands/summon.rs | 4 +- pumpkin/src/command/commands/weather.rs | 11 +- pumpkin/src/command/mod.rs | 4 +- pumpkin/src/entity/ai/goal/look_at_entity.rs | 2 + pumpkin/src/entity/ai/goal/target_goal.rs | 2 + pumpkin/src/entity/ai/path/mod.rs | 2 + pumpkin/src/entity/living.rs | 6 + pumpkin/src/entity/mod.rs | 17 ++- pumpkin/src/entity/player.rs | 113 +++++++++++++++-- pumpkin/src/item/items/egg.rs | 4 +- pumpkin/src/item/items/snowball.rs | 4 +- pumpkin/src/net/container.rs | 2 + pumpkin/src/net/packet/play.rs | 34 ++--- pumpkin/src/world/chunker.rs | 10 +- pumpkin/src/world/mod.rs | 127 ++++++++++--------- 21 files changed, 257 insertions(+), 111 deletions(-) diff --git a/pumpkin/src/block/blocks/chest.rs b/pumpkin/src/block/blocks/chest.rs index e062c7f24..d3be956b4 100644 --- a/pumpkin/src/block/blocks/chest.rs +++ b/pumpkin/src/block/blocks/chest.rs @@ -109,11 +109,13 @@ impl ChestBlock { if state == ChestState::IsClosed && num_players == 0 { player .world() + .await .play_block_sound(Sound::BlockChestClose, SoundCategory::Blocks, location) .await; } else if state == ChestState::IsOpened && num_players == 1 { player .world() + .await .play_block_sound(Sound::BlockChestOpen, SoundCategory::Blocks, location) .await; } diff --git a/pumpkin/src/block/blocks/jukebox.rs b/pumpkin/src/block/blocks/jukebox.rs index 3abc5ebc9..7ea1b219d 100644 --- a/pumpkin/src/block/blocks/jukebox.rs +++ b/pumpkin/src/block/blocks/jukebox.rs @@ -22,7 +22,7 @@ impl PumpkinBlock for JukeboxBlock { _server: &Server, ) { // For now just stop the music at this position - let world = &player.living_entity.entity.world; + let world = &player.living_entity.entity.world.read().await; world.stop_record(location).await; } @@ -35,7 +35,7 @@ impl PumpkinBlock for JukeboxBlock { item: &Item, _server: &Server, ) -> BlockActionResult { - let world = &player.living_entity.entity.world; + let world = &player.living_entity.entity.world.read().await; let Some(jukebox_playable) = &item.components.jukebox_playable else { return BlockActionResult::Continue; @@ -59,7 +59,7 @@ impl PumpkinBlock for JukeboxBlock { async fn broken(&self, _block: &Block, player: &Player, location: BlockPos, _server: &Server) { // For now just stop the music at this position - let world = &player.living_entity.entity.world; + let world = &player.living_entity.entity.world.read().await; world.stop_record(location).await; } diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index 9a47083c5..9068b1e08 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -35,7 +35,7 @@ pub async fn standard_on_broken_with_container( /// The standard open container creates a new container if a container of the same block /// type does not exist at the selected block location. If a container of the same type exists, the player -/// is added to the currently connected players to that container. +/// is added to the currently connected players to that container. pub async fn standard_open_container( block: &Block, player: &Player, @@ -113,7 +113,7 @@ pub async fn standard_open_container_unique( pub async fn close_all_in_container(player: &Player, container: &OpenContainer) { for id in container.all_player_ids() { - if let Some(remote_player) = player.world().get_player_by_id(id).await { + if let Some(remote_player) = player.world().await.get_player_by_id(id).await { remote_player.close_container().await; } } diff --git a/pumpkin/src/command/commands/fill.rs b/pumpkin/src/command/commands/fill.rs index 34a6df5eb..05a278815 100644 --- a/pumpkin/src/command/commands/fill.rs +++ b/pumpkin/src/command/commands/fill.rs @@ -58,7 +58,10 @@ impl CommandExecutor for SetblockExecutor { let end_y = from.0.y.max(to.0.y); let end_z = from.0.z.max(to.0.z); - let world = sender.world().ok_or(CommandError::InvalidRequirement)?; + let world = sender + .world() + .await + .ok_or(CommandError::InvalidRequirement)?; let mut placed_blocks = 0; match mode { diff --git a/pumpkin/src/command/commands/seed.rs b/pumpkin/src/command/commands/seed.rs index c976218c1..da6c380db 100644 --- a/pumpkin/src/command/commands/seed.rs +++ b/pumpkin/src/command/commands/seed.rs @@ -22,7 +22,9 @@ impl CommandExecutor for PumpkinExecutor { _args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let seed = match sender { - CommandSender::Player(player) => player.living_entity.entity.world.level.seed.0, + CommandSender::Player(player) => { + player.living_entity.entity.world.read().await.level.seed.0 + } // TODO: Maybe ask player for world, or get the current world _ => match server.worlds.read().await.first() { Some(world) => world.level.seed.0, diff --git a/pumpkin/src/command/commands/setblock.rs b/pumpkin/src/command/commands/setblock.rs index 2e55427e6..7d755424c 100644 --- a/pumpkin/src/command/commands/setblock.rs +++ b/pumpkin/src/command/commands/setblock.rs @@ -42,7 +42,10 @@ impl CommandExecutor for SetblockExecutor { let pos = BlockPosArgumentConsumer::find_arg(args, ARG_BLOCK_POS)?; let mode = self.0; // TODO: allow console to use the command (seed sender.world) - let world = sender.world().ok_or(CommandError::InvalidRequirement)?; + let world = sender + .world() + .await + .ok_or(CommandError::InvalidRequirement)?; let success = match mode { Mode::Destroy => { diff --git a/pumpkin/src/command/commands/summon.rs b/pumpkin/src/command/commands/summon.rs index 26f4e3338..65494918f 100644 --- a/pumpkin/src/command/commands/summon.rs +++ b/pumpkin/src/command/commands/summon.rs @@ -37,8 +37,8 @@ impl CommandExecutor for SummonExecutor { // TODO: Make this work in console if let Some(player) = sender.as_player() { let pos = pos.unwrap_or(player.living_entity.entity.pos.load()); - let mob = mob::from_type(entity, server, pos, player.world()).await; - player.world().spawn_entity(mob).await; + let mob = mob::from_type(entity, server, pos, &player.world().await).await; + player.world().await.spawn_entity(mob).await; sender .send_message(TextComponent::translate( "commands.summon.success", diff --git a/pumpkin/src/command/commands/weather.rs b/pumpkin/src/command/commands/weather.rs index 64e305d20..c6dc9d609 100644 --- a/pumpkin/src/command/commands/weather.rs +++ b/pumpkin/src/command/commands/weather.rs @@ -30,14 +30,17 @@ impl CommandExecutor for WeatherExecutor { _server: &crate::server::Server, args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { - let world = sender.world().ok_or(CommandError::InvalidRequirement)?; + let world = sender + .world() + .await + .ok_or(CommandError::InvalidRequirement)?; let duration = TimeArgumentConsumer::find_arg(args, ARG_DURATION).unwrap_or(6000); let mut weather = world.weather.lock().await; match self.mode { WeatherMode::Clear => { weather - .set_weather_parameters(world, duration, 0, false, false) + .set_weather_parameters(&world, duration, 0, false, false) .await; sender .send_message(TextComponent::translate( @@ -48,7 +51,7 @@ impl CommandExecutor for WeatherExecutor { } WeatherMode::Rain => { weather - .set_weather_parameters(world, 0, duration, true, false) + .set_weather_parameters(&world, 0, duration, true, false) .await; sender .send_message(TextComponent::translate( @@ -59,7 +62,7 @@ impl CommandExecutor for WeatherExecutor { } WeatherMode::Thunder => { weather - .set_weather_parameters(world, 0, duration, true, true) + .set_weather_parameters(&world, 0, duration, true, true) .await; sender .send_message(TextComponent::translate( diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index 0da5ba1a9..3bcf975fe 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -97,11 +97,11 @@ impl CommandSender<'_> { } #[must_use] - pub fn world(&self) -> Option<&Arc> { + pub async fn world(&self) -> Option> { match self { // TODO: maybe return first world when console CommandSender::Console | CommandSender::Rcon(..) => None, - CommandSender::Player(p) => Some(&p.living_entity.entity.world), + CommandSender::Player(p) => Some(p.living_entity.entity.world.read().await.clone()), } } } diff --git a/pumpkin/src/entity/ai/goal/look_at_entity.rs b/pumpkin/src/entity/ai/goal/look_at_entity.rs index ab045b8c0..e68d399b7 100644 --- a/pumpkin/src/entity/ai/goal/look_at_entity.rs +++ b/pumpkin/src/entity/ai/goal/look_at_entity.rs @@ -33,6 +33,8 @@ impl Goal for LookAtEntityGoal { .living_entity .entity .world + .read() + .await .get_closest_player(mob.living_entity.entity.pos.load(), self.range) .await; target.is_some() diff --git a/pumpkin/src/entity/ai/goal/target_goal.rs b/pumpkin/src/entity/ai/goal/target_goal.rs index 1bd3b952f..57929040e 100644 --- a/pumpkin/src/entity/ai/goal/target_goal.rs +++ b/pumpkin/src/entity/ai/goal/target_goal.rs @@ -34,6 +34,8 @@ impl Goal for TargetGoal { .living_entity .entity .world + .read() + .await .get_closest_player(mob.living_entity.entity.pos.load(), self.range) .await; // we can't use filter, because of async clousrers diff --git a/pumpkin/src/entity/ai/path/mod.rs b/pumpkin/src/entity/ai/path/mod.rs index e48b4e1b8..e4a2061e4 100644 --- a/pumpkin/src/entity/ai/path/mod.rs +++ b/pumpkin/src/entity/ai/path/mod.rs @@ -72,6 +72,8 @@ impl Navigator { entity .entity .world + .read() + .await .broadcast_packet_all(&CUpdateEntityPos::new( entity.entity.entity_id.into(), Vector3::new( diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 88b8226e9..0b689e79a 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -87,6 +87,8 @@ impl LivingEntity { self.entity .world + .read() + .await .broadcast_packet_all(&CDamageEvent::new( self.entity.entity_id.into(), damage_type.id.into(), @@ -181,11 +183,15 @@ impl LivingEntity { // Spawns death smoke particles self.entity .world + .read() + .await .broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 60)) .await; // Plays the death sound and death animation self.entity .world + .read() + .await .broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 3)) .await; } diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 61237c41b..02175a306 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -27,6 +27,7 @@ use pumpkin_util::math::{ wrap_degrees, }; use serde::Serialize; +use tokio::sync::RwLock; use crate::world::World; @@ -61,7 +62,7 @@ pub struct Entity { /// The type of entity (e.g., player, zombie, item) pub entity_type: EntityType, /// The world in which the entity exists. - pub world: Arc, + pub world: Arc>>, /// The entity's current position in the world pub pos: AtomicCell>, /// The entity's position rounded to the nearest block coordinates @@ -124,7 +125,7 @@ impl Entity { block_pos: AtomicCell::new(BlockPos(Vector3::new(floor_x, floor_y, floor_z))), chunk_pos: AtomicCell::new(Vector2::new(floor_x, floor_z)), sneaking: AtomicBool::new(false), - world, + world: Arc::new(RwLock::new(world)), // TODO: Load this from previous instance sprinting: AtomicBool::new(false), fall_flying: AtomicBool::new(false), @@ -210,6 +211,8 @@ impl Entity { let yaw = (yaw * 256.0 / 360.0).rem_euclid(256.0); let pitch = (pitch * 256.0 / 360.0).rem_euclid(256.0); self.world + .read() + .await .broadcast_packet_all(&CUpdateEntityRot::new( self.entity_id.into(), yaw as u8, @@ -218,12 +221,16 @@ impl Entity { )) .await; self.world + .read() + .await .broadcast_packet_all(&CHeadRot::new(self.entity_id.into(), yaw as u8)) .await; } pub async fn teleport(&self, position: Vector3, yaw: f32, pitch: f32) { self.world + .read() + .await .broadcast_packet_all(&CTeleportEntity::new( self.entity_id.into(), position, @@ -248,7 +255,7 @@ impl Entity { /// Removes the Entity from their current World pub async fn remove(&self) { - self.world.remove_entity(self).await; + self.world.read().await.remove_entity(self).await; } pub fn create_spawn_packet(&self) -> CSpawnEntity { @@ -337,6 +344,8 @@ impl Entity { /// Plays sound at this entity's position with the entity's sound category pub async fn play_sound(&self, sound: Sound) { self.world + .read() + .await .play_sound(sound, SoundCategory::Neutral, &self.pos.load()) .await; } @@ -346,6 +355,8 @@ impl Entity { T: Serialize, { self.world + .read() + .await .broadcast_packet_all(&CSetEntityMetadata::new(self.entity_id.into(), meta)) .await; } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 022310d0c..5a105e124 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -23,8 +23,8 @@ use pumpkin_protocol::{ client::play::{ CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus, CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, - CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, GameEvent, - MetaDataType, PlayerAction, + CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage, CTitleText, + CUnloadChunk, GameEvent, MetaDataType, PlayerAction, }, server::play::{ SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd, @@ -233,10 +233,10 @@ impl Player { /// Removes the Player out of the current World #[allow(unused_variables)] pub async fn remove(self: Arc) { - let world = self.world(); + let world = self.world().await; self.cancel_tasks.notify_waiters(); - world.remove_player(self.clone()).await; + world.remove_player(self.clone(), true).await; let cylindrical = self.watched_section.load(); @@ -272,7 +272,7 @@ impl Player { } pub async fn attack(&self, victim: Arc) { - let world = self.world(); + let world = self.world().await; let victim_entity = victim.get_entity(); let victim_living_entity = victim.get_living_entity(); let attacker_entity = &self.living_entity.entity; @@ -341,7 +341,7 @@ impl Player { let attack_type = AttackType::new(self, attack_cooldown_progress as f32).await; - player_attack_sound(&pos, world, attack_type).await; + player_attack_sound(&pos, &world, attack_type).await; if matches!(attack_type, AttackType::Critical) { damage *= 1.5; @@ -357,13 +357,13 @@ impl Player { match attack_type { AttackType::Knockback => knockback_strength += 1.0, AttackType::Sweeping => { - combat::spawn_sweep_particle(attacker_entity, world, &pos).await; + combat::spawn_sweep_particle(attacker_entity, &world, &pos).await; } _ => {} }; if config.knockback { - combat::handle_knockback(attacker_entity, world, victim_entity, knockback_strength) + combat::handle_knockback(attacker_entity, &world, victim_entity, knockback_strength) .await; } @@ -499,8 +499,8 @@ impl Player { self.living_entity.entity.entity_id } - pub const fn world(&self) -> &Arc { - &self.living_entity.entity.world + pub async fn world(&self) -> Arc { + self.living_entity.entity.world.read().await.clone() } pub fn position(&self) -> Vector3 { @@ -577,6 +577,93 @@ impl Player { } } + async fn unload_watched_chunks(&self, world: &World) { + let radial_chunks = self.watched_section.load().all_chunks_within(); + let level = &world.level; + let chunks_to_clean = level.mark_chunks_as_not_watched(&radial_chunks); + level.clean_chunks(&chunks_to_clean).await; + let client = self.client.clone(); + tokio::spawn(async move { + for chunk in chunks_to_clean { + if client.closed.load(std::sync::atomic::Ordering::Relaxed) { + break; + } + client + .send_packet(&CUnloadChunk::new(chunk.x, chunk.z)) + .await; + } + }); + self.watched_section.store(Cylindrical::new( + Vector2::new(i32::MAX >> 1, i32::MAX >> 1), + unsafe { NonZeroU8::new_unchecked(1) }, + )); + } + + /// Teleports the player to a different world or dimension with an optional position, yaw, and pitch. + pub async fn teleport_world( + self: Arc, + new_world: Arc, + position: Option>, + yaw: Option, + pitch: Option, + ) { + self.set_client_loaded(false); + let current_world = self.living_entity.entity.world.read().await.clone(); + let uuid = self.gameprofile.id; + current_world.remove_player(self.clone(), false).await; + *self.living_entity.entity.world.write().await = new_world.clone(); + new_world.players.lock().await.insert(uuid, self.clone()); + self.unload_watched_chunks(¤t_world).await; + let last_pos = self.living_entity.last_pos.load(); + let death_dimension = self.world().await.dimension_type.name(); + let death_location = BlockPos(Vector3::new( + last_pos.x.round() as i32, + last_pos.y.round() as i32, + last_pos.z.round() as i32, + )); + self.client + .send_packet(&CRespawn::new( + (new_world.dimension_type as u8).into(), + new_world.dimension_type.name(), + 0, // seed + self.gamemode.load() as u8, + self.gamemode.load() as i8, + false, + false, + Some((death_dimension, death_location)), + 0.into(), + 0.into(), + 1, + )) + .await; + self.send_abilities_update().await; + self.send_permission_lvl_update().await; + let info = &new_world.level.level_info; + let position = if let Some(pos) = position { + pos + } else { + Vector3::new( + f64::from(info.spawn_x), + f64::from( + new_world + .get_top_block(Vector2::new( + f64::from(info.spawn_x) as i32, + f64::from(info.spawn_x) as i32, + )) + .await + + 1, + ), + f64::from(info.spawn_z), + ) + }; + let yaw = yaw.unwrap_or(info.spawn_angle); + let pitch = pitch.unwrap_or(10.0); + self.request_teleport(position, yaw, pitch).await; + self.living_entity.last_pos.store(position); + + new_world.send_world_info(&self, position, yaw, pitch).await; + } + /// Yaw and Pitch in degrees /// Rarly used, For example when waking up player from bed or first time spawn. Otherwise entity teleport is used /// Player should respond with the `SConfirmTeleport` packet @@ -727,6 +814,8 @@ impl Player { self.living_entity .entity .world + .read() + .await .broadcast_packet_all(&CPlayerInfoUpdate::new( 0x04, &[pumpkin_protocol::client::play::Player { @@ -785,10 +874,10 @@ impl Player { let entity = server.add_entity( self.living_entity.entity.pos.load(), EntityType::ITEM, - self.world(), + &self.world().await, ); let item_entity = Arc::new(ItemEntity::new(entity, &item.clone())); - self.world().spawn_entity(item_entity.clone()).await; + self.world().await.spawn_entity(item_entity.clone()).await; item_entity.send_meta_packet().await; // decrase item in hotbar inv.decrease_current_stack(1); diff --git a/pumpkin/src/item/items/egg.rs b/pumpkin/src/item/items/egg.rs index 6c3a74ee0..8ddbb242a 100644 --- a/pumpkin/src/item/items/egg.rs +++ b/pumpkin/src/item/items/egg.rs @@ -19,7 +19,7 @@ const POWER: f32 = 1.5; impl PumpkinItem for EggItem { async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) { let position = player.position(); - let world = player.world(); + let world = player.world().await; world .play_sound( Sound::EntityEggThrow, @@ -28,7 +28,7 @@ impl PumpkinItem for EggItem { ) .await; // TODO: Implement eggs the right way, so there is a chance of spawning chickens - let entity = server.add_entity(position, EntityType::EGG, world); + let entity = server.add_entity(position, EntityType::EGG, &world); let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); let yaw = player.living_entity.entity.yaw.load(); let pitch = player.living_entity.entity.pitch.load(); diff --git a/pumpkin/src/item/items/snowball.rs b/pumpkin/src/item/items/snowball.rs index d03b62777..55f1132ac 100644 --- a/pumpkin/src/item/items/snowball.rs +++ b/pumpkin/src/item/items/snowball.rs @@ -19,7 +19,7 @@ const POWER: f32 = 1.5; impl PumpkinItem for SnowBallItem { async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) { let position = player.position(); - let world = player.world(); + let world = player.world().await; world .play_sound( Sound::EntitySnowballThrow, @@ -27,7 +27,7 @@ impl PumpkinItem for SnowBallItem { &position, ) .await; - let entity = server.add_entity(position, EntityType::SNOWBALL, world); + let entity = server.add_entity(position, EntityType::SNOWBALL, &world); let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); let yaw = player.living_entity.entity.yaw.load(); let pitch = player.living_entity.entity.pitch.load(); diff --git a/pumpkin/src/net/container.rs b/pumpkin/src/net/container.rs index 34c4c3932..9687b7175 100644 --- a/pumpkin/src/net/container.rs +++ b/pumpkin/src/net/container.rs @@ -490,6 +490,8 @@ impl Player { .living_entity .entity .world + .read() + .await .players .lock() .await diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 7ee7a00d4..60b1374b4 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -171,7 +171,7 @@ impl Player { let entity_id = entity.entity_id; let Vector3 { x, y, z } = position; - let world = &entity.world; + let world = &entity.world.read().await; // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); // let velocity = self.velocity; @@ -266,7 +266,7 @@ impl Player { let yaw = (entity.yaw.load() * 256.0 / 360.0).rem_euclid(256.0); let pitch = (entity.pitch.load() * 256.0 / 360.0).rem_euclid(256.0); // let head_yaw = (entity.head_yaw * 256.0 / 360.0).floor(); - let world = &entity.world; + let world = &entity.world.read().await; // let delta = Vector3::new(x - lastx, y - lasty, z - lastz); // let velocity = self.velocity; @@ -348,7 +348,7 @@ impl Player { let pitch = (entity.pitch.load() * 256.0 / 360.0).rem_euclid(256.0); // let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0); - let world = &entity.world; + let world = &entity.world.read().await; let packet = CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, rotation.ground); world @@ -416,7 +416,8 @@ impl Player { return; } - let Ok(block) = self.world().get_block(&pick_item.pos).await else { + let world = self.world().await; + let Ok(block) = world.get_block(&pick_item.pos).await else { return; }; @@ -563,7 +564,7 @@ impl Player { }; let id = self.entity_id(); - let world = self.world(); + let world = self.world().await; world .broadcast_packet_except( &[self.gameprofile.id], @@ -592,7 +593,7 @@ impl Player { log::info!("{}: {}", gameprofile.name, message); let entity = &self.living_entity.entity; - let world = &entity.world; + let world = &entity.world.read().await; world .broadcast_packet_all(&CPlayerChatMessage::new( gameprofile.id, @@ -700,7 +701,10 @@ impl Player { if self.living_entity.health.load() > 0.0 { return; } - self.world().respawn_player(&self.clone(), false).await; + self.world() + .await + .respawn_player(&self.clone(), false) + .await; // Restore abilities based on gamemode after respawn let mut abilities = self.abilities.lock().await; @@ -745,7 +749,7 @@ impl Player { // TODO: set as camera entity when specator - let world = &entity.world; + let world = &entity.world.read().await; let player_victim = world.get_player_by_id(entity_id.0).await; if entity_id.0 == self.entity_id() { // this can't be triggered from a non-modded client. @@ -822,7 +826,7 @@ impl Player { let location = player_action.location; // Block break & block break sound let entity = &self.living_entity.entity; - let world = &entity.world; + let world = &entity.world.read().await; let block = world.get_block(&location).await; world @@ -867,7 +871,7 @@ impl Player { } // Block break & block break sound let entity = &self.living_entity.entity; - let world = &entity.world; + let world = &entity.world.read().await; let block = world.get_block(&location).await; world @@ -956,7 +960,7 @@ impl Player { let mut inventory = self.inventory().lock().await; let entity = &self.living_entity.entity; - let world = &entity.world; + let world = &entity.world.read().await; let slot_id = inventory.get_selected(); let mut state_id = inventory.state_id; let item_slot = *inventory.held_item_mut(); @@ -1053,7 +1057,7 @@ impl Player { } pub async fn handle_sign_update(&self, sign_data: SUpdateSign) { - let world = &self.living_entity.entity.world; + let world = &self.living_entity.entity.world.read().await; let updated_sign = Sign::new( sign_data.location, sign_data.is_front_text, @@ -1202,13 +1206,13 @@ impl Player { // create rotation like Vanilla let yaw = wrap_degrees(rand::random::() * 360.0) % 360.0; - let world = self.world(); + let world = self.world().await; // create new mob and uuid based on spawn egg id let mob = mob::from_type( EntityType::from_raw(entity_type.id).unwrap(), server, pos, - world, + &world, ) .await; @@ -1243,7 +1247,7 @@ impl Player { face: &BlockDirection, ) -> Result> { let entity = &self.living_entity.entity; - let world = &entity.world; + let world = &entity.world.read().await; let clicked_block_pos = BlockPos(location.0); let clicked_block_state = world.get_block_state(&clicked_block_pos).await?; diff --git a/pumpkin/src/world/chunker.rs b/pumpkin/src/world/chunker.rs index 3145103be..bf7f92bfe 100644 --- a/pumpkin/src/world/chunker.rs +++ b/pumpkin/src/world/chunker.rs @@ -69,7 +69,7 @@ pub async fn update_position(player: &Arc) { // Make sure the watched section and the chunk watcher updates are async atomic. We want to // ensure what we unload when the player disconnects is correct - let level = &entity.world.level; + let level = &entity.world.read().await.level; level.mark_chunks_as_newly_watched(&loading_chunks); let chunks_to_clean = level.mark_chunks_as_not_watched(&unloading_chunks); player.watched_section.store(new_cylindrical); @@ -93,9 +93,11 @@ pub async fn update_position(player: &Arc) { } if !loading_chunks.is_empty() { - entity - .world - .spawn_world_chunks(player.clone(), loading_chunks, new_chunk_center); + entity.world.read().await.spawn_world_chunks( + player.clone(), + loading_chunks, + new_chunk_center, + ); } } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 20c77e706..2c8f34870 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -513,9 +513,55 @@ impl World { player.send_mobs(self).await; } + pub async fn send_world_info( + &self, + player: &Arc, + position: Vector3, + yaw: f32, + pitch: f32, + ) { + 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; + + self.broadcast_packet_except( + &[player.gameprofile.id], + // TODO: add velo + &CSpawnEntity::new( + entity.entity_id.into(), + player.gameprofile.id, + i32::from(EntityType::PLAYER.id).into(), + position, + pitch, + yaw, + yaw, + 0.into(), + Vector3::new(0.0, 0.0, 0.0), + ), + ) + .await; + player.send_client_information().await; + + chunker::player_join(player).await; + // update commands + + player.set_health(20.0).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_dimension = player.world().await.dimension_type.name(); let death_location = BlockPos(Vector3::new( last_pos.x.round() as i32, last_pos.y.round() as i32, @@ -566,43 +612,7 @@ impl World { // 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; - - self.broadcast_packet_except( - &[player.gameprofile.id], - // TODO: add velo - &CSpawnEntity::new( - entity.entity_id.into(), - player.gameprofile.id, - i32::from(EntityType::PLAYER.id).into(), - position, - pitch, - yaw, - yaw, - 0.into(), - Vector3::new(0.0, 0.0, 0.0), - ), - ) - .await; - player.send_client_information().await; - - chunker::player_join(player).await; - // update commands - - player.set_health(20.0).await; + self.send_world_info(player, position, yaw, pitch).await; } /// IMPORTANT: Chunks have to be non-empty @@ -654,11 +664,11 @@ impl World { } let (world, chunk) = if level.is_chunk_watched(&position) { - (player.world().clone(), chunk) + (player.world().await.clone(), chunk) } else { send_cancellable! {{ ChunkSave { - world: player.world().clone(), + world: player.world().await.clone(), chunk, cancelled: false, }; @@ -890,12 +900,13 @@ impl World { /// # Arguments /// /// * `player`: A reference to the `Player` object to be removed. + /// * `fire_event`: A boolean flag indicating whether to fire a `PlayerLeaveEvent` event. /// /// # Notes /// /// - 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) { + pub async fn remove_player(&self, player: Arc, fire_event: bool) { self.players .lock() .await @@ -910,25 +921,27 @@ impl World { self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()])) .await; - let msg_comp = TextComponent::translate( - "multiplayer.player.left", - [TextComponent::text(player.gameprofile.name.clone())].into(), - ) - .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())].into(), + ) + .color_named(NamedColor::Yellow); + let event = PlayerLeaveEvent::new(player.clone(), msg_comp); - let event = PLUGIN_MANAGER - .lock() - .await - .fire::(event) - .await; + let event = PLUGIN_MANAGER + .lock() + .await + .fire::(event) + .await; - if !event.cancelled { - let players = self.players.lock().await; - for player in players.values() { - player.send_system_message(&event.leave_message).await; + if !event.cancelled { + let players = self.players.lock().await; + for player in players.values() { + player.send_system_message(&event.leave_message).await; + } + log::info!("{}", event.leave_message.clone().to_pretty_console()); } - log::info!("{}", event.leave_message.clone().to_pretty_console()); } }