1pub mod indexing;
2mod relative_updates;
3
4use std::collections::HashSet;
5
6use azalea_block::{fluid_state::FluidKind, BlockState};
7use azalea_core::{
8 position::{BlockPos, ChunkPos, Vec3},
9 tick::GameTick,
10};
11use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId};
12use bevy_app::{App, Plugin, PreUpdate, Update};
13use bevy_ecs::prelude::*;
14use derive_more::{Deref, DerefMut};
15use indexing::EntityUuidIndex;
16pub use relative_updates::RelativeEntityUpdate;
17use tracing::debug;
18
19use crate::{
20 metadata::Health, Dead, EyeHeight, FluidOnEyes, LocalEntity, LookDirection, OnClimbable,
21 Physics, Position,
22};
23
24#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
26pub enum EntityUpdateSet {
27 Index,
29 Deindex,
31}
32
33pub struct EntityPlugin;
35impl Plugin for EntityPlugin {
36 fn build(&self, app: &mut App) {
37 app.add_systems(
42 PreUpdate,
43 indexing::remove_despawned_entities_from_indexes.in_set(EntityUpdateSet::Deindex),
44 )
45 .add_systems(
46 Update,
47 (
48 (
49 indexing::update_entity_chunk_positions,
50 indexing::insert_entity_chunk_position,
51 )
52 .chain()
53 .in_set(EntityUpdateSet::Index),
54 (
55 relative_updates::debug_detect_updates_received_on_local_entities,
56 debug_new_entity,
57 add_dead,
58 clamp_look_direction,
59 update_fluid_on_eyes,
60 update_on_climbable,
61 ),
62 ),
63 )
64 .add_systems(Update, update_bounding_box)
65 .add_systems(GameTick, update_in_loaded_chunk)
66 .init_resource::<EntityUuidIndex>();
67 }
68}
69
70fn debug_new_entity(query: Query<(Entity, Option<&LocalEntity>), Added<MinecraftEntityId>>) {
71 for (entity, local) in query.iter() {
72 if local.is_some() {
73 debug!("new local entity: {:?}", entity);
74 } else {
75 debug!("new entity: {:?}", entity);
76 }
77 }
78}
79
80pub fn add_dead(mut commands: Commands, query: Query<(Entity, &Health), Changed<Health>>) {
86 for (entity, health) in query.iter() {
87 if **health <= 0.0 {
88 commands.entity(entity).insert(Dead);
89 }
90 }
91}
92
93pub fn update_fluid_on_eyes(
94 mut query: Query<(&mut FluidOnEyes, &Position, &EyeHeight, &InstanceName)>,
95 instance_container: Res<InstanceContainer>,
96) {
97 for (mut fluid_on_eyes, position, eye_height, instance_name) in query.iter_mut() {
98 let Some(instance) = instance_container.get(instance_name) else {
99 continue;
100 };
101
102 let adjusted_eye_y = position.y + (**eye_height as f64) - 0.1111111119389534;
103 let eye_block_pos = BlockPos::from(Vec3::new(position.x, adjusted_eye_y, position.z));
104 let fluid_at_eye = instance
105 .read()
106 .get_fluid_state(&eye_block_pos)
107 .unwrap_or_default();
108 let fluid_cutoff_y = (eye_block_pos.y as f32 + fluid_at_eye.height()) as f64;
109 if fluid_cutoff_y > adjusted_eye_y {
110 **fluid_on_eyes = fluid_at_eye.kind;
111 } else {
112 **fluid_on_eyes = FluidKind::Empty;
113 }
114 }
115}
116
117pub fn update_on_climbable(
118 mut query: Query<(&mut OnClimbable, &Position, &InstanceName)>,
119 instance_container: Res<InstanceContainer>,
120) {
121 for (mut on_climbable, position, instance_name) in query.iter_mut() {
122 let Some(instance) = instance_container.get(instance_name) else {
131 continue;
132 };
133
134 let instance = instance.read();
135
136 let block_pos = BlockPos::from(position);
137 let block_state_at_feet = instance.get_block_state(&block_pos).unwrap_or_default();
138 let block_at_feet = Box::<dyn azalea_block::Block>::from(block_state_at_feet);
139 let registry_block_at_feet = block_at_feet.as_registry_block();
140
141 **on_climbable = azalea_registry::tags::blocks::CLIMBABLE.contains(®istry_block_at_feet)
142 || (azalea_registry::tags::blocks::TRAPDOORS.contains(®istry_block_at_feet)
143 && is_trapdoor_useable_as_ladder(block_state_at_feet, block_pos, &instance));
144 }
145}
146
147fn is_trapdoor_useable_as_ladder(
148 block_state: BlockState,
149 block_pos: BlockPos,
150 instance: &azalea_world::Instance,
151) -> bool {
152 if !block_state
154 .property::<azalea_block::properties::Open>()
155 .unwrap_or_default()
156 {
157 return false;
158 }
159
160 let block_below = instance
162 .get_block_state(&block_pos.down(1))
163 .unwrap_or_default();
164 let registry_block_below =
165 Box::<dyn azalea_block::Block>::from(block_below).as_registry_block();
166 if registry_block_below != azalea_registry::Block::Ladder {
167 return false;
168 }
169 let ladder_facing = block_below
171 .property::<azalea_block::properties::Facing>()
172 .expect("ladder block must have facing property");
173 let trapdoor_facing = block_state
174 .property::<azalea_block::properties::Facing>()
175 .expect("trapdoor block must have facing property");
176 if ladder_facing != trapdoor_facing {
177 return false;
178 }
179
180 true
181}
182
183#[derive(Component, Clone, Deref, DerefMut)]
186pub struct LoadedBy(pub HashSet<Entity>);
187
188pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
189 for mut look_direction in &mut query {
190 look_direction.y_rot = look_direction.y_rot.rem_euclid(360.0);
191 look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
192 }
193}
194
195pub fn update_bounding_box(mut query: Query<(&Position, &mut Physics), Changed<Position>>) {
201 for (position, mut physics) in query.iter_mut() {
202 let bounding_box = physics.dimensions.make_bounding_box(position);
203 physics.bounding_box = bounding_box;
204 }
205}
206
207#[derive(Component, Clone, Debug, Copy)]
210pub struct InLoadedChunk;
211
212pub fn update_in_loaded_chunk(
214 mut commands: bevy_ecs::system::Commands,
215 query: Query<(Entity, &InstanceName, &Position)>,
216 instance_container: Res<InstanceContainer>,
217) {
218 for (entity, instance_name, position) in &query {
219 let player_chunk_pos = ChunkPos::from(position);
220 let Some(instance_lock) = instance_container.get(instance_name) else {
221 commands.entity(entity).remove::<InLoadedChunk>();
222 continue;
223 };
224
225 let in_loaded_chunk = instance_lock.read().chunks.get(&player_chunk_pos).is_some();
226 if in_loaded_chunk {
227 commands.entity(entity).insert(InLoadedChunk);
228 } else {
229 commands.entity(entity).remove::<InLoadedChunk>();
230 }
231 }
232}