aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2024-02-24 21:03:02 -0600
committermat <git@matdoes.dev>2024-02-24 21:03:02 -0600
commit13426b035e43c4435854f175155439ab28a18544 (patch)
tree20b6c64ccf6947320f926bb3bc0951971d5c4c22
parentc38957374c288eb41b7dbd905071a469c7bbb2b1 (diff)
downloadazalea-drasl-13426b035e43c4435854f175155439ab28a18544.tar.xz
add Display for Vec3, add SimulationSet, and add EntityChunkPos component
-rwxr-xr-xazalea-core/src/position.rs6
-rw-r--r--azalea-core/src/tick.rs4
-rw-r--r--azalea-entity/src/lib.rs5
-rw-r--r--azalea-entity/src/plugin/indexing.rs35
-rw-r--r--azalea/src/lib.rs1
-rw-r--r--azalea/src/pathfinder/simulation.rs163
6 files changed, 142 insertions, 72 deletions
diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs
index 369607c5..e2a9c4cc 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 db10d80a..c6f02c12 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 e08284a9..1b643e4a 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 86e91ff2..c8aaffb0 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<Position>>,
+ mut query: Query<(Entity, &Position, &InstanceName, &mut EntityChunkPos), Changed<Position>>,
instance_container: Res<InstanceContainer>,
) {
- 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 fd2cb83a..84c215d5 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 ab3b340e..bea99e93 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<RwLock<Instance>>) {
+ 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::<SendPacketEvent>();
+
+ 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<RwLock<Instance>>,
+ 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::<SendPacketEvent>();
-
- 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::<Position>(self.entity).unwrap()
}
}
+
+/// A set of simulations, useful for efficiently doing multiple simulations.
+pub struct SimulationSet {
+ pub app: App,
+ instance: Arc<RwLock<Instance>>,
+}
+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::<Position>(entity).unwrap()
+ }
+}