pub mod indexing;
mod relative_updates;
use std::collections::HashSet;
use azalea_block::BlockState;
use azalea_core::position::{BlockPos, ChunkPos, Vec3};
use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
use bevy_app::{App, Plugin, PreUpdate, Update};
use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
use indexing::EntityUuidIndex;
pub use relative_updates::RelativeEntityUpdate;
use tracing::debug;
use crate::{
metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable,
Physics, Position,
};
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
pub enum EntityUpdateSet {
Index,
Deindex,
}
pub struct EntityPlugin;
impl Plugin for EntityPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PreUpdate,
indexing::remove_despawned_entities_from_indexes.in_set(EntityUpdateSet::Deindex),
)
.add_systems(
Update,
(
(
indexing::update_entity_chunk_positions,
indexing::insert_entity_chunk_position,
)
.chain()
.in_set(EntityUpdateSet::Index),
(
relative_updates::debug_detect_updates_received_on_local_entities,
debug_new_entity,
add_dead,
clamp_look_direction,
update_fluid_on_eyes,
update_on_climbable,
),
),
)
.add_systems(Update, update_bounding_box)
.add_systems(PreUpdate, update_in_loaded_chunk)
.init_resource::<EntityUuidIndex>();
}
}
fn debug_new_entity(query: Query<(Entity, Option<&LocalEntity>), Added<MinecraftEntityId>>) {
for (entity, local) in query.iter() {
if local.is_some() {
debug!("new local entity: {:?}", entity);
} else {
debug!("new entity: {:?}", entity);
}
}
}
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;
}
}
}
pub fn update_on_climbable(
mut query: Query<(&mut OnClimbable, &Position, &InstanceName)>,
instance_container: Res<InstanceContainer>,
) {
for (mut on_climbable, position, instance_name) in query.iter_mut() {
let Some(instance) = instance_container.get(instance_name) else {
continue;
};
let instance = instance.read();
let block_pos = BlockPos::from(position);
let block_state_at_feet = instance.get_block_state(&block_pos).unwrap_or_default();
let block_at_feet = Box::<dyn azalea_block::Block>::from(block_state_at_feet);
let registry_block_at_feet = block_at_feet.as_registry_block();
**on_climbable = azalea_registry::tags::blocks::CLIMBABLE.contains(®istry_block_at_feet)
|| (azalea_registry::tags::blocks::TRAPDOORS.contains(®istry_block_at_feet)
&& is_trapdoor_useable_as_ladder(block_state_at_feet, block_pos, &instance));
}
}
fn is_trapdoor_useable_as_ladder(
block_state: BlockState,
block_pos: BlockPos,
instance: &azalea_world::Instance,
) -> bool {
if !block_state
.property::<azalea_block::properties::Open>()
.unwrap_or_default()
{
return false;
}
let block_below = instance
.get_block_state(&block_pos.down(1))
.unwrap_or_default();
let registry_block_below =
Box::<dyn azalea_block::Block>::from(block_below).as_registry_block();
if registry_block_below != azalea_registry::Block::Ladder {
return false;
}
let ladder_facing = block_below
.property::<azalea_block::properties::Facing>()
.expect("ladder block must have facing property");
let trapdoor_facing = block_state
.property::<azalea_block::properties::Facing>()
.expect("trapdoor block must have facing property");
if ladder_facing != trapdoor_facing {
return false;
}
true
}
#[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 = look_direction.y_rot.rem_euclid(360.0);
look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
}
}
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;
}
}
#[derive(Component, Clone, Debug, Copy)]
pub struct InLoadedChunk;
pub fn update_in_loaded_chunk(
mut commands: bevy_ecs::system::Commands,
query: Query<(Entity, &InstanceName, &Position)>,
instance_container: Res<InstanceContainer>,
) {
for (entity, instance_name, position) in &query {
let player_chunk_pos = ChunkPos::from(position);
let Some(instance_lock) = instance_container.get(instance_name) else {
continue;
};
let in_loaded_chunk = instance_lock.read().chunks.get(&player_chunk_pos).is_some();
if in_loaded_chunk {
commands.entity(entity).insert(InLoadedChunk);
} else {
commands.entity(entity).remove::<InLoadedChunk>();
}
}
}