azalea_entity/
lib.rs

1#![allow(clippy::derived_hash_with_manual_eq)]
2
3pub mod attributes;
4mod data;
5mod dimensions;
6mod effects;
7mod enchantments;
8pub mod metadata;
9pub mod mining;
10pub mod particle;
11mod plugin;
12pub mod vec_delta_codec;
13
14use std::{
15    fmt::{self, Debug},
16    hash::{Hash, Hasher},
17};
18
19pub use attributes::Attributes;
20use azalea_block::{BlockState, fluid_state::FluidKind};
21use azalea_buf::AzBuf;
22use azalea_core::{
23    aabb::AABB,
24    math,
25    position::{BlockPos, ChunkPos, Vec3},
26    resource_location::ResourceLocation,
27};
28use azalea_registry::EntityKind;
29use azalea_world::{ChunkStorage, InstanceName};
30use bevy_ecs::{bundle::Bundle, component::Component};
31pub use data::*;
32use derive_more::{Deref, DerefMut};
33pub use dimensions::EntityDimensions;
34use plugin::indexing::EntityChunkPos;
35use uuid::Uuid;
36use vec_delta_codec::VecDeltaCodec;
37
38use self::attributes::AttributeInstance;
39pub use crate::plugin::*;
40
41pub fn move_relative(
42    physics: &mut Physics,
43    direction: LookDirection,
44    speed: f32,
45    acceleration: Vec3,
46) {
47    let input_vector = input_vector(direction, speed, acceleration);
48    physics.velocity += input_vector;
49}
50
51pub fn input_vector(direction: LookDirection, speed: f32, acceleration: Vec3) -> Vec3 {
52    let distance = acceleration.length_squared();
53    if distance < 1.0E-7 {
54        return Vec3::ZERO;
55    }
56    let acceleration = if distance > 1.0 {
57        acceleration.normalize()
58    } else {
59        acceleration
60    }
61    .scale(speed as f64);
62    let y_rot = math::sin(direction.y_rot * 0.017453292f32);
63    let x_rot = math::cos(direction.y_rot * 0.017453292f32);
64    Vec3 {
65        x: acceleration.x * (x_rot as f64) - acceleration.z * (y_rot as f64),
66        y: acceleration.y,
67        z: acceleration.z * (x_rot as f64) + acceleration.x * (y_rot as f64),
68    }
69}
70
71pub fn view_vector(look_direction: LookDirection) -> Vec3 {
72    let x_rot = look_direction.x_rot * 0.017453292;
73    let y_rot = -look_direction.y_rot * 0.017453292;
74    let y_rot_cos = math::cos(y_rot);
75    let y_rot_sin = math::sin(y_rot);
76    let x_rot_cos = math::cos(x_rot);
77    let x_rot_sin = math::sin(x_rot);
78    Vec3 {
79        x: (y_rot_sin * x_rot_cos) as f64,
80        y: (-x_rot_sin) as f64,
81        z: (y_rot_cos * x_rot_cos) as f64,
82    }
83}
84
85/// Get the position of the block below the entity, but a little lower.
86pub fn on_pos_legacy(chunk_storage: &ChunkStorage, position: Position) -> BlockPos {
87    on_pos(0.2, chunk_storage, position)
88}
89
90// int x = Mth.floor(this.position.x);
91// int y = Mth.floor(this.position.y - (double)var1);
92// int z = Mth.floor(this.position.z);
93// BlockPos var5 = new BlockPos(x, y, z);
94// if (this.level.getBlockState(var5).isAir()) {
95//    BlockPos var6 = var5.below();
96//    BlockState var7 = this.level.getBlockState(var6);
97//    if (var7.is(BlockTags.FENCES) || var7.is(BlockTags.WALLS) ||
98// var7.getBlock() instanceof FenceGateBlock) {       return var6;
99//    }
100// }
101// return var5;
102pub fn on_pos(offset: f32, chunk_storage: &ChunkStorage, pos: Position) -> BlockPos {
103    let x = pos.x.floor() as i32;
104    let y = (pos.y - offset as f64).floor() as i32;
105    let z = pos.z.floor() as i32;
106    let pos = BlockPos { x, y, z };
107
108    // TODO: check if block below is a fence, wall, or fence gate
109    let block_pos = pos.down(1);
110    let block_state = chunk_storage.get_block_state(block_pos);
111    if block_state == Some(BlockState::AIR) {
112        let block_pos_below = block_pos.down(1);
113        let block_state_below = chunk_storage.get_block_state(block_pos_below);
114        if let Some(_block_state_below) = block_state_below {
115            // if block_state_below.is_fence()
116            //     || block_state_below.is_wall()
117            //     || block_state_below.is_fence_gate()
118            // {
119            //     return block_pos_below;
120            // }
121        }
122    }
123
124    pos
125}
126
127/// The Minecraft UUID of the entity. For players, this is their actual player
128/// UUID, and for other entities it's just random.
129#[derive(Component, Deref, DerefMut, Clone, Copy, Default)]
130pub struct EntityUuid(Uuid);
131impl EntityUuid {
132    pub fn new(uuid: Uuid) -> Self {
133        Self(uuid)
134    }
135}
136impl Debug for EntityUuid {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        (self.0).fmt(f)
139    }
140}
141
142/// The position of the entity right now.
143///
144/// You are free to change the value of this component; there's systems that
145/// update the indexes automatically.
146///
147/// Its value is set to a default of [`Vec3::ZERO`] when it receives the login
148/// packet, its true position may be set ticks later.
149#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
150pub struct Position(Vec3);
151impl Position {
152    pub fn new(pos: Vec3) -> Self {
153        Self(pos)
154    }
155}
156impl From<&Position> for Vec3 {
157    fn from(value: &Position) -> Self {
158        value.0
159    }
160}
161impl From<Position> for ChunkPos {
162    fn from(value: Position) -> Self {
163        ChunkPos::from(&value.0)
164    }
165}
166impl From<Position> for BlockPos {
167    fn from(value: Position) -> Self {
168        BlockPos::from(&value.0)
169    }
170}
171impl From<&Position> for ChunkPos {
172    fn from(value: &Position) -> Self {
173        ChunkPos::from(value.0)
174    }
175}
176impl From<&Position> for BlockPos {
177    fn from(value: &Position) -> Self {
178        BlockPos::from(value.0)
179    }
180}
181
182/// The second most recent position of the entity that was sent over the
183/// network. This is currently only updated for our own local player entities.
184#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Deref, DerefMut)]
185pub struct LastSentPosition(Vec3);
186impl From<&LastSentPosition> for Vec3 {
187    fn from(value: &LastSentPosition) -> Self {
188        value.0
189    }
190}
191impl From<LastSentPosition> for ChunkPos {
192    fn from(value: LastSentPosition) -> Self {
193        ChunkPos::from(&value.0)
194    }
195}
196impl From<LastSentPosition> for BlockPos {
197    fn from(value: LastSentPosition) -> Self {
198        BlockPos::from(&value.0)
199    }
200}
201impl From<&LastSentPosition> for ChunkPos {
202    fn from(value: &LastSentPosition) -> Self {
203        ChunkPos::from(value.0)
204    }
205}
206impl From<&LastSentPosition> for BlockPos {
207    fn from(value: &LastSentPosition) -> Self {
208        BlockPos::from(value.0)
209    }
210}
211
212/// A component for entities that can jump.
213///
214/// If this is true, the entity will try to jump every tick. It's equivalent to
215/// the space key being held in vanilla.
216#[derive(Debug, Component, Copy, Clone, Deref, DerefMut, Default, PartialEq, Eq)]
217pub struct Jumping(pub bool);
218
219/// A component that contains the direction an entity is looking.
220#[derive(Debug, Component, Copy, Clone, Default, PartialEq, AzBuf)]
221pub struct LookDirection {
222    /// Left and right. AKA yaw.
223    pub y_rot: f32,
224    /// Up and down. AKA pitch.
225    pub x_rot: f32,
226}
227
228impl LookDirection {
229    pub fn new(y_rot: f32, x_rot: f32) -> Self {
230        Self { y_rot, x_rot }
231    }
232}
233
234impl From<LookDirection> for (f32, f32) {
235    fn from(value: LookDirection) -> Self {
236        (value.y_rot, value.x_rot)
237    }
238}
239impl From<(f32, f32)> for LookDirection {
240    fn from((y_rot, x_rot): (f32, f32)) -> Self {
241        Self { y_rot, x_rot }
242    }
243}
244
245impl Hash for LookDirection {
246    fn hash<H: Hasher>(&self, state: &mut H) {
247        self.y_rot.to_bits().hash(state);
248        self.x_rot.to_bits().hash(state);
249    }
250}
251impl Eq for LookDirection {}
252
253/// The physics data relating to the entity, such as position, velocity, and
254/// bounding box.
255#[derive(Debug, Component, Clone, Default)]
256pub struct Physics {
257    /// How fast the entity is moving. Sometimes referred to as the delta
258    /// movement.
259    ///
260    /// Note that our Y velocity will be approximately -0.0784 when we're on the
261    /// ground due to how Minecraft applies gravity.
262    pub velocity: Vec3,
263    pub vec_delta_codec: VecDeltaCodec,
264
265    /// The position of the entity before it moved this tick.
266    ///
267    /// This is set immediately before physics is done.
268    pub old_position: Vec3,
269
270    /// The acceleration here is the force that will be attempted to be added to
271    /// the entity's velocity next tick.
272    ///
273    /// You should typically not set this yourself, since it's controlled by how
274    /// the entity is trying to move.
275    pub x_acceleration: f32,
276    pub y_acceleration: f32,
277    pub z_acceleration: f32,
278
279    on_ground: bool,
280    last_on_ground: bool,
281
282    /// The number of ticks until we jump again, if the jump key is being held.
283    ///
284    /// This must be 0 for us to be able to jump. Sets to 10 when we do a jump
285    /// and sets to 0 if we're not trying to jump.
286    pub no_jump_delay: u32,
287
288    /// The width and height of the entity.
289    pub dimensions: EntityDimensions,
290    /// The bounding box of the entity. This is more than just width and height,
291    /// unlike dimensions.
292    pub bounding_box: AABB,
293
294    pub has_impulse: bool,
295
296    pub horizontal_collision: bool,
297    // pub minor_horizontal_collision: bool,
298    pub vertical_collision: bool,
299
300    pub water_fluid_height: f64,
301    pub lava_fluid_height: f64,
302    pub was_touching_water: bool,
303
304    // TODO: implement fall_distance
305    pub fall_distance: f32,
306    // TODO: implement remaining_fire_ticks
307    pub remaining_fire_ticks: i32,
308}
309
310impl Physics {
311    pub fn new(dimensions: EntityDimensions, pos: Vec3) -> Self {
312        Self {
313            velocity: Vec3::ZERO,
314            vec_delta_codec: VecDeltaCodec::new(pos),
315
316            old_position: pos,
317
318            x_acceleration: 0.,
319            y_acceleration: 0.,
320            z_acceleration: 0.,
321
322            on_ground: false,
323            last_on_ground: false,
324
325            no_jump_delay: 0,
326
327            bounding_box: dimensions.make_bounding_box(pos),
328            dimensions,
329
330            has_impulse: false,
331
332            horizontal_collision: false,
333            vertical_collision: false,
334
335            water_fluid_height: 0.,
336            lava_fluid_height: 0.,
337            was_touching_water: false,
338
339            fall_distance: 0.,
340            remaining_fire_ticks: 0,
341        }
342    }
343
344    pub fn on_ground(&self) -> bool {
345        self.on_ground
346    }
347    /// Updates [`Self::on_ground`] and [`Self::last_on_ground`].
348    pub fn set_on_ground(&mut self, on_ground: bool) {
349        self.last_on_ground = self.on_ground;
350        self.on_ground = on_ground;
351    }
352
353    /// The last value of the on_ground value.
354    ///
355    /// This is used by Azalea internally for physics, it might not work as you
356    /// expect since it can be influenced by packets sent by the server.
357    pub fn last_on_ground(&self) -> bool {
358        self.last_on_ground
359    }
360    pub fn set_last_on_ground(&mut self, last_on_ground: bool) {
361        self.last_on_ground = last_on_ground;
362    }
363
364    pub fn reset_fall_distance(&mut self) {
365        self.fall_distance = 0.;
366    }
367    pub fn clear_fire(&mut self) {
368        self.remaining_fire_ticks = 0;
369    }
370
371    pub fn is_in_water(&self) -> bool {
372        self.was_touching_water
373    }
374    pub fn is_in_lava(&self) -> bool {
375        // TODO: also check `!this.firstTick &&`
376        self.lava_fluid_height > 0.
377    }
378
379    pub fn set_old_pos(&mut self, pos: Position) {
380        self.old_position = *pos;
381    }
382}
383
384/// Marker component for entities that are dead.
385///
386/// "Dead" means that the entity has 0 health.
387#[derive(Component, Copy, Clone, Default)]
388pub struct Dead;
389
390/// A component that contains the offset of the entity's eyes from the entity
391/// coordinates.
392///
393/// This is used to calculate the camera position for players, when spectating
394/// an entity, and when raycasting from the entity.
395///
396/// The default eye height for a player is 1.62 blocks.
397#[derive(Component, Clone, Copy, Debug, PartialEq, Deref, DerefMut)]
398pub struct EyeHeight(f32);
399impl EyeHeight {
400    pub fn new(height: f32) -> Self {
401        Self(height)
402    }
403}
404impl From<EyeHeight> for f32 {
405    fn from(value: EyeHeight) -> Self {
406        value.0
407    }
408}
409impl From<EyeHeight> for f64 {
410    fn from(value: EyeHeight) -> Self {
411        value.0 as f64
412    }
413}
414impl From<&EyeHeight> for f32 {
415    fn from(value: &EyeHeight) -> Self {
416        value.0
417    }
418}
419impl From<&EyeHeight> for f64 {
420    fn from(value: &EyeHeight) -> Self {
421        value.0 as f64
422    }
423}
424
425/// A component NewType for [`azalea_registry::EntityKind`].
426///
427/// Most of the time, you should be using `azalea_registry::EntityKind`
428/// directly instead.
429#[derive(Component, Clone, Copy, Debug, PartialEq, Deref)]
430pub struct EntityKindComponent(pub azalea_registry::EntityKind);
431
432/// A bundle of components that every entity has. This doesn't contain metadata,
433/// that has to be added separately.
434#[derive(Bundle)]
435pub struct EntityBundle {
436    pub kind: EntityKindComponent,
437    pub uuid: EntityUuid,
438    pub world_name: InstanceName,
439    pub position: Position,
440    pub last_sent_position: LastSentPosition,
441
442    pub chunk_pos: EntityChunkPos,
443
444    pub physics: Physics,
445    pub direction: LookDirection,
446    pub eye_height: EyeHeight,
447    pub attributes: Attributes,
448    pub jumping: Jumping,
449    pub fluid_on_eyes: FluidOnEyes,
450    pub on_climbable: OnClimbable,
451}
452
453impl EntityBundle {
454    pub fn new(
455        uuid: Uuid,
456        pos: Vec3,
457        kind: azalea_registry::EntityKind,
458        world_name: ResourceLocation,
459    ) -> Self {
460        let dimensions = EntityDimensions::from(kind);
461        let eye_height = match kind {
462            // TODO: codegen hardcoded eye heights, search withEyeHeight with mojmap
463            // also, eye height should change depending on pose (like sneaking, swimming, etc)
464            azalea_registry::EntityKind::Player => 1.62,
465            _ => dimensions.height * 0.85,
466        };
467
468        Self {
469            kind: EntityKindComponent(kind),
470            uuid: EntityUuid(uuid),
471            world_name: InstanceName(world_name),
472            position: Position(pos),
473            chunk_pos: EntityChunkPos(ChunkPos::from(&pos)),
474            last_sent_position: LastSentPosition(pos),
475            physics: Physics::new(dimensions, pos),
476            eye_height: EyeHeight(eye_height),
477            direction: LookDirection::default(),
478
479            attributes: default_attributes(EntityKind::Player),
480
481            jumping: Jumping(false),
482            fluid_on_eyes: FluidOnEyes(FluidKind::Empty),
483            on_climbable: OnClimbable(false),
484        }
485    }
486}
487
488pub fn default_attributes(_entity_kind: EntityKind) -> Attributes {
489    // TODO: do the correct defaults for everything, some
490    // entities have different defaults
491    Attributes {
492        speed: AttributeInstance::new(0.1),
493        attack_speed: AttributeInstance::new(4.0),
494        water_movement_efficiency: AttributeInstance::new(0.0),
495        block_interaction_range: AttributeInstance::new(4.5),
496        entity_interaction_range: AttributeInstance::new(3.0),
497    }
498}
499
500/// A marker component that signifies that this entity is "local" and shouldn't
501/// be updated by other clients.
502///
503/// If this is for a client then all of our clients will have this.
504///
505/// This component is not removed from clients when they disconnect.
506#[derive(Component, Clone, Copy, Debug, Default)]
507pub struct LocalEntity;
508
509#[derive(Component, Clone, Copy, Debug, PartialEq, Deref, DerefMut)]
510pub struct FluidOnEyes(FluidKind);
511
512impl FluidOnEyes {
513    pub fn new(fluid: FluidKind) -> Self {
514        Self(fluid)
515    }
516}
517
518#[derive(Component, Clone, Copy, Debug, PartialEq, Deref, DerefMut)]
519pub struct OnClimbable(bool);