From 13426b035e43c4435854f175155439ab28a18544 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 24 Feb 2024 21:03:02 -0600 Subject: [PATCH] add Display for Vec3, add SimulationSet, and add EntityChunkPos component --- azalea-core/src/position.rs | 6 + azalea-core/src/tick.rs | 4 + azalea-entity/src/lib.rs | 5 + azalea-entity/src/plugin/indexing.rs | 35 +++--- azalea/src/lib.rs | 1 + azalea/src/pathfinder/simulation.rs | 163 +++++++++++++++++---------- 6 files changed, 142 insertions(+), 72 deletions(-) diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index 369607c5c..e2a9c4cc2 100755 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -564,6 +564,12 @@ impl fmt::Display for BlockPos { write!(f, "{} {} {}", self.x, self.y, self.z) } } +impl fmt::Display for Vec3 { + /// Display a position as `x y z`. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.x, self.y, self.z) + } +} const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25 const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH; diff --git a/azalea-core/src/tick.rs b/azalea-core/src/tick.rs index db10d80ad..c6f02c129 100644 --- a/azalea-core/src/tick.rs +++ b/azalea-core/src/tick.rs @@ -1,4 +1,8 @@ use bevy_ecs::schedule::ScheduleLabel; +/// A Bevy schedule that runs every Minecraft game tick, i.e. every 50ms. +/// +/// Many client systems run on this schedule, the most important one being +/// physics. #[derive(ScheduleLabel, Hash, Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct GameTick; diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs index e08284a92..1b643e4a4 100644 --- a/azalea-entity/src/lib.rs +++ b/azalea-entity/src/lib.rs @@ -23,6 +23,7 @@ use bevy_ecs::{bundle::Bundle, component::Component}; pub use data::*; use derive_more::{Deref, DerefMut}; pub use dimensions::EntityDimensions; +use plugin::indexing::EntityChunkPos; use std::{ fmt::Debug, hash::{Hash, Hasher}, @@ -347,6 +348,9 @@ pub struct EntityBundle { pub world_name: InstanceName, pub position: Position, pub last_sent_position: LastSentPosition, + + pub chunk_pos: EntityChunkPos, + pub physics: Physics, pub direction: LookDirection, pub eye_height: EyeHeight, @@ -375,6 +379,7 @@ impl EntityBundle { uuid: EntityUuid(uuid), world_name: InstanceName(world_name), position: Position(pos), + chunk_pos: EntityChunkPos(ChunkPos::from(&pos)), last_sent_position: LastSentPosition(pos), physics: Physics::new(dimensions, &pos), eye_height: EyeHeight(eye_height), diff --git a/azalea-entity/src/plugin/indexing.rs b/azalea-entity/src/plugin/indexing.rs index 86e91ff21..c8aaffb00 100644 --- a/azalea-entity/src/plugin/indexing.rs +++ b/azalea-entity/src/plugin/indexing.rs @@ -8,12 +8,13 @@ use bevy_ecs::{ query::Changed, system::{Commands, Query, Res, ResMut, Resource}, }; +use derive_more::{Deref, DerefMut}; use nohash_hasher::IntMap; use std::{collections::HashMap, fmt::Debug}; use tracing::{debug, warn}; use uuid::Uuid; -use crate::{EntityUuid, LastSentPosition, Position}; +use crate::{EntityUuid, Position}; use super::LoadedBy; @@ -83,33 +84,37 @@ impl Debug for EntityUuidIndex { } } -// TODO: this should keep track of chunk position changes in a better way -// instead of relying on LastSentPosition +/// The chunk position that an entity is currently in. +#[derive(Component, Debug, Deref, DerefMut)] +pub struct EntityChunkPos(pub ChunkPos); /// Update the chunk position indexes in [`Instance::entities_by_chunk`]. /// /// [`Instance::entities_by_chunk`]: azalea_world::Instance::entities_by_chunk pub fn update_entity_chunk_positions( - mut query: Query<(Entity, &Position, &LastSentPosition, &InstanceName), Changed>, + mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed>, instance_container: Res, ) { - for (entity, pos, last_pos, world_name) in query.iter_mut() { + for (entity, pos, world_name, mut entity_chunk_pos) in query.iter_mut() { let instance_lock = instance_container.get(world_name).unwrap(); let mut instance = instance_lock.write(); - let old_chunk = ChunkPos::from(*last_pos); + let old_chunk = **entity_chunk_pos; let new_chunk = ChunkPos::from(*pos); - if old_chunk != new_chunk { - // move the entity from the old chunk to the new one - if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) { - entities.remove(&entity); + **entity_chunk_pos = new_chunk; + + if old_chunk != new_chunk { + // move the entity from the old chunk to the new one + if let Some(entities) = instance.entities_by_chunk.get_mut(&old_chunk) { + entities.remove(&entity); + } + instance + .entities_by_chunk + .entry(new_chunk) + .or_default() + .insert(entity); } - instance - .entities_by_chunk - .entry(new_chunk) - .or_default() - .insert(entity); } } } diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index fd2cb83ac..84c215d54 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -29,6 +29,7 @@ pub use azalea_core::{ resource_location::ResourceLocation, }; pub use azalea_entity as entity; +pub use azalea_physics as physics; pub use azalea_protocol as protocol; pub use azalea_registry as registry; pub use azalea_world as world; diff --git a/azalea/src/pathfinder/simulation.rs b/azalea/src/pathfinder/simulation.rs index ab3b340e8..bea99e934 100644 --- a/azalea/src/pathfinder/simulation.rs +++ b/azalea/src/pathfinder/simulation.rs @@ -7,7 +7,7 @@ use azalea_client::{ }; use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick}; use azalea_entity::{ - attributes::AttributeInstance, Attributes, EntityDimensions, Physics, Position, + attributes::AttributeInstance, Attributes, EntityDimensions, LookDirection, Physics, Position, }; use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance}; use bevy_app::App; @@ -20,6 +20,7 @@ pub struct SimulatedPlayerBundle { pub position: Position, pub physics: Physics, pub physics_state: PhysicsState, + pub look_direction: LookDirection, pub attributes: Attributes, pub inventory: InventoryComponent, } @@ -35,6 +36,7 @@ impl SimulatedPlayerBundle { position: Position::new(position), physics: Physics::new(dimensions, &position), physics_state: PhysicsState::default(), + look_direction: LookDirection::new(0.0, 0.0), attributes: Attributes { speed: AttributeInstance::new(0.1), attack_speed: AttributeInstance::new(4.0), @@ -44,6 +46,77 @@ impl SimulatedPlayerBundle { } } +fn simulation_instance_name() -> ResourceLocation { + ResourceLocation::new("azalea:simulation") +} + +fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc>) { + let instance_name = simulation_instance_name(); + + let instance = Arc::new(RwLock::new(Instance { + chunks, + ..Default::default() + })); + + let mut app = App::new(); + // we don't use all the default azalea plugins because we don't need all of them + app.add_plugins(( + azalea_physics::PhysicsPlugin, + azalea_entity::EntityPlugin, + azalea_client::movement::PlayerMovePlugin, + super::PathfinderPlugin, + crate::BotPlugin, + azalea_client::task_pool::TaskPoolPlugin::default(), + // for mining + azalea_client::inventory::InventoryPlugin, + azalea_client::mining::MinePlugin, + azalea_client::interact::InteractPlugin, + )) + .insert_resource(InstanceContainer { + instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] + .iter() + .cloned() + .collect(), + }) + .add_event::(); + + app.edit_schedule(bevy_app::Main, |schedule| { + schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); + }); + + (app, instance) +} + +fn create_simulation_player( + ecs: &mut World, + instance: Arc>, + player: SimulatedPlayerBundle, +) -> Entity { + let instance_name = simulation_instance_name(); + + let mut entity = ecs.spawn(( + MinecraftEntityId(0), + azalea_entity::LocalEntity, + azalea_entity::metadata::PlayerMetadataBundle::default(), + azalea_entity::EntityBundle::new( + Uuid::nil(), + *player.position, + azalea_registry::EntityKind::Player, + instance_name, + ), + azalea_client::InstanceHolder { + // partial_instance is never actually used by the pathfinder so + partial_instance: Arc::new(RwLock::new(PartialInstance::default())), + instance: instance.clone(), + }, + InventoryComponent::default(), + )); + entity.insert(player); + + let entity_id = entity.id(); + entity_id +} + /// Simulate the Minecraft world to see if certain movements would be possible. pub struct Simulation { pub app: App, @@ -53,71 +126,47 @@ pub struct Simulation { impl Simulation { pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self { - let instance_name = ResourceLocation::new("azalea:simulation"); - - let instance = Arc::new(RwLock::new(Instance { - chunks, - ..Default::default() - })); - - let mut app = App::new(); - // we don't use all the default azalea plugins because we don't need all of them - app.add_plugins(( - azalea_physics::PhysicsPlugin, - azalea_entity::EntityPlugin, - azalea_client::movement::PlayerMovePlugin, - super::PathfinderPlugin, - crate::BotPlugin, - azalea_client::task_pool::TaskPoolPlugin::default(), - // for mining - azalea_client::inventory::InventoryPlugin, - azalea_client::mining::MinePlugin, - azalea_client::interact::InteractPlugin, - )) - .insert_resource(InstanceContainer { - instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))] - .iter() - .cloned() - .collect(), - }) - .add_event::(); - - app.edit_schedule(bevy_app::Main, |schedule| { - schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded); - }); - - let mut entity = app.world.spawn(( - MinecraftEntityId(0), - azalea_entity::LocalEntity, - azalea_entity::metadata::PlayerMetadataBundle::default(), - azalea_entity::EntityBundle::new( - Uuid::nil(), - *player.position, - azalea_registry::EntityKind::Player, - instance_name, - ), - azalea_client::InstanceHolder { - // partial_instance is never actually used by the pathfinder so - partial_instance: Arc::new(RwLock::new(PartialInstance::default())), - instance: instance.clone(), - }, - InventoryComponent::default(), - )); - entity.insert(player); - - let entity_id = entity.id(); - + let (mut app, instance) = create_simulation_instance(chunks); + let entity = create_simulation_player(&mut app.world, instance.clone(), player); Self { app, - entity: entity_id, + entity, _instance: instance, } } + pub fn tick(&mut self) { - self.app.world.run_schedule(GameTick); self.app.update(); + self.app.world.run_schedule(GameTick); } pub fn position(&self) -> Vec3 { **self.app.world.get::(self.entity).unwrap() } } + +/// A set of simulations, useful for efficiently doing multiple simulations. +pub struct SimulationSet { + pub app: App, + instance: Arc>, +} +impl SimulationSet { + pub fn new(chunks: ChunkStorage) -> Self { + let (app, instance) = create_simulation_instance(chunks); + Self { app, instance } + } + pub fn tick(&mut self) { + self.app.update(); + self.app.world.run_schedule(GameTick); + } + + pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity { + create_simulation_player(&mut self.app.world, self.instance.clone(), player) + } + pub fn despawn(&mut self, entity: Entity) { + self.app.world.despawn(entity); + } + + pub fn position(&self, entity: Entity) -> Vec3 { + **self.app.world.get::(entity).unwrap() + } +}