diff --git a/Cargo.lock b/Cargo.lock index af4f94dc..a95ec186 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "azalea-core", "azalea-nbt", "azalea-registry", + "bevy_app", "bevy_ecs", "derive_more", "enum-as-inner", @@ -454,6 +455,31 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bevy_app" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_utils", + "downcast-rs", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7baf73c58d41c353c6fd08e6764a2e7420c9f19e8227b391c50981db6d0282a6" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + [[package]] name = "bevy_ecs" version = "0.9.1" diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 98afc23a..61c121e5 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -36,7 +36,7 @@ use azalea_world::{ metadata::{self, PlayerMetadataBundle}, EntityId, }, - PartialChunkStorage, PartialWorld, WeakWorld, WeakWorldContainer, + PartialChunkStorage, PartialWorld, WeakWorldContainer, World, }; use bevy_ecs::{ prelude::Component, @@ -96,6 +96,8 @@ pub enum Event { Death(Option>), } +/// Client has the things that a user interacting with the library will want. +/// Things that a player in the world will want to know are in [`LocalPlayer`]. pub struct Client { pub profile: GameProfile, pub entity_id: Arc>, @@ -150,7 +152,6 @@ impl Client { world: Arc::new(RwLock::new(PartialWorld::default())), world_container: world_container .unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))), - world_name: Arc::new(RwLock::new(None)), // The plugins can be modified by the user by replacing the plugins // field right after this. No Mutex so the user doesn't need to .lock(). plugins: Arc::new(PluginStates::default()), @@ -196,12 +197,19 @@ impl Client { // we got the GameConnection, so the server is now connected :) let client = Client::new(game_profile.clone(), None); - let local_player = - crate::local_player::LocalPlayer::new(game_profile, conn, client.world.clone(), tx); + + let (read_conn, write_conn) = conn.into_split(); + + let local_player = crate::local_player::LocalPlayer::new( + game_profile, + write_conn, + client.world.clone(), + tx, + ); // just start up the game loop and we're ready! - client.start_tasks(); + client.start_tasks(read_conn); Ok((client, rx)) } @@ -326,7 +334,7 @@ impl Client { /// Write a packet directly to the server. pub async fn write_packet(&self, packet: ServerboundGamePacket) -> Result<(), std::io::Error> { - self.local_player_mut().write_packet(packet).await + self.local_player_mut().write_packet_async(packet).await } /// Disconnect this client from the server, ending all tasks. @@ -353,37 +361,26 @@ impl Client { /// Start the protocol and game tick loop. #[doc(hidden)] - pub fn start_tasks(&self) { + pub fn start_tasks(&self, read_conn: ReadConnection) { // if you get an error right here that means you're doing something with locks // wrong read the error to see where the issue is // you might be able to just drop the lock or put it in its own scope to fix let mut tasks = self.tasks.lock(); - tasks.push(tokio::spawn(Client::protocol_loop( - self.clone(), - tx.clone(), - ))); + tasks.push(tokio::spawn(self.protocol_loop(tx.clone(), read_conn))); let ecs = self.world_container.clone().read().ecs; tasks.push(tokio::spawn(Client::game_tick_loop(ecs.clone()))); } - async fn protocol_loop(local_player: LocalPlayer, tx: Sender) { + async fn protocol_loop( + &self, + tx: Sender, + read_conn: ReadConnection, + ) { loop { - let r = local_player.read_conn.lock().await.read().await; + let r = read_conn.lock().await.read().await; match r { - Ok(packet) => { - match { - LocalPlayer::send_event(Event::Packet(packet.clone()), &tx); - LocalPlayer::handle_packet(&packet, &client, &tx) - } { - Ok(_) => {} - Err(e) => { - error!("Error handling packet: {}", e); - if !IGNORE_ERRORS { - panic!("Error handling packet: {e}"); - } - } - }, + Ok(packet) => LocalPlayer::send_event(Event::Packet(packet.clone()), &tx), Err(e) => { let e = *e; if let ReadPacketError::ConnectionClosed = e { @@ -408,10 +405,591 @@ impl Client { panic!("{e}\n{backtrace}") } } - }; + } } } + // handling packets is outside of the ecs schedule since it's not bound to + // ticks. + pub fn handle_packet( + client: &Client, + packet: ClientboundGamePacket, + ecs: &mut bevy_ecs::world::World, + tx: &mpsc::Sender, + ) -> Result<(), HandlePacketError> { + let (mut local_player,) = query.get_mut((*player_entity_id).into()).unwrap(); + + match &packet { + ClientboundGamePacket::Login(p) => { + debug!("Got login packet"); + + { + // // write p into login.txt + // std::io::Write::write_all( + // &mut std::fs::File::create("login.txt").unwrap(), + // format!("{:#?}", p).as_bytes(), + // ) + // .unwrap(); + + // TODO: have registry_holder be a struct because this sucks rn + // best way would be to add serde support to azalea-nbt + + let registry_holder = p + .registry_holder + .as_compound() + .expect("Registry holder is not a compound") + .get("") + .expect("No \"\" tag") + .as_compound() + .expect("\"\" tag is not a compound"); + let dimension_types = registry_holder + .get("minecraft:dimension_type") + .expect("No dimension_type tag") + .as_compound() + .expect("dimension_type is not a compound") + .get("value") + .expect("No dimension_type value") + .as_list() + .expect("dimension_type value is not a list"); + let dimension_type = dimension_types + .iter() + .find(|t| { + t.as_compound() + .expect("dimension_type value is not a compound") + .get("name") + .expect("No name tag") + .as_string() + .expect("name is not a string") + == p.dimension_type.to_string() + }) + .unwrap_or_else(|| { + panic!("No dimension_type with name {}", p.dimension_type) + }) + .as_compound() + .unwrap() + .get("element") + .expect("No element tag") + .as_compound() + .expect("element is not a compound"); + let height = (*dimension_type + .get("height") + .expect("No height tag") + .as_int() + .expect("height tag is not an int")) + .try_into() + .expect("height is not a u32"); + let min_y = *dimension_type + .get("min_y") + .expect("No min_y tag") + .as_int() + .expect("min_y tag is not an int"); + + let world_name = p.dimension.clone(); + + local_player.world_name = Some(world_name.clone()); + // add this world to the world_container (or don't if it's already there) + let weak_world = world_container.insert(world_name, height, min_y); + // set the loaded_world to an empty world + // (when we add chunks or entities those will be in the world_container) + let mut world_lock = local_player.world.write(); + *world_lock = PartialWorld::new( + local_player.client_information.view_distance.into(), + weak_world, + Some(EntityId(p.player_id)), + ); + + let player_bundle = entity::PlayerBundle { + entity: entity::EntityBundle::new( + local_player.profile.uuid, + Vec3::default(), + azalea_registry::EntityKind::Player, + ), + metadata: PlayerMetadataBundle::default(), + }; + // let entity = EntityData::new( + // client.profile.uuid, + // Vec3::default(), + // EntityMetadata::Player(metadata::Player::default()), + // ); + // the first argument makes it so other entities don't update this entity in + // a shared world + world_lock.add_entity(EntityId(p.player_id), player_bundle); + + *client.entity_id.write() = EntityId(p.player_id); + } + + // send the client information that we have set + let client_information_packet: ClientInformation = + client.local_player().client_information.clone(); + log::debug!( + "Sending client information because login: {:?}", + client_information_packet + ); + client.write_packet(client_information_packet.get()).await?; + + // brand + client + .write_packet( + ServerboundCustomPayloadPacket { + identifier: ResourceLocation::new("brand").unwrap(), + // they don't have to know :) + data: "vanilla".into(), + } + .get(), + ) + .await?; + + tx.send(Event::Login).await?; + } + ClientboundGamePacket::SetChunkCacheRadius(p) => { + debug!("Got set chunk cache radius packet {:?}", p); + } + ClientboundGamePacket::CustomPayload(p) => { + debug!("Got custom payload packet {:?}", p); + } + ClientboundGamePacket::ChangeDifficulty(p) => { + debug!("Got difficulty packet {:?}", p); + } + ClientboundGamePacket::Commands(_p) => { + debug!("Got declare commands packet"); + } + ClientboundGamePacket::PlayerAbilities(p) => { + debug!("Got player abilities packet {:?}", p); + } + ClientboundGamePacket::SetCarriedItem(p) => { + debug!("Got set carried item packet {:?}", p); + } + ClientboundGamePacket::UpdateTags(_p) => { + debug!("Got update tags packet"); + } + ClientboundGamePacket::Disconnect(p) => { + debug!("Got disconnect packet {:?}", p); + client.disconnect().await?; + } + ClientboundGamePacket::UpdateRecipes(_p) => { + debug!("Got update recipes packet"); + } + ClientboundGamePacket::EntityEvent(_p) => { + // debug!("Got entity event packet {:?}", p); + } + ClientboundGamePacket::Recipe(_p) => { + debug!("Got recipe packet"); + } + ClientboundGamePacket::PlayerPosition(p) => { + // TODO: reply with teleport confirm + debug!("Got player position packet {:?}", p); + + let (new_pos, y_rot, x_rot) = { + let player_entity_id = *client.entity(); + let world = client.world(); + // let mut player_entity = world.entity_mut(player_entity_id).unwrap(); + let (mut physics, position) = + client.query::<(&mut entity::Physics, &mut entity::Position)>(); + + let delta_movement = physics.delta; + + let is_x_relative = p.relative_arguments.x; + let is_y_relative = p.relative_arguments.y; + let is_z_relative = p.relative_arguments.z; + + let (delta_x, new_pos_x) = if is_x_relative { + physics.last_pos.x += p.x; + (delta_movement.x, position.x + p.x) + } else { + physics.last_pos.x = p.x; + (0.0, p.x) + }; + let (delta_y, new_pos_y) = if is_y_relative { + physics.last_pos.y += p.y; + (delta_movement.y, position.y + p.y) + } else { + physics.last_pos.y = p.y; + (0.0, p.y) + }; + let (delta_z, new_pos_z) = if is_z_relative { + physics.last_pos.z += p.z; + (delta_movement.z, position.z + p.z) + } else { + physics.last_pos.z = p.z; + (0.0, p.z) + }; + + let mut y_rot = p.y_rot; + let mut x_rot = p.x_rot; + if p.relative_arguments.x_rot { + x_rot += physics.x_rot; + } + if p.relative_arguments.y_rot { + y_rot += physics.y_rot; + } + + physics.delta = Vec3 { + x: delta_x, + y: delta_y, + z: delta_z, + }; + entity::set_rotation(physics.into_inner(), y_rot, x_rot); + // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means + // so investigate that ig + let new_pos = Vec3 { + x: new_pos_x, + y: new_pos_y, + z: new_pos_z, + }; + world + .set_entity_pos( + player_entity_id, + new_pos, + position.into_inner(), + physics.into_inner(), + ) + .expect("The player entity should always exist"); + + (new_pos, y_rot, x_rot) + }; + + client + .write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()) + .await?; + client + .write_packet( + ServerboundMovePlayerPosRotPacket { + x: new_pos.x, + y: new_pos.y, + z: new_pos.z, + y_rot, + x_rot, + // this is always false + on_ground: false, + } + .get(), + ) + .await?; + } + ClientboundGamePacket::PlayerInfoUpdate(p) => { + debug!("Got player info packet {:?}", p); + let mut events = Vec::new(); + { + let mut players_lock = client.players.write(); + for updated_info in &p.entries { + // add the new player maybe + if p.actions.add_player { + let player_info = PlayerInfo { + profile: updated_info.profile.clone(), + uuid: updated_info.profile.uuid, + gamemode: updated_info.game_mode, + latency: updated_info.latency, + display_name: updated_info.display_name.clone(), + }; + players_lock.insert(updated_info.profile.uuid, player_info.clone()); + events.push(Event::AddPlayer(player_info)); + } else if let Some(info) = players_lock.get_mut(&updated_info.profile.uuid) + { + // `else if` because the block for add_player above + // already sets all the fields + if p.actions.update_game_mode { + info.gamemode = updated_info.game_mode; + } + if p.actions.update_latency { + info.latency = updated_info.latency; + } + if p.actions.update_display_name { + info.display_name = updated_info.display_name.clone(); + } + events.push(Event::UpdatePlayer(info.clone())); + } else { + warn!( + "Ignoring PlayerInfoUpdate for unknown player {}", + updated_info.profile.uuid + ); + } + } + } + for event in events { + tx.send(event).await?; + } + } + ClientboundGamePacket::PlayerInfoRemove(p) => { + let mut events = Vec::new(); + { + let mut players_lock = client.players.write(); + for uuid in &p.profile_ids { + if let Some(info) = players_lock.remove(uuid) { + events.push(Event::RemovePlayer(info)); + } + } + } + for event in events { + tx.send(event).await?; + } + } + ClientboundGamePacket::SetChunkCacheCenter(p) => { + debug!("Got chunk cache center packet {:?}", p); + client + .world + .write() + .update_view_center(&ChunkPos::new(p.x, p.z)); + } + ClientboundGamePacket::LevelChunkWithLight(p) => { + // debug!("Got chunk with light packet {} {}", p.x, p.z); + let pos = ChunkPos::new(p.x, p.z); + + // OPTIMIZATION: if we already know about the chunk from the + // shared world (and not ourselves), then we don't need to + // parse it again. This is only used when we have a shared + // world, since we check that the chunk isn't currently owned + // by this client. + let shared_has_chunk = client.world.read().get_chunk(&pos).is_some(); + let this_client_has_chunk = client.world.read().chunks.limited_get(&pos).is_some(); + if shared_has_chunk && !this_client_has_chunk { + trace!( + "Skipping parsing chunk {:?} because we already know about it", + pos + ); + return Ok(()); + } + + // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); + // debug("chunk {:?}") + if let Err(e) = client + .world + .write() + .replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data)) + { + error!("Couldn't set chunk data: {}", e); + } + } + ClientboundGamePacket::LightUpdate(_p) => { + // debug!("Got light update packet {:?}", p); + } + ClientboundGamePacket::AddEntity(p) => { + debug!("Got add entity packet {:?}", p); + let bundle = p.as_entity_bundle(); + let mut world = client.world.write(); + world.add_entity(EntityId(p.id), bundle); + // the bundle doesn't include the default entity metadata so we add that + // separately + let mut entities = world.entity_infos.shared.write(); + let mut entity = entities.ecs_entity_mut(EntityId(p.id)).unwrap(); + p.apply_metadata(&mut entity); + } + ClientboundGamePacket::SetEntityData(p) => { + debug!("Got set entity data packet {:?}", p); + let world = client.world.write(); + let mut entities = world.entity_infos.shared.write(); + let entity = entities.ecs_entity_mut(EntityId(p.id)); + if let Some(mut entity) = entity { + entity::metadata::apply_metadata(&mut entity, p.packed_items.0.clone()); + } else { + // warn!("Server sent an entity data packet for an + // entity id ({}) that we don't + // know about", p.id); + } + } + ClientboundGamePacket::UpdateAttributes(_p) => { + // debug!("Got update attributes packet {:?}", p); + } + ClientboundGamePacket::SetEntityMotion(_p) => { + // debug!("Got entity velocity packet {:?}", p); + } + ClientboundGamePacket::SetEntityLink(p) => { + debug!("Got set entity link packet {:?}", p); + } + ClientboundGamePacket::AddPlayer(p) => { + debug!("Got add player packet {:?}", p); + let bundle = p.as_player_bundle(); + let mut world = client.world.write(); + world.add_entity(EntityId(p.id), bundle); + // the default metadata was already included in the bundle + // for us + } + ClientboundGamePacket::InitializeBorder(p) => { + debug!("Got initialize border packet {:?}", p); + } + ClientboundGamePacket::SetTime(p) => { + debug!("Got set time packet {:?}", p); + } + ClientboundGamePacket::SetDefaultSpawnPosition(p) => { + debug!("Got set default spawn position packet {:?}", p); + } + ClientboundGamePacket::ContainerSetContent(p) => { + debug!("Got container set content packet {:?}", p); + } + ClientboundGamePacket::SetHealth(p) => { + debug!("Got set health packet {:?}", p); + if p.health == 0.0 { + // we can't define a variable here with client.dead.lock() + // because of https://github.com/rust-lang/rust/issues/57478 + if !*client.dead.lock() { + *client.dead.lock() = true; + tx.send(Event::Death(None)).await?; + } + } + } + ClientboundGamePacket::SetExperience(p) => { + debug!("Got set experience packet {:?}", p); + } + ClientboundGamePacket::TeleportEntity(p) => { + let mut world = client.world.write(); + let (pos, physics) = self.query::<(&entity::Position, &entity::Physics)>(); + let _ = world.set_entity_pos( + EntityId(p.id), + Vec3 { + x: p.x, + y: p.y, + z: p.z, + }, + pos, + physics, + ); + } + ClientboundGamePacket::UpdateAdvancements(p) => { + debug!("Got update advancements packet {:?}", p); + } + ClientboundGamePacket::RotateHead(_p) => { + // debug!("Got rotate head packet {:?}", p); + } + ClientboundGamePacket::MoveEntityPos(p) => { + let mut world = client.world.write(); + let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta); + } + ClientboundGamePacket::MoveEntityPosRot(p) => { + let mut world = client.world.write(); + let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta); + } + ClientboundGamePacket::MoveEntityRot(_p) => { + // debug!("Got move entity rot packet {:?}", p); + } + ClientboundGamePacket::KeepAlive(p) => { + debug!("Got keep alive packet {:?}", p); + client + .write_packet(ServerboundKeepAlivePacket { id: p.id }.get()) + .await?; + } + ClientboundGamePacket::RemoveEntities(p) => { + debug!("Got remove entities packet {:?}", p); + } + ClientboundGamePacket::PlayerChat(p) => { + debug!("Got player chat packet {:?}", p); + tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone())))) + .await?; + } + ClientboundGamePacket::SystemChat(p) => { + debug!("Got system chat packet {:?}", p); + tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone())))) + .await?; + } + ClientboundGamePacket::Sound(_p) => { + // debug!("Got sound packet {:?}", p); + } + ClientboundGamePacket::LevelEvent(p) => { + debug!("Got level event packet {:?}", p); + } + ClientboundGamePacket::BlockUpdate(p) => { + debug!("Got block update packet {:?}", p); + let mut world = client.world.write(); + world.set_block_state(&p.pos, p.block_state); + } + ClientboundGamePacket::Animate(p) => { + debug!("Got animate packet {:?}", p); + } + ClientboundGamePacket::SectionBlocksUpdate(p) => { + debug!("Got section blocks update packet {:?}", p); + let mut world = client.world.write(); + for state in &p.states { + world.set_block_state(&(p.section_pos + state.pos.clone()), state.state); + } + } + ClientboundGamePacket::GameEvent(p) => { + debug!("Got game event packet {:?}", p); + } + ClientboundGamePacket::LevelParticles(p) => { + debug!("Got level particles packet {:?}", p); + } + ClientboundGamePacket::ServerData(p) => { + debug!("Got server data packet {:?}", p); + } + ClientboundGamePacket::SetEquipment(p) => { + debug!("Got set equipment packet {:?}", p); + } + ClientboundGamePacket::UpdateMobEffect(p) => { + debug!("Got update mob effect packet {:?}", p); + } + ClientboundGamePacket::AddExperienceOrb(_) => {} + ClientboundGamePacket::AwardStats(_) => {} + ClientboundGamePacket::BlockChangedAck(_) => {} + ClientboundGamePacket::BlockDestruction(_) => {} + ClientboundGamePacket::BlockEntityData(_) => {} + ClientboundGamePacket::BlockEvent(_) => {} + ClientboundGamePacket::BossEvent(_) => {} + ClientboundGamePacket::CommandSuggestions(_) => {} + ClientboundGamePacket::ContainerSetData(_) => {} + ClientboundGamePacket::ContainerSetSlot(_) => {} + ClientboundGamePacket::Cooldown(_) => {} + ClientboundGamePacket::CustomChatCompletions(_) => {} + ClientboundGamePacket::DeleteChat(_) => {} + ClientboundGamePacket::Explode(_) => {} + ClientboundGamePacket::ForgetLevelChunk(_) => {} + ClientboundGamePacket::HorseScreenOpen(_) => {} + ClientboundGamePacket::MapItemData(_) => {} + ClientboundGamePacket::MerchantOffers(_) => {} + ClientboundGamePacket::MoveVehicle(_) => {} + ClientboundGamePacket::OpenBook(_) => {} + ClientboundGamePacket::OpenScreen(_) => {} + ClientboundGamePacket::OpenSignEditor(_) => {} + ClientboundGamePacket::Ping(_) => {} + ClientboundGamePacket::PlaceGhostRecipe(_) => {} + ClientboundGamePacket::PlayerCombatEnd(_) => {} + ClientboundGamePacket::PlayerCombatEnter(_) => {} + ClientboundGamePacket::PlayerCombatKill(p) => { + debug!("Got player kill packet {:?}", p); + if client.entity() == EntityId(p.player_id) { + // we can't define a variable here with client.dead.lock() + // because of https://github.com/rust-lang/rust/issues/57478 + if !*client.dead.lock() { + *client.dead.lock() = true; + tx.send(Event::Death(Some(Arc::new(p.clone())))).await?; + } + } + } + ClientboundGamePacket::PlayerLookAt(_) => {} + ClientboundGamePacket::RemoveMobEffect(_) => {} + ClientboundGamePacket::ResourcePack(_) => {} + ClientboundGamePacket::Respawn(p) => { + debug!("Got respawn packet {:?}", p); + // Sets clients dead state to false. + let mut dead_lock = client.dead.lock(); + *dead_lock = false; + } + ClientboundGamePacket::SelectAdvancementsTab(_) => {} + ClientboundGamePacket::SetActionBarText(_) => {} + ClientboundGamePacket::SetBorderCenter(_) => {} + ClientboundGamePacket::SetBorderLerpSize(_) => {} + ClientboundGamePacket::SetBorderSize(_) => {} + ClientboundGamePacket::SetBorderWarningDelay(_) => {} + ClientboundGamePacket::SetBorderWarningDistance(_) => {} + ClientboundGamePacket::SetCamera(_) => {} + ClientboundGamePacket::SetDisplayObjective(_) => {} + ClientboundGamePacket::SetObjective(_) => {} + ClientboundGamePacket::SetPassengers(_) => {} + ClientboundGamePacket::SetPlayerTeam(_) => {} + ClientboundGamePacket::SetScore(_) => {} + ClientboundGamePacket::SetSimulationDistance(_) => {} + ClientboundGamePacket::SetSubtitleText(_) => {} + ClientboundGamePacket::SetTitleText(_) => {} + ClientboundGamePacket::SetTitlesAnimation(_) => {} + ClientboundGamePacket::SoundEntity(_) => {} + ClientboundGamePacket::StopSound(_) => {} + ClientboundGamePacket::TabList(_) => {} + ClientboundGamePacket::TagQuery(_) => {} + ClientboundGamePacket::TakeItemEntity(_) => {} + ClientboundGamePacket::DisguisedChat(_) => {} + ClientboundGamePacket::UpdateEnabledFeatures(_) => {} + ClientboundGamePacket::ContainerClose(_) => {} + } + Ok(()) + } + /// Start the game tick loop for every client in the shared world. This /// should only be run once per shared world! async fn game_tick_loop(ecs: Arc>) { @@ -443,7 +1021,7 @@ impl Client { /// client, then it'll be the same as the world the client has loaded. /// If the client using a shared world, then the shared world will be a /// superset of the client's world. - pub fn world(&self) -> Arc { + pub fn world(&self) -> Arc { self.world.read().shared.clone() } diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 3de7704f..3b7050e7 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -30,8 +30,7 @@ use crate::{ChatPacket, ClientInformation, Event, PlayerInfo, WalkDirection}; #[derive(Component)] pub struct LocalPlayer { pub profile: GameProfile, - // Arc, + pub write_conn: WriteConnection, // pub world: Arc>, pub physics_state: PhysicsState, @@ -72,26 +71,21 @@ impl LocalPlayer { /// defaults, otherwise use [`Client::join`]. pub fn new( profile: GameProfile, - conn: Connection, + write_conn: WriteConnection, world: Arc>, tx: mpsc::Sender, ) -> Self { - let (read_conn, write_conn) = conn.into_split(); - let (read_conn, write_conn) = ( - // Arc::new(tokio::sync::Mutex::new(read_conn)), - // Arc::new(tokio::sync::Mutex::new(write_conn)), - read_conn, write_conn, - ); - LocalPlayer { profile, - read_conn, write_conn, physics_state: PhysicsState::default(), client_information: ClientInformation::default(), dead: false, players: HashMap::new(), + world, + world_name: Arc::new(RwLock::new(None)), + tx, world_name: None, } @@ -146,590 +140,6 @@ impl LocalPlayer { Self::send_event(Event::Tick, &tx); } } - - pub fn handle_packet( - mut event_reader: EventReader<(EntityId, ClientboundGamePacket)>, - query: Query<(&mut LocalPlayer,)>, - world_container: ResMut, - // client: &Client, - // tx: &mpsc::Sender, - ) -> Result<(), HandlePacketError> { - for (player_entity_id, packet) in event_reader.iter() { - let (mut local_player,) = query.get_mut((*player_entity_id).into()).unwrap(); - - match &packet { - ClientboundGamePacket::Login(p) => { - debug!("Got login packet"); - - { - // // write p into login.txt - // std::io::Write::write_all( - // &mut std::fs::File::create("login.txt").unwrap(), - // format!("{:#?}", p).as_bytes(), - // ) - // .unwrap(); - - // TODO: have registry_holder be a struct because this sucks rn - // best way would be to add serde support to azalea-nbt - - let registry_holder = p - .registry_holder - .as_compound() - .expect("Registry holder is not a compound") - .get("") - .expect("No \"\" tag") - .as_compound() - .expect("\"\" tag is not a compound"); - let dimension_types = registry_holder - .get("minecraft:dimension_type") - .expect("No dimension_type tag") - .as_compound() - .expect("dimension_type is not a compound") - .get("value") - .expect("No dimension_type value") - .as_list() - .expect("dimension_type value is not a list"); - let dimension_type = dimension_types - .iter() - .find(|t| { - t.as_compound() - .expect("dimension_type value is not a compound") - .get("name") - .expect("No name tag") - .as_string() - .expect("name is not a string") - == p.dimension_type.to_string() - }) - .unwrap_or_else(|| { - panic!("No dimension_type with name {}", p.dimension_type) - }) - .as_compound() - .unwrap() - .get("element") - .expect("No element tag") - .as_compound() - .expect("element is not a compound"); - let height = (*dimension_type - .get("height") - .expect("No height tag") - .as_int() - .expect("height tag is not an int")) - .try_into() - .expect("height is not a u32"); - let min_y = *dimension_type - .get("min_y") - .expect("No min_y tag") - .as_int() - .expect("min_y tag is not an int"); - - let world_name = p.dimension.clone(); - - local_player.world_name = Some(world_name.clone()); - // add this world to the world_container (or don't if it's already there) - let weak_world = world_container.insert(world_name, height, min_y); - // set the loaded_world to an empty world - // (when we add chunks or entities those will be in the world_container) - let mut world_lock = local_player.world.write(); - *world_lock = PartialWorld::new( - local_player.client_information.view_distance.into(), - weak_world, - Some(EntityId(p.player_id)), - ); - - let player_bundle = entity::PlayerBundle { - entity: entity::EntityBundle::new( - local_player.profile.uuid, - Vec3::default(), - azalea_registry::EntityKind::Player, - ), - metadata: PlayerMetadataBundle::default(), - }; - // let entity = EntityData::new( - // client.profile.uuid, - // Vec3::default(), - // EntityMetadata::Player(metadata::Player::default()), - // ); - // the first argument makes it so other entities don't update this entity in - // a shared world - world_lock.add_entity(EntityId(p.player_id), player_bundle); - - *client.entity_id.write() = EntityId(p.player_id); - } - - // send the client information that we have set - let client_information_packet: ClientInformation = - client.local_player().client_information.clone(); - log::debug!( - "Sending client information because login: {:?}", - client_information_packet - ); - client.write_packet(client_information_packet.get()).await?; - - // brand - client - .write_packet( - ServerboundCustomPayloadPacket { - identifier: ResourceLocation::new("brand").unwrap(), - // they don't have to know :) - data: "vanilla".into(), - } - .get(), - ) - .await?; - - tx.send(Event::Login).await?; - } - ClientboundGamePacket::SetChunkCacheRadius(p) => { - debug!("Got set chunk cache radius packet {:?}", p); - } - ClientboundGamePacket::CustomPayload(p) => { - debug!("Got custom payload packet {:?}", p); - } - ClientboundGamePacket::ChangeDifficulty(p) => { - debug!("Got difficulty packet {:?}", p); - } - ClientboundGamePacket::Commands(_p) => { - debug!("Got declare commands packet"); - } - ClientboundGamePacket::PlayerAbilities(p) => { - debug!("Got player abilities packet {:?}", p); - } - ClientboundGamePacket::SetCarriedItem(p) => { - debug!("Got set carried item packet {:?}", p); - } - ClientboundGamePacket::UpdateTags(_p) => { - debug!("Got update tags packet"); - } - ClientboundGamePacket::Disconnect(p) => { - debug!("Got disconnect packet {:?}", p); - client.disconnect().await?; - } - ClientboundGamePacket::UpdateRecipes(_p) => { - debug!("Got update recipes packet"); - } - ClientboundGamePacket::EntityEvent(_p) => { - // debug!("Got entity event packet {:?}", p); - } - ClientboundGamePacket::Recipe(_p) => { - debug!("Got recipe packet"); - } - ClientboundGamePacket::PlayerPosition(p) => { - // TODO: reply with teleport confirm - debug!("Got player position packet {:?}", p); - - let (new_pos, y_rot, x_rot) = { - let player_entity_id = *client.entity(); - let world = client.world(); - // let mut player_entity = world.entity_mut(player_entity_id).unwrap(); - let (mut physics, position) = - client.query::<(&mut entity::Physics, &mut entity::Position)>(); - - let delta_movement = physics.delta; - - let is_x_relative = p.relative_arguments.x; - let is_y_relative = p.relative_arguments.y; - let is_z_relative = p.relative_arguments.z; - - let (delta_x, new_pos_x) = if is_x_relative { - physics.last_pos.x += p.x; - (delta_movement.x, position.x + p.x) - } else { - physics.last_pos.x = p.x; - (0.0, p.x) - }; - let (delta_y, new_pos_y) = if is_y_relative { - physics.last_pos.y += p.y; - (delta_movement.y, position.y + p.y) - } else { - physics.last_pos.y = p.y; - (0.0, p.y) - }; - let (delta_z, new_pos_z) = if is_z_relative { - physics.last_pos.z += p.z; - (delta_movement.z, position.z + p.z) - } else { - physics.last_pos.z = p.z; - (0.0, p.z) - }; - - let mut y_rot = p.y_rot; - let mut x_rot = p.x_rot; - if p.relative_arguments.x_rot { - x_rot += physics.x_rot; - } - if p.relative_arguments.y_rot { - y_rot += physics.y_rot; - } - - physics.delta = Vec3 { - x: delta_x, - y: delta_y, - z: delta_z, - }; - entity::set_rotation(physics.into_inner(), y_rot, x_rot); - // TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means - // so investigate that ig - let new_pos = Vec3 { - x: new_pos_x, - y: new_pos_y, - z: new_pos_z, - }; - world - .set_entity_pos( - player_entity_id, - new_pos, - position.into_inner(), - physics.into_inner(), - ) - .expect("The player entity should always exist"); - - (new_pos, y_rot, x_rot) - }; - - client - .write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get()) - .await?; - client - .write_packet( - ServerboundMovePlayerPosRotPacket { - x: new_pos.x, - y: new_pos.y, - z: new_pos.z, - y_rot, - x_rot, - // this is always false - on_ground: false, - } - .get(), - ) - .await?; - } - ClientboundGamePacket::PlayerInfoUpdate(p) => { - debug!("Got player info packet {:?}", p); - let mut events = Vec::new(); - { - let mut players_lock = client.players.write(); - for updated_info in &p.entries { - // add the new player maybe - if p.actions.add_player { - let player_info = PlayerInfo { - profile: updated_info.profile.clone(), - uuid: updated_info.profile.uuid, - gamemode: updated_info.game_mode, - latency: updated_info.latency, - display_name: updated_info.display_name.clone(), - }; - players_lock.insert(updated_info.profile.uuid, player_info.clone()); - events.push(Event::AddPlayer(player_info)); - } else if let Some(info) = - players_lock.get_mut(&updated_info.profile.uuid) - { - // `else if` because the block for add_player above - // already sets all the fields - if p.actions.update_game_mode { - info.gamemode = updated_info.game_mode; - } - if p.actions.update_latency { - info.latency = updated_info.latency; - } - if p.actions.update_display_name { - info.display_name = updated_info.display_name.clone(); - } - events.push(Event::UpdatePlayer(info.clone())); - } else { - warn!( - "Ignoring PlayerInfoUpdate for unknown player {}", - updated_info.profile.uuid - ); - } - } - } - for event in events { - tx.send(event).await?; - } - } - ClientboundGamePacket::PlayerInfoRemove(p) => { - let mut events = Vec::new(); - { - let mut players_lock = client.players.write(); - for uuid in &p.profile_ids { - if let Some(info) = players_lock.remove(uuid) { - events.push(Event::RemovePlayer(info)); - } - } - } - for event in events { - tx.send(event).await?; - } - } - ClientboundGamePacket::SetChunkCacheCenter(p) => { - debug!("Got chunk cache center packet {:?}", p); - client - .world - .write() - .update_view_center(&ChunkPos::new(p.x, p.z)); - } - ClientboundGamePacket::LevelChunkWithLight(p) => { - // debug!("Got chunk with light packet {} {}", p.x, p.z); - let pos = ChunkPos::new(p.x, p.z); - - // OPTIMIZATION: if we already know about the chunk from the - // shared world (and not ourselves), then we don't need to - // parse it again. This is only used when we have a shared - // world, since we check that the chunk isn't currently owned - // by this client. - let shared_has_chunk = client.world.read().get_chunk(&pos).is_some(); - let this_client_has_chunk = - client.world.read().chunks.limited_get(&pos).is_some(); - if shared_has_chunk && !this_client_has_chunk { - trace!( - "Skipping parsing chunk {:?} because we already know about it", - pos - ); - return Ok(()); - } - - // let chunk = Chunk::read_with_world_height(&mut p.chunk_data); - // debug("chunk {:?}") - if let Err(e) = client - .world - .write() - .replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data)) - { - error!("Couldn't set chunk data: {}", e); - } - } - ClientboundGamePacket::LightUpdate(_p) => { - // debug!("Got light update packet {:?}", p); - } - ClientboundGamePacket::AddEntity(p) => { - debug!("Got add entity packet {:?}", p); - let bundle = p.as_entity_bundle(); - let mut world = client.world.write(); - world.add_entity(EntityId(p.id), bundle); - // the bundle doesn't include the default entity metadata so we add that - // separately - let mut entities = world.entity_infos.shared.write(); - let mut entity = entities.ecs_entity_mut(EntityId(p.id)).unwrap(); - p.apply_metadata(&mut entity); - } - ClientboundGamePacket::SetEntityData(p) => { - debug!("Got set entity data packet {:?}", p); - let world = client.world.write(); - let mut entities = world.entity_infos.shared.write(); - let entity = entities.ecs_entity_mut(EntityId(p.id)); - if let Some(mut entity) = entity { - entity::metadata::apply_metadata(&mut entity, p.packed_items.0.clone()); - } else { - // warn!("Server sent an entity data packet for an - // entity id ({}) that we don't - // know about", p.id); - } - } - ClientboundGamePacket::UpdateAttributes(_p) => { - // debug!("Got update attributes packet {:?}", p); - } - ClientboundGamePacket::SetEntityMotion(_p) => { - // debug!("Got entity velocity packet {:?}", p); - } - ClientboundGamePacket::SetEntityLink(p) => { - debug!("Got set entity link packet {:?}", p); - } - ClientboundGamePacket::AddPlayer(p) => { - debug!("Got add player packet {:?}", p); - let bundle = p.as_player_bundle(); - let mut world = client.world.write(); - world.add_entity(EntityId(p.id), bundle); - // the default metadata was already included in the bundle - // for us - } - ClientboundGamePacket::InitializeBorder(p) => { - debug!("Got initialize border packet {:?}", p); - } - ClientboundGamePacket::SetTime(p) => { - debug!("Got set time packet {:?}", p); - } - ClientboundGamePacket::SetDefaultSpawnPosition(p) => { - debug!("Got set default spawn position packet {:?}", p); - } - ClientboundGamePacket::ContainerSetContent(p) => { - debug!("Got container set content packet {:?}", p); - } - ClientboundGamePacket::SetHealth(p) => { - debug!("Got set health packet {:?}", p); - if p.health == 0.0 { - // we can't define a variable here with client.dead.lock() - // because of https://github.com/rust-lang/rust/issues/57478 - if !*client.dead.lock() { - *client.dead.lock() = true; - tx.send(Event::Death(None)).await?; - } - } - } - ClientboundGamePacket::SetExperience(p) => { - debug!("Got set experience packet {:?}", p); - } - ClientboundGamePacket::TeleportEntity(p) => { - let mut world = client.world.write(); - let (pos, physics) = self.query::<(&entity::Position, &entity::Physics)>(); - let _ = world.set_entity_pos( - EntityId(p.id), - Vec3 { - x: p.x, - y: p.y, - z: p.z, - }, - pos, - physics, - ); - } - ClientboundGamePacket::UpdateAdvancements(p) => { - debug!("Got update advancements packet {:?}", p); - } - ClientboundGamePacket::RotateHead(_p) => { - // debug!("Got rotate head packet {:?}", p); - } - ClientboundGamePacket::MoveEntityPos(p) => { - let mut world = client.world.write(); - let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta); - } - ClientboundGamePacket::MoveEntityPosRot(p) => { - let mut world = client.world.write(); - let _ = world.move_entity_with_delta(EntityId(p.entity_id), &p.delta); - } - ClientboundGamePacket::MoveEntityRot(_p) => { - // debug!("Got move entity rot packet {:?}", p); - } - ClientboundGamePacket::KeepAlive(p) => { - debug!("Got keep alive packet {:?}", p); - client - .write_packet(ServerboundKeepAlivePacket { id: p.id }.get()) - .await?; - } - ClientboundGamePacket::RemoveEntities(p) => { - debug!("Got remove entities packet {:?}", p); - } - ClientboundGamePacket::PlayerChat(p) => { - debug!("Got player chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::Player(Arc::new(p.clone())))) - .await?; - } - ClientboundGamePacket::SystemChat(p) => { - debug!("Got system chat packet {:?}", p); - tx.send(Event::Chat(ChatPacket::System(Arc::new(p.clone())))) - .await?; - } - ClientboundGamePacket::Sound(_p) => { - // debug!("Got sound packet {:?}", p); - } - ClientboundGamePacket::LevelEvent(p) => { - debug!("Got level event packet {:?}", p); - } - ClientboundGamePacket::BlockUpdate(p) => { - debug!("Got block update packet {:?}", p); - let mut world = client.world.write(); - world.set_block_state(&p.pos, p.block_state); - } - ClientboundGamePacket::Animate(p) => { - debug!("Got animate packet {:?}", p); - } - ClientboundGamePacket::SectionBlocksUpdate(p) => { - debug!("Got section blocks update packet {:?}", p); - let mut world = client.world.write(); - for state in &p.states { - world.set_block_state(&(p.section_pos + state.pos.clone()), state.state); - } - } - ClientboundGamePacket::GameEvent(p) => { - debug!("Got game event packet {:?}", p); - } - ClientboundGamePacket::LevelParticles(p) => { - debug!("Got level particles packet {:?}", p); - } - ClientboundGamePacket::ServerData(p) => { - debug!("Got server data packet {:?}", p); - } - ClientboundGamePacket::SetEquipment(p) => { - debug!("Got set equipment packet {:?}", p); - } - ClientboundGamePacket::UpdateMobEffect(p) => { - debug!("Got update mob effect packet {:?}", p); - } - ClientboundGamePacket::AddExperienceOrb(_) => {} - ClientboundGamePacket::AwardStats(_) => {} - ClientboundGamePacket::BlockChangedAck(_) => {} - ClientboundGamePacket::BlockDestruction(_) => {} - ClientboundGamePacket::BlockEntityData(_) => {} - ClientboundGamePacket::BlockEvent(_) => {} - ClientboundGamePacket::BossEvent(_) => {} - ClientboundGamePacket::CommandSuggestions(_) => {} - ClientboundGamePacket::ContainerSetData(_) => {} - ClientboundGamePacket::ContainerSetSlot(_) => {} - ClientboundGamePacket::Cooldown(_) => {} - ClientboundGamePacket::CustomChatCompletions(_) => {} - ClientboundGamePacket::DeleteChat(_) => {} - ClientboundGamePacket::Explode(_) => {} - ClientboundGamePacket::ForgetLevelChunk(_) => {} - ClientboundGamePacket::HorseScreenOpen(_) => {} - ClientboundGamePacket::MapItemData(_) => {} - ClientboundGamePacket::MerchantOffers(_) => {} - ClientboundGamePacket::MoveVehicle(_) => {} - ClientboundGamePacket::OpenBook(_) => {} - ClientboundGamePacket::OpenScreen(_) => {} - ClientboundGamePacket::OpenSignEditor(_) => {} - ClientboundGamePacket::Ping(_) => {} - ClientboundGamePacket::PlaceGhostRecipe(_) => {} - ClientboundGamePacket::PlayerCombatEnd(_) => {} - ClientboundGamePacket::PlayerCombatEnter(_) => {} - ClientboundGamePacket::PlayerCombatKill(p) => { - debug!("Got player kill packet {:?}", p); - if client.entity() == EntityId(p.player_id) { - // we can't define a variable here with client.dead.lock() - // because of https://github.com/rust-lang/rust/issues/57478 - if !*client.dead.lock() { - *client.dead.lock() = true; - tx.send(Event::Death(Some(Arc::new(p.clone())))).await?; - } - } - } - ClientboundGamePacket::PlayerLookAt(_) => {} - ClientboundGamePacket::RemoveMobEffect(_) => {} - ClientboundGamePacket::ResourcePack(_) => {} - ClientboundGamePacket::Respawn(p) => { - debug!("Got respawn packet {:?}", p); - // Sets clients dead state to false. - let mut dead_lock = client.dead.lock(); - *dead_lock = false; - } - ClientboundGamePacket::SelectAdvancementsTab(_) => {} - ClientboundGamePacket::SetActionBarText(_) => {} - ClientboundGamePacket::SetBorderCenter(_) => {} - ClientboundGamePacket::SetBorderLerpSize(_) => {} - ClientboundGamePacket::SetBorderSize(_) => {} - ClientboundGamePacket::SetBorderWarningDelay(_) => {} - ClientboundGamePacket::SetBorderWarningDistance(_) => {} - ClientboundGamePacket::SetCamera(_) => {} - ClientboundGamePacket::SetDisplayObjective(_) => {} - ClientboundGamePacket::SetObjective(_) => {} - ClientboundGamePacket::SetPassengers(_) => {} - ClientboundGamePacket::SetPlayerTeam(_) => {} - ClientboundGamePacket::SetScore(_) => {} - ClientboundGamePacket::SetSimulationDistance(_) => {} - ClientboundGamePacket::SetSubtitleText(_) => {} - ClientboundGamePacket::SetTitleText(_) => {} - ClientboundGamePacket::SetTitlesAnimation(_) => {} - ClientboundGamePacket::SoundEntity(_) => {} - ClientboundGamePacket::StopSound(_) => {} - ClientboundGamePacket::TabList(_) => {} - ClientboundGamePacket::TagQuery(_) => {} - ClientboundGamePacket::TakeItemEntity(_) => {} - ClientboundGamePacket::DisguisedChat(_) => {} - ClientboundGamePacket::UpdateEnabledFeatures(_) => {} - ClientboundGamePacket::ContainerClose(_) => {} - } - } - Ok(()) - } } #[derive(Error, Debug)] diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 27b8c4e3..a00cb4ac 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -7,7 +7,7 @@ mod world_collisions; use azalea_core::{Axis, Vec3, AABB, EPSILON}; use azalea_world::{ entity::{self, EntityId}, - MoveEntityError, WeakWorld, + MoveEntityError, World, }; pub use blocks::BlockWithShape; pub use discrete_voxel_shape::*; @@ -53,7 +53,7 @@ pub enum MoverType { // return var4; // } -fn collide(movement: &Vec3, world: &WeakWorld, physics: &entity::Physics) -> Vec3 { +fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 { let entity_bounding_box = physics.bounding_box; // TODO: get_entity_collisions // let entity_collisions = world.get_entity_collisions(self, @@ -75,7 +75,7 @@ pub fn move_colliding( _mover_type: &MoverType, movement: &Vec3, entity_id: EntityId, - world: &WeakWorld, + world: &World, position: &mut entity::Position, physics: &mut entity::Physics, ) -> Result<(), MoveEntityError> { @@ -188,7 +188,7 @@ pub fn move_colliding( fn collide_bounding_box( movement: &Vec3, entity_bounding_box: &AABB, - world: &WeakWorld, + world: &World, entity_collisions: Vec, ) -> Vec3 { let mut collision_boxes: Vec = Vec::with_capacity(entity_collisions.len() + 1); diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index 1f241609..d4a552d6 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -2,16 +2,16 @@ use super::Shapes; use crate::collision::{BlockWithShape, VoxelShape, AABB}; use azalea_block::BlockState; use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; -use azalea_world::{entity::EntityId, Chunk, WeakWorld}; +use azalea_world::{entity::EntityId, Chunk, World}; use parking_lot::RwLock; use std::sync::Arc; -pub fn get_block_collisions<'a>(world: &'a WeakWorld, aabb: AABB) -> BlockCollisions<'a> { +pub fn get_block_collisions<'a>(world: &'a World, aabb: AABB) -> BlockCollisions<'a> { BlockCollisions::new(world, aabb) } pub struct BlockCollisions<'a> { - pub world: &'a WeakWorld, + pub world: &'a World, pub aabb: AABB, pub entity_shape: VoxelShape, pub cursor: Cursor3d, @@ -19,7 +19,7 @@ pub struct BlockCollisions<'a> { } impl<'a> BlockCollisions<'a> { - pub fn new(world: &'a WeakWorld, aabb: AABB) -> Self { + pub fn new(world: &'a World, aabb: AABB) -> Self { let origin_x = (aabb.min_x - EPSILON) as i32 - 1; let origin_y = (aabb.min_y - EPSILON) as i32 - 1; let origin_z = (aabb.min_z - EPSILON) as i32 - 1; diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index ac384e2e..b249232f 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -6,7 +6,7 @@ use azalea_block::{Block, BlockState}; use azalea_core::{BlockPos, Vec3}; use azalea_world::{ entity::{self, EntityId}, - WeakWorld, + World, }; use collision::{move_colliding, MoverType}; use std::ops::Deref; @@ -15,7 +15,7 @@ use std::ops::Deref; /// gravity, collisions, and some other stuff. fn travel( entity_id: EntityId, - world: &WeakWorld, + world: &World, physics: &mut entity::Physics, position: &mut entity::Position, attributes: &entity::Attributes, @@ -84,7 +84,7 @@ fn travel( /// stuff. pub fn ai_step( entity_id: EntityId, - world: &WeakWorld, + world: &World, physics: &mut entity::Physics, position: &mut entity::Position, sprinting: &entity::metadata::Sprinting, @@ -132,7 +132,7 @@ pub fn ai_step( } fn jump_from_ground( - world: &WeakWorld, + world: &World, physics: &mut entity::Physics, position: &entity::Position, sprinting: &entity::metadata::Sprinting, @@ -169,7 +169,7 @@ fn handle_relative_friction_and_calculate_movement( acceleration: &Vec3, block_friction: f32, entity_id: EntityId, - world: &WeakWorld, + world: &World, physics: &mut entity::Physics, position: &mut entity::Position, attributes: &entity::Attributes, @@ -220,7 +220,7 @@ fn get_friction_influenced_speed( /// Returns the what the entity's jump should be multiplied by based on the /// block they're standing on. -fn block_jump_factor(world: &WeakWorld, position: &entity::Position) -> f32 { +fn block_jump_factor(world: &World, position: &entity::Position) -> f32 { let block_at_pos = world.get_block_state(&position.into()); let block_below = world.get_block_state(&get_block_pos_below_that_affects_movement(position)); @@ -246,7 +246,7 @@ fn block_jump_factor(world: &WeakWorld, position: &entity::Position) -> f32 { // public double getJumpBoostPower() { // return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * // (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } -fn jump_power(world: &WeakWorld, position: &entity::Position) -> f32 { +fn jump_power(world: &World, position: &entity::Position) -> f32 { 0.42 * block_jump_factor(world, position) } diff --git a/azalea-world/Cargo.toml b/azalea-world/Cargo.toml index f5100105..5d826830 100644 --- a/azalea-world/Cargo.toml +++ b/azalea-world/Cargo.toml @@ -15,6 +15,7 @@ azalea-chat = {path = "../azalea-chat", version = "^0.5.0"} azalea-core = {path = "../azalea-core", version = "^0.5.0", features = ["bevy_ecs"]} azalea-nbt = {path = "../azalea-nbt", version = "^0.5.0"} azalea-registry = {path = "../azalea-registry", version = "^0.5.0"} +bevy_app = { version = "0.9.1", default-features = false } bevy_ecs = {version = "0.9.1", default-features = false} derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} enum-as-inner = "0.5.1" diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs index 5d51a204..97939c6c 100644 --- a/azalea-world/src/container.rs +++ b/azalea-world/src/container.rs @@ -1,4 +1,3 @@ -use crate::WeakWorld; use azalea_core::ResourceLocation; use bevy_ecs::system::Resource; use log::error; @@ -8,11 +7,25 @@ use std::{ sync::{Arc, Weak}, }; +use crate::World; + /// A container of [`WeakWorld`]s. Worlds are stored as a Weak pointer here, so /// if no clients are using a world it will be forgotten. #[derive(Default, Resource)] pub struct WeakWorldContainer { - pub worlds: HashMap>, + // We just refer to the chunks here and don't include entities because there's not that many + // cases where we'd want to get every entity in the world (just getting the entities in chunks + // should work fine). + + // Entities are garbage collected (by manual reference counting in EntityInfos) so we don't + // need to worry about them here. + + // If it looks like we're relying on the server giving us unique world names, that's because we + // are. An evil server could give us two worlds with the same name and then we'd have no way of + // 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, /// The ECS world that contains all of the entities in all of the worlds. pub ecs: Arc>, @@ -27,14 +40,14 @@ impl WeakWorldContainer { } /// Get a world from the container. - pub fn get(&self, name: &ResourceLocation) -> Option> { + pub fn get(&self, name: &ResourceLocation) -> Option> { self.worlds.get(name).and_then(|world| world.upgrade()) } /// Add an empty world to the container (or not if it already exists) and /// returns a strong reference to the world. #[must_use = "the world will be immediately forgotten if unused"] - pub fn insert(&mut self, name: ResourceLocation, height: u32, min_y: i32) -> Arc { + pub fn insert(&mut self, name: ResourceLocation, height: u32, min_y: i32) -> Arc { if let Some(existing) = self.worlds.get(&name).and_then(|world| world.upgrade()) { if existing.height() != height { error!( @@ -52,7 +65,7 @@ impl WeakWorldContainer { } existing } else { - let world = Arc::new(WeakWorld::new(height, min_y, self.ecs.clone())); + let world = Arc::new(World::new(height, min_y, self.ecs.clone())); self.worlds.insert(name, Arc::downgrade(&world)); world } diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs index 6e7da7eb..e201aaf0 100644 --- a/azalea-world/src/entity/mod.rs +++ b/azalea-world/src/entity/mod.rs @@ -4,111 +4,48 @@ mod dimensions; pub mod metadata; use self::{attributes::AttributeInstance, metadata::UpdateMetadataError}; -use crate::WeakWorld; pub use attributes::Attributes; use azalea_block::BlockState; use azalea_core::{BlockPos, ChunkPos, Vec3, AABB}; -use bevy_ecs::{bundle::Bundle, component::Component, world::EntityMut}; +use bevy_ecs::{ + bundle::Bundle, component::Component, query::Changed, system::Query, world::EntityMut, +}; pub use data::*; use derive_more::{Deref, DerefMut}; pub use dimensions::EntityDimensions; use std::fmt::{Debug, Display, Formatter}; use uuid::Uuid; -/// An entity ID that's used by ECS library. -pub type EcsEntityId = bevy_ecs::entity::Entity; +/// A lightweight identifier of an entity. +/// +/// Don't rely on the index of this being the same as a Minecraft entity id! +/// (unless you're implementin a server, in which case you can decide your +/// entity ids however you want) +/// +/// If you want to refer to a Minecraft entity id, use [`MinecraftEntityId`]. +pub type Entity = bevy_ecs::entity::Entity; -/// The unique 32-bit unsigned id of an entity. -#[derive(Deref, Eq, PartialEq, DerefMut, Copy, Clone)] -pub struct EntityId(pub u32); -impl From for EcsEntityId { - // bevy_ecs `Entity`s also store the "generation" which adds another 32 bits, - // but we don't care about the generation - fn from(id: EntityId) -> Self { - Self::from_raw(*id) - } -} -impl From for EntityId { - fn from(id: EcsEntityId) -> Self { - Self(id.index()) - } -} -impl Debug for EntityId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "EntityId({})", **self) - } -} -impl Display for EntityId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", **self) - } -} -impl std::hash::Hash for EntityId { +/// An entity ID used by Minecraft. These are not guaranteed to be unique in +/// shared worlds, that's what [`Entity`] is for. +#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Deref, DerefMut)] +pub struct MinecraftEntityId(pub u32); +impl std::hash::Hash for MinecraftEntityId { fn hash(&self, hasher: &mut H) { hasher.write_u32(self.0) } } -impl nohash_hasher::IsEnabled for EntityId {} - -// /// A mutable reference to an entity in a world. -// pub struct Entity<'w, W = &'w WeakWorld> { -// /// The [`WeakWorld`] this entity is in. -// pub world: W, -// /// The incrementing numerical id of the entity. -// pub id: u32, -// /// The ECS data for the entity. -// pub data: bevy_ecs::world::EntityMut<'w>, -// } - -/// Create an entity if you only have a [`bevy_ecs::world::World`]. -/// -/// If you do have access to a [`PartialEntityStorage`] though then just call -/// [`PartialEntityStorage::insert`]. -pub(crate) fn new_entity<'w>( - ecs: &'w mut bevy_ecs::world::World, - id: EntityId, - bundle: impl bevy_ecs::bundle::Bundle, -) -> EntityMut<'w> { - // bevy_ecs only returns None if the entity only exists with a different - // generation, which shouldn't be possible here - let mut entity = ecs - .get_or_spawn(id.into()) - .expect("Entities should always be generation 0 if we're manually spawning from ids"); - entity.insert(bundle); - entity -} - -// impl<'d, D: Deref> Entity<'d, D> { -// /// Create an Entity when we already know its id and data. -// pub(crate) fn new(world: D, id: u32, bundle: impl -// bevy_ecs::bundle::Bundle) -> Self { let ecs = -// world.entity_storage.write().ecs; let data = new_entity(&mut ecs, id, -// bundle); Self { world, id, data } -// } -// } - -// impl<'d, D: Deref> Entity<'d, D> { -// // todo: write more here and add an example too -// /// Get data from the entity. -// pub fn get(&self) -> Option<&T> { -// self.data.get() -// } -// pub fn get_mut( -// &mut self, -// ) -> Option> { -// self.data.get_mut() -// } -// } +impl nohash_hasher::IsEnabled for MinecraftEntityId {} /// Sets the position of the entity. This doesn't update the cache in /// azalea-world, and should only be used within azalea-world! /// /// # Safety /// Cached position in the world must be updated. -pub unsafe fn move_unchecked(pos: &mut Position, physics: &mut Physics, new_pos: Vec3) { - *pos = Position(new_pos); - let bounding_box = make_bounding_box(&pos, &physics); - physics.bounding_box = bounding_box; +pub fn update_bounding_box(query: Query<(&mut Position, &Physics), Changed>) { + for (mut position, physics) in query.iter_mut() { + let bounding_box = physics.dimensions.make_bounding_box(&position); + physics.bounding_box = bounding_box; + } } pub fn set_rotation(physics: &mut Physics, y_rot: f32, x_rot: f32) { @@ -158,7 +95,7 @@ pub fn make_bounding_box(pos: &Position, physics: &Physics) -> AABB { } /// Get the position of the block below the entity, but a little lower. -pub fn on_pos_legacy(world: &WeakWorld, position: &Position, physics: &Physics) -> BlockPos { +pub fn on_pos_legacy(world: &World, position: &Position, physics: &Physics) -> BlockPos { on_pos(world, position, physics, 0.2) } @@ -174,7 +111,7 @@ pub fn on_pos_legacy(world: &WeakWorld, position: &Position, physics: &Physics) // } // } // return var5; -pub fn on_pos(world: &WeakWorld, pos: &Position, physics: &Physics, offset: f32) -> BlockPos { +pub fn on_pos(world: &World, pos: &Position, physics: &Physics, offset: f32) -> BlockPos { let x = pos.x.floor() as i32; let y = (pos.y - offset as f64).floor() as i32; let z = pos.z.floor() as i32; @@ -207,23 +144,45 @@ pub struct EntityUuid(Uuid); /// world.move_entity #[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] pub struct Position(Vec3); -impl From<&Position> for ChunkPos { - fn from(value: &Position) -> Self { +impl From for ChunkPos { + fn from(value: Position) -> Self { ChunkPos::from(&value.0) } } -impl From<&Position> for BlockPos { - fn from(value: &Position) -> Self { +impl From for BlockPos { + fn from(value: Position) -> Self { BlockPos::from(&value.0) } } +/// The position of the entity last tick. +#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)] +pub struct LastPosition(Vec3); +impl From for ChunkPos { + fn from(value: LastPosition) -> Self { + ChunkPos::from(&value.0) + } +} +impl From for BlockPos { + fn from(value: LastPosition) -> Self { + BlockPos::from(&value.0) + } +} + +/// Set the [`LastPosition`] component to the current [`Position`] component. +/// This should happen at the end of every tick. +pub fn update_last_position( + mut query: Query<(&mut Position, &mut LastPosition), Changed>, +) { + for (mut position, mut last_position) in query.iter_mut() { + *last_position = LastPosition(**position); + } +} + /// The physics data relating to the entity, such as position, velocity, and /// bounding box. #[derive(Debug, Component)] pub struct Physics { - /// The position of the entity last tick. - pub last_pos: Vec3, pub delta: Vec3, /// X acceleration. @@ -269,6 +228,7 @@ pub struct EntityBundle { pub kind: EntityKind, pub uuid: EntityUuid, pub position: Position, + pub last_position: LastPosition, pub physics: Physics, pub attributes: Attributes, } @@ -284,8 +244,8 @@ impl EntityBundle { kind: EntityKind(kind), uuid: EntityUuid(uuid), position: Position(pos), + last_position: Position(pos), physics: Physics { - last_pos: pos, delta: Vec3::default(), xxa: 0., @@ -326,24 +286,24 @@ pub struct PlayerBundle { pub metadata: metadata::PlayerMetadataBundle, } -#[cfg(test)] -mod tests { - use super::*; - use crate::PartialWorld; +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::PartialWorld; - #[test] - fn from_mut_entity_to_ref_entity() { - let mut world = PartialWorld::default(); - let uuid = Uuid::from_u128(100); - world.add_entity( - 0, - EntityData::new( - uuid, - Vec3::default(), - EntityMetadata::Player(metadata::Player::default()), - ), - ); - let entity: Entity = world.entity_mut(0).unwrap(); - assert_eq!(entity.uuid, uuid); - } -} +// #[test] +// fn from_mut_entity_to_ref_entity() { +// let mut world = PartialWorld::default(); +// let uuid = Uuid::from_u128(100); +// world.add_entity( +// 0, +// EntityData::new( +// uuid, +// Vec3::default(), +// EntityMetadata::Player(metadata::Player::default()), +// ), +// ); +// let entity: Entity = world.entity_mut(0).unwrap(); +// assert_eq!(entity.uuid, uuid); +// } +// } diff --git a/azalea-world/src/entity_info.rs b/azalea-world/src/entity_info.rs index 9d7f2b06..ab168a58 100644 --- a/azalea-world/src/entity_info.rs +++ b/azalea-world/src/entity_info.rs @@ -1,20 +1,34 @@ -use crate::entity::{self, new_entity, EcsEntityId, EntityId, EntityUuid, Position}; -use azalea_core::{ChunkPos, Vec3}; +use crate::entity::{self, Entity, EntityUuid, MinecraftEntityId, Position}; +use azalea_core::{ChunkPos, ResourceLocation, Vec3}; +use bevy_app::{App, CoreStage, Plugin}; use bevy_ecs::{ - query::{QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery}, + query::{Changed, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery}, + schedule::SystemSet, + system::{Query, ResMut, Resource}, world::{EntityMut, EntityRef}, }; use log::warn; use nohash_hasher::{IntMap, IntSet}; use once_cell::sync::Lazy; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, fmt::Debug, sync::{Arc, Weak}, }; use uuid::Uuid; +/// Plugin handling some basic entity functionality. +pub struct EntityPlugin; +impl Plugin for EntityPlugin { + fn build(&self, app: &mut App) { + app.add_system_set_to_stage( + CoreStage::Last, + SystemSet::new().with_system(update_entity_chunk_positions), + ); + } +} + // How entity updates are processed (to avoid issues with shared worlds) // - each bot contains a map of { entity id: updates received } // - the shared world also contains a canonical "true" updates received for each @@ -40,46 +54,31 @@ use uuid::Uuid; /// You can access the shared storage with `world.shared.read()`. #[derive(Debug, Default)] pub struct PartialEntityInfos { - pub shared: Arc>, - + // note: using MinecraftEntityId for entity ids is acceptable here since there's no chance of + // collisions here /// The entity id of the player that owns this partial world. This will /// make [`PartialWorld::entity_mut`] pretend the entity doesn't exist so /// it doesn't get modified from outside sources. /// /// [`PartialWorld::entity_mut`]: crate::PartialWorld::entity_mut - pub owner_entity_id: Option, + pub owner_entity: Option, /// A counter for each entity that tracks how many updates we've observed /// for it. /// /// This is used for shared worlds (i.e. swarms), to make sure we don't /// update entities twice on accident. - pub updates_received: IntMap, + pub updates_received: IntMap, /// A set of all the entity ids in render distance. - pub(crate) loaded_entity_ids: IntSet, -} - -#[derive(Default)] -pub struct WeakEntityInfos { - /// The number of `PartialWorld`s that have this entity loaded. - /// (this is reference counting) - pub(crate) entity_reference_count: IntMap, - /// An index of all the entity ids we know are in a chunk - pub(crate) ids_by_chunk: HashMap>, - /// An index of entity ids by their UUIDs - pub(crate) id_by_uuid: HashMap, - - /// The canonical number of updates we've gotten for every entity. - pub updates_received: IntMap, + pub(crate) loaded_entity_ids: IntSet, } impl PartialEntityInfos { - pub fn new(shared: Arc>, owner_entity_id: Option) -> Self { - if let Some(owner_entity_id) = owner_entity_id { - shared.write().updates_received.insert(owner_entity_id, 0); + pub fn new(owner_entity: Option, entity_infos: &mut EntityInfos) -> Self { + if let Some(owner_entity) = owner_entity { + entity_infos.updates_received.insert(owner_entity, 0); } Self { - shared, - owner_entity_id, + owner_entity, updates_received: IntMap::default(), loaded_entity_ids: IntSet::default(), } @@ -89,19 +88,23 @@ impl PartialEntityInfos { /// If you want to check whether the entity is in the shared storage, use /// [`WeakEntityStorage::contains_id`]. #[inline] - pub fn limited_contains_id(&self, id: &EntityId) -> bool { - self.loaded_entity_ids.contains(id) + pub fn limited_contains_id(&self, id: MinecraftEntityId) -> bool { + self.loaded_entity_ids.contains(&id) } - /// Get an [`EntityId`] from this u32 entity ID if the entity is being - /// loaded by this storage. - /// - /// Note that you can just create an `EntityId` directly if you want, and - /// it'll work if the entity is being loaded by any storage. + /// Get an [`Entity`] from the given [`MinecraftEntityId`] (which is just a + /// u32 internally) if the entity is being loaded by this storage. #[inline] - pub fn limited_get_by_id(&self, id: u32) -> Option { - if self.limited_contains_id(&EntityId(id)) { - Some(EntityId(id)) + pub fn limited_get_by_id( + &self, + id: MinecraftEntityId, + entity_infos: &mut EntityInfos, + ) -> Option { + if self.limited_contains_id(id) { + entity_infos + .minecraft_entity_ids_to_azalea_entities + .get(&id) + .copied() } else { None } @@ -112,91 +115,97 @@ impl PartialEntityInfos { /// we WILL update it if it's true. Don't call this unless you actually /// got an entity update that all other clients within render distance /// will get too. - pub fn maybe_update(&mut self, id: EntityId) -> bool { + pub fn maybe_update(&mut self, id: MinecraftEntityId, entity_infos: &mut EntityInfos) -> bool { let this_client_updates_received = self.updates_received.get(&id).copied(); - let shared_updates_received = self.shared.read().updates_received.get(&id).copied(); + + let entity = entity_infos + .minecraft_entity_ids_to_azalea_entities + .get(&id) + .expect("entity should be in minecraft_entity_ids_to_azalea_entities") + .clone(); + + let shared_updates_received = entity_infos.updates_received.get(&entity).copied(); let can_update = this_client_updates_received == shared_updates_received; if can_update { let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; self.updates_received.insert(id, new_updates_received); - self.shared - .write() + entity_infos .updates_received - .insert(id, new_updates_received); + .insert(entity, new_updates_received); true } else { false } } - - /// Get a reference to an entity by its UUID, if it's being loaded by this - /// storage. - #[inline] - pub fn limited_get_by_uuid(&self, uuid: &Uuid) -> Option { - let shared = self.shared.read(); - let entity_id = shared.id_by_uuid.get(uuid)?; - self.limited_get_by_id(entity_id.0) - } - - /// Move an entity from its old chunk to a new chunk. - #[inline] - pub fn update_entity_chunk( - &mut self, - entity_id: EntityId, - old_chunk: &ChunkPos, - new_chunk: &ChunkPos, - ) { - self.shared - .write() - .update_entity_chunk(entity_id, old_chunk, new_chunk); - } } /// This is useful so functions that return an IntSet of entity ids can return a /// reference to nothing. -static EMPTY_ENTITY_ID_INTSET: Lazy> = Lazy::new(|| IntSet::default()); +static EMPTY_ENTITY_ID_INTSET: Lazy> = Lazy::new(|| HashSet::default()); -impl WeakEntityInfos { +// TODO: optimization: switch out the `HashMap`s for `IntMap`s + +/// Things that are shared between all the partial worlds. +#[derive(Resource, Default)] +pub struct EntityInfos { + // in WeakEntityInfos, we have to use [`Entity`] since there *is* a chance of collision if + // we'd have used Minecraft entity IDs + /// The number of `PartialWorld`s that have this entity loaded. + /// (this is reference counting) + pub(crate) entity_reference_count: HashMap, + /// An index of all the entities we know are in a chunk + pub(crate) entities_by_world_name_and_chunk: + HashMap>>, + /// An index of entities by their UUIDs + pub(crate) entity_by_uuid: HashMap, + + /// The canonical number of updates we've gotten for every entity. + pub updates_received: HashMap, + + /// The map of Minecraft entity ids to Azalea ECS entities. + pub minecraft_entity_ids_to_azalea_entities: HashMap, +} + +impl EntityInfos { pub fn new() -> Self { Self { - entity_reference_count: IntMap::default(), - ids_by_chunk: HashMap::default(), - id_by_uuid: HashMap::default(), - updates_received: IntMap::default(), + entity_reference_count: HashMap::default(), + entities_by_world_name_and_chunk: HashMap::default(), + entity_by_uuid: HashMap::default(), + updates_received: HashMap::default(), + + minecraft_entity_ids_to_azalea_entities: HashMap::default(), } } /// Call this if a [`PartialEntityStorage`] just removed an entity. /// - /// It'll - /// decrease the reference count and remove the entity from the storage if - /// there's no more references to it. + /// It'll decrease the reference count and remove the entity from the + /// storage if there's no more references to it. /// /// Returns whether the entity was removed. - pub fn remove_entity_if_unused(&mut self, id: EntityId, uuid: Uuid, chunk: ChunkPos) -> bool { - if let Some(count) = self.entity_reference_count.get_mut(&id) { + pub fn remove_entity_if_unused(&mut self, entity: Entity, uuid: Uuid, chunk: ChunkPos) -> bool { + if let Some(count) = self.entity_reference_count.get_mut(&entity) { *count -= 1; if *count == 0 { - self.entity_reference_count.remove(&id); + self.entity_reference_count.remove(&entity); return true; } } else { - warn!("Tried to remove entity with id {id} but it was not found."); + warn!("Tried to remove entity but it was not found."); return false; } - if self.ids_by_chunk.remove(&chunk).is_none() { - warn!("Tried to remove entity with id {id} from chunk {chunk:?} but it was not found."); + if self.entities_by_chunk.remove(&chunk).is_none() { + warn!("Tried to remove entity from chunk {chunk:?} but it was not found."); } - if self.id_by_uuid.remove(&uuid).is_none() { - warn!("Tried to remove entity with id {id} from uuid {uuid:?} but it was not found."); + if self.entity_by_uuid.remove(&uuid).is_none() { + warn!("Tried to remove entity from uuid {uuid:?} but it was not found."); } - if self.updates_received.remove(&id).is_none() { + if self.updates_received.remove(&entity).is_none() { // if this happens it means we weren't tracking the updates_received for the // client (bad) - warn!( - "Tried to remove entity with id {id} from updates_received but it was not found." - ); + warn!("Tried to remove entity from updates_received but it was not found."); } true } @@ -204,105 +213,97 @@ impl WeakEntityInfos { /// Remove a chunk from the storage if the entities in it have no strong /// references left. pub fn remove_chunk_if_unused(&mut self, chunk: &ChunkPos) { - if let Some(entities) = self.ids_by_chunk.get(chunk) { + if let Some(entities) = self.entities_by_chunk.get(chunk) { if entities.is_empty() { - self.ids_by_chunk.remove(chunk); + self.entities_by_chunk.remove(chunk); } } } - /// Whether the entity with the given id is in the shared storage. + /// Whether the entity is in the shared storage. To check if a Minecraft + /// entity ID is in the storage, you'll have to use + /// [`PartialEntityInfo::limited_contains_id`]. #[inline] - pub fn contains_entity(&self, id: EntityId) -> bool { + pub fn contains_entity(&self, id: Entity) -> bool { self.entity_reference_count.contains_key(&id) } /// Returns a set of entities in the given chunk. - pub fn entities_in_chunk(&self, chunk: &ChunkPos) -> &IntSet { - self.ids_by_chunk + pub fn entities_in_chunk(&self, chunk: &ChunkPos) -> &HashSet { + self.entities_by_chunk .get(chunk) .unwrap_or(&EMPTY_ENTITY_ID_INTSET) } - pub fn id_by_uuid(&self, uuid: &Uuid) -> Option<&EntityId> { - self.id_by_uuid.get(uuid) + pub fn entity_by_uuid(&self, uuid: &Uuid) -> Option<&Entity> { + self.entity_by_uuid.get(uuid) } +} - /// Move an entity from its old chunk to a new chunk. - #[inline] - pub fn update_entity_chunk( - &mut self, - entity_id: EntityId, - old_chunk: &ChunkPos, - new_chunk: &ChunkPos, - ) { - if let Some(entities) = self.ids_by_chunk.get_mut(old_chunk) { - entities.remove(&entity_id); - } - self.ids_by_chunk - .entry(*new_chunk) - .or_default() - .insert(entity_id); - } +/// Update the chunk position indexes in [`EntityInfos`]. +fn update_entity_chunk_positions( + query: Query< + ( + Entity, + &entity::Position, + &mut entity::LastPosition, + &mut entity::Physics, + ), + Changed, + >, + entity_infos: ResMut, +) { + for (entity, pos, last_pos, mut physics) in query.iter_mut() { + let old_chunk = ChunkPos::from(*last_pos); + let new_chunk = ChunkPos::from(*pos); - /// Set an entity's position in the world when we already have references - /// to the [`Position`] and [`Physics`] components. - pub fn set_entity_pos( - &mut self, - entity_id: EntityId, - new_pos: Vec3, - pos: &mut entity::Position, - physics: &mut entity::Physics, - ) { - let old_chunk = ChunkPos::from(&*pos); - let new_chunk = ChunkPos::from(&new_pos); - // this is fine because we update the chunk below - unsafe { entity::move_unchecked(pos, physics, new_pos) }; if old_chunk != new_chunk { - self.update_entity_chunk(entity_id, &old_chunk, &new_chunk); + // move the entity from the old chunk to the new one + if let Some(entities) = entity_infos.entities_by_chunk.get_mut(&old_chunk) { + entities.remove(&entity); + } + entity_infos + .entities_by_chunk + .entry(new_chunk) + .or_default() + .insert(entity); } } } -impl Debug for WeakEntityInfos { +impl Debug for EntityInfos { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WeakEntityStorage") - // .field("ecs", &self.ecs) - .field("entity_reference_count", &self.entity_reference_count) - .field("ids_by_chunk", &self.ids_by_chunk) - .field("id_by_uuid", &self.id_by_uuid) - .field("updates_received", &self.updates_received) - .finish() + f.debug_struct("EntityInfos").finish() } } -#[cfg(test)] -mod tests { - use crate::entity::metadata; +// #[cfg(test)] +// mod tests { +// use crate::entity::metadata; - use super::*; - use azalea_core::Vec3; +// use super::*; +// use azalea_core::Vec3; - #[test] - fn test_store_entity() { - let mut storage = PartialEntityInfos::default(); - assert!(storage.limited_get_by_id(0).is_none()); - assert!(storage.shared.read().get_by_id(0).is_none()); +// #[test] +// fn test_store_entity() { +// let mut storage = PartialEntityInfos::default(); +// assert!(storage.limited_get_by_id(0).is_none()); +// assert!(storage.shared.read().get_by_id(0).is_none()); - let uuid = Uuid::from_u128(100); - storage.insert( - 0, - EntityData::new( - uuid, - Vec3::default(), - EntityMetadata::Player(metadata::Player::default()), - ), - ); - assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid); - assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid); +// let uuid = Uuid::from_u128(100); +// storage.insert( +// 0, +// EntityData::new( +// uuid, +// Vec3::default(), +// EntityMetadata::Player(metadata::Player::default()), +// ), +// ); +// assert_eq!(storage.limited_get_by_id(0).unwrap().uuid, uuid); +// assert_eq!(storage.shared.read().get_by_id(0).unwrap().uuid, uuid); - storage.remove_by_id(0); - assert!(storage.limited_get_by_id(0).is_none()); - assert!(storage.shared.read().get_by_id(0).is_none()); - } -} +// storage.remove_by_id(0); +// assert!(storage.limited_get_by_id(0).is_none()); +// assert!(storage.shared.read().get_by_id(0).is_none()); +// } +// } diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index cbed4158..1dcff1c0 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -15,7 +15,7 @@ use std::backtrace::Backtrace; pub use bit_storage::BitStorage; pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage}; pub use container::*; -pub use entity_info::{PartialEntityInfos, WeakEntityInfos}; +pub use entity_info::{EntityInfos, PartialEntityInfos}; use thiserror::Error; pub use world::*; diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 052f7e2a..aecdc430 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -1,12 +1,10 @@ use crate::{ - entity::{self, move_unchecked, EntityId}, - Chunk, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage, - WeakEntityInfos, + entity::{self, Entity, MinecraftEntityId}, + Chunk, EntityInfos, MoveEntityError, PartialChunkStorage, PartialEntityInfos, WeakChunkStorage, }; use azalea_block::BlockState; use azalea_buf::BufReadError; use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3}; -use bevy_ecs::query::{QueryState, WorldQuery}; use log::warn; use parking_lot::{Mutex, RwLock}; use std::{backtrace::Backtrace, fmt::Debug}; @@ -24,7 +22,7 @@ use uuid::Uuid; pub struct PartialWorld { // we just need to keep a strong reference to `shared` so it doesn't get // dropped, we don't need to do anything with it - pub shared: Arc, + pub shared: Arc, pub chunks: PartialChunkStorage, pub entity_infos: PartialEntityInfos, @@ -33,13 +31,14 @@ pub struct PartialWorld { impl PartialWorld { pub fn new( chunk_radius: u32, - shared: Arc, - owner_entity_id: Option, + shared: Arc, + owner_entity: Option, + entity_infos: &mut EntityInfos, ) -> Self { PartialWorld { shared: shared.clone(), chunks: PartialChunkStorage::new(chunk_radius, shared.chunks.clone()), - entity_infos: PartialEntityInfos::new(shared.entity_infos.clone(), owner_entity_id), + entity_infos: PartialEntityInfos::new(owner_entity, entity_infos), } } @@ -73,9 +72,11 @@ impl PartialWorld { /// /// Only call this if you're actually updating the entity, otherwise it'll /// cause the update tracker to get out of sync. - fn maybe_update_entity(&mut self, id: EntityId) -> bool { + fn maybe_update_entity(&mut self, entity: Entity, entity_infos: &mut EntityInfos) -> bool { // no entity for you (we're processing this entity somewhere else) - if Some(id) != self.entity_infos.owner_entity_id && !self.entity_infos.maybe_update(id) { + if Some(entity) != self.entity_infos.owner_entity + && !self.entity_infos.maybe_update(id, entity_infos) + { false } else { true @@ -218,66 +219,59 @@ impl PartialWorld { } } -/// A world where the chunks are stored as weak pointers. This is used for -/// shared worlds. -#[derive(Default, Debug)] -pub struct WeakWorld { - pub chunks: Arc>, - pub(crate) entity_infos: Arc>, +// /// A world where the chunks are stored as weak pointers. This is used for +// /// shared worlds. +// #[derive(Default, Debug)] +// pub struct World { +// pub chunks: Arc>, +// } - /// A reference to the ECS world that contains all of the entities in all of - /// the worlds. - pub(crate) global_ecs: Arc>, -} +// impl World { +// pub fn new(height: u32, min_y: i32) -> Self { +// World { +// chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height, +// min_y))), } +// } -impl WeakWorld { - pub fn new(height: u32, min_y: i32, global_ecs: Arc>) -> Self { - WeakWorld { - chunks: Arc::new(RwLock::new(WeakChunkStorage::new(height, min_y))), - entity_infos: Arc::new(RwLock::new(WeakEntityInfos::new())), - global_ecs, - } - } +// /// Read the total height of the world. You can add this to +// [`Self::min_y`] /// to get the highest possible y coordinate a block can +// be placed at. pub fn height(&self) -> u32 { +// self.chunks.read().height +// } - /// Read the total height of the world. You can add this to [`Self::min_y`] - /// to get the highest possible y coordinate a block can be placed at. - pub fn height(&self) -> u32 { - self.chunks.read().height - } +// /// Get the lowest possible y coordinate a block can be placed at. +// pub fn min_y(&self) -> i32 { +// self.chunks.read().min_y +// } - /// Get the lowest possible y coordinate a block can be placed at. - pub fn min_y(&self) -> i32 { - self.chunks.read().min_y - } +// pub fn contains_entity(&self, id: EntityId) -> bool { +// self.entity_infos.read().contains_entity(id) +// } - pub fn contains_entity(&self, id: EntityId) -> bool { - self.entity_infos.read().contains_entity(id) - } +// pub fn id_by_uuid(&self, uuid: &Uuid) -> Option { +// self.entity_infos.read().id_by_uuid(uuid).copied() +// } - pub fn id_by_uuid(&self, uuid: &Uuid) -> Option { - self.entity_infos.read().id_by_uuid(uuid).copied() - } +// pub fn get_block_state(&self, pos: &BlockPos) -> Option { +// self.chunks.read().get_block_state(pos) +// } - pub fn get_block_state(&self, pos: &BlockPos) -> Option { - self.chunks.read().get_block_state(pos) - } +// pub fn get_chunk(&self, pos: &ChunkPos) -> Option>> { +// self.chunks.read().get(pos) +// } - pub fn get_chunk(&self, pos: &ChunkPos) -> Option>> { - self.chunks.read().get(pos) - } - - pub fn set_entity_pos( - &self, - entity_id: EntityId, - new_pos: Vec3, - pos: &mut entity::Position, - physics: &mut entity::Physics, - ) { - self.entity_infos - .write() - .set_entity_pos(entity_id, new_pos, pos, physics); - } -} +// pub fn set_entity_pos( +// &self, +// entity_id: EntityId, +// new_pos: Vec3, +// pos: &mut entity::Position, +// physics: &mut entity::Physics, +// ) { +// self.entity_infos +// .write() +// .set_entity_pos(entity_id, new_pos, pos, physics); +// } +// } impl Debug for PartialWorld { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -296,7 +290,7 @@ impl Default for PartialWorld { let chunk_storage = PartialChunkStorage::default(); let entity_storage = PartialEntityInfos::default(); Self { - shared: Arc::new(WeakWorld { + shared: Arc::new(World { chunks: chunk_storage.shared.clone(), entity_infos: entity_storage.shared.clone(), global_ecs: Arc::new(Mutex::new(bevy_ecs::world::World::default())), diff --git a/azalea/src/pathfinder/moves.rs b/azalea/src/pathfinder/moves.rs index ccf8ba1a..a130ac60 100644 --- a/azalea/src/pathfinder/moves.rs +++ b/azalea/src/pathfinder/moves.rs @@ -1,10 +1,10 @@ use super::{Node, VerticalVel}; use azalea_core::{BlockPos, CardinalDirection}; use azalea_physics::collision::{self, BlockWithShape}; -use azalea_world::WeakWorld; +use azalea_world::World; /// whether this block is passable -fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_block_passable(pos: &BlockPos, world: &World) -> bool { if let Some(block) = world.get_block_state(pos) { block.shape() == &collision::empty_shape() } else { @@ -13,7 +13,7 @@ fn is_block_passable(pos: &BlockPos, world: &WeakWorld) -> bool { } /// whether this block has a solid hitbox (i.e. we can stand on it) -fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_block_solid(pos: &BlockPos, world: &World) -> bool { if let Some(block) = world.get_block_state(pos) { block.shape() == &collision::block_shape() } else { @@ -22,14 +22,14 @@ fn is_block_solid(pos: &BlockPos, world: &WeakWorld) -> bool { } /// Whether this block and the block above are passable -fn is_passable(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_passable(pos: &BlockPos, world: &World) -> bool { is_block_passable(pos, world) && is_block_passable(&pos.up(1), world) } /// Whether we can stand in this position. Checks if the block below is solid, /// and that the two blocks above that are passable. -fn is_standable(pos: &BlockPos, world: &WeakWorld) -> bool { +fn is_standable(pos: &BlockPos, world: &World) -> bool { is_block_solid(&pos.down(1), world) && is_passable(pos, world) } @@ -37,7 +37,7 @@ const JUMP_COST: f32 = 0.5; const WALK_ONE_BLOCK_COST: f32 = 1.0; pub trait Move { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32; + fn cost(&self, world: &World, node: &Node) -> f32; /// Returns by how much the entity's position should be changed when this /// move is executed. fn offset(&self) -> BlockPos; @@ -51,7 +51,7 @@ pub trait Move { pub struct ForwardMove(pub CardinalDirection); impl Move for ForwardMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if is_standable(&(node.pos + self.offset()), world) && node.vertical_vel == VerticalVel::None { @@ -67,7 +67,7 @@ impl Move for ForwardMove { pub struct AscendMove(pub CardinalDirection); impl Move for AscendMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if node.vertical_vel == VerticalVel::None && is_block_passable(&node.pos.up(2), world) && is_standable(&(node.pos + self.offset()), world) @@ -89,7 +89,7 @@ impl Move for AscendMove { } pub struct DescendMove(pub CardinalDirection); impl Move for DescendMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { // check whether 3 blocks vertically forward are passable if node.vertical_vel == VerticalVel::None && is_standable(&(node.pos + self.offset()), world) @@ -112,7 +112,7 @@ impl Move for DescendMove { } pub struct DiagonalMove(pub CardinalDirection); impl Move for DiagonalMove { - fn cost(&self, world: &WeakWorld, node: &Node) -> f32 { + fn cost(&self, world: &World, node: &Node) -> f32 { if node.vertical_vel != VerticalVel::None { return f32::INFINITY; }