1
2
Fork 0
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:
mat 2023-08-24 22:59:40 -05:00 committed by GitHub
parent 57e5a0f0b9
commit 11d14c74c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 426 additions and 276 deletions

View file

@ -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,

View file

@ -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> {

View file

@ -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;

View file

@ -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(

View file

@ -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>();

View 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);

View file

@ -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.");

View file

@ -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,

View file

@ -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(());
}

View file

@ -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())
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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 {