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()
This commit is contained in:
vyPal
2025-02-11 17:08:11 +01:00
committed by GitHub
parent 79ad1c40f3
commit af0573d9b8
21 changed files with 257 additions and 111 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<C: Container + Default + 'static>(
block: &Block,
player: &Player,
@@ -113,7 +113,7 @@ pub async fn standard_open_container_unique<C: Container + Default + 'static>(
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;
}
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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 => {

View File

@@ -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",

View File

@@ -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(

View File

@@ -97,11 +97,11 @@ impl CommandSender<'_> {
}
#[must_use]
pub fn world(&self) -> Option<&Arc<World>> {
pub async fn world(&self) -> Option<Arc<World>> {
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()),
}
}
}

View File

@@ -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()

View File

@@ -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

View File

@@ -72,6 +72,8 @@ impl Navigator {
entity
.entity
.world
.read()
.await
.broadcast_packet_all(&CUpdateEntityPos::new(
entity.entity.entity_id.into(),
Vector3::new(

View File

@@ -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;
}

View File

@@ -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<World>,
pub world: Arc<RwLock<Arc<World>>>,
/// The entity's current position in the world
pub pos: AtomicCell<Vector3<f64>>,
/// 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<f64>, 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;
}

View File

@@ -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<Self>) {
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<dyn EntityBase>) {
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<World> {
&self.living_entity.entity.world
pub async fn world(&self) -> Arc<World> {
self.living_entity.entity.world.read().await.clone()
}
pub fn position(&self) -> Vector3<f64> {
@@ -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<Self>,
new_world: Arc<World>,
position: Option<Vector3<f64>>,
yaw: Option<f32>,
pitch: Option<f32>,
) {
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(&current_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);

View File

@@ -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();

View File

@@ -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();

View File

@@ -490,6 +490,8 @@ impl Player {
.living_entity
.entity
.world
.read()
.await
.players
.lock()
.await

View File

@@ -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!("<chat>{}: {}", 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::<f32>() * 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<bool, Box<dyn PumpkinError>> {
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?;

View File

@@ -69,7 +69,7 @@ pub async fn update_position(player: &Arc<Player>) {
// 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<Player>) {
}
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,
);
}
}
}

View File

@@ -513,9 +513,55 @@ impl World {
player.send_mobs(self).await;
}
pub async fn send_world_info(
&self,
player: &Arc<Player>,
position: Vector3<f64>,
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<Player>, 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<Player>) {
pub async fn remove_player(&self, player: Arc<Player>, 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::<PlayerLeaveEvent>(event)
.await;
let event = PLUGIN_MANAGER
.lock()
.await
.fire::<PlayerLeaveEvent>(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());
}
}