diff --git a/pumpkin-util/src/math/position.rs b/pumpkin-util/src/math/position.rs index f44d9bffa..b8463e236 100644 --- a/pumpkin-util/src/math/position.rs +++ b/pumpkin-util/src/math/position.rs @@ -226,21 +226,28 @@ impl BlockPos { } pub fn chunk_and_chunk_relative_position(&self) -> (Vector2, Vector3) { - let (z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); - let (x_chunk, x_rem) = self.0.x.div_rem_euclid(&16); - ( - Vector2 { - x: x_chunk, - y: z_chunk, - }, - // Since we divide by 16, remnant can never exceed u8 - Vector3 { - x: x_rem, - z: z_rem, - y: self.0.y, - }, - ) + (self.chunk_position(), self.chunk_relative_position()) } + + pub fn chunk_position(&self) -> Vector2 { + let z_chunk = self.0.z.div_euclid(16); + let x_chunk = self.0.x.div_euclid(16); + Vector2 { + x: x_chunk, + y: z_chunk, + } + } + + pub fn chunk_relative_position(&self) -> Vector3 { + let z_chunk = self.0.z.rem_euclid(16); + let x_chunk = self.0.x.rem_euclid(16); + Vector3 { + x: x_chunk, + y: self.0.y, + z: z_chunk, + } + } + pub fn section_relative_position(&self) -> Vector3 { let (_z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); let (_x_chunk, x_rem) = self.0.x.div_rem_euclid(&16); diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index a283b180c..5b13ede20 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -844,9 +844,7 @@ impl Level { delay: u8, priority: TickPriority, ) { - let chunk = self - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.get_chunk(block_pos.chunk_position()).await; let mut chunk = chunk.write().await; chunk.block_ticks.schedule_tick( &ScheduledTick { @@ -867,9 +865,7 @@ impl Level { delay: u8, priority: TickPriority, ) { - let chunk = self - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.get_chunk(block_pos.chunk_position()).await; let mut chunk = chunk.write().await; chunk.fluid_ticks.schedule_tick( &ScheduledTick { @@ -888,9 +884,7 @@ impl Level { block_pos: &BlockPos, block: &Block, ) -> bool { - let chunk = self - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = chunk.read().await; chunk.block_ticks.is_scheduled(*block_pos, block) } @@ -900,9 +894,7 @@ impl Level { block_pos: &BlockPos, fluid: &Fluid, ) -> bool { - let chunk = self - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = chunk.read().await; chunk.fluid_ticks.is_scheduled(*block_pos, fluid) } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 566a67f19..b17cb8b9b 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -93,7 +93,6 @@ impl LivingEntity { } else { 0.8 }; - // TODO: Extract default MOVEMENT_SPEED Entity Attribute let default_movement_speed = 0.25; let health = entity.entity_type.max_health.unwrap_or(20.0); @@ -361,8 +360,8 @@ impl LivingEntity { .slipperiness, ); - let speed = - self.movement_speed.load() * 0.216 / (slipperiness * slipperiness * slipperiness); + let speed = self.movement_speed.load() * 0.216_000_02 + / (slipperiness * slipperiness * slipperiness); (speed, slipperiness * 0.91) } else { diff --git a/pumpkin/src/entity/mob/drowned.rs b/pumpkin/src/entity/mob/drowned.rs new file mode 100644 index 000000000..dba4c281c --- /dev/null +++ b/pumpkin/src/entity/mob/drowned.rs @@ -0,0 +1,26 @@ +use std::sync::Arc; + +use crate::entity::{ + Entity, NBTStorage, + mob::{Mob, MobEntity, zombie::ZombieEntity}, +}; + +pub struct DrownedEntity { + entity: Arc, +} + +impl DrownedEntity { + pub async fn make(entity: Entity) -> Arc { + Arc::new(Self { + entity: ZombieEntity::make(entity).await, + }) + } +} + +impl NBTStorage for DrownedEntity {} + +impl Mob for DrownedEntity { + fn get_mob_entity(&self) -> &MobEntity { + &self.entity.mob_entity + } +} diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 19184f4ec..829f0ece2 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -14,6 +14,8 @@ use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering::Relaxed; use tokio::sync::Mutex; +pub mod drowned; +pub mod skeleton; pub mod zombie; pub struct MobEntity { diff --git a/pumpkin/src/entity/mob/skeleton/mod.rs b/pumpkin/src/entity/mob/skeleton/mod.rs new file mode 100644 index 000000000..f47e8690b --- /dev/null +++ b/pumpkin/src/entity/mob/skeleton/mod.rs @@ -0,0 +1,55 @@ +use std::sync::{Arc, Weak}; + +use pumpkin_data::entity::EntityType; + +use crate::entity::{ + Entity, NBTStorage, + ai::goal::{ + active_target_goal::ActiveTargetGoal, look_around_goal::LookAroundGoal, + look_at_entity::LookAtEntityGoal, + }, + mob::{Mob, MobEntity}, +}; + +//pub mod skeleton; + +pub struct SkeletonEntityBase { + pub mob_entity: MobEntity, +} + +impl SkeletonEntityBase { + pub async fn make(entity: Entity) -> Arc { + let mob_entity = MobEntity::new(entity); + let mob = Self { mob_entity }; + let mob_arc = Arc::new(mob); + let mob_weak: Weak = { + let mob_arc: Arc = mob_arc.clone(); + Arc::downgrade(&mob_arc) + }; + { + let mut goal_selector = mob_arc.mob_entity.goals_selector.lock().await; + let mut target_selector = mob_arc.mob_entity.target_selector.lock().await; + + goal_selector.add_goal( + 8, + LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0), + ); + goal_selector.add_goal(8, Box::new(LookAroundGoal::default())); + + target_selector.add_goal( + 2, + ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, true), + ); + }; + + mob_arc + } +} + +impl NBTStorage for SkeletonEntityBase {} + +impl Mob for SkeletonEntityBase { + fn get_mob_entity(&self) -> &MobEntity { + &self.mob_entity + } +} diff --git a/pumpkin/src/entity/mob/skeleton/skeleton.rs b/pumpkin/src/entity/mob/skeleton/skeleton.rs new file mode 100644 index 000000000..e69de29bb diff --git a/pumpkin/src/entity/mob/zombie.rs b/pumpkin/src/entity/mob/zombie.rs index 582a00ab8..eab3fde88 100644 --- a/pumpkin/src/entity/mob/zombie.rs +++ b/pumpkin/src/entity/mob/zombie.rs @@ -19,11 +19,11 @@ use rand::{Rng, rng}; use std::pin::Pin; use std::sync::{Arc, Weak}; -pub struct Zombie { - mob_entity: MobEntity, +pub struct ZombieEntity { + pub mob_entity: MobEntity, } -impl Zombie { +impl ZombieEntity { pub async fn make(entity: Entity) -> Arc { let mob_entity = MobEntity::new(entity); let zombie = Self { mob_entity }; @@ -55,9 +55,9 @@ impl Zombie { } } -impl NBTStorage for Zombie {} +impl NBTStorage for ZombieEntity {} -impl Mob for Zombie { +impl Mob for ZombieEntity { fn get_mob_entity(&self) -> &MobEntity { &self.mob_entity } diff --git a/pumpkin/src/entity/type.rs b/pumpkin/src/entity/type.rs index 7f2158295..d1fc392c1 100644 --- a/pumpkin/src/entity/type.rs +++ b/pumpkin/src/entity/type.rs @@ -9,7 +9,7 @@ use crate::{ Entity, EntityBase, decoration::{end_crystal::EndCrystalEntity, painting::PaintingEntity}, living::LivingEntity, - mob::zombie::Zombie, + mob::{drowned::DrownedEntity, zombie::ZombieEntity}, }, world::World, }; @@ -23,7 +23,8 @@ pub async fn from_type( let entity = Entity::new(uuid, world.clone(), position, entity_type, false); let mob: Arc = match entity_type.id { - id if id == EntityType::ZOMBIE.id => Zombie::make(entity).await, + id if id == EntityType::ZOMBIE.id => ZombieEntity::make(entity).await, + id if id == EntityType::DROWNED.id => DrownedEntity::make(entity).await, id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)), id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)), // Fallback Entity diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 1a6053bda..23c4b0208 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -236,10 +236,7 @@ impl World { // First lets see if the entity was saved on an other chunk, and if the current chunk does not match we remove it // Otherwise we just update the nbt data let base_entity = entity.get_entity(); - let (current_chunk_coordinate, _) = base_entity - .block_pos - .load() - .chunk_and_chunk_relative_position(); + let current_chunk_coordinate = base_entity.block_pos.load().chunk_position(); let mut nbt = NbtCompound::new(); entity.write_nbt(&mut nbt).await; if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { @@ -263,8 +260,7 @@ impl World { } async fn remove_entity_data(&self, entity: &Entity) { - let (current_chunk_coordinate, _) = - entity.block_pos.load().chunk_and_chunk_relative_position(); + let current_chunk_coordinate = entity.block_pos.load().chunk_position(); if let Some(old_chunk) = entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); let chunk = self.level.get_entity_chunk(old_chunk).await; @@ -351,7 +347,7 @@ impl World { pub async fn broadcast_packet_all(&self, packet: &P) { let current_players = self.players.read().await; - for (_, player) in current_players.iter() { + for player in current_players.values() { player.client.enqueue_packet(packet).await; } } @@ -378,7 +374,7 @@ impl World { ) { let current_players = self.players.read().await; - for (_, player) in current_players.iter() { + for player in current_players.values() { match &player.client { ClientPlatform::Java(client) => client.enqueue_packet(je_packet).await, ClientPlatform::Bedrock(client) => client.send_game_packet(be_packet).await, @@ -563,7 +559,6 @@ impl World { self.flush_block_updates().await; self.flush_synced_block_events().await; self.tick_environment().await; - let env_done = start.elapsed(); // 2. Chunks let chunk_start = tokio::time::Instant::now(); @@ -610,14 +605,13 @@ impl World { let total_elapsed = start.elapsed(); if total_elapsed.as_millis() > 50 { log::warn!( - "Slow Tick [{}ms]: Chunks: {:?} | Players({}): {:?} | Entities({}): {:?} | Env: {:?}", + "Slow Tick [{}ms]: Chunks: {:?} | Players({}): {:?} | Entities({}): {:?}", total_elapsed.as_millis(), chunk_elapsed, player_count, player_elapsed, entity_count, entity_elapsed, - env_done ); } } @@ -1843,6 +1837,7 @@ impl World { // NOTE: This function doesn't actually await on anything, it just spawns two tokio tasks /// IMPORTANT: Chunks have to be non-empty + #[expect(clippy::too_many_lines)] fn spawn_world_entity_chunks( self: &Arc, player: Arc, @@ -1896,6 +1891,7 @@ impl World { let mut ids = Vec::new(); // Remove all the entities from the world let mut entities = world.entities.write().await; + for (uuid, entity_nbt) in &chunk.read().await.data { let Some(id) = entity_nbt.get_string("id") else { log::warn!("Entity has no ID"); @@ -1917,8 +1913,30 @@ impl World { entities.remove(&base_entity.entity_uuid); ids.push(VarInt(base_entity.entity_id)); - world.save_entity(uuid, &entity).await; + let mut nbt = NbtCompound::new(); + entity.write_nbt(&mut nbt).await; + if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { + let old_chunk = old_chunk.to_vec2_i32(); + let chunk = world.level.get_entity_chunk(old_chunk).await; + let mut chunk = chunk.write().await; + chunk.mark_dirty(true); + let base_entity = entity.get_entity(); + let current_chunk_coordinate = + base_entity.block_pos.load().chunk_position(); + + if old_chunk == current_chunk_coordinate { + chunk.data.insert(*uuid, nbt); + return; + } + + // The chunk has changed, lets remove the entity from the old chunk + chunk.data.remove(uuid); + } + let mut chunk = chunk.write().await; + chunk.data.insert(*uuid, nbt); + chunk.mark_dirty(true); } + if !ids.is_empty() { player .client @@ -2268,17 +2286,15 @@ impl World { .await; entity.init_data_tracker().await; - let (chunk_coordinate, _) = base_entity - .block_pos - .load() - .chunk_and_chunk_relative_position(); + let chunk_coordinate = base_entity.block_pos.load().chunk_position(); let chunk = self.level.get_entity_chunk(chunk_coordinate).await; - let mut chunk = chunk.write().await; - let mut nbt = NbtCompound::new(); - entity.write_nbt(&mut nbt).await; - chunk.data.insert(base_entity.entity_uuid, nbt); - chunk.mark_dirty(true); - drop(chunk); + { + let mut chunk = chunk.write().await; + let mut nbt = NbtCompound::new(); + entity.write_nbt(&mut nbt).await; + chunk.data.insert(base_entity.entity_uuid, nbt); + chunk.mark_dirty(true); + }; let mut current_entities = self.entities.write().await; current_entities.insert(base_entity.entity_uuid, entity); @@ -3016,10 +3032,7 @@ impl World { } pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option> { - let chunk = self - .level - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; chunk.block_entities.get(block_pos).cloned() @@ -3027,10 +3040,7 @@ impl World { pub async fn add_block_entity(&self, block_entity: Arc) { let block_pos = block_entity.get_position(); - let chunk = self - .level - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; let block_entity_nbt = block_entity.chunk_data_nbt(); @@ -3050,10 +3060,7 @@ impl World { } pub async fn remove_block_entity(&self, block_pos: &BlockPos) { - let chunk = self - .level - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; chunk.block_entities.remove(block_pos); chunk.mark_dirty(true); @@ -3061,10 +3068,7 @@ impl World { pub async fn update_block_entity(&self, block_entity: &Arc) { let block_pos = block_entity.get_position(); - let chunk = self - .level - .get_chunk(block_pos.chunk_and_chunk_relative_position().0) - .await; + let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; let block_entity_nbt = block_entity.chunk_data_nbt(); diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 277762b6b..a18987394 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -8,6 +8,7 @@ use pumpkin_data::tag::Fluid::{MINECRAFT_LAVA, MINECRAFT_WATER}; use pumpkin_data::tag::Taggable; use pumpkin_data::tag::WorldgenBiome::MINECRAFT_REDUCE_WATER_AMBIENT_SPAWNS; use pumpkin_data::{Block, BlockDirection, BlockState}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::GameMode; use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions}; use pumpkin_util::math::get_section_cord; @@ -16,6 +17,7 @@ use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::random::xoroshiro128::Xoroshiro; use pumpkin_util::random::{RandomImpl, get_seed}; +use pumpkin_world::chunk::io::Dirtiable; use pumpkin_world::chunk::{ChunkData, ChunkHeightmapType}; use rand::seq::IndexedRandom; use rand::{Rng, rng}; @@ -42,8 +44,8 @@ impl MobCounts { } } +#[derive(Default)] pub struct LocalMobCapCalculator { - world: Arc, player_mob_counts: HashMap, players_near_chunk: HashMap, Vec>, } @@ -59,14 +61,6 @@ impl fmt::Debug for LocalMobCapCalculator { } impl LocalMobCapCalculator { - pub fn new(world: &Arc) -> Self { - Self { - world: world.clone(), // can anybody get rid of this clone? - player_mob_counts: HashMap::new(), - players_near_chunk: HashMap::new(), - } - } - const fn calc_distance(chunk_pos: Vector2, player_pos: &Vector3) -> f64 { let dx = ((chunk_pos.x << 4) + 8) as f64 - player_pos.x; let dy = ((chunk_pos.y << 4) + 8) as f64 - player_pos.z; @@ -79,10 +73,7 @@ impl LocalMobCapCalculator { chunk_pos: &Vector2, ) -> &'b Vec { match players_near_chunk.entry(*chunk_pos) { - Entry::Occupied(value) => { - // debug!("chunk {chunk_pos:?} near player {:?}", value.get()); - value.into_mut() - } + Entry::Occupied(value) => value.into_mut(), Entry::Vacant(entry) => { let mut players = Vec::new(); for (_uuid, player) in world.players.read().await.iter() { @@ -93,14 +84,17 @@ impl LocalMobCapCalculator { players.push(player.entity_id()); } } - // debug!("chunk {chunk_pos:?} near player {:?}", players); entry.insert(players) } } } - pub async fn add_mob(&mut self, chunk_pos: &Vector2, category: &'static MobCategory) { - let players = - Self::get_players_near(&mut self.players_near_chunk, &self.world, chunk_pos).await; + pub async fn add_mob( + &mut self, + chunk_pos: &Vector2, + world: &Arc, + category: &'static MobCategory, + ) { + let players = Self::get_players_near(&mut self.players_near_chunk, world, chunk_pos).await; for player in players { self.player_mob_counts .entry(*player) @@ -115,10 +109,10 @@ impl LocalMobCapCalculator { pub async fn can_spawn( &mut self, category: &'static MobCategory, + world: &Arc, chunk_pos: &Vector2, ) -> bool { - let players = - Self::get_players_near(&mut self.players_near_chunk, &self.world, chunk_pos).await; + let players = Self::get_players_near(&mut self.players_near_chunk, world, chunk_pos).await; for player in players { if let Some(count) = self.player_mob_counts.get(player) { if count.can_spawn(category) { @@ -195,7 +189,7 @@ impl SpawnState { world: &Arc, ) -> Self { let mut potential = PotentialCalculator::default(); - let mut local_mob_cap = LocalMobCapCalculator::new(world); + let mut local_mob_cap = LocalMobCapCalculator::default(); let mut counter = MobCounts::default(); for entity in entities.read().await.values() { let entity = entity.get_entity(); @@ -211,7 +205,7 @@ impl SpawnState { } if entity_type.mob { local_mob_cap - .add_mob(&entity.chunk_pos.load(), entity_type.category) + .add_mob(&entity.chunk_pos.load(), world, entity_type.category) .await; } counter.add(entity_type.category); @@ -234,11 +228,12 @@ impl SpawnState { } async fn can_spawn_for_category_local( &mut self, + world: &Arc, category: &'static MobCategory, chunk_pos: &Vector2, ) -> bool { self.local_mob_cap_calculator - .can_spawn(category, chunk_pos) + .can_spawn(category, world, chunk_pos) .await } async fn can_spawn( @@ -285,6 +280,7 @@ impl SpawnState { self.local_mob_cap_calculator .add_mob( &Vector2::::new(get_section_cord(pos.0.x), get_section_cord(pos.0.z)), + world, entity_type.category, ) .await; @@ -331,7 +327,7 @@ pub async fn spawn_for_chunk( // debug!("spawn for chunk {:?}", chunk_pos); for category in spawn_list { if spawn_state - .can_spawn_for_category_local(category, chunk_pos) + .can_spawn_for_category_local(world, category, chunk_pos) .await { let random_pos = get_random_pos_within(world.min_y, chunk_pos, chunk).await; @@ -371,25 +367,30 @@ pub async fn spawn_category_for_position( ) { // TODO StructureManager structureManager = level.structureManager(); // TODO blockState.isRedstoneConductor(chunk, pos) is true then return + let mut batch_buffer = vec![]; let mut spawn_cluster_size = 0; let mut new_pos = pos; + let player_positions: Vec<_> = world + .players + .read() + .await + .values() + .map(|p| p.position()) + .collect(); for _ in 0..3 { let mut new_x = new_pos.0.x; let mut new_z = new_pos.0.z; + let mut random_group_size = (rng().random::() * 4.).ceil() as i32; let mut inc = 0; + 'outer: while inc < random_group_size { new_x += rng().random_range(0..6) - rng().random_range(0..6); new_z += rng().random_range(0..6) - rng().random_range(0..6); new_pos = BlockPos::new(new_x, new_pos.0.y, new_z); let new_pos_center = new_pos.to_centered_f64(); - let player_distance = get_nearest_player(&new_pos_center, world).await; - if !is_right_distance_to_player_and_spawn_point( - &new_pos, - player_distance, - world, - chunk_pos, - ) { + let player_distance = get_nearest_player(&new_pos_center, &player_positions); + if !is_right_distance_to_player_and_spawn_point(&new_pos, player_distance, chunk_pos) { inc += 1; continue; } @@ -423,7 +424,7 @@ pub async fn spawn_category_for_position( // TODO spawnGroupData = mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData); spawn_cluster_size += 1; //group_size += 1; - world.spawn_entity(entity).await; + batch_buffer.push(entity); spawn_state.after_spawn(entity_type, &new_pos, world).await; if spawn_cluster_size >= entity_type.limit_per_chunk { return; @@ -433,25 +434,59 @@ pub async fn spawn_category_for_position( inc += 1; } } -} -pub async fn get_nearest_player(pos: &Vector3, world: &Arc) -> f64 { - let mut dst = f64::MAX; - for (_uuid, player) in world.players.read().await.iter() { - if player.gamemode.load() == GameMode::Spectator { - continue; + // Spawn in batch + if !batch_buffer.is_empty() { + let mut prepared_data = Vec::with_capacity(batch_buffer.len()); + + for entity in &batch_buffer { + entity.init_data_tracker().await; + let base_entity = entity.get_entity(); + let packet = base_entity.create_spawn_packet(); + let mut nbt = NbtCompound::new(); + entity.write_nbt(&mut nbt).await; + prepared_data.push((base_entity.entity_uuid, nbt, packet)); } - let cur_dst = player.position().squared_distance_to_vec(*pos); - if cur_dst < dst { - dst = cur_dst; + { + let chunk_handle = world.level.get_entity_chunk(*chunk_pos).await; + let mut chunk_lock = chunk_handle.write().await; + let mut entities_lock = world.entities.write().await; + + for (uuid, nbt, _) in &prepared_data { + let entity_ref = batch_buffer + .iter() + .find(|e| e.get_entity().entity_uuid == *uuid) + .unwrap(); + + entities_lock.insert(*uuid, entity_ref.clone()); + chunk_lock.data.insert(*uuid, nbt.clone()); + } + chunk_lock.mark_dirty(true); + }; + + for (_, _, packet) in prepared_data { + world.broadcast_packet_all(&packet).await; } } - dst } + +#[must_use] +pub fn get_nearest_player(pos: &Vector3, player_positions: &[Vector3]) -> f64 { + let mut min_dst_sq = f64::MAX; + + for player_pos in player_positions { + let cur_dst_sq = player_pos.squared_distance_to_vec(*pos); + if cur_dst_sq < min_dst_sq { + min_dst_sq = cur_dst_sq; + } + } + min_dst_sq +} + +#[must_use] pub fn is_right_distance_to_player_and_spawn_point( pos: &BlockPos, distance: f64, - _world: &Arc, chunk_pos: &Vector2, ) -> bool { if distance <= 24. * 24. {