From aa886c101b398f52372df6054c00f9387428cac6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 7 Feb 2023 20:30:47 +0000 Subject: move az_world::entity_info to az_world::entities::info --- azalea-client/src/client.rs | 2 +- azalea-client/src/local_player.rs | 2 +- azalea-client/src/packet_handling.rs | 3 +- azalea-client/src/player.rs | 2 +- azalea-physics/src/lib.rs | 9 +- azalea-world/src/entity/info.rs | 327 +++++++++++++++++++++++++++++++++++ azalea-world/src/entity/mod.rs | 7 + azalea-world/src/entity_info.rs | 326 ---------------------------------- azalea-world/src/lib.rs | 4 - azalea-world/src/world.rs | 7 +- azalea/examples/matbot.rs | 194 --------------------- azalea/examples/testbot.rs | 194 +++++++++++++++++++++ azalea/src/bot.rs | 5 +- azalea/src/pathfinder/mod.rs | 2 +- 14 files changed, 544 insertions(+), 540 deletions(-) create mode 100644 azalea-world/src/entity/info.rs delete mode 100644 azalea-world/src/entity_info.rs delete mode 100644 azalea/examples/matbot.rs create mode 100644 azalea/examples/testbot.rs diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index ddeeeef3..87079af4 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -42,7 +42,7 @@ use azalea_protocol::{ }, resolver, ServerAddress, }; -use azalea_world::{entity::WorldName, EntityPlugin, Local, PartialWorld, World, WorldContainer}; +use azalea_world::{entity::{WorldName, EntityPlugin, Local}, PartialWorld, World, WorldContainer}; use log::{debug, error}; use parking_lot::{Mutex, RwLock}; use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc}; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index a6921d76..23a08bd0 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -28,7 +28,7 @@ use crate::{ /// You can also use the [`Local`] marker component for queries if you're only /// checking for a local player and don't need the contents of this component. /// -/// [`Local`]: azalea_world::Local +/// [`Local`]: azalea_world::entity::Local /// [`Client`]: crate::Client #[derive(Component)] pub struct LocalPlayer { diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs index 42e24462..0e03b37c 100644 --- a/azalea-client/src/packet_handling.rs +++ b/azalea-client/src/packet_handling.rs @@ -28,7 +28,8 @@ use azalea_world::{ set_rotation, Dead, EntityBundle, EntityKind, LastSentPosition, MinecraftEntityId, Physics, PlayerBundle, Position, WorldName, }, - LoadedBy, PartialWorld, RelativeEntityUpdate, WorldContainer, + entity::{LoadedBy, RelativeEntityUpdate}, + PartialWorld, WorldContainer, }; use log::{debug, error, trace, warn}; use parking_lot::Mutex; diff --git a/azalea-client/src/player.rs b/azalea-client/src/player.rs index 00b6b25e..b8ca83bb 100755 --- a/azalea-client/src/player.rs +++ b/azalea-client/src/player.rs @@ -5,7 +5,7 @@ use azalea_ecs::{ event::EventReader, system::{Commands, Res}, }; -use azalea_world::EntityInfos; +use azalea_world::entity::EntityInfos; use uuid::Uuid; use crate::{packet_handling::AddPlayerEvent, GameProfileComponent}; diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 96bebd1a..35841d77 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -16,9 +16,10 @@ use azalea_ecs::{ }; use azalea_world::{ entity::{ - metadata::Sprinting, move_relative, Attributes, Jumping, Physics, Position, WorldName, + metadata::Sprinting, move_relative, Attributes, Jumping, Local, Physics, Position, + WorldName, }, - Local, World, WorldContainer, + World, WorldContainer, }; use collision::{move_colliding, MoverType}; @@ -310,8 +311,8 @@ mod tests { use azalea_core::{ChunkPos, ResourceLocation}; use azalea_ecs::{app::App, TickPlugin}; use azalea_world::{ - entity::{EntityBundle, MinecraftEntityId}, - Chunk, EntityPlugin, PartialWorld, + entity::{EntityBundle, EntityPlugin, MinecraftEntityId}, + Chunk, PartialWorld, }; use uuid::Uuid; diff --git a/azalea-world/src/entity/info.rs b/azalea-world/src/entity/info.rs new file mode 100644 index 00000000..bf7e0051 --- /dev/null +++ b/azalea-world/src/entity/info.rs @@ -0,0 +1,327 @@ +//! Implement things relating to entity datas, like an index of uuids to +//! entities. + +use crate::{ + deduplicate_entities, deduplicate_local_entities, + entity::{ + self, add_dead, update_bounding_box, EntityUuid, MinecraftEntityId, Position, WorldName, + }, + update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer, +}; +use azalea_core::ChunkPos; +use azalea_ecs::{ + app::{App, CoreStage, Plugin}, + component::Component, + ecs::Ecs, + ecs::EntityMut, + entity::Entity, + query::{Added, Changed, With, Without}, + schedule::{IntoSystemDescriptor, SystemSet}, + system::{Command, Commands, Query, Res, ResMut, Resource}, +}; +use derive_more::{Deref, DerefMut}; +use log::{debug, warn}; +use nohash_hasher::IntMap; +use parking_lot::RwLock; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, +}; +use uuid::Uuid; + +use super::Local; + +/// Plugin handling some basic entity functionality. +pub struct EntityPlugin; +impl Plugin for EntityPlugin { + fn build(&self, app: &mut App) { + app.add_system_set( + SystemSet::new() + .after("tick") + .after("packet") + .with_system(update_entity_chunk_positions) + .with_system(remove_despawned_entities_from_indexes) + .with_system(update_bounding_box) + .with_system(add_dead) + .with_system( + add_updates_received + .after("deduplicate_entities") + .after("deduplicate_local_entities") + .label("add_updates_received"), + ) + .with_system( + update_uuid_index + .label("update_uuid_index") + .after("deduplicate_local_entities") + .after("deduplicate_entities"), + ) + .with_system(debug_detect_updates_received_on_local_entities) + .with_system( + update_entity_by_id_index + .label("update_entity_by_id_index") + .after("deduplicate_entities"), + ) + .with_system(debug_new_entity), + ) + .add_system_set_to_stage( + CoreStage::PostUpdate, + SystemSet::new() + .with_system(deduplicate_entities.label("deduplicate_entities")) + .with_system( + deduplicate_local_entities + .label("deduplicate_local_entities") + .before("update_uuid_index") + .before("update_entity_by_id_index"), + ), + ) + .init_resource::(); + } +} + +fn debug_new_entity(query: Query>) { + for entity in query.iter() { + debug!("new entity: {:?}", entity); + } +} + +// 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 +// entity +// - when a client loads an entity, its "updates received" is set to the same as +// the global "updates received" +// - when the shared world sees an entity for the first time, the "updates +// received" is set to 1. +// - clients can force the shared "updates received" to 0 to make it so certain +// entities (i.e. other bots in our swarm) don't get confused and updated by +// other bots +// - when a client gets an update to an entity, we check if our "updates +// received" is the same as the shared world's "updates received": if it is, +// then process the update and increment the client's and shared world's +// "updates received" if not, then we simply increment our local "updates +// received" and do nothing else + +/// Keep track of certain metadatas that are only relevant for this partial +/// world. +#[derive(Debug, Default)] +pub struct PartialEntityInfos { + // 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 [`RelativeEntityUpdate`] pretend the entity doesn't exist so + /// it doesn't get modified from outside sources. + 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, +} + +impl PartialEntityInfos { + pub fn new(owner_entity: Option) -> Self { + Self { + owner_entity, + updates_received: IntMap::default(), + } + } +} + +/// A [`Command`] that applies a "relative update" to an entity, which means +/// this update won't be run multiple times by different clients in the same +/// world. +/// +/// This is used to avoid a bug where when there's multiple clients in the same +/// world and an entity sends a relative move packet to all clients, its +/// position gets desynced since the relative move is applied multiple times. +/// +/// Don't use this unless you actually got an entity update packet that all +/// other clients within render distance will get too. You usually don't need +/// this when the change isn't relative either. +pub struct RelativeEntityUpdate { + pub entity: Entity, + pub partial_world: Arc>, + // a function that takes the entity and updates it + pub update: Box, +} +impl Command for RelativeEntityUpdate { + fn write(self, world: &mut Ecs) { + let partial_entity_infos = &mut self.partial_world.write().entity_infos; + + let mut entity = world.entity_mut(self.entity); + + if Some(self.entity) == partial_entity_infos.owner_entity { + // if the entity owns this partial world, it's always allowed to update itself + (self.update)(&mut entity); + return; + }; + + let entity_id = *entity.get::().unwrap(); + + let Some(updates_received) = entity.get_mut::() else { + // a client tried to update another client, which isn't allowed + return; + }; + + let this_client_updates_received = partial_entity_infos + .updates_received + .get(&entity_id) + .copied(); + + let can_update = this_client_updates_received.unwrap_or(1) == **updates_received; + if can_update { + let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; + partial_entity_infos + .updates_received + .insert(entity_id, new_updates_received); + + **entity.get_mut::().unwrap() = new_updates_received; + + let mut entity = world.entity_mut(self.entity); + (self.update)(&mut entity); + } + } +} + +/// Things that are shared between all the partial worlds. +#[derive(Resource, Default)] +pub struct EntityInfos { + /// An index of entities by their UUIDs + pub(crate) entity_by_uuid: HashMap, +} + +impl EntityInfos { + pub fn new() -> Self { + Self { + entity_by_uuid: HashMap::default(), + } + } + + pub fn get_entity_by_uuid(&self, uuid: &Uuid) -> Option { + self.entity_by_uuid.get(uuid).copied() + } +} + +/// Update the chunk position indexes in [`EntityInfos`]. +fn update_entity_chunk_positions( + mut query: Query< + ( + Entity, + &entity::Position, + &mut entity::LastSentPosition, + &entity::WorldName, + ), + Changed, + >, + world_container: Res, +) { + for (entity, pos, last_pos, world_name) in query.iter_mut() { + let world_lock = world_container.get(world_name).unwrap(); + let mut world = world_lock.write(); + + let old_chunk = ChunkPos::from(*last_pos); + let new_chunk = ChunkPos::from(*pos); + + if old_chunk != new_chunk { + // move the entity from the old chunk to the new one + if let Some(entities) = world.entities_by_chunk.get_mut(&old_chunk) { + entities.remove(&entity); + } + world + .entities_by_chunk + .entry(new_chunk) + .or_default() + .insert(entity); + } + } +} +/// A component that lists all the local player entities that have this entity +/// loaded. If this is empty, the entity will be removed from the ECS. +#[derive(Component, Clone, Deref, DerefMut)] +pub struct LoadedBy(pub HashSet); + +/// A component that counts the number of times this entity has been modified. +/// This is used for making sure two clients don't do the same relative update +/// on an entity. +/// +/// If an entity is local (i.e. it's a client/localplayer), this component +/// should NOT be present in the entity. +#[derive(Component, Debug, Deref, DerefMut)] +pub struct UpdatesReceived(u32); + +#[allow(clippy::type_complexity)] +pub fn add_updates_received( + mut commands: Commands, + query: Query< + Entity, + ( + Changed, + (Without, Without), + ), + >, +) { + for entity in query.iter() { + // entities always start with 1 update received + commands.entity(entity).insert(UpdatesReceived(1)); + } +} + + +/// The [`UpdatesReceived`] component should never be on [`Local`] entities. +/// This warns if an entity has both components. +fn debug_detect_updates_received_on_local_entities( + query: Query, With)>, +) { + for entity in &query { + warn!("Entity {:?} has both Local and UpdatesReceived", entity); + } +} + +/// Despawn entities that aren't being loaded by anything. +fn remove_despawned_entities_from_indexes( + mut commands: Commands, + mut entity_infos: ResMut, + world_container: Res, + query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed>, +) { + for (entity, uuid, position, world_name, loaded_by) in &query { + let world_lock = world_container.get(world_name).unwrap(); + let mut world = world_lock.write(); + + // if the entity has no references left, despawn it + if !loaded_by.is_empty() { + continue; + } + + // remove the entity from the chunk index + let chunk = ChunkPos::from(*position); + if let Some(entities_in_chunk) = world.entities_by_chunk.get_mut(&chunk) { + if entities_in_chunk.remove(&entity) { + // remove the chunk if there's no entities in it anymore + if entities_in_chunk.is_empty() { + world.entities_by_chunk.remove(&chunk); + } + } else { + warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there."); + } + } else { + warn!("Tried to remove entity from chunk {chunk:?} but the chunk was not found."); + } + // remove it from the uuid index + if entity_infos.entity_by_uuid.remove(uuid).is_none() { + warn!("Tried to remove entity {entity:?} from the uuid index but it was not there."); + } + // and now remove the entity from the ecs + commands.entity(entity).despawn(); + debug!("Despawned entity {entity:?} because it was not loaded by anything."); + return; + } +} + +impl Debug for EntityInfos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EntityInfos").finish() + } +} diff --git a/azalea-world/src/entity/mod.rs b/azalea-world/src/entity/mod.rs index 60a00bfd..0d0449ac 100644 --- a/azalea-world/src/entity/mod.rs +++ b/azalea-world/src/entity/mod.rs @@ -3,6 +3,7 @@ pub mod attributes; mod data; mod dimensions; +mod info; pub mod metadata; use crate::ChunkStorage; @@ -21,6 +22,7 @@ use azalea_ecs::{ pub use data::*; use derive_more::{Deref, DerefMut}; pub use dimensions::{update_bounding_box, EntityDimensions}; +pub use info::{EntityInfos, EntityPlugin, LoadedBy, PartialEntityInfos, RelativeEntityUpdate}; use std::fmt::Debug; use uuid::Uuid; @@ -314,6 +316,11 @@ pub struct PlayerBundle { pub metadata: metadata::PlayerMetadataBundle, } +/// A marker component that signifies that this entity is "local" and shouldn't +/// be updated by other clients. +#[derive(Component)] +pub struct Local; + // #[cfg(test)] // mod tests { // use super::*; diff --git a/azalea-world/src/entity_info.rs b/azalea-world/src/entity_info.rs deleted file mode 100644 index 428336f0..00000000 --- a/azalea-world/src/entity_info.rs +++ /dev/null @@ -1,326 +0,0 @@ -use crate::{ - deduplicate_entities, deduplicate_local_entities, - entity::{ - self, add_dead, update_bounding_box, EntityUuid, MinecraftEntityId, Position, WorldName, - }, - update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer, -}; -use azalea_core::ChunkPos; -use azalea_ecs::{ - app::{App, CoreStage, Plugin}, - component::Component, - ecs::Ecs, - ecs::EntityMut, - entity::Entity, - query::{Added, Changed, With, Without}, - schedule::{IntoSystemDescriptor, SystemSet}, - system::{Command, Commands, Query, Res, ResMut, Resource}, -}; -use derive_more::{Deref, DerefMut}; -use log::{debug, warn}; -use nohash_hasher::IntMap; -use parking_lot::RwLock; -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - sync::Arc, -}; -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( - SystemSet::new() - .after("tick") - .after("packet") - .with_system(update_entity_chunk_positions) - .with_system(remove_despawned_entities_from_indexes) - .with_system(update_bounding_box) - .with_system(add_dead) - .with_system( - add_updates_received - .after("deduplicate_entities") - .after("deduplicate_local_entities") - .label("add_updates_received"), - ) - .with_system( - update_uuid_index - .label("update_uuid_index") - .after("deduplicate_local_entities") - .after("deduplicate_entities"), - ) - .with_system(debug_detect_updates_received_on_local_entities) - .with_system( - update_entity_by_id_index - .label("update_entity_by_id_index") - .after("deduplicate_entities"), - ) - .with_system(debug_new_entity), - ) - .add_system_set_to_stage( - CoreStage::PostUpdate, - SystemSet::new() - .with_system(deduplicate_entities.label("deduplicate_entities")) - .with_system( - deduplicate_local_entities - .label("deduplicate_local_entities") - .before("update_uuid_index") - .before("update_entity_by_id_index"), - ), - ) - .init_resource::(); - } -} - -fn debug_new_entity(query: Query>) { - for entity in query.iter() { - debug!("new entity: {:?}", entity); - } -} - -// 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 -// entity -// - when a client loads an entity, its "updates received" is set to the same as -// the global "updates received" -// - when the shared world sees an entity for the first time, the "updates -// received" is set to 1. -// - clients can force the shared "updates received" to 0 to make it so certain -// entities (i.e. other bots in our swarm) don't get confused and updated by -// other bots -// - when a client gets an update to an entity, we check if our "updates -// received" is the same as the shared world's "updates received": if it is, -// then process the update and increment the client's and shared world's -// "updates received" if not, then we simply increment our local "updates -// received" and do nothing else - -/// Keep track of certain metadatas that are only relevant for this partial -/// world. -#[derive(Debug, Default)] -pub struct PartialEntityInfos { - // 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 [`RelativeEntityUpdate`] pretend the entity doesn't exist so - /// it doesn't get modified from outside sources. - 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, -} - -impl PartialEntityInfos { - pub fn new(owner_entity: Option) -> Self { - Self { - owner_entity, - updates_received: IntMap::default(), - } - } -} - -/// A [`Command`] that applies a "relative update" to an entity, which means -/// this update won't be run multiple times by different clients in the same -/// world. -/// -/// This is used to avoid a bug where when there's multiple clients in the same -/// world and an entity sends a relative move packet to all clients, its -/// position gets desynced since the relative move is applied multiple times. -/// -/// Don't use this unless you actually got an entity update packet that all -/// other clients within render distance will get too. You usually don't need -/// this when the change isn't relative either. -pub struct RelativeEntityUpdate { - pub entity: Entity, - pub partial_world: Arc>, - // a function that takes the entity and updates it - pub update: Box, -} -impl Command for RelativeEntityUpdate { - fn write(self, world: &mut Ecs) { - let partial_entity_infos = &mut self.partial_world.write().entity_infos; - - let mut entity = world.entity_mut(self.entity); - - if Some(self.entity) == partial_entity_infos.owner_entity { - // if the entity owns this partial world, it's always allowed to update itself - (self.update)(&mut entity); - return; - }; - - let entity_id = *entity.get::().unwrap(); - - let Some(updates_received) = entity.get_mut::() else { - // a client tried to update another client, which isn't allowed - return; - }; - - let this_client_updates_received = partial_entity_infos - .updates_received - .get(&entity_id) - .copied(); - - let can_update = this_client_updates_received.unwrap_or(1) == **updates_received; - if can_update { - let new_updates_received = this_client_updates_received.unwrap_or(0) + 1; - partial_entity_infos - .updates_received - .insert(entity_id, new_updates_received); - - **entity.get_mut::().unwrap() = new_updates_received; - - let mut entity = world.entity_mut(self.entity); - (self.update)(&mut entity); - } - } -} - -/// Things that are shared between all the partial worlds. -#[derive(Resource, Default)] -pub struct EntityInfos { - /// An index of entities by their UUIDs - pub(crate) entity_by_uuid: HashMap, -} - -impl EntityInfos { - pub fn new() -> Self { - Self { - entity_by_uuid: HashMap::default(), - } - } - - pub fn get_entity_by_uuid(&self, uuid: &Uuid) -> Option { - self.entity_by_uuid.get(uuid).copied() - } -} - -/// Update the chunk position indexes in [`EntityInfos`]. -fn update_entity_chunk_positions( - mut query: Query< - ( - Entity, - &entity::Position, - &mut entity::LastSentPosition, - &entity::WorldName, - ), - Changed, - >, - world_container: Res, -) { - for (entity, pos, last_pos, world_name) in query.iter_mut() { - let world_lock = world_container.get(world_name).unwrap(); - let mut world = world_lock.write(); - - let old_chunk = ChunkPos::from(*last_pos); - let new_chunk = ChunkPos::from(*pos); - - if old_chunk != new_chunk { - // move the entity from the old chunk to the new one - if let Some(entities) = world.entities_by_chunk.get_mut(&old_chunk) { - entities.remove(&entity); - } - world - .entities_by_chunk - .entry(new_chunk) - .or_default() - .insert(entity); - } - } -} -/// A component that lists all the local player entities that have this entity -/// loaded. If this is empty, the entity will be removed from the ECS. -#[derive(Component, Clone, Deref, DerefMut)] -pub struct LoadedBy(pub HashSet); - -/// A component that counts the number of times this entity has been modified. -/// This is used for making sure two clients don't do the same relative update -/// on an entity. -/// -/// If an entity is local (i.e. it's a client/localplayer), this component -/// should NOT be present in the entity. -#[derive(Component, Debug, Deref, DerefMut)] -pub struct UpdatesReceived(u32); - -#[allow(clippy::type_complexity)] -pub fn add_updates_received( - mut commands: Commands, - query: Query< - Entity, - ( - Changed, - (Without, Without), - ), - >, -) { - for entity in query.iter() { - // entities always start with 1 update received - commands.entity(entity).insert(UpdatesReceived(1)); - } -} - -/// A marker component that signifies that this entity is "local" and shouldn't -/// be updated by other clients. -#[derive(Component)] -pub struct Local; - -/// The [`UpdatesReceived`] component should never be on [`Local`] entities. -/// This warns if an entity has both components. -fn debug_detect_updates_received_on_local_entities( - query: Query, With)>, -) { - for entity in &query { - warn!("Entity {:?} has both Local and UpdatesReceived", entity); - } -} - -/// Despawn entities that aren't being loaded by anything. -fn remove_despawned_entities_from_indexes( - mut commands: Commands, - mut entity_infos: ResMut, - world_container: Res, - query: Query<(Entity, &EntityUuid, &Position, &WorldName, &LoadedBy), Changed>, -) { - for (entity, uuid, position, world_name, loaded_by) in &query { - let world_lock = world_container.get(world_name).unwrap(); - let mut world = world_lock.write(); - - // if the entity has no references left, despawn it - if !loaded_by.is_empty() { - continue; - } - - // remove the entity from the chunk index - let chunk = ChunkPos::from(*position); - if let Some(entities_in_chunk) = world.entities_by_chunk.get_mut(&chunk) { - if entities_in_chunk.remove(&entity) { - // remove the chunk if there's no entities in it anymore - if entities_in_chunk.is_empty() { - world.entities_by_chunk.remove(&chunk); - } - } else { - warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there."); - } - } else { - warn!("Tried to remove entity from chunk {chunk:?} but the chunk was not found."); - } - // remove it from the uuid index - if entity_infos.entity_by_uuid.remove(uuid).is_none() { - warn!("Tried to remove entity {entity:?} from the uuid index but it was not there."); - } - // and now remove the entity from the ecs - commands.entity(entity).despawn(); - debug!("Despawned entity {entity:?} because it was not loaded by anything."); - return; - } -} - -impl Debug for EntityInfos { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EntityInfos").finish() - } -} diff --git a/azalea-world/src/lib.rs b/azalea-world/src/lib.rs index 88715f44..f0fd5387 100644 --- a/azalea-world/src/lib.rs +++ b/azalea-world/src/lib.rs @@ -7,7 +7,6 @@ mod bit_storage; mod chunk_storage; mod container; pub mod entity; -mod entity_info; mod palette; mod world; @@ -16,9 +15,6 @@ use std::backtrace::Backtrace; pub use bit_storage::BitStorage; pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage}; pub use container::*; -pub use entity_info::{ - EntityInfos, EntityPlugin, LoadedBy, Local, PartialEntityInfos, RelativeEntityUpdate, -}; use thiserror::Error; pub use world::*; diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs index 5ec2bbac..53a1f377 100644 --- a/azalea-world/src/world.rs +++ b/azalea-world/src/world.rs @@ -1,7 +1,8 @@ use crate::{ - entity::{EntityUuid, MinecraftEntityId, WorldName}, - entity_info::LoadedBy, - ChunkStorage, EntityInfos, Local, PartialChunkStorage, PartialEntityInfos, WorldContainer, + entity::{ + EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName, + }, + ChunkStorage, PartialChunkStorage, WorldContainer, }; use azalea_core::ChunkPos; use azalea_ecs::{ diff --git a/azalea/examples/matbot.rs b/azalea/examples/matbot.rs deleted file mode 100644 index 1ddd9197..00000000 --- a/azalea/examples/matbot.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! mat's bot for testing new azalea features - -#![feature(type_alias_impl_trait)] - -use azalea::ecs::query::With; -use azalea::entity::metadata::Player; -use azalea::entity::Position; -use azalea::pathfinder::BlockPosGoal; -use azalea::{prelude::*, BlockPos, GameProfileComponent, Swarm, SwarmEvent, WalkDirection}; -use azalea::{Account, Client, Event}; -use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket; -use std::time::Duration; - -#[derive(Default, Clone, Component)] -struct State {} - -#[derive(Default, Clone, Resource)] -struct SwarmState {} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - env_logger::init(); - - { - use parking_lot::deadlock; - use std::thread; - use std::time::Duration; - - // Create a background thread which checks for deadlocks every 10s - thread::spawn(move || loop { - thread::sleep(Duration::from_secs(10)); - let deadlocks = deadlock::check_deadlock(); - if deadlocks.is_empty() { - continue; - } - - println!("{} deadlocks detected", deadlocks.len()); - for (i, threads) in deadlocks.iter().enumerate() { - println!("Deadlock #{i}"); - for t in threads { - println!("Thread Id {:#?}", t.thread_id()); - println!("{:#?}", t.backtrace()); - } - } - }); - } - - let mut accounts = Vec::new(); - let mut states = Vec::new(); - - for i in 0..1 { - accounts.push(Account::offline(&format!("bot{i}"))); - states.push(State::default()); - } - - loop { - let e = SwarmBuilder::new() - .add_accounts(accounts.clone()) - .set_handler(handle) - .set_swarm_handler(swarm_handle) - .join_delay(Duration::from_millis(1000)) - .start("localhost") - .await; - // let e = azalea::ClientBuilder::new() - // .set_handler(handle) - // .start(Account::offline("bot"), "localhost") - // .await; - eprintln!("{e:?}"); - } -} - -async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> { - match event { - Event::Init => { - println!("bot init"); - // bot.set_client_information(ClientInformation { - // view_distance: 2, - // ..Default::default() - // }) - // .await?; - } - Event::Login => { - bot.chat("Hello world"); - } - Event::Chat(m) => { - println!("client chat message: {}", m.content()); - if m.content() == bot.profile.name { - bot.chat("Bye"); - tokio::time::sleep(Duration::from_millis(50)).await; - bot.disconnect(); - } - let Some(sender) = m.username() else { - return Ok(()) - }; - // let mut ecs = bot.ecs.lock(); - // let entity = bot - // .ecs - // .lock() - // .query::<&Player>() - // .iter(&mut ecs) - // .find(|e| e.name() == Some(sender)); - // let entity = bot.entity_by::>(|name: &Name| name == sender); - let entity = bot.entity_by::, (&GameProfileComponent,)>( - |profile: &&GameProfileComponent| { - println!("entity {profile:?}"); - profile.name == sender - }, - ); - println!("sender entity: {entity:?}"); - if let Some(entity) = entity { - match m.content().as_str() { - "whereami" => { - let pos = bot.entity_component::(entity); - bot.chat(&format!("You're at {pos:?}",)); - } - "whereareyou" => { - let pos = bot.component::(); - bot.chat(&format!("I'm at {pos:?}",)); - } - "goto" => { - let entity_pos = bot.entity_component::(entity); - let target_pos: BlockPos = entity_pos.into(); - println!("going to {target_pos:?}"); - bot.goto(BlockPosGoal::from(target_pos)); - } - "look" => { - let entity_pos = bot.entity_component::(entity); - let target_pos: BlockPos = entity_pos.into(); - println!("target_pos: {target_pos:?}"); - bot.look_at(target_pos.center()); - } - "jump" => { - bot.set_jumping(true); - } - "walk" => { - bot.walk(WalkDirection::Forward); - } - "stop" => { - bot.set_jumping(false); - bot.walk(WalkDirection::None); - } - "lag" => { - std::thread::sleep(Duration::from_millis(1000)); - } - _ => {} - } - } - } - Event::Death(_) => { - bot.write_packet(ServerboundClientCommandPacket { - action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn, - }.get()); - } - _ => {} - } - - Ok(()) -} - -async fn swarm_handle( - mut swarm: Swarm, - event: SwarmEvent, - _state: SwarmState, -) -> anyhow::Result<()> { - match &event { - SwarmEvent::Disconnect(account) => { - println!("bot got kicked! {}", account.username); - tokio::time::sleep(Duration::from_secs(5)).await; - swarm.add(account, State::default()).await?; - } - SwarmEvent::Chat(m) => { - println!("swarm chat message: {}", m.message().to_ansi()); - if m.message().to_string() == " world" { - for (name, world) in &swarm.world_container.read().worlds { - println!("world name: {name}"); - if let Some(w) = world.upgrade() { - for chunk_pos in w.read().chunks.chunks.values() { - println!("chunk: {chunk_pos:?}"); - } - } else { - println!("nvm world is gone"); - } - } - } - if m.message().to_string() == " hi" { - for bot in swarm { - bot.chat("hello"); - } - } - } - _ => {} - } - Ok(()) -} diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs new file mode 100644 index 00000000..3fe86fa6 --- /dev/null +++ b/azalea/examples/testbot.rs @@ -0,0 +1,194 @@ +//! a bot for testing new azalea features + +#![feature(type_alias_impl_trait)] + +use azalea::ecs::query::With; +use azalea::entity::metadata::Player; +use azalea::entity::Position; +use azalea::pathfinder::BlockPosGoal; +use azalea::{prelude::*, BlockPos, GameProfileComponent, Swarm, SwarmEvent, WalkDirection}; +use azalea::{Account, Client, Event}; +use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket; +use std::time::Duration; + +#[derive(Default, Clone, Component)] +struct State {} + +#[derive(Default, Clone, Resource)] +struct SwarmState {} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::init(); + + { + use parking_lot::deadlock; + use std::thread; + use std::time::Duration; + + // Create a background thread which checks for deadlocks every 10s + thread::spawn(move || loop { + thread::sleep(Duration::from_secs(10)); + let deadlocks = deadlock::check_deadlock(); + if deadlocks.is_empty() { + continue; + } + + println!("{} deadlocks detected", deadlocks.len()); + for (i, threads) in deadlocks.iter().enumerate() { + println!("Deadlock #{i}"); + for t in threads { + println!("Thread Id {:#?}", t.thread_id()); + println!("{:#?}", t.backtrace()); + } + } + }); + } + + let mut accounts = Vec::new(); + let mut states = Vec::new(); + + for i in 0..1 { + accounts.push(Account::offline(&format!("bot{i}"))); + states.push(State::default()); + } + + loop { + let e = SwarmBuilder::new() + .add_accounts(accounts.clone()) + .set_handler(handle) + .set_swarm_handler(swarm_handle) + .join_delay(Duration::from_millis(1000)) + .start("localhost") + .await; + // let e = azalea::ClientBuilder::new() + // .set_handler(handle) + // .start(Account::offline("bot"), "localhost") + // .await; + eprintln!("{e:?}"); + } +} + +async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> { + match event { + Event::Init => { + println!("bot init"); + // bot.set_client_information(ClientInformation { + // view_distance: 2, + // ..Default::default() + // }) + // .await?; + } + Event::Login => { + bot.chat("Hello world"); + } + Event::Chat(m) => { + println!("client chat message: {}", m.content()); + if m.content() == bot.profile.name { + bot.chat("Bye"); + tokio::time::sleep(Duration::from_millis(50)).await; + bot.disconnect(); + } + let Some(sender) = m.username() else { + return Ok(()) + }; + // let mut ecs = bot.ecs.lock(); + // let entity = bot + // .ecs + // .lock() + // .query::<&Player>() + // .iter(&mut ecs) + // .find(|e| e.name() == Some(sender)); + // let entity = bot.entity_by::>(|name: &Name| name == sender); + let entity = bot.entity_by::, (&GameProfileComponent,)>( + |profile: &&GameProfileComponent| { + println!("entity {profile:?}"); + profile.name == sender + }, + ); + println!("sender entity: {entity:?}"); + if let Some(entity) = entity { + match m.content().as_str() { + "whereami" => { + let pos = bot.entity_component::(entity); + bot.chat(&format!("You're at {pos:?}",)); + } + "whereareyou" => { + let pos = bot.component::(); + bot.chat(&format!("I'm at {pos:?}",)); + } + "goto" => { + let entity_pos = bot.entity_component::(entity); + let target_pos: BlockPos = entity_pos.into(); + println!("going to {target_pos:?}"); + bot.goto(BlockPosGoal::from(target_pos)); + } + "look" => { + let entity_pos = bot.entity_component::(entity); + let target_pos: BlockPos = entity_pos.into(); + println!("target_pos: {target_pos:?}"); + bot.look_at(target_pos.center()); + } + "jump" => { + bot.set_jumping(true); + } + "walk" => { + bot.walk(WalkDirection::Forward); + } + "stop" => { + bot.set_jumping(false); + bot.walk(WalkDirection::None); + } + "lag" => { + std::thread::sleep(Duration::from_millis(1000)); + } + _ => {} + } + } + } + Event::Death(_) => { + bot.write_packet(ServerboundClientCommandPacket { + action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn, + }.get()); + } + _ => {} + } + + Ok(()) +} + +async fn swarm_handle( + mut swarm: Swarm, + event: SwarmEvent, + _state: SwarmState, +) -> anyhow::Result<()> { + match &event { + SwarmEvent::Disconnect(account) => { + println!("bot got kicked! {}", account.username); + tokio::time::sleep(Duration::from_secs(5)).await; + swarm.add(account, State::default()).await?; + } + SwarmEvent::Chat(m) => { + println!("swarm chat message: {}", m.message().to_ansi()); + if m.message().to_string() == " world" { + for (name, world) in &swarm.world_container.read().worlds { + println!("world name: {name}"); + if let Some(w) = world.upgrade() { + for chunk_pos in w.read().chunks.chunks.values() { + println!("chunk: {chunk_pos:?}"); + } + } else { + println!("nvm world is gone"); + } + } + } + if m.message().to_string() == " hi" { + for bot in swarm { + bot.chat("hello"); + } + } + } + _ => {} + } + Ok(()) +} diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 510449d3..1f3822ac 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -9,10 +9,7 @@ use azalea_ecs::{ system::{Commands, Query}, AppTickExt, }; -use azalea_world::{ - entity::{metadata::Player, set_rotation, Jumping, Physics, Position}, - Local, -}; +use azalea_world::entity::{metadata::Player, set_rotation, Jumping, Local, Physics, Position}; use std::f64::consts::PI; use crate::pathfinder::PathfinderPlugin; diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 5246290d..1dfb7a9b 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -17,7 +17,7 @@ use azalea_ecs::{ AppTickExt, }; use azalea_world::entity::metadata::Player; -use azalea_world::Local; +use azalea_world::entity::Local; use azalea_world::{ entity::{Physics, Position, WorldName}, WorldContainer, -- cgit v1.2.3