diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs index 25c9757d..44cc30ca 100644 --- a/azalea-client/src/packet_handling.rs +++ b/azalea-client/src/packet_handling.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, io::Cursor, sync::Arc}; +use std::{ + collections::HashSet, + io::Cursor, + sync::{Arc, Weak}, +}; use azalea_buf::McBufWritable; use azalea_chat::FormattedText; @@ -23,7 +27,7 @@ use azalea_protocol::{ }, read::ReadPacketError, }; -use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance}; +use azalea_world::{Instance, InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance}; use bevy_app::{App, First, Plugin, PreUpdate, Update}; use bevy_ecs::{ component::Component, @@ -36,7 +40,7 @@ use bevy_ecs::{ world::World, }; use log::{debug, error, trace, warn}; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use tokio::sync::mpsc; use crate::{ @@ -100,7 +104,8 @@ impl Plugin for PacketHandlerPlugin { .add_event::() .add_event::() .add_event::() - .add_event::(); + .add_event::() + .add_event::(); } } @@ -171,6 +176,17 @@ pub struct ResourcePackEvent { pub prompt: Option, } +/// An instance (aka world, dimension) was loaded by a client. +/// +/// Since the instance is given to you as a weak reference, it won't be able to +/// be `upgrade`d if all local players leave it. +#[derive(Event, Debug, Clone)] +pub struct InstanceLoadedEvent { + pub entity: Entity, + pub name: ResourceLocation, + pub instance: Weak>, +} + /// Something that receives packets from the server. #[derive(Event, Component, Clone)] pub struct PacketReceiver { @@ -227,9 +243,11 @@ pub fn process_packet_events(ecs: &mut World) { &GameProfileComponent, &ClientInformation, )>, + EventWriter, ResMut, )> = SystemState::new(ecs); - let (mut commands, mut query, mut instance_container) = system_state.get_mut(ecs); + let (mut commands, mut query, mut instance_loaded_events, mut instance_container) = + system_state.get_mut(ecs); let (mut local_player, mut entity_id_index, game_profile, client_information) = query.get_mut(player_entity).unwrap(); @@ -246,15 +264,21 @@ pub fn process_packet_events(ecs: &mut World) { }) .element; - let new_world_name = p.dimension.clone(); + let new_instance_name = p.dimension.clone(); // add this world to the instance_container (or don't if it's already // there) - let weak_world = instance_container.insert( - new_world_name.clone(), + let instance = instance_container.insert( + new_instance_name.clone(), dimension.height, dimension.min_y, ); + instance_loaded_events.send(InstanceLoadedEvent { + entity: player_entity, + name: new_instance_name.clone(), + instance: Arc::downgrade(&instance), + }); + // set the partial_world to an empty world // (when we add chunks or entities those will be in the // instance_container) @@ -267,14 +291,14 @@ pub fn process_packet_events(ecs: &mut World) { // in a shared world Some(player_entity), ); - local_player.world = weak_world; + local_player.world = instance; let player_bundle = PlayerBundle { entity: EntityBundle::new( game_profile.uuid, Vec3::default(), azalea_registry::EntityKind::Player, - new_world_name, + new_instance_name, ), metadata: PlayerMetadataBundle::default(), }; @@ -1161,9 +1185,11 @@ pub fn process_packet_events(ecs: &mut World) { &ClientInformation, &ReceivedRegistries, )>, + EventWriter, ResMut, )> = SystemState::new(ecs); - let (mut commands, mut query, mut instance_container) = system_state.get_mut(ecs); + let (mut commands, mut query, mut instance_loaded_events, mut instance_container) = + system_state.get_mut(ecs); let (mut local_player, game_profile, client_information, received_registries) = query.get_mut(player_entity).unwrap(); @@ -1178,15 +1204,20 @@ pub fn process_packet_events(ecs: &mut World) { }) .element; - let new_world_name = p.dimension.clone(); + let new_instance_name = p.dimension.clone(); // add this world to the instance_container (or don't if it's already // there) - let weak_world = instance_container.insert( - new_world_name.clone(), + let instance = instance_container.insert( + new_instance_name.clone(), dimension.height, dimension.min_y, ); + instance_loaded_events.send(InstanceLoadedEvent { + entity: player_entity, + name: new_instance_name.clone(), + instance: Arc::downgrade(&instance), + }); // set the partial_world to an empty world // (when we add chunks or entities those will be in the @@ -1197,7 +1228,7 @@ pub fn process_packet_events(ecs: &mut World) { ), Some(player_entity), ); - local_player.world = weak_world; + local_player.world = instance; // this resets a bunch of our components like physics and stuff let player_bundle = PlayerBundle { @@ -1205,7 +1236,7 @@ pub fn process_packet_events(ecs: &mut World) { game_profile.uuid, Vec3::default(), azalea_registry::EntityKind::Player, - new_world_name, + new_instance_name, ), metadata: PlayerMetadataBundle::default(), }; diff --git a/azalea-world/src/bit_storage.rs b/azalea-world/src/bit_storage.rs index 9f9b7abf..13fa0698 100755 --- a/azalea-world/src/bit_storage.rs +++ b/azalea-world/src/bit_storage.rs @@ -218,6 +218,32 @@ impl BitStorage { pub fn size(&self) -> usize { self.size } + + pub fn iter(&self) -> BitStorageIter { + BitStorageIter { + storage: self, + index: 0, + } + } +} + +pub struct BitStorageIter<'a> { + storage: &'a BitStorage, + index: usize, +} + +impl<'a> Iterator for BitStorageIter<'a> { + type Item = u64; + + fn next(&mut self) -> Option { + if self.index >= self.storage.size { + return None; + } + + let value = self.storage.get(self.index); + self.index += 1; + Some(value) + } } #[cfg(test)] diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 866ac157..79cf2105 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -27,20 +27,18 @@ pub struct InstanceContainer { // telling them apart. We hope most servers are nice and don't do that though. It's only an // issue when there's multiple clients with the same WorldContainer in different worlds // anyways. - pub worlds: HashMap>>, + pub instances: HashMap>>, } impl InstanceContainer { pub fn new() -> Self { - InstanceContainer { - worlds: HashMap::new(), - } + InstanceContainer::default() } /// Get a world from the container. Returns `None` if none of the clients /// are in this world. pub fn get(&self, name: &InstanceName) -> Option>> { - self.worlds.get(name).and_then(|world| world.upgrade()) + self.instances.get(name).and_then(|world| world.upgrade()) } /// Add an empty world to the container (or not if it already exists) and @@ -52,7 +50,7 @@ impl InstanceContainer { height: u32, min_y: i32, ) -> Arc> { - if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) { + if let Some(existing_lock) = self.instances.get(&name).and_then(|world| world.upgrade()) { let existing = existing_lock.read(); if existing.chunks.height != height { error!( @@ -73,7 +71,7 @@ impl InstanceContainer { entities_by_chunk: HashMap::new(), entity_by_id: IntMap::default(), })); - self.worlds.insert(name, Arc::downgrade(&world)); + self.instances.insert(name, Arc::downgrade(&world)); world } } diff --git a/azalea-world/src/heightmap.rs b/azalea-world/src/heightmap.rs index ec73adf9..81aeb1e2 100644 --- a/azalea-world/src/heightmap.rs +++ b/azalea-world/src/heightmap.rs @@ -119,6 +119,25 @@ impl Heightmap { false } + + /// Get an iterator over the top available block positions in this + /// heightmap. + pub fn iter_first_available<'a>(&'a self) -> impl Iterator + 'a { + self.data.iter().enumerate().map(move |(index, height)| { + let x = (index % 16) as u8; + let z = (index / 16) as u8; + ChunkBlockPos::new(x, height as i32 + self.min_y, z) + }) + } + + /// Get an iterator over the top block positions in this heightmap. + pub fn iter_highest_taken<'a>(&'a self) -> impl Iterator + 'a { + self.data.iter().enumerate().map(move |(index, height)| { + let x = (index % 16) as u8; + let z = (index / 16) as u8; + ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z) + }) + } } impl FromStr for HeightmapKind { diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index eaa36832..c178a1d8 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -339,7 +339,7 @@ async fn swarm_handle( SwarmEvent::Chat(m) => { println!("swarm chat message: {}", m.message().to_ansi()); if m.message().to_string() == " world" { - for (name, world) in &swarm.instance_container.read().worlds { + for (name, world) in &swarm.instance_container.read().instances { println!("world name: {name}"); if let Some(w) = world.upgrade() { for chunk_pos in w.read().chunks.map.values() { diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 397e6f90..02ea1207 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -20,7 +20,7 @@ pub use azalea_block as blocks; pub use azalea_brigadier as brigadier; pub use azalea_chat::FormattedText; pub use azalea_client::*; -pub use azalea_core::{BlockPos, ChunkPos, Vec3}; +pub use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3}; pub use azalea_entity as entity; pub use azalea_protocol as protocol; pub use azalea_registry::{Block, EntityKind, Item}; diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index 42d42ff9..b077b04d 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -70,7 +70,7 @@ impl Simulation { // make sure it doesn't do fixed ticks without us telling it to .insert_resource(FixedTime::new(Duration::from_secs(60))) .insert_resource(InstanceContainer { - worlds: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] + instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] .iter() .cloned() .collect(),