aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2023-08-24 22:59:40 -0500
committerGitHub <noreply@github.com>2023-08-24 22:59:40 -0500
commit11d14c74c53c07231c8ca33b622380df99bf9a59 (patch)
treeea1d0c61a6d5f8af550a708ff3b71fbbaed5d122
parent57e5a0f0b96a38674bd18ac38d2d07e4f4ca2fd6 (diff)
downloadazalea-drasl-11d14c74c53c07231c8ca33b622380df99bf9a59.tar.xz
Support properly switching instances (#106)
* start implementing switching dimensions * fix removeentity in shared worlds * also store entity ids per local player * uncomment a trace in pathfinder * cleanup --------- Co-authored-by: mat <git@matdoes.dev>
-rw-r--r--azalea-client/src/client.rs13
-rw-r--r--azalea-client/src/entity_query.rs10
-rw-r--r--azalea-client/src/lib.rs1
-rw-r--r--azalea-client/src/local_player.rs9
-rw-r--r--azalea-client/src/packet_handling.rs218
-rw-r--r--azalea-client/src/received_registries.rs7
-rw-r--r--azalea-entity/src/plugin/indexing.rs58
-rwxr-xr-xazalea-protocol/src/packets/game/clientbound_respawn_packet.rs4
-rwxr-xr-xazalea-world/src/chunk_storage.rs6
-rw-r--r--azalea-world/src/container.rs3
-rw-r--r--azalea-world/src/world.rs8
-rw-r--r--azalea/examples/testbot.rs313
-rw-r--r--azalea/src/pathfinder/mod.rs22
13 files changed, 411 insertions, 261 deletions
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index e2ca6c4e..39b86b98 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -21,7 +21,10 @@ use crate::{
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
use azalea_chat::FormattedText;
use azalea_core::Vec3;
-use azalea_entity::{metadata::Health, EntityPlugin, EntityUpdateSet, EyeHeight, Local, Position};
+use azalea_entity::{
+ indexing::EntityIdIndex, metadata::Health, EntityPlugin, EntityUpdateSet, EyeHeight, Local,
+ Position,
+};
use azalea_physics::{PhysicsPlugin, PhysicsSet};
use azalea_protocol::{
connect::{Connection, ConnectionError},
@@ -306,8 +309,13 @@ impl Client {
last_sent_direction: LastSentLookDirection::default(),
abilities: PlayerAbilities::default(),
permission_level: PermissionLevel::default(),
+ hunger: Hunger::default(),
+
+ entity_id_index: EntityIdIndex::default(),
+
mining: mining::MineBundle::default(),
attack: attack::AttackBundle::default(),
+
_local: Local,
});
@@ -591,6 +599,9 @@ pub struct JoinedClientBundle {
pub last_sent_direction: LastSentLookDirection,
pub abilities: PlayerAbilities,
pub permission_level: PermissionLevel,
+ pub hunger: Hunger,
+
+ pub entity_id_index: EntityIdIndex,
pub mining: mining::MineBundle,
pub attack: attack::AttackBundle,
diff --git a/azalea-client/src/entity_query.rs b/azalea-client/src/entity_query.rs
index d3fa522b..ca41c872 100644
--- a/azalea-client/src/entity_query.rs
+++ b/azalea-client/src/entity_query.rs
@@ -71,6 +71,16 @@ impl Client {
.expect("Entity components must be present in Client::entity)components.");
components.clone()
}
+
+ /// Get a component from an entity, if it exists. This is similar to
+ /// [`Self::entity_component`] but returns an `Option` instead of panicking
+ /// if the component isn't present.
+ pub fn get_entity_component<Q: Component + Clone>(&mut self, entity: Entity) -> Option<Q> {
+ let mut ecs = self.ecs.lock();
+ let mut q = ecs.query::<&Q>();
+ let components = q.get(&ecs, entity).ok();
+ components.cloned()
+ }
}
pub trait EntityPredicate<Q: ReadOnlyWorldQuery, Filter: ReadOnlyWorldQuery> {
diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs
index 92a44f6c..0321a396 100644
--- a/azalea-client/src/lib.rs
+++ b/azalea-client/src/lib.rs
@@ -26,6 +26,7 @@ mod movement;
pub mod packet_handling;
pub mod ping;
mod player;
+pub mod received_registries;
pub mod respawn;
pub mod task_pool;
diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs
index 27ac28dc..a66b7ad5 100644
--- a/azalea-client/src/local_player.rs
+++ b/azalea-client/src/local_player.rs
@@ -99,6 +99,15 @@ pub struct Hunger {
pub saturation: f32,
}
+impl Default for Hunger {
+ fn default() -> Self {
+ Hunger {
+ food: 20,
+ saturation: 5.,
+ }
+ }
+}
+
impl LocalPlayer {
/// Create a new `LocalPlayer`.
pub fn new(
diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs
index 5767d4d2..0b6e23b1 100644
--- a/azalea-client/src/packet_handling.rs
+++ b/azalea-client/src/packet_handling.rs
@@ -3,7 +3,7 @@ use std::{collections::HashSet, io::Cursor, sync::Arc};
use azalea_buf::McBufWritable;
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
use azalea_entity::{
- indexing::EntityUuidIndex,
+ indexing::{EntityIdIndex, EntityUuidIndex},
metadata::{apply_metadata, Health, PlayerMetadataBundle},
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy, LookDirection,
Physics, PlayerBundle, Position, RelativeEntityUpdate,
@@ -47,6 +47,7 @@ use crate::{
SetContainerContentEvent,
},
local_player::{GameProfileComponent, Hunger, LocalGameMode, LocalPlayer},
+ received_registries::ReceivedRegistries,
ClientInformation, PlayerInfo,
};
@@ -208,22 +209,17 @@ pub fn process_packet_events(ecs: &mut World) {
#[allow(clippy::type_complexity)]
let mut system_state: SystemState<(
Commands,
- Query<(
- &mut LocalPlayer,
- Option<&mut InstanceName>,
- &GameProfileComponent,
- &ClientInformation,
- )>,
+ Query<(&mut LocalPlayer, &GameProfileComponent, &ClientInformation)>,
ResMut<InstanceContainer>,
)> = SystemState::new(ecs);
let (mut commands, mut query, mut instance_container) = system_state.get_mut(ecs);
- let (mut local_player, world_name, game_profile, client_information) =
+ let (mut local_player, game_profile, client_information) =
query.get_mut(player_entity).unwrap();
{
- let dimension = &p
- .registry_holder
- .root
+ let received_registries = ReceivedRegistries(p.registry_holder.root);
+
+ let dimension = &received_registries
.dimension_type
.value
.iter()
@@ -235,13 +231,6 @@ pub fn process_packet_events(ecs: &mut World) {
let new_world_name = p.dimension.clone();
- if let Some(mut world_name) = world_name {
- *world_name = world_name.clone();
- } else {
- commands
- .entity(player_entity)
- .insert(InstanceName(new_world_name.clone()));
- }
// add this world to the instance_container (or don't if it's already
// there)
let weak_world = instance_container.insert(
@@ -257,8 +246,7 @@ pub fn process_packet_events(ecs: &mut World) {
azalea_world::calculate_chunk_storage_range(
client_information.view_distance.into(),
),
- // this argument makes it so other clients don't update this
- // player entity
+ // this argument makes it so other clients don't update this player entity
// in a shared world
Some(player_entity),
);
@@ -281,10 +269,7 @@ pub fn process_packet_events(ecs: &mut World) {
previous: p.previous_game_type.into(),
},
// this gets overwritten later by the SetHealth packet
- Hunger {
- food: 20,
- saturation: 5.,
- },
+ received_registries,
player_bundle,
));
}
@@ -588,21 +573,22 @@ pub fn process_packet_events(ecs: &mut World) {
#[allow(clippy::type_complexity)]
let mut system_state: SystemState<(
Commands,
- Query<Option<&InstanceName>>,
+ Query<(&mut EntityIdIndex, Option<&InstanceName>)>,
Res<InstanceContainer>,
ResMut<EntityUuidIndex>,
)> = SystemState::new(ecs);
let (mut commands, mut query, instance_container, mut entity_uuid_index) =
system_state.get_mut(ecs);
- let instance_name = query.get_mut(player_entity).unwrap();
+ let (mut entity_id_index, instance_name) = query.get_mut(player_entity).unwrap();
if let Some(instance_name) = instance_name {
let bundle = p.as_entity_bundle((**instance_name).clone());
- let mut entity_commands = commands.spawn((
+ let mut spawned = commands.spawn((
MinecraftEntityId(p.id),
LoadedBy(HashSet::from([player_entity])),
bundle,
));
+ entity_id_index.insert(MinecraftEntityId(p.id), spawned.id());
{
// add it to the indexes immediately so if there's a packet that references
@@ -611,13 +597,13 @@ pub fn process_packet_events(ecs: &mut World) {
instance
.write()
.entity_by_id
- .insert(MinecraftEntityId(p.id), entity_commands.id());
- entity_uuid_index.insert(p.uuid, entity_commands.id());
+ .insert(MinecraftEntityId(p.id), spawned.id());
+ entity_uuid_index.insert(p.uuid, spawned.id());
}
// the bundle doesn't include the default entity metadata so we add that
// separately
- p.apply_metadata(&mut entity_commands);
+ p.apply_metadata(&mut spawned);
} else {
warn!("got add player packet but we haven't gotten a login packet yet");
}
@@ -629,28 +615,26 @@ pub fn process_packet_events(ecs: &mut World) {
let mut system_state: SystemState<(
Commands,
- Query<&mut LocalPlayer>,
+ Query<&EntityIdIndex>,
Query<&EntityKind>,
)> = SystemState::new(ecs);
let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
+ let entity_id_index = query.get_mut(player_entity).unwrap();
- let world = local_player.world.read();
- let entity = world.entity_by_id(&MinecraftEntityId(p.id));
- drop(world);
+ let entity = entity_id_index.get(&MinecraftEntityId(p.id));
- if let Some(entity) = entity {
- let entity_kind = entity_kind_query.get(entity).unwrap();
- let mut entity_commands = commands.entity(entity);
- if let Err(e) = apply_metadata(
- &mut entity_commands,
- **entity_kind,
- (*p.packed_items).clone(),
- ) {
- warn!("{e}");
- }
- } else {
+ let Some(entity) = entity else {
warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
+ continue;
+ };
+ let entity_kind = entity_kind_query.get(entity).unwrap();
+ let mut entity_commands = commands.entity(entity);
+ if let Err(e) = apply_metadata(
+ &mut entity_commands,
+ **entity_kind,
+ (*p.packed_items).clone(),
+ ) {
+ warn!("{e}");
}
system_state.apply(ecs);
@@ -670,10 +654,11 @@ pub fn process_packet_events(ecs: &mut World) {
#[allow(clippy::type_complexity)]
let mut system_state: SystemState<(
Commands,
- Query<(&TabList, Option<&InstanceName>)>,
+ Query<(&mut EntityIdIndex, &TabList, Option<&InstanceName>)>,
)> = SystemState::new(ecs);
let (mut commands, mut query) = system_state.get_mut(ecs);
- let (tab_list, world_name) = query.get_mut(player_entity).unwrap();
+ let (mut entity_id_index, tab_list, world_name) =
+ query.get_mut(player_entity).unwrap();
if let Some(InstanceName(world_name)) = world_name {
let bundle = p.as_player_bundle(world_name.clone());
@@ -682,6 +667,7 @@ pub fn process_packet_events(ecs: &mut World) {
LoadedBy(HashSet::from([player_entity])),
bundle,
));
+ entity_id_index.insert(MinecraftEntityId(p.id), spawned.id());
if let Some(player_info) = tab_list.get(&p.uuid) {
spawned.insert(GameProfileComponent(player_info.profile.clone()));
@@ -720,14 +706,14 @@ pub fn process_packet_events(ecs: &mut World) {
debug!("Got set experience packet {:?}", p);
}
ClientboundGamePacket::TeleportEntity(p) => {
- let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
- SystemState::new(ecs);
+ let mut system_state: SystemState<(
+ Commands,
+ Query<(&EntityIdIndex, &LocalPlayer)>,
+ )> = SystemState::new(ecs);
let (mut commands, mut query) = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
+ let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
- let world = local_player.world.read();
- let entity = world.entity_by_id(&MinecraftEntityId(p.id));
- drop(world);
+ let entity = entity_id_index.get(&MinecraftEntityId(p.id));
if let Some(entity) = entity {
let new_position = p.position;
@@ -751,14 +737,14 @@ pub fn process_packet_events(ecs: &mut World) {
// debug!("Got rotate head packet {:?}", p);
}
ClientboundGamePacket::MoveEntityPos(p) => {
- let mut system_state: SystemState<(Commands, Query<&LocalPlayer>)> =
- SystemState::new(ecs);
+ let mut system_state: SystemState<(
+ Commands,
+ Query<(&EntityIdIndex, &LocalPlayer)>,
+ )> = SystemState::new(ecs);
let (mut commands, mut query) = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
+ let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
- let world = local_player.world.read();
- let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id));
- drop(world);
+ let entity = entity_id_index.get(&MinecraftEntityId(p.entity_id));
if let Some(entity) = entity {
let delta = p.delta.clone();
@@ -779,14 +765,14 @@ pub fn process_packet_events(ecs: &mut World) {
system_state.apply(ecs);
}
ClientboundGamePacket::MoveEntityPosRot(p) => {
- let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
- SystemState::new(ecs);
+ let mut system_state: SystemState<(
+ Commands,
+ Query<(&EntityIdIndex, &LocalPlayer)>,
+ )> = SystemState::new(ecs);
let (mut commands, mut query) = system_state.get_mut(ecs);
- let local_player = query.get_mut(player_entity).unwrap();
+ let (entity_id_index, local_player) = query.get_mut(player_entity).unwrap();
- let world = local_player.world.read();
- let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id));
- drop(world);
+ let entity = entity_id_index.get(&MinecraftEntityId(p.entity_id));
if let Some(entity) = entity {
let delta = p.delta.clone();
@@ -832,29 +818,29 @@ pub fn process_packet_events(ecs: &mut World) {
debug!("Got remove entities packet {:?}", p);
let mut system_state: SystemState<(
- Commands,
- Query<&mut InstanceName>,
- Res<InstanceContainer>,
+ Query<&mut EntityIdIndex>,
+ Query<&mut LoadedBy>,
)> = SystemState::new(ecs);
- let (mut commands, mut query, instance_container) = system_state.get_mut(ecs);
- let Ok(instance_name) = query.get_mut(player_entity) else {
+ let (mut query, mut entity_query) = system_state.get_mut(ecs);
+ let Ok(mut entity_id_index) = query.get_mut(player_entity) else {
+ warn!("our local player doesn't have EntityIdIndex");
continue;
};
- let Some(instance) = instance_container.get(&instance_name) else {
- continue;
- };
for &id in &p.entity_ids {
- if let Some(entity) =
- instance.write().entity_by_id.remove(&MinecraftEntityId(id))
- {
- trace!("despawning entity");
- commands.entity(entity).despawn();
- }
+ let Some(entity) = entity_id_index.remove(&MinecraftEntityId(id)) else {
+ warn!("There is no entity with id {id:?}");
+ continue;
+ };
+ let Ok(mut loaded_by) = entity_query.get_mut(entity) else {
+ warn!(
+ "tried to despawn entity {id} but it doesn't have a LoadedBy component",
+ );
+ continue;
+ };
+ loaded_by.remove(&player_entity);
}
-
- system_state.apply(ecs);
}
ClientboundGamePacket::PlayerChat(p) => {
debug!("Got player chat packet {:?}", p);
@@ -1114,8 +1100,72 @@ pub fn process_packet_events(ecs: &mut World) {
ClientboundGamePacket::Respawn(p) => {
debug!("Got respawn packet {:?}", p);
- let mut system_state: SystemState<Commands> = SystemState::new(ecs);
- let mut commands = system_state.get(ecs);
+ #[allow(clippy::type_complexity)]
+ let mut system_state: SystemState<(
+ Commands,
+ Query<(
+ &mut LocalPlayer,
+ &GameProfileComponent,
+ &ClientInformation,
+ &ReceivedRegistries,
+ )>,
+ ResMut<InstanceContainer>,
+ )> = SystemState::new(ecs);
+ let (mut commands, mut query, mut instance_container) = system_state.get_mut(ecs);
+ let (mut local_player, game_profile, client_information, received_registries) =
+ query.get_mut(player_entity).unwrap();
+
+ {
+ let dimension = &received_registries
+ .dimension_type
+ .value
+ .iter()
+ .find(|t| t.name == p.dimension_type)
+ .unwrap_or_else(|| {
+ panic!("No dimension_type with name {}", p.dimension_type)
+ })
+ .element;
+
+ let new_world_name = p.dimension.clone();
+
+ // add this world to the instance_container (or don't if it's already
+ // there)
+ let weak_world = instance_container.insert(
+ new_world_name.clone(),
+ dimension.height,
+ dimension.min_y,
+ );
+
+ // set the partial_world to an empty world
+ // (when we add chunks or entities those will be in the
+ // instance_container)
+ *local_player.partial_instance.write() = PartialInstance::new(
+ azalea_world::calculate_chunk_storage_range(
+ client_information.view_distance.into(),
+ ),
+ Some(player_entity),
+ );
+ local_player.world = weak_world;
+
+ // this resets a bunch of our components like physics and stuff
+ let player_bundle = PlayerBundle {
+ entity: EntityBundle::new(
+ game_profile.uuid,
+ Vec3::default(),
+ azalea_registry::EntityKind::Player,
+ new_world_name,
+ ),
+ metadata: PlayerMetadataBundle::default(),
+ };
+ // update the local gamemode and metadata things
+ commands.entity(player_entity).insert((
+ LocalGameMode {
+ current: p.game_type,
+ previous: p.previous_game_type.into(),
+ },
+ player_bundle,
+ ));
+ }
// Remove the Dead marker component from the player.
commands.entity(player_entity).remove::<Dead>();
diff --git a/azalea-client/src/received_registries.rs b/azalea-client/src/received_registries.rs
new file mode 100644
index 00000000..845527ae
--- /dev/null
+++ b/azalea-client/src/received_registries.rs
@@ -0,0 +1,7 @@
+use azalea_protocol::packets::game::clientbound_login_packet::registry::RegistryRoot;
+use bevy_ecs::component::Component;
+use derive_more::Deref;
+
+/// The registries that the server sent us on login.
+#[derive(Clone, Debug, Component, Deref)]
+pub struct ReceivedRegistries(pub RegistryRoot);
diff --git a/azalea-entity/src/plugin/indexing.rs b/azalea-entity/src/plugin/indexing.rs
index f7dfe0fa..0f6232fa 100644
--- a/azalea-entity/src/plugin/indexing.rs
+++ b/azalea-entity/src/plugin/indexing.rs
@@ -3,11 +3,13 @@
use azalea_core::ChunkPos;
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
use bevy_ecs::{
+ component::Component,
entity::Entity,
query::{Changed, With, Without},
system::{Commands, Query, Res, ResMut, Resource},
};
use log::{debug, error, info, warn};
+use nohash_hasher::IntMap;
use std::{collections::HashMap, fmt::Debug};
use uuid::Uuid;
@@ -21,6 +23,15 @@ pub struct EntityUuidIndex {
entity_by_uuid: HashMap<Uuid, Entity>,
}
+/// An index of Minecraft entity IDs to Azalea ECS entities. This is a
+/// `Component` so local players can keep track of entity IDs independently from
+/// the instance.
+#[derive(Component, Default)]
+pub struct EntityIdIndex {
+ /// An index of entities by their MinecraftEntityId
+ entity_by_id: IntMap<MinecraftEntityId, Entity>,
+}
+
impl EntityUuidIndex {
pub fn new() -> Self {
Self {
@@ -41,6 +52,24 @@ impl EntityUuidIndex {
}
}
+impl EntityIdIndex {
+ pub fn get(&self, id: &MinecraftEntityId) -> Option<Entity> {
+ self.entity_by_id.get(id).copied()
+ }
+
+ pub fn contains_key(&self, id: &MinecraftEntityId) -> bool {
+ self.entity_by_id.contains_key(id)
+ }
+
+ pub fn insert(&mut self, id: MinecraftEntityId, entity: Entity) {
+ self.entity_by_id.insert(id, entity);
+ }
+
+ pub fn remove(&mut self, id: &MinecraftEntityId) -> Option<Entity> {
+ self.entity_by_id.remove(id)
+ }
+}
+
impl Debug for EntityUuidIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EntityUuidIndex").finish()
@@ -140,8 +169,7 @@ pub fn update_uuid_index(
if local.is_none() {
if let Some(old_entity) = entity_infos.entity_by_uuid.get(&uuid) {
debug!(
- "Entity with UUID {uuid:?} already existed in the world, not adding to
- index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
+ "Entity with UUID {uuid:?} already existed in the world, not adding to index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
);
continue;
}
@@ -164,8 +192,7 @@ pub fn update_entity_by_id_index(
if local.is_none() {
if let Some(old_entity) = world.entity_by_id.get(id) {
debug!(
- "Entity with ID {id:?} already existed in the world, not adding to
- index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
+ "Entity with ID {id:?} already existed in the world, not adding to index (old ecs id: {old_entity:?} / new ecs id: {entity:?})"
);
continue;
}
@@ -209,8 +236,23 @@ pub fn remove_despawned_entities_from_indexes(
query: Query<(Entity, &EntityUuid, &Position, &InstanceName, &LoadedBy), Changed<LoadedBy>>,
) {
for (entity, uuid, position, world_name, loaded_by) in &query {
- let world_lock = instance_container.get(world_name).unwrap();
- let mut world = world_lock.write();
+ let Some(instance_lock) = instance_container.get(world_name) else {
+ // the instance isn't even loaded by us, so we can safely delete the entity
+ debug!(
+ "Despawned entity {entity:?} because it's in an instance that isn't loaded anymore"
+ );
+ 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();
+
+ continue;
+ };
+
+ let mut instance = instance_lock.write();
// if the entity has no references left, despawn it
if !loaded_by.is_empty() {
@@ -219,11 +261,11 @@ pub fn remove_despawned_entities_from_indexes(
// 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 let Some(entities_in_chunk) = instance.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);
+ instance.entities_by_chunk.remove(&chunk);
}
} else {
warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there.");
diff --git a/azalea-protocol/src/packets/game/clientbound_respawn_packet.rs b/azalea-protocol/src/packets/game/clientbound_respawn_packet.rs
index 3518aa08..71ccfd18 100755
--- a/azalea-protocol/src/packets/game/clientbound_respawn_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_respawn_packet.rs
@@ -7,8 +7,8 @@ pub struct ClientboundRespawnPacket {
pub dimension_type: ResourceLocation,
pub dimension: ResourceLocation,
pub seed: u64,
- pub player_game_type: GameMode,
- pub previous_player_game_type: OptionalGameType,
+ pub game_type: GameMode,
+ pub previous_game_type: OptionalGameType,
pub is_debug: bool,
pub is_flat: bool,
pub data_to_keep: u8,
diff --git a/azalea-world/src/chunk_storage.rs b/azalea-world/src/chunk_storage.rs
index f0f053fa..133d522b 100755
--- a/azalea-world/src/chunk_storage.rs
+++ b/azalea-world/src/chunk_storage.rs
@@ -123,11 +123,7 @@ impl PartialChunkStorage {
) -> Result<(), BufReadError> {
debug!("Replacing chunk at {:?}", pos);
if !self.in_range(pos) {
- trace!(
- "Ignoring chunk since it's not in the view range: {}, {}",
- pos.x,
- pos.z
- );
+ warn!("Ignoring chunk since it's not in the view range: {pos:?}");
return Ok(());
}
diff --git a/azalea-world/src/container.rs b/azalea-world/src/container.rs
index 895d8d2d..866ac157 100644
--- a/azalea-world/src/container.rs
+++ b/azalea-world/src/container.rs
@@ -37,7 +37,8 @@ impl InstanceContainer {
}
}
- /// Get a world from the container.
+ /// Get a world from the container. Returns `None` if none of the clients
+ /// are in this world.
pub fn get(&self, name: &InstanceName) -> Option<Arc<RwLock<Instance>>> {
self.worlds.get(name).and_then(|world| world.upgrade())
}
diff --git a/azalea-world/src/world.rs b/azalea-world/src/world.rs
index ecd2e653..df462be4 100644
--- a/azalea-world/src/world.rs
+++ b/azalea-world/src/world.rs
@@ -82,16 +82,12 @@ pub struct Instance {
/// An index of all the entities we know are in the chunks of the world
pub entities_by_chunk: HashMap<ChunkPos, HashSet<Entity>>,
- /// An index of Minecraft entity IDs to Azalea ECS entities.
+ /// An index of Minecraft entity IDs to Azalea ECS entities. You should
+ /// avoid using this and instead use `azalea_entity::EntityIdIndex`
pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
}
impl Instance {
- /// Get an ECS [`Entity`] from a Minecraft entity ID.
- pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> {
- self.entity_by_id.get(entity_id).copied()
- }
-
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
self.chunks.get_block_state(pos)
}
diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs
index e2f1c436..3d566410 100644
--- a/azalea/examples/testbot.rs
+++ b/azalea/examples/testbot.rs
@@ -105,174 +105,183 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
},
);
println!("sender entity: {entity:?}");
- if let Some(entity) = entity {
- match m.content().as_str() {
- "whereami" => {
- let pos = bot.entity_component::<Position>(entity);
- bot.chat(&format!("You're at {pos:?}",));
- }
- "whereareyou" => {
- let pos = bot.position();
- bot.chat(&format!("I'm at {pos:?}",));
- }
- "goto" => {
- let entity_pos = bot.entity_component::<Position>(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::<Position>(entity)
- .up(bot.entity_component::<EyeHeight>(entity).into());
- println!("entity_pos: {entity_pos:?}");
- bot.look_at(entity_pos);
- }
- "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));
- }
- "inventory" => {
- println!("inventory: {:?}", bot.menu());
- }
- "findblock" => {
- let target_pos = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
- bot.chat(&format!("target_pos: {target_pos:?}",));
- }
- "gotoblock" => {
- let target_pos = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
- if let Some(target_pos) = target_pos {
- // +1 to stand on top of the block
- bot.goto(BlockPosGoal::from(target_pos.up(1)));
- } else {
- bot.chat("no diamond block found");
- }
- }
- "mineblock" => {
- let target_pos = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
- if let Some(target_pos) = target_pos {
- // +1 to stand on top of the block
- bot.chat("ok mining diamond block");
- bot.look_at(target_pos.center());
- bot.mine(target_pos).await;
- bot.chat("finished mining");
- } else {
- bot.chat("no diamond block found");
- }
+ match m.content().as_str() {
+ "whereami" => {
+ let Some(entity) = entity else {
+ bot.chat("I can't see you");
+ return Ok(());
+ };
+ let pos = bot.entity_component::<Position>(entity);
+ bot.chat(&format!("You're at {pos:?}"));
+ }
+ "whereareyou" => {
+ let pos = bot.position();
+ bot.chat(&format!("I'm at {pos:?}"));
+ }
+ "goto" => {
+ let Some(entity) = entity else {
+ bot.chat("I can't see you");
+ return Ok(());
+ };
+ let entity_pos = bot.entity_component::<Position>(entity);
+ let target_pos: BlockPos = entity_pos.into();
+ println!("going to {target_pos:?}");
+ bot.goto(BlockPosGoal::from(target_pos));
+ }
+ "look" => {
+ let Some(entity) = entity else {
+ bot.chat("I can't see you");
+ return Ok(());
+ };
+ let entity_pos = bot
+ .entity_component::<Position>(entity)
+ .up(bot.entity_component::<EyeHeight>(entity).into());
+ println!("entity_pos: {entity_pos:?}");
+ bot.look_at(entity_pos);
+ }
+ "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));
+ }
+ "inventory" => {
+ println!("inventory: {:?}", bot.menu());
+ }
+ "findblock" => {
+ let target_pos = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
+ bot.chat(&format!("target_pos: {target_pos:?}",));
+ }
+ "gotoblock" => {
+ let target_pos = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
+ if let Some(target_pos) = target_pos {
+ // +1 to stand on top of the block
+ bot.goto(BlockPosGoal::from(target_pos.up(1)));
+ } else {
+ bot.chat("no diamond block found");
}
- "lever" => {
- let target_pos = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::Lever.into());
- let Some(target_pos) = target_pos else {
- bot.chat("no lever found");
- return Ok(());
- };
- bot.goto(BlockPosGoal::from(target_pos));
+ }
+ "mineblock" => {
+ let target_pos = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::DiamondBlock.into());
+ if let Some(target_pos) = target_pos {
+ // +1 to stand on top of the block
+ bot.chat("ok mining diamond block");
bot.look_at(target_pos.center());
- bot.block_interact(target_pos);
- }
- "hitresult" => {
- let hit_result = bot.get_component::<HitResultComponent>();
- bot.chat(&format!("hit_result: {hit_result:?}",));
+ bot.mine(target_pos).await;
+ bot.chat("finished mining");
+ } else {
+ bot.chat("no diamond block found");
}
- "chest" => {
- let target_pos = bot
- .world()
- .read()
- .find_block(bot.position(), &azalea::Block::Chest.into());
- let Some(target_pos) = target_pos else {
- bot.chat("no chest found");
- return Ok(());
- };
- bot.look_at(target_pos.center());
- let container = bot.open_container(target_pos).await;
- println!("container: {:?}", container);
- if let Some(container) = container {
- if let Some(contents) = container.contents() {
- for item in contents {
- if let ItemSlot::Present(item) = item {
- println!("item: {:?}", item);
- }
+ }
+ "lever" => {
+ let target_pos = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::Lever.into());
+ let Some(target_pos) = target_pos else {
+ bot.chat("no lever found");
+ return Ok(());
+ };
+ bot.goto(BlockPosGoal::from(target_pos));
+ bot.look_at(target_pos.center());
+ bot.block_interact(target_pos);
+ }
+ "hitresult" => {
+ let hit_result = bot.get_component::<HitResultComponent>();
+ bot.chat(&format!("hit_result: {hit_result:?}",));
+ }
+ "chest" => {
+ let target_pos = bot
+ .world()
+ .read()
+ .find_block(bot.position(), &azalea::Block::Chest.into());
+ let Some(target_pos) = target_pos else {
+ bot.chat("no chest found");
+ return Ok(());
+ };
+ bot.look_at(target_pos.center());
+ let container = bot.open_container(target_pos).await;
+ println!("container: {:?}", container);
+ if let Some(container) = container {
+ if let Some(contents) = container.contents() {
+ for item in contents {
+ if let ItemSlot::Present(item) = item {
+ println!("item: {:?}", item);
}
- } else {
- println!("container was immediately closed");
}
} else {
- println!("no container found");
+ println!("container was immediately closed");
}
+ } else {
+ println!("no container found");
}
- "attack" => {
- let mut nearest_entity = None;
- let mut nearest_distance = f64::INFINITY;
- let mut nearest_pos = Vec3::default();
- let bot_position = bot.position();
- let bot_entity = bot.entity;
- let bot_instance_name = bot.component::<InstanceName>();
+ }
+ "attack" => {
+ let mut nearest_entity = None;
+ let mut nearest_distance = f64::INFINITY;
+ let mut nearest_pos = Vec3::default();
+ let bot_position = bot.position();
+ let bot_entity = bot.entity;
+ let bot_instance_name = bot.component::<InstanceName>();
+ {
+ let mut ecs = bot.ecs.lock();
+ let mut query = ecs.query_filtered::<(
+ azalea::ecs::entity::Entity,
+ &MinecraftEntityId,
+ &Position,
+ &InstanceName,
+ &EyeHeight,
+ ), With<MinecraftEntityId>>();
+ for (entity, &entity_id, position, instance_name, eye_height) in
+ query.iter(&ecs)
{
- let mut ecs = bot.ecs.lock();
- let mut query = ecs.query_filtered::<(
- azalea::ecs::entity::Entity,
- &MinecraftEntityId,
- &Position,
- &InstanceName,
- &EyeHeight,
- ), With<MinecraftEntityId>>(
- );
- for (entity, &entity_id, position, instance_name, eye_height) in
- query.iter(&ecs)
- {
- if entity == bot_entity {
- continue;
- }
- if instance_name != &bot_instance_name {
- continue;
- }
+ if entity == bot_entity {
+ continue;
+ }
+ if instance_name != &bot_instance_name {
+ continue;
+ }
- let distance = bot_position.distance_to(position);
- if distance < 4.0 && distance < nearest_distance {
- nearest_entity = Some(entity_id);
- nearest_distance = distance;
- nearest_pos = position.up(**eye_height as f64);
- }
+ let distance = bot_position.distance_to(position);
+ if distance < 4.0 && distance < nearest_distance {
+ nearest_entity = Some(entity_id);
+ nearest_distance = distance;
+ nearest_pos = position.up(**eye_height as f64);
}
}
- if let Some(nearest_entity) = nearest_entity {
- bot.look_at(nearest_pos);
- bot.attack(nearest_entity);
- bot.chat("attacking");
- let mut ticks = bot.get_tick_broadcaster();
- while ticks.recv().await.is_ok() {
- if !bot.has_attack_cooldown() {
- break;
- }
+ }
+ if let Some(nearest_entity) = nearest_entity {
+ bot.look_at(nearest_pos);
+ bot.attack(nearest_entity);
+ bot.chat("attacking");
+ let mut ticks = bot.get_tick_broadcaster();
+ while ticks.recv().await.is_ok() {
+ if !bot.has_attack_cooldown() {
+ break;
}
- bot.chat("finished attacking");
- } else {
- bot.chat("no entities found");
}
+ bot.chat("finished attacking");
+ } else {
+ bot.chat("no entities found");
}
- _ => {}
}
+ _ => {}
}
}
Event::Packet(packet) => match *packet {
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 9237aa81..58b59fd4 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -24,10 +24,11 @@ use azalea_physics::PhysicsSet;
use azalea_world::{InstanceContainer, InstanceName};
use bevy_app::{FixedUpdate, Update};
use bevy_ecs::prelude::Event;
+use bevy_ecs::query::Changed;
use bevy_ecs::schedule::IntoSystemConfigs;
use bevy_tasks::{AsyncComputeTaskPool, Task};
use futures_lite::future;
-use log::{debug, error};
+use log::{debug, error, trace};
use std::collections::VecDeque;
use std::sync::Arc;
@@ -49,6 +50,7 @@ impl Plugin for PathfinderPlugin {
goto_listener,
add_default_pathfinder,
(handle_tasks, path_found_listener).chain(),
+ stop_pathfinding_on_instance_change,
),
);
}
@@ -249,7 +251,7 @@ fn tick_execute_path(
entity,
position: center,
});
- debug!(
+ trace!(
"tick: pathfinder {entity:?}; going to {:?}; currently at {position:?}",
target.pos
);
@@ -280,6 +282,22 @@ fn tick_execute_path(
}
}
+fn stop_pathfinding_on_instance_change(
+ mut query: Query<(Entity, &mut Pathfinder), Changed<InstanceName>>,
+ mut walk_events: EventWriter<StartWalkEvent>,
+) {
+ for (entity, mut pathfinder) in &mut query {
+ if !pathfinder.path.is_empty() {
+ debug!("instance changed, clearing path");
+ pathfinder.path.clear();
+ walk_events.send(StartWalkEvent {
+ entity,
+ direction: WalkDirection::None,
+ });
+ }
+ }
+}
+
/// Information about our vertical velocity
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug)]
pub enum VerticalVel {