mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
add Display for Vec3, add SimulationSet, and add EntityChunkPos component
This commit is contained in:
parent
c38957374c
commit
13426b035e
6 changed files with 142 additions and 72 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue