mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
clean up azalea-entity a little
This commit is contained in:
parent
2ab16402de
commit
d99ba0da55
12 changed files with 399 additions and 415 deletions
|
@ -2,9 +2,10 @@ use std::{collections::HashSet, io::Cursor, sync::Arc};
|
||||||
|
|
||||||
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
|
use azalea_core::{ChunkPos, GameMode, ResourceLocation, Vec3};
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
|
indexing::EntityUuidIndex,
|
||||||
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
||||||
Dead, EntityBundle, EntityInfos, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy,
|
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy, LookDirection,
|
||||||
LookDirection, Physics, PlayerBundle, Position, RelativeEntityUpdate,
|
Physics, PlayerBundle, Position, RelativeEntityUpdate,
|
||||||
};
|
};
|
||||||
use azalea_protocol::{
|
use azalea_protocol::{
|
||||||
connect::{ReadConnection, WriteConnection},
|
connect::{ReadConnection, WriteConnection},
|
||||||
|
@ -579,9 +580,9 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
Commands,
|
Commands,
|
||||||
Query<Option<&InstanceName>>,
|
Query<Option<&InstanceName>>,
|
||||||
Res<InstanceContainer>,
|
Res<InstanceContainer>,
|
||||||
ResMut<EntityInfos>,
|
ResMut<EntityUuidIndex>,
|
||||||
)> = SystemState::new(ecs);
|
)> = SystemState::new(ecs);
|
||||||
let (mut commands, mut query, instance_container, mut entity_infos) =
|
let (mut commands, mut query, instance_container, mut entity_uuid_index) =
|
||||||
system_state.get_mut(ecs);
|
system_state.get_mut(ecs);
|
||||||
let instance_name = query.get_mut(player_entity).unwrap();
|
let instance_name = query.get_mut(player_entity).unwrap();
|
||||||
|
|
||||||
|
@ -601,9 +602,7 @@ fn process_packet_events(ecs: &mut World) {
|
||||||
.write()
|
.write()
|
||||||
.entity_by_id
|
.entity_by_id
|
||||||
.insert(MinecraftEntityId(p.id), entity_commands.id());
|
.insert(MinecraftEntityId(p.id), entity_commands.id());
|
||||||
entity_infos
|
entity_uuid_index.insert(p.uuid, entity_commands.id());
|
||||||
.entity_by_uuid
|
|
||||||
.insert(p.uuid, entity_commands.id());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bundle doesn't include the default entity metadata so we add that
|
// the bundle doesn't include the default entity metadata so we add that
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use azalea_auth::game_profile::GameProfile;
|
use azalea_auth::game_profile::GameProfile;
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
use azalea_core::GameMode;
|
use azalea_core::GameMode;
|
||||||
use azalea_entity::EntityInfos;
|
use azalea_entity::indexing::EntityUuidIndex;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
system::{Commands, Res},
|
system::{Commands, Res},
|
||||||
|
@ -35,10 +35,10 @@ pub struct PlayerInfo {
|
||||||
pub fn retroactively_add_game_profile_component(
|
pub fn retroactively_add_game_profile_component(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut events: EventReader<AddPlayerEvent>,
|
mut events: EventReader<AddPlayerEvent>,
|
||||||
entity_infos: Res<EntityInfos>,
|
entity_uuid_index: Res<EntityUuidIndex>,
|
||||||
) {
|
) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
if let Some(entity) = entity_infos.get_entity_by_uuid(&event.info.uuid) {
|
if let Some(entity) = entity_uuid_index.get(&event.info.uuid) {
|
||||||
commands
|
commands
|
||||||
.entity(entity)
|
.entity(entity)
|
||||||
.insert(GameProfileComponent(event.info.profile.clone()));
|
.insert(GameProfileComponent(event.info.profile.clone()));
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use azalea_core::{Vec3, AABB};
|
use azalea_core::{Vec3, AABB};
|
||||||
use bevy_ecs::{query::Changed, system::Query};
|
|
||||||
|
|
||||||
use super::{Physics, Position};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct EntityDimensions {
|
pub struct EntityDimensions {
|
||||||
|
@ -24,15 +21,3 @@ impl EntityDimensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the position of the entity. This doesn't update the cache in
|
|
||||||
/// azalea-world, and should only be used within azalea-world!
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// Cached position in the world must be updated.
|
|
||||||
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
|
||||||
for (position, mut physics) in query.iter_mut() {
|
|
||||||
let bounding_box = physics.dimensions.make_bounding_box(position);
|
|
||||||
physics.bounding_box = bounding_box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,306 +0,0 @@
|
||||||
//! Implement things relating to entity datas, like an index of uuids to
|
|
||||||
//! entities.
|
|
||||||
|
|
||||||
use azalea_core::ChunkPos;
|
|
||||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance};
|
|
||||||
use bevy_app::{App, Plugin, PostUpdate, PreUpdate, Update};
|
|
||||||
use bevy_ecs::{
|
|
||||||
component::Component,
|
|
||||||
entity::Entity,
|
|
||||||
query::{Added, Changed, With, Without},
|
|
||||||
schedule::{IntoSystemConfigs, SystemSet},
|
|
||||||
system::{Commands, EntityCommand, Query, Res, ResMut, Resource},
|
|
||||||
world::{EntityMut, World},
|
|
||||||
};
|
|
||||||
use derive_more::{Deref, DerefMut};
|
|
||||||
use log::{debug, warn};
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
fmt::Debug,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
add_dead,
|
|
||||||
systems::{
|
|
||||||
deduplicate_entities, deduplicate_local_entities, update_entity_by_id_index,
|
|
||||||
update_fluid_on_eyes, update_uuid_index,
|
|
||||||
},
|
|
||||||
update_bounding_box, EntityUuid, LastSentPosition, Position,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Local, LookDirection};
|
|
||||||
|
|
||||||
/// A Bevy [`SystemSet`] for various types of entity updates.
|
|
||||||
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
|
|
||||||
pub enum EntityUpdateSet {
|
|
||||||
/// Remove ECS entities that refer to an entity that was already in the ECS
|
|
||||||
/// before.
|
|
||||||
Deduplicate,
|
|
||||||
/// Create search indexes for entities.
|
|
||||||
Index,
|
|
||||||
/// Remove despawned entities from search indexes.
|
|
||||||
Deindex,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Plugin handling some basic entity functionality.
|
|
||||||
pub struct EntityPlugin;
|
|
||||||
impl Plugin for EntityPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
// entities get added pre-update
|
|
||||||
// added to indexes during update (done by this plugin)
|
|
||||||
// modified during update
|
|
||||||
// despawned post-update (done by this plugin)
|
|
||||||
app.add_systems(
|
|
||||||
PreUpdate,
|
|
||||||
remove_despawned_entities_from_indexes.in_set(EntityUpdateSet::Deindex),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
PostUpdate,
|
|
||||||
(deduplicate_entities, deduplicate_local_entities).in_set(EntityUpdateSet::Deduplicate),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
(
|
|
||||||
update_entity_chunk_positions,
|
|
||||||
update_uuid_index,
|
|
||||||
update_entity_by_id_index,
|
|
||||||
)
|
|
||||||
.in_set(EntityUpdateSet::Index),
|
|
||||||
(
|
|
||||||
add_updates_received,
|
|
||||||
debug_new_entity,
|
|
||||||
debug_detect_updates_received_on_local_entities,
|
|
||||||
add_dead,
|
|
||||||
update_bounding_box,
|
|
||||||
clamp_look_direction,
|
|
||||||
update_fluid_on_eyes,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.init_resource::<EntityInfos>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_new_entity(query: Query<(Entity, Option<&Local>), Added<MinecraftEntityId>>) {
|
|
||||||
for (entity, local) in query.iter() {
|
|
||||||
if local.is_some() {
|
|
||||||
debug!("new local entity: {:?}", entity);
|
|
||||||
} else {
|
|
||||||
debug!("new entity: {:?}", entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// How entity updates are processed (to avoid issues with shared worlds)
|
|
||||||
// - each bot contains a map of { entity id: updates received }
|
|
||||||
// - the shared world also contains a canonical "true" updates received for each
|
|
||||||
// entity
|
|
||||||
// - when a client loads an entity, its "updates received" is set to the same as
|
|
||||||
// the global "updates received"
|
|
||||||
// - when the shared world sees an entity for the first time, the "updates
|
|
||||||
// received" is set to 1.
|
|
||||||
// - clients can force the shared "updates received" to 0 to make it so certain
|
|
||||||
// entities (i.e. other bots in our swarm) don't get confused and updated by
|
|
||||||
// other bots
|
|
||||||
// - when a client gets an update to an entity, we check if our "updates
|
|
||||||
// received" is the same as the shared world's "updates received": if it is,
|
|
||||||
// then process the update and increment the client's and shared world's
|
|
||||||
// "updates received" if not, then we simply increment our local "updates
|
|
||||||
// received" and do nothing else
|
|
||||||
|
|
||||||
/// An [`EntityCommand`] that applies a "relative update" to an entity, which
|
|
||||||
/// means this update won't be run multiple times by different clients in the
|
|
||||||
/// same world.
|
|
||||||
///
|
|
||||||
/// This is used to avoid a bug where when there's multiple clients in the same
|
|
||||||
/// world and an entity sends a relative move packet to all clients, its
|
|
||||||
/// position gets desynced since the relative move is applied multiple times.
|
|
||||||
///
|
|
||||||
/// Don't use this unless you actually got an entity update packet that all
|
|
||||||
/// other clients within render distance will get too. You usually don't need
|
|
||||||
/// this when the change isn't relative either.
|
|
||||||
pub struct RelativeEntityUpdate {
|
|
||||||
pub partial_world: Arc<RwLock<PartialInstance>>,
|
|
||||||
// a function that takes the entity and updates it
|
|
||||||
pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>,
|
|
||||||
}
|
|
||||||
impl EntityCommand for RelativeEntityUpdate {
|
|
||||||
fn apply(self, entity: Entity, world: &mut World) {
|
|
||||||
let partial_entity_infos = &mut self.partial_world.write().entity_infos;
|
|
||||||
|
|
||||||
let mut entity_mut = world.entity_mut(entity);
|
|
||||||
|
|
||||||
if Some(entity) == partial_entity_infos.owner_entity {
|
|
||||||
// if the entity owns this partial world, it's always allowed to update itself
|
|
||||||
(self.update)(&mut entity_mut);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let entity_id = *entity_mut.get::<MinecraftEntityId>().unwrap();
|
|
||||||
let Some(updates_received) = entity_mut.get_mut::<UpdatesReceived>() else {
|
|
||||||
// a client tried to update another client, which isn't allowed
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let this_client_updates_received = partial_entity_infos
|
|
||||||
.updates_received
|
|
||||||
.get(&entity_id)
|
|
||||||
.copied();
|
|
||||||
|
|
||||||
let can_update = this_client_updates_received.unwrap_or(1) == **updates_received;
|
|
||||||
if can_update {
|
|
||||||
let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
|
|
||||||
partial_entity_infos
|
|
||||||
.updates_received
|
|
||||||
.insert(entity_id, new_updates_received);
|
|
||||||
|
|
||||||
**entity_mut.get_mut::<UpdatesReceived>().unwrap() = new_updates_received;
|
|
||||||
|
|
||||||
let mut entity = world.entity_mut(entity);
|
|
||||||
(self.update)(&mut entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Things that are shared between all the partial worlds.
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
pub struct EntityInfos {
|
|
||||||
/// An index of entities by their UUIDs
|
|
||||||
pub entity_by_uuid: HashMap<Uuid, Entity>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EntityInfos {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
entity_by_uuid: HashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_entity_by_uuid(&self, uuid: &Uuid) -> Option<Entity> {
|
|
||||||
self.entity_by_uuid.get(uuid).copied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the chunk position indexes in [`EntityInfos`].
|
|
||||||
fn update_entity_chunk_positions(
|
|
||||||
mut query: Query<(Entity, &Position, &mut LastSentPosition, &InstanceName), Changed<Position>>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
) {
|
|
||||||
for (entity, pos, last_pos, world_name) in query.iter_mut() {
|
|
||||||
let world_lock = instance_container.get(world_name).unwrap();
|
|
||||||
let mut world = world_lock.write();
|
|
||||||
|
|
||||||
let old_chunk = ChunkPos::from(*last_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) = world.entities_by_chunk.get_mut(&old_chunk) {
|
|
||||||
entities.remove(&entity);
|
|
||||||
}
|
|
||||||
world
|
|
||||||
.entities_by_chunk
|
|
||||||
.entry(new_chunk)
|
|
||||||
.or_default()
|
|
||||||
.insert(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A component that lists all the local player entities that have this entity
|
|
||||||
/// loaded. If this is empty, the entity will be removed from the ECS.
|
|
||||||
#[derive(Component, Clone, Deref, DerefMut)]
|
|
||||||
pub struct LoadedBy(pub HashSet<Entity>);
|
|
||||||
|
|
||||||
/// A component that counts the number of times this entity has been modified.
|
|
||||||
/// This is used for making sure two clients don't do the same relative update
|
|
||||||
/// on an entity.
|
|
||||||
///
|
|
||||||
/// If an entity is local (i.e. it's a client/localplayer), this component
|
|
||||||
/// should NOT be present in the entity.
|
|
||||||
#[derive(Component, Debug, Deref, DerefMut)]
|
|
||||||
pub struct UpdatesReceived(u32);
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn add_updates_received(
|
|
||||||
mut commands: Commands,
|
|
||||||
query: Query<
|
|
||||||
Entity,
|
|
||||||
(
|
|
||||||
Changed<MinecraftEntityId>,
|
|
||||||
(Without<UpdatesReceived>, Without<Local>),
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
for entity in query.iter() {
|
|
||||||
// entities always start with 1 update received
|
|
||||||
commands.entity(entity).insert(UpdatesReceived(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The [`UpdatesReceived`] component should never be on [`Local`] entities.
|
|
||||||
/// This warns if an entity has both components.
|
|
||||||
fn debug_detect_updates_received_on_local_entities(
|
|
||||||
query: Query<Entity, (With<Local>, With<UpdatesReceived>)>,
|
|
||||||
) {
|
|
||||||
for entity in &query {
|
|
||||||
warn!("Entity {:?} has both Local and UpdatesReceived", entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Despawn entities that aren't being loaded by anything.
|
|
||||||
fn remove_despawned_entities_from_indexes(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut entity_infos: ResMut<EntityInfos>,
|
|
||||||
instance_container: Res<InstanceContainer>,
|
|
||||||
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();
|
|
||||||
|
|
||||||
// if the entity has no references left, despawn it
|
|
||||||
if !loaded_by.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!("Tried to remove entity from chunk {chunk:?} but the chunk was not found.");
|
|
||||||
}
|
|
||||||
// remove it from the uuid index
|
|
||||||
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();
|
|
||||||
debug!("Despawned entity {entity:?} because it was not loaded by anything.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
|
|
||||||
for mut look_direction in &mut query {
|
|
||||||
look_direction.y_rot %= 360.0;
|
|
||||||
look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for EntityInfos {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("EntityInfos").finish()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,33 +5,24 @@ mod data;
|
||||||
mod dimensions;
|
mod dimensions;
|
||||||
mod effects;
|
mod effects;
|
||||||
mod enchantments;
|
mod enchantments;
|
||||||
mod info;
|
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod mining;
|
pub mod mining;
|
||||||
mod systems;
|
mod plugin;
|
||||||
|
|
||||||
use self::{attributes::AttributeInstance, metadata::Health};
|
use self::attributes::AttributeInstance;
|
||||||
pub use attributes::Attributes;
|
pub use attributes::Attributes;
|
||||||
use azalea_block::BlockState;
|
use azalea_block::BlockState;
|
||||||
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
|
||||||
use azalea_world::{ChunkStorage, InstanceName};
|
use azalea_world::{ChunkStorage, InstanceName};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
bundle::Bundle,
|
|
||||||
component::Component,
|
|
||||||
entity::Entity,
|
|
||||||
query::Changed,
|
|
||||||
system::{Commands, Query},
|
|
||||||
};
|
|
||||||
pub use data::*;
|
pub use data::*;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
pub use dimensions::{update_bounding_box, EntityDimensions};
|
pub use dimensions::EntityDimensions;
|
||||||
pub use info::{
|
|
||||||
clamp_look_direction, EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy,
|
|
||||||
RelativeEntityUpdate,
|
|
||||||
};
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use crate::plugin::*;
|
||||||
|
|
||||||
pub fn move_relative(
|
pub fn move_relative(
|
||||||
physics: &mut Physics,
|
physics: &mut Physics,
|
||||||
direction: &LookDirection,
|
direction: &LookDirection,
|
||||||
|
@ -234,21 +225,6 @@ pub struct Physics {
|
||||||
#[derive(Component, Copy, Clone, Default)]
|
#[derive(Component, Copy, Clone, Default)]
|
||||||
pub struct Dead;
|
pub struct Dead;
|
||||||
|
|
||||||
/// System that adds the [`Dead`] marker component if an entity's health is set
|
|
||||||
/// to 0 (or less than 0). This will be present if an entity is doing the death
|
|
||||||
/// animation.
|
|
||||||
///
|
|
||||||
/// Entities that are dead can not be revived.
|
|
||||||
/// TODO: fact check this in-game by setting an entity's health to 0 and then
|
|
||||||
/// not 0
|
|
||||||
pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed<Health>>) {
|
|
||||||
for (entity, health) in query.iter() {
|
|
||||||
if **health <= 0.0 {
|
|
||||||
commands.entity(entity).insert(Dead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A component that contains the offset of the entity's eyes from the entity
|
/// A component that contains the offset of the entity's eyes from the entity
|
||||||
/// coordinates.
|
/// coordinates.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,9 +1,51 @@
|
||||||
use azalea_core::{BlockPos, Vec3};
|
//! Stuff related to entity indexes and keeping track of entities in the world.
|
||||||
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
|
||||||
use bevy_ecs::prelude::*;
|
|
||||||
use log::{debug, error, info};
|
|
||||||
|
|
||||||
use crate::{EntityInfos, EntityUuid, EyeHeight, FluidOnEyes, LoadedBy, Local, Position};
|
use azalea_core::ChunkPos;
|
||||||
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||||
|
use bevy_ecs::{
|
||||||
|
entity::Entity,
|
||||||
|
query::{Changed, With, Without},
|
||||||
|
system::{Commands, Query, Res, ResMut, Resource},
|
||||||
|
};
|
||||||
|
use log::{debug, error, info, warn};
|
||||||
|
use std::{collections::HashMap, fmt::Debug};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{EntityUuid, LastSentPosition, Local, Position};
|
||||||
|
|
||||||
|
use super::LoadedBy;
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct EntityUuidIndex {
|
||||||
|
/// An index of entities by their UUIDs
|
||||||
|
entity_by_uuid: HashMap<Uuid, Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityUuidIndex {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entity_by_uuid: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, uuid: &Uuid) -> Option<Entity> {
|
||||||
|
self.entity_by_uuid.get(uuid).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_key(&self, uuid: &Uuid) -> bool {
|
||||||
|
self.entity_by_uuid.contains_key(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, uuid: Uuid, entity: Entity) {
|
||||||
|
self.entity_by_uuid.insert(uuid, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for EntityUuidIndex {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("EntityUuidIndex").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove new entities that have the same id as an existing entity, and
|
/// Remove new entities that have the same id as an existing entity, and
|
||||||
/// increase the reference counts.
|
/// increase the reference counts.
|
||||||
|
@ -89,7 +131,7 @@ pub fn deduplicate_local_entities(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_uuid_index(
|
pub fn update_uuid_index(
|
||||||
mut entity_infos: ResMut<EntityInfos>,
|
mut entity_infos: ResMut<EntityUuidIndex>,
|
||||||
query: Query<(Entity, &EntityUuid, Option<&Local>), Changed<EntityUuid>>,
|
query: Query<(Entity, &EntityUuid, Option<&Local>), Changed<EntityUuid>>,
|
||||||
) {
|
) {
|
||||||
for (entity, &uuid, local) in query.iter() {
|
for (entity, &uuid, local) in query.iter() {
|
||||||
|
@ -108,29 +150,6 @@ pub fn update_uuid_index(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Clear all entities in a chunk. This will not clear them from the
|
|
||||||
// /// shared storage unless there are no other references to them.
|
|
||||||
// pub fn clear_entities_in_chunk(
|
|
||||||
// mut commands: Commands,
|
|
||||||
// partial_entity_infos: &mut PartialEntityInfos,
|
|
||||||
// chunk: &ChunkPos,
|
|
||||||
// instance_container: &WorldContainer,
|
|
||||||
// world_name: &InstanceName,
|
|
||||||
// mut query: Query<(&MinecraftEntityId, &mut ReferenceCount)>,
|
|
||||||
// ) { let world_lock = instance_container.get(world_name).unwrap(); let world =
|
|
||||||
// world_lock.read();
|
|
||||||
|
|
||||||
// if let Some(entities) = world.entities_by_chunk.get(chunk).cloned() {
|
|
||||||
// for &entity in &entities {
|
|
||||||
// let (id, mut reference_count) = query.get_mut(entity).unwrap();
|
|
||||||
// if partial_entity_infos.loaded_entity_ids.remove(id) {
|
|
||||||
// // decrease the reference count
|
|
||||||
// **reference_count -= 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// System to keep the entity_by_id index up-to-date.
|
/// System to keep the entity_by_id index up-to-date.
|
||||||
pub fn update_entity_by_id_index(
|
pub fn update_entity_by_id_index(
|
||||||
mut query: Query<
|
mut query: Query<
|
||||||
|
@ -156,26 +175,69 @@ pub fn update_entity_by_id_index(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_fluid_on_eyes(
|
/// Update the chunk position indexes in [`EntityUuidIndex`].
|
||||||
mut query: Query<(&mut FluidOnEyes, &Position, &EyeHeight, &InstanceName)>,
|
pub fn update_entity_chunk_positions(
|
||||||
|
mut query: Query<(Entity, &Position, &mut LastSentPosition, &InstanceName), Changed<Position>>,
|
||||||
instance_container: Res<InstanceContainer>,
|
instance_container: Res<InstanceContainer>,
|
||||||
) {
|
) {
|
||||||
for (mut fluid_on_eyes, position, eye_height, instance_name) in query.iter_mut() {
|
for (entity, pos, last_pos, world_name) in query.iter_mut() {
|
||||||
let Some(instance) = instance_container.get(instance_name) else {
|
let world_lock = instance_container.get(world_name).unwrap();
|
||||||
continue;
|
let mut world = world_lock.write();
|
||||||
};
|
|
||||||
|
|
||||||
let adjusted_eye_y = position.y + (**eye_height as f64) - 0.1111111119389534;
|
let old_chunk = ChunkPos::from(*last_pos);
|
||||||
let eye_block_pos = BlockPos::from(Vec3::new(position.x, adjusted_eye_y, position.z));
|
let new_chunk = ChunkPos::from(*pos);
|
||||||
let fluid_at_eye = instance
|
|
||||||
.read()
|
if old_chunk != new_chunk {
|
||||||
.get_fluid_state(&eye_block_pos)
|
// move the entity from the old chunk to the new one
|
||||||
.unwrap_or_default();
|
if let Some(entities) = world.entities_by_chunk.get_mut(&old_chunk) {
|
||||||
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64);
|
entities.remove(&entity);
|
||||||
if fluid_cutoff_y > adjusted_eye_y {
|
}
|
||||||
**fluid_on_eyes = fluid_at_eye.fluid;
|
world
|
||||||
} else {
|
.entities_by_chunk
|
||||||
**fluid_on_eyes = azalea_registry::Fluid::Empty;
|
.entry(new_chunk)
|
||||||
|
.or_default()
|
||||||
|
.insert(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Despawn entities that aren't being loaded by anything.
|
||||||
|
pub fn remove_despawned_entities_from_indexes(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut entity_infos: ResMut<EntityUuidIndex>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
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();
|
||||||
|
|
||||||
|
// if the entity has no references left, despawn it
|
||||||
|
if !loaded_by.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Tried to remove entity from chunk {chunk:?} but the entity was not there.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Tried to remove entity from chunk {chunk:?} but the chunk was not found.");
|
||||||
|
}
|
||||||
|
// remove it from the uuid index
|
||||||
|
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();
|
||||||
|
debug!("Despawned entity {entity:?} because it was not loaded by anything.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
147
azalea-entity/src/plugin/mod.rs
Normal file
147
azalea-entity/src/plugin/mod.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
pub mod indexing;
|
||||||
|
mod relative_updates;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use azalea_core::{BlockPos, Vec3};
|
||||||
|
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
|
||||||
|
use bevy_app::{App, Plugin, PostUpdate, PreUpdate, Update};
|
||||||
|
use bevy_ecs::prelude::*;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
metadata::Health, Dead, EyeHeight, FluidOnEyes, Local, LookDirection, Physics, Position,
|
||||||
|
};
|
||||||
|
|
||||||
|
use indexing::EntityUuidIndex;
|
||||||
|
pub use relative_updates::RelativeEntityUpdate;
|
||||||
|
|
||||||
|
/// A Bevy [`SystemSet`] for various types of entity updates.
|
||||||
|
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
|
||||||
|
pub enum EntityUpdateSet {
|
||||||
|
/// Remove ECS entities that refer to an entity that was already in the ECS
|
||||||
|
/// before.
|
||||||
|
Deduplicate,
|
||||||
|
/// Create search indexes for entities.
|
||||||
|
Index,
|
||||||
|
/// Remove despawned entities from search indexes.
|
||||||
|
Deindex,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plugin handling some basic entity functionality.
|
||||||
|
pub struct EntityPlugin;
|
||||||
|
impl Plugin for EntityPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
// entities get added pre-update
|
||||||
|
// added to indexes during update (done by this plugin)
|
||||||
|
// modified during update
|
||||||
|
// despawned post-update (done by this plugin)
|
||||||
|
app.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
indexing::remove_despawned_entities_from_indexes.in_set(EntityUpdateSet::Deindex),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
(
|
||||||
|
indexing::deduplicate_entities,
|
||||||
|
indexing::deduplicate_local_entities,
|
||||||
|
)
|
||||||
|
.in_set(EntityUpdateSet::Deduplicate),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
indexing::update_entity_chunk_positions,
|
||||||
|
indexing::update_uuid_index,
|
||||||
|
indexing::update_entity_by_id_index,
|
||||||
|
)
|
||||||
|
.in_set(EntityUpdateSet::Index),
|
||||||
|
(
|
||||||
|
relative_updates::add_updates_received,
|
||||||
|
relative_updates::debug_detect_updates_received_on_local_entities,
|
||||||
|
debug_new_entity,
|
||||||
|
add_dead,
|
||||||
|
update_bounding_box,
|
||||||
|
clamp_look_direction,
|
||||||
|
update_fluid_on_eyes,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.init_resource::<EntityUuidIndex>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_new_entity(query: Query<(Entity, Option<&Local>), Added<MinecraftEntityId>>) {
|
||||||
|
for (entity, local) in query.iter() {
|
||||||
|
if local.is_some() {
|
||||||
|
debug!("new local entity: {:?}", entity);
|
||||||
|
} else {
|
||||||
|
debug!("new entity: {:?}", entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System that adds the [`Dead`] marker component if an entity's health is set
|
||||||
|
/// to 0 (or less than 0). This will be present if an entity is doing the death
|
||||||
|
/// animation.
|
||||||
|
///
|
||||||
|
/// Entities that are dead can not be revived.
|
||||||
|
/// TODO: fact check this in-game by setting an entity's health to 0 and then
|
||||||
|
/// not 0
|
||||||
|
pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed<Health>>) {
|
||||||
|
for (entity, health) in query.iter() {
|
||||||
|
if **health <= 0.0 {
|
||||||
|
commands.entity(entity).insert(Dead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_fluid_on_eyes(
|
||||||
|
mut query: Query<(&mut FluidOnEyes, &Position, &EyeHeight, &InstanceName)>,
|
||||||
|
instance_container: Res<InstanceContainer>,
|
||||||
|
) {
|
||||||
|
for (mut fluid_on_eyes, position, eye_height, instance_name) in query.iter_mut() {
|
||||||
|
let Some(instance) = instance_container.get(instance_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let adjusted_eye_y = position.y + (**eye_height as f64) - 0.1111111119389534;
|
||||||
|
let eye_block_pos = BlockPos::from(Vec3::new(position.x, adjusted_eye_y, position.z));
|
||||||
|
let fluid_at_eye = instance
|
||||||
|
.read()
|
||||||
|
.get_fluid_state(&eye_block_pos)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let fluid_cutoff_y = eye_block_pos.y as f64 + (fluid_at_eye.height as f64 / 16f64);
|
||||||
|
if fluid_cutoff_y > adjusted_eye_y {
|
||||||
|
**fluid_on_eyes = fluid_at_eye.fluid;
|
||||||
|
} else {
|
||||||
|
**fluid_on_eyes = azalea_registry::Fluid::Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that lists all the local player entities that have this entity
|
||||||
|
/// loaded. If this is empty, the entity will be removed from the ECS.
|
||||||
|
#[derive(Component, Clone, Deref, DerefMut)]
|
||||||
|
pub struct LoadedBy(pub HashSet<Entity>);
|
||||||
|
|
||||||
|
pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
|
||||||
|
for mut look_direction in &mut query {
|
||||||
|
look_direction.y_rot %= 360.0;
|
||||||
|
look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the position of the entity. This doesn't update the cache in
|
||||||
|
/// azalea-world, and should only be used within azalea-world!
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Cached position in the world must be updated.
|
||||||
|
pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
|
||||||
|
for (position, mut physics) in query.iter_mut() {
|
||||||
|
let bounding_box = physics.dimensions.make_bounding_box(position);
|
||||||
|
physics.bounding_box = bounding_box;
|
||||||
|
}
|
||||||
|
}
|
122
azalea-entity/src/plugin/relative_updates.rs
Normal file
122
azalea-entity/src/plugin/relative_updates.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// How entity updates are processed (to avoid issues with shared worlds)
|
||||||
|
// - each bot contains a map of { entity id: updates received }
|
||||||
|
// - the shared world also contains a canonical "true" updates received for each
|
||||||
|
// entity
|
||||||
|
// - when a client loads an entity, its "updates received" is set to the same as
|
||||||
|
// the global "updates received"
|
||||||
|
// - when the shared world sees an entity for the first time, the "updates
|
||||||
|
// received" is set to 1.
|
||||||
|
// - clients can force the shared "updates received" to 0 to make it so certain
|
||||||
|
// entities (i.e. other bots in our swarm) don't get confused and updated by
|
||||||
|
// other bots
|
||||||
|
// - when a client gets an update to an entity, we check if our "updates
|
||||||
|
// received" is the same as the shared world's "updates received": if it is,
|
||||||
|
// then process the update and increment the client's and shared world's
|
||||||
|
// "updates received" if not, then we simply increment our local "updates
|
||||||
|
// received" and do nothing else
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use azalea_world::{MinecraftEntityId, PartialInstance};
|
||||||
|
use bevy_ecs::{
|
||||||
|
prelude::{Component, Entity},
|
||||||
|
query::{Changed, With, Without},
|
||||||
|
system::{Commands, EntityCommand, Query},
|
||||||
|
world::{EntityMut, World},
|
||||||
|
};
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use log::warn;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
use crate::Local;
|
||||||
|
|
||||||
|
/// An [`EntityCommand`] that applies a "relative update" to an entity, which
|
||||||
|
/// means this update won't be run multiple times by different clients in the
|
||||||
|
/// same world.
|
||||||
|
///
|
||||||
|
/// This is used to avoid a bug where when there's multiple clients in the same
|
||||||
|
/// world and an entity sends a relative move packet to all clients, its
|
||||||
|
/// position gets desynced since the relative move is applied multiple times.
|
||||||
|
///
|
||||||
|
/// Don't use this unless you actually got an entity update packet that all
|
||||||
|
/// other clients within render distance will get too. You usually don't need
|
||||||
|
/// this when the change isn't relative either.
|
||||||
|
pub struct RelativeEntityUpdate {
|
||||||
|
pub partial_world: Arc<RwLock<PartialInstance>>,
|
||||||
|
// a function that takes the entity and updates it
|
||||||
|
pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component that counts the number of times this entity has been modified.
|
||||||
|
/// This is used for making sure two clients don't do the same relative update
|
||||||
|
/// on an entity.
|
||||||
|
///
|
||||||
|
/// If an entity is local (i.e. it's a client/localplayer), this component
|
||||||
|
/// should NOT be present in the entity.
|
||||||
|
#[derive(Component, Debug, Deref, DerefMut)]
|
||||||
|
pub struct UpdatesReceived(u32);
|
||||||
|
|
||||||
|
impl EntityCommand for RelativeEntityUpdate {
|
||||||
|
fn apply(self, entity: Entity, world: &mut World) {
|
||||||
|
let partial_entity_infos = &mut self.partial_world.write().entity_infos;
|
||||||
|
|
||||||
|
let mut entity_mut = world.entity_mut(entity);
|
||||||
|
|
||||||
|
if Some(entity) == partial_entity_infos.owner_entity {
|
||||||
|
// if the entity owns this partial world, it's always allowed to update itself
|
||||||
|
(self.update)(&mut entity_mut);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entity_id = *entity_mut.get::<MinecraftEntityId>().unwrap();
|
||||||
|
let Some(updates_received) = entity_mut.get_mut::<UpdatesReceived>() else {
|
||||||
|
// a client tried to update another client, which isn't allowed
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let this_client_updates_received = partial_entity_infos
|
||||||
|
.updates_received
|
||||||
|
.get(&entity_id)
|
||||||
|
.copied();
|
||||||
|
|
||||||
|
let can_update = this_client_updates_received.unwrap_or(1) == **updates_received;
|
||||||
|
if can_update {
|
||||||
|
let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
|
||||||
|
partial_entity_infos
|
||||||
|
.updates_received
|
||||||
|
.insert(entity_id, new_updates_received);
|
||||||
|
|
||||||
|
**entity_mut.get_mut::<UpdatesReceived>().unwrap() = new_updates_received;
|
||||||
|
|
||||||
|
let mut entity = world.entity_mut(entity);
|
||||||
|
(self.update)(&mut entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn add_updates_received(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<
|
||||||
|
Entity,
|
||||||
|
(
|
||||||
|
Changed<MinecraftEntityId>,
|
||||||
|
(Without<UpdatesReceived>, Without<Local>),
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for entity in query.iter() {
|
||||||
|
// entities always start with 1 update received
|
||||||
|
commands.entity(entity).insert(UpdatesReceived(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`UpdatesReceived`] component should never be on [`Local`] entities.
|
||||||
|
/// This warns if an entity has both components.
|
||||||
|
pub fn debug_detect_updates_received_on_local_entities(
|
||||||
|
query: Query<Entity, (With<Local>, With<UpdatesReceived>)>,
|
||||||
|
) {
|
||||||
|
for entity in &query {
|
||||||
|
warn!("Entity {:?} has both Local and UpdatesReceived", entity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,9 @@ pub mod collision;
|
||||||
|
|
||||||
use azalea_block::{Block, BlockState};
|
use azalea_block::{Block, BlockState};
|
||||||
use azalea_core::{BlockPos, Vec3};
|
use azalea_core::{BlockPos, Vec3};
|
||||||
use azalea_entity::update_bounding_box;
|
|
||||||
use azalea_entity::{
|
use azalea_entity::{
|
||||||
clamp_look_direction, metadata::Sprinting, move_relative, Attributes, Jumping, Local,
|
metadata::Sprinting, move_relative, Attributes, Jumping, Local, LookDirection, Physics,
|
||||||
LookDirection, Physics, Position,
|
Position,
|
||||||
};
|
};
|
||||||
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
use azalea_world::{Instance, InstanceContainer, InstanceName};
|
||||||
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
use bevy_app::{App, FixedUpdate, Plugin, Update};
|
||||||
|
@ -34,8 +33,8 @@ impl Plugin for PhysicsPlugin {
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
force_jump_listener
|
force_jump_listener
|
||||||
.before(update_bounding_box)
|
.before(azalea_entity::update_bounding_box)
|
||||||
.after(clamp_look_direction),
|
.after(azalea_entity::clamp_look_direction),
|
||||||
)
|
)
|
||||||
.add_systems(FixedUpdate, (ai_step, travel).chain().in_set(PhysicsSet));
|
.add_systems(FixedUpdate, (ai_step, travel).chain().in_set(PhysicsSet));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub struct InstanceContainer {
|
||||||
// cases where we'd want to get every entity in the world (just getting the entities in chunks
|
// cases where we'd want to get every entity in the world (just getting the entities in chunks
|
||||||
// should work fine).
|
// should work fine).
|
||||||
|
|
||||||
// Entities are garbage collected (by manual reference counting in EntityInfos) so we don't
|
// Entities are garbage collected (by manual reference counting in EntityUuidIndex) so we don't
|
||||||
// need to worry about them here.
|
// need to worry about them here.
|
||||||
|
|
||||||
// If it looks like we're relying on the server giving us unique world names, that's because we
|
// If it looks like we're relying on the server giving us unique world names, that's because we
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub struct PartialEntityInfos {
|
||||||
// note: using MinecraftEntityId for entity ids is acceptable here since
|
// note: using MinecraftEntityId for entity ids is acceptable here since
|
||||||
// there's no chance of collisions here
|
// there's no chance of collisions here
|
||||||
/// The entity id of the player that owns this partial world. This will
|
/// The entity id of the player that owns this partial world. This will
|
||||||
/// make `RelativeEntityUpdate` pretend the entity doesn't exist so
|
/// make `RelativeEntityUpdate` pretend this entity doesn't exist so
|
||||||
/// it doesn't get modified from outside sources.
|
/// it doesn't get modified from outside sources.
|
||||||
pub owner_entity: Option<Entity>,
|
pub owner_entity: Option<Entity>,
|
||||||
/// A counter for each entity that tracks how many updates we've observed
|
/// A counter for each entity that tracks how many updates we've observed
|
||||||
|
|
|
@ -106,6 +106,6 @@ impl<W: PartialOrd + Debug> Ord for Weight<W> {
|
||||||
impl<W: PartialOrd + Debug> Eq for Weight<W> {}
|
impl<W: PartialOrd + Debug> Eq for Weight<W> {}
|
||||||
impl<W: PartialOrd + Debug> PartialOrd for Weight<W> {
|
impl<W: PartialOrd + Debug> PartialOrd for Weight<W> {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
self.0.partial_cmp(&other.0)
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue