mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
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>
This commit is contained in:
parent
57e5a0f0b9
commit
11d14c74c5
13 changed files with 426 additions and 276 deletions
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>();
|
||||
|
|
7
azalea-client/src/received_registries.rs
Normal file
7
azalea-client/src/received_registries.rs
Normal file
|
@ -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);
|
|
@ -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.");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
"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");
|
||||
}
|
||||
}
|
||||
"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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
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");
|
||||
}
|
||||
}
|
||||
"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");
|
||||
}
|
||||
}
|
||||
"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");
|
||||
}
|
||||
}
|
||||
"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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::Packet(packet) => match *packet {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue