valence_entity/
hitbox.rs

1#![allow(clippy::type_complexity)]
2
3use bevy_app::prelude::*;
4use bevy_ecs::prelude::*;
5use derive_more::Deref;
6use valence_math::{Aabb, UVec3, Vec3Swizzles};
7use valence_protocol::Direction;
8
9use crate::*;
10
11#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)]
12pub struct HitboxShapeUpdateSet;
13
14#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)]
15pub struct HitboxComponentsAddSet;
16
17#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)]
18pub struct HitboxUpdateSet;
19
20pub struct HitboxPlugin;
21
22#[derive(Resource)]
23/// Settings for hitbox plugin
24pub struct EntityHitboxSettings {
25    /// Controls if a plugin should add hitbox component on each created entity.
26    /// Otherwise you should add hitbox component by yourself in order to use
27    /// it.
28    pub add_hitbox_component: bool,
29}
30
31impl Default for EntityHitboxSettings {
32    fn default() -> Self {
33        Self {
34            add_hitbox_component: true,
35        }
36    }
37}
38
39impl Plugin for HitboxPlugin {
40    fn build(&self, app: &mut App) {
41        app.init_resource::<EntityHitboxSettings>()
42            .configure_sets(PreUpdate, HitboxShapeUpdateSet)
43            .add_systems(
44                PreUpdate,
45                (
46                    update_constant_hitbox,
47                    update_warden_hitbox,
48                    update_area_effect_cloud_hitbox,
49                    update_armor_stand_hitbox,
50                    update_passive_child_hitbox,
51                    update_zombie_hitbox,
52                    update_piglin_hitbox,
53                    update_zoglin_hitbox,
54                    update_player_hitbox,
55                    update_item_frame_hitbox,
56                    update_slime_hitbox,
57                    update_painting_hitbox,
58                    update_shulker_hitbox,
59                ),
60            )
61            .configure_sets(PostUpdate, HitboxComponentsAddSet)
62            .add_systems(
63                PostUpdate,
64                add_hitbox_component.in_set(HitboxComponentsAddSet),
65            )
66            .configure_sets(PreUpdate, HitboxUpdateSet.after(HitboxShapeUpdateSet))
67            .add_systems(PreUpdate, update_hitbox.in_set(HitboxUpdateSet));
68    }
69}
70
71/// Size of hitbox. The only way to manipulate it without losing it on the next
72/// tick is using a marker entity. Marker entity's hitbox is never updated.
73#[derive(Component, Debug, PartialEq, Deref)]
74pub struct HitboxShape(pub Aabb);
75
76/// Hitbox, aabb of which is calculated each tick using its position and
77/// [`Hitbox`]. In order to change size of this hitbox you need to change
78/// [`Hitbox`].
79#[derive(Component, Debug, Deref)]
80pub struct Hitbox(Aabb);
81
82impl HitboxShape {
83    pub const ZERO: HitboxShape = HitboxShape(Aabb::ZERO);
84
85    pub fn get(&self) -> Aabb {
86        self.0
87    }
88
89    pub(crate) fn centered(&mut self, size: DVec3) {
90        self.0 = Aabb::from_bottom_size(DVec3::ZERO, size);
91    }
92
93    pub(crate) fn in_world(&self, pos: DVec3) -> Aabb {
94        self.0 + pos
95    }
96}
97
98impl Hitbox {
99    pub fn get(&self) -> Aabb {
100        self.0
101    }
102}
103
104fn add_hitbox_component(
105    settings: Res<EntityHitboxSettings>,
106    mut commands: Commands,
107    query: Query<(Entity, &Position), Added<entity::Entity>>,
108    alt_query: Query<(Entity, &Position, &HitboxShape), Added<HitboxShape>>,
109) {
110    if settings.add_hitbox_component {
111        for (entity, pos) in query.iter() {
112            commands
113                .entity(entity)
114                .insert(HitboxShape::ZERO)
115                .insert(Hitbox(HitboxShape::ZERO.in_world(pos.0)));
116        }
117    } else {
118        for (entity, pos, hitbox) in alt_query.iter() {
119            commands
120                .entity(entity)
121                .insert(Hitbox(hitbox.in_world(pos.0)));
122        }
123    }
124}
125
126fn update_hitbox(
127    mut hitbox_query: Query<
128        (&mut Hitbox, &HitboxShape, &Position),
129        Or<(Changed<HitboxShape>, Changed<Position>)>,
130    >,
131) {
132    for (mut in_world, hitbox, pos) in &mut hitbox_query {
133        in_world.0 = hitbox.in_world(pos.0);
134    }
135}
136
137fn update_constant_hitbox(
138    mut hitbox_query: Query<
139        (&mut HitboxShape, &EntityKind),
140        Or<(Changed<EntityKind>, Added<HitboxShape>)>,
141    >,
142) {
143    for (mut hitbox, entity_kind) in &mut hitbox_query {
144        let size = match *entity_kind {
145            EntityKind::ALLAY => [0.6, 0.35, 0.6],
146            EntityKind::CHEST_BOAT | EntityKind::BOAT => [1.375, 0.5625, 1.375],
147            EntityKind::FROG => [0.5, 0.5, 0.5],
148            EntityKind::TADPOLE => [0.4, 0.3, 0.4],
149            EntityKind::SPECTRAL_ARROW | EntityKind::ARROW => [0.5, 0.5, 0.5],
150            EntityKind::AXOLOTL => [1.3, 0.6, 1.3],
151            EntityKind::BAT => [0.5, 0.9, 0.5],
152            EntityKind::BLAZE => [0.6, 1.8, 0.6],
153            EntityKind::CAT => [0.6, 0.7, 0.6],
154            EntityKind::CAVE_SPIDER => [0.7, 0.5, 0.7],
155            EntityKind::COD => [0.5, 0.3, 0.5],
156            EntityKind::CREEPER => [0.6, 1.7, 0.6],
157            EntityKind::DOLPHIN => [0.9, 0.6, 0.9],
158            EntityKind::DRAGON_FIREBALL => [1.0, 1.0, 1.0],
159            EntityKind::ELDER_GUARDIAN => [1.9975, 1.9975, 1.9975],
160            EntityKind::END_CRYSTAL => [2.0, 2.0, 2.0],
161            EntityKind::ENDER_DRAGON => [16.0, 8.0, 16.0],
162            EntityKind::ENDERMAN => [0.6, 2.9, 0.6],
163            EntityKind::ENDERMITE => [0.4, 0.3, 0.4],
164            EntityKind::EVOKER => [0.6, 1.95, 0.6],
165            EntityKind::EVOKER_FANGS => [0.5, 0.8, 0.5],
166            EntityKind::EXPERIENCE_ORB => [0.5, 0.5, 0.5],
167            EntityKind::EYE_OF_ENDER => [0.25, 0.25, 0.25],
168            EntityKind::FALLING_BLOCK => [0.98, 0.98, 0.98],
169            EntityKind::FIREWORK_ROCKET => [0.25, 0.25, 0.25],
170            EntityKind::GHAST => [4.0, 4.0, 4.0],
171            EntityKind::GIANT => [3.6, 12.0, 3.6],
172            EntityKind::GLOW_SQUID | EntityKind::SQUID => [0.8, 0.8, 0.8],
173            EntityKind::GUARDIAN => [0.85, 0.85, 0.85],
174            EntityKind::ILLUSIONER => [0.6, 1.95, 0.6],
175            EntityKind::IRON_GOLEM => [1.4, 2.7, 1.4],
176            EntityKind::ITEM => [0.25, 0.25, 0.25],
177            EntityKind::FIREBALL => [1.0, 1.0, 1.0],
178            EntityKind::LEASH_KNOT => [0.375, 0.5, 0.375],
179            EntityKind::LIGHTNING /* | EntityKind::MARKER - marker hitbox */ => [0.0; 3],
180            EntityKind::LLAMA_SPIT => [0.25, 0.25, 0.25],
181            EntityKind::MINECART
182            | EntityKind::CHEST_MINECART
183            | EntityKind::TNT_MINECART
184            | EntityKind::HOPPER_MINECART
185            | EntityKind::FURNACE_MINECART
186            | EntityKind::SPAWNER_MINECART
187            | EntityKind::COMMAND_BLOCK_MINECART => [0.98, 0.7, 0.98],
188            EntityKind::PARROT => [0.5, 0.9, 0.5],
189            EntityKind::PHANTOM => [0.9, 0.5, 0.9],
190            EntityKind::PIGLIN_BRUTE => [0.6, 1.95, 0.6],
191            EntityKind::PILLAGER => [0.6, 1.95, 0.6],
192            EntityKind::TNT => [0.98, 0.98, 0.98],
193            EntityKind::PUFFERFISH => [0.7, 0.7, 0.7],
194            EntityKind::RAVAGER => [1.95, 2.2, 1.95],
195            EntityKind::SALMON => [0.7, 0.4, 0.7],
196            EntityKind::SHULKER_BULLET => [0.3125, 0.3125, 0.3125],
197            EntityKind::SILVERFISH => [0.4, 0.3, 0.4],
198            EntityKind::SMALL_FIREBALL => [0.3125, 0.3125, 0.3125],
199            EntityKind::SNOW_GOLEM => [0.7, 1.9, 0.7],
200            EntityKind::SPIDER => [1.4, 0.9, 1.4],
201            EntityKind::STRAY => [0.6, 1.99, 0.6],
202            EntityKind::EGG => [0.25, 0.25, 0.25],
203            EntityKind::ENDER_PEARL => [0.25, 0.25, 0.25],
204            EntityKind::EXPERIENCE_BOTTLE => [0.25, 0.25, 0.25],
205            EntityKind::POTION => [0.25, 0.25, 0.25],
206            EntityKind::TRIDENT => [0.5, 0.5, 0.5],
207            EntityKind::TRADER_LLAMA => [0.9, 1.87, 0.9],
208            EntityKind::TROPICAL_FISH => [0.5, 0.4, 0.5],
209            EntityKind::VEX => [0.4, 0.8, 0.4],
210            EntityKind::VINDICATOR => [0.6, 1.95, 0.6],
211            EntityKind::WITHER => [0.9, 3.5, 0.9],
212            EntityKind::WITHER_SKELETON => [0.7, 2.4, 0.7],
213            EntityKind::WITHER_SKULL => [0.3125, 0.3125, 0.3125],
214            EntityKind::FISHING_BOBBER => [0.25, 0.25, 0.25],
215            _ => {
216                continue;
217            }
218        }
219        .into();
220        hitbox.centered(size);
221    }
222}
223
224fn update_warden_hitbox(
225    mut query: Query<
226        (&mut HitboxShape, &entity::Pose),
227        (
228            Or<(Changed<entity::Pose>, Added<HitboxShape>)>,
229            With<warden::WardenEntity>,
230        ),
231    >,
232) {
233    for (mut hitbox, entity_pose) in &mut query {
234        hitbox.centered(
235            match entity_pose.0 {
236                Pose::Emerging | Pose::Digging => [0.9, 1.0, 0.9],
237                _ => [0.9, 2.9, 0.9],
238            }
239            .into(),
240        );
241    }
242}
243
244fn update_area_effect_cloud_hitbox(
245    mut query: Query<
246        (&mut HitboxShape, &area_effect_cloud::Radius),
247        Or<(Changed<area_effect_cloud::Radius>, Added<HitboxShape>)>,
248    >,
249) {
250    for (mut hitbox, cloud_radius) in &mut query {
251        let diameter = f64::from(cloud_radius.0) * 2.0;
252        hitbox.centered([diameter, 0.5, diameter].into());
253    }
254}
255
256fn update_armor_stand_hitbox(
257    mut query: Query<
258        (&mut HitboxShape, &armor_stand::ArmorStandFlags),
259        Or<(Changed<armor_stand::ArmorStandFlags>, Added<HitboxShape>)>,
260    >,
261) {
262    for (mut hitbox, stand_flags) in &mut query {
263        hitbox.centered(
264            if stand_flags.0 & 16 != 0 {
265                // Marker armor stand
266                [0.0; 3]
267            } else if stand_flags.0 & 1 != 0 {
268                // Small armor stand
269                [0.5, 0.9875, 0.5]
270            } else {
271                [0.5, 1.975, 0.5]
272            }
273            .into(),
274        );
275    }
276}
277
278fn child_hitbox(child: bool, v: DVec3) -> DVec3 {
279    if child {
280        v / 2.0
281    } else {
282        v
283    }
284}
285
286fn update_passive_child_hitbox(
287    mut query: Query<
288        (Entity, &mut HitboxShape, &EntityKind, &passive::Child),
289        Or<(Changed<passive::Child>, Added<HitboxShape>)>,
290    >,
291    pose_query: Query<&entity::Pose>,
292) {
293    for (entity, mut hitbox, entity_kind, child) in &mut query {
294        let big_s = match *entity_kind {
295            EntityKind::BEE => [0.7, 0.6, 0.7],
296            EntityKind::CAMEL => [1.7, 2.375, 1.7],
297            EntityKind::CHICKEN => [0.4, 0.7, 0.4],
298            EntityKind::DONKEY => [1.5, 1.39648, 1.5],
299            EntityKind::FOX => [0.6, 0.7, 0.6],
300            EntityKind::GOAT => {
301                if pose_query
302                    .get(entity)
303                    .is_ok_and(|v| v.0 == Pose::LongJumping)
304                {
305                    [0.63, 0.91, 0.63]
306                } else {
307                    [0.9, 1.3, 0.9]
308                }
309            }
310            EntityKind::HOGLIN => [1.39648, 1.4, 1.39648],
311            EntityKind::HORSE | EntityKind::SKELETON_HORSE | EntityKind::ZOMBIE_HORSE => {
312                [1.39648, 1.6, 1.39648]
313            }
314            EntityKind::LLAMA => [0.9, 1.87, 0.9],
315            EntityKind::MULE => [1.39648, 1.6, 1.39648],
316            EntityKind::MOOSHROOM => [0.9, 1.4, 0.9],
317            EntityKind::OCELOT => [0.6, 0.7, 0.6],
318            EntityKind::PANDA => [1.3, 1.25, 1.3],
319            EntityKind::PIG => [0.9, 0.9, 0.9],
320            EntityKind::POLAR_BEAR => [1.4, 1.4, 1.4],
321            EntityKind::RABBIT => [0.4, 0.5, 0.4],
322            EntityKind::SHEEP => [0.9, 1.3, 0.9],
323            EntityKind::TURTLE => {
324                hitbox.centered(
325                    if child.0 {
326                        [0.36, 0.12, 0.36]
327                    } else {
328                        [1.2, 0.4, 1.2]
329                    }
330                    .into(),
331                );
332                continue;
333            }
334            EntityKind::VILLAGER => [0.6, 1.95, 0.6],
335            EntityKind::WOLF => [0.6, 0.85, 0.6],
336            _ => {
337                continue;
338            }
339        };
340        hitbox.centered(child_hitbox(child.0, big_s.into()));
341    }
342}
343
344fn update_zombie_hitbox(
345    mut query: Query<
346        (&mut HitboxShape, &zombie::Baby),
347        Or<(Changed<zombie::Baby>, Added<HitboxShape>)>,
348    >,
349) {
350    for (mut hitbox, baby) in &mut query {
351        hitbox.centered(child_hitbox(baby.0, [0.6, 1.95, 0.6].into()));
352    }
353}
354
355fn update_piglin_hitbox(
356    mut query: Query<
357        (&mut HitboxShape, &piglin::Baby),
358        Or<(Changed<piglin::Baby>, Added<HitboxShape>)>,
359    >,
360) {
361    for (mut hitbox, baby) in &mut query {
362        hitbox.centered(child_hitbox(baby.0, [0.6, 1.95, 0.6].into()));
363    }
364}
365
366fn update_zoglin_hitbox(
367    mut query: Query<
368        (&mut HitboxShape, &zoglin::Baby),
369        Or<(Changed<zoglin::Baby>, Added<HitboxShape>)>,
370    >,
371) {
372    for (mut hitbox, baby) in &mut query {
373        hitbox.centered(child_hitbox(baby.0, [1.39648, 1.4, 1.39648].into()));
374    }
375}
376
377fn update_player_hitbox(
378    mut query: Query<
379        (&mut HitboxShape, &entity::Pose),
380        (
381            Or<(Changed<entity::Pose>, Added<HitboxShape>)>,
382            With<player::PlayerEntity>,
383        ),
384    >,
385) {
386    for (mut hitbox, pose) in &mut query {
387        hitbox.centered(
388            match pose.0 {
389                Pose::Sleeping | Pose::Dying => [0.2, 0.2, 0.2],
390                Pose::FallFlying | Pose::Swimming | Pose::SpinAttack => [0.6, 0.6, 0.6],
391                Pose::Sneaking => [0.6, 1.5, 0.6],
392                _ => [0.6, 1.8, 0.6],
393            }
394            .into(),
395        );
396    }
397}
398
399fn update_item_frame_hitbox(
400    mut query: Query<
401        (&mut HitboxShape, &item_frame::Rotation),
402        Or<(Changed<item_frame::Rotation>, Added<HitboxShape>)>,
403    >,
404) {
405    for (mut hitbox, rotation) in &mut query {
406        let mut center_pos = DVec3::splat(0.5);
407
408        const A: f64 = 0.46875;
409
410        match rotation.0 {
411            0 => center_pos.y += A,
412            1 => center_pos.y -= A,
413            2 => center_pos.z += A,
414            3 => center_pos.z -= A,
415            4 => center_pos.x += A,
416            5 => center_pos.x -= A,
417            _ => center_pos.y -= A,
418        }
419
420        const BOUNDS23: DVec3 = DVec3::new(0.375, 0.375, 0.03125);
421
422        let bounds = match rotation.0 {
423            2 | 3 => BOUNDS23,
424            4 | 5 => BOUNDS23.zxy(),
425            _ => BOUNDS23.zxy(),
426        };
427
428        hitbox.0 = Aabb::new(center_pos - bounds, center_pos + bounds);
429    }
430}
431
432fn update_slime_hitbox(
433    mut query: Query<
434        (&mut HitboxShape, &slime::SlimeSize),
435        Or<(Changed<slime::SlimeSize>, Added<HitboxShape>)>,
436    >,
437) {
438    for (mut hitbox, slime_size) in &mut query {
439        let s = 0.5202 * f64::from(slime_size.0);
440        hitbox.centered([s, s, s].into());
441    }
442}
443
444fn update_painting_hitbox(
445    mut query: Query<
446        (&mut HitboxShape, &painting::Variant, &Look),
447        Or<(
448            Changed<Look>,
449            Changed<painting::Variant>,
450            Added<HitboxShape>,
451        )>,
452    >,
453) {
454    for (mut hitbox, painting_variant, look) in &mut query {
455        let bounds: UVec3 = match painting_variant.0 {
456            PaintingKind::Kebab => [1, 1, 1],
457            PaintingKind::Aztec => [1, 1, 1],
458            PaintingKind::Alban => [1, 1, 1],
459            PaintingKind::Aztec2 => [1, 1, 1],
460            PaintingKind::Bomb => [1, 1, 1],
461            PaintingKind::Plant => [1, 1, 1],
462            PaintingKind::Wasteland => [1, 1, 1],
463            PaintingKind::Pool => [2, 1, 2],
464            PaintingKind::Courbet => [2, 1, 2],
465            PaintingKind::Sea => [2, 1, 2],
466            PaintingKind::Sunset => [2, 1, 2],
467            PaintingKind::Creebet => [2, 1, 2],
468            PaintingKind::Wanderer => [1, 2, 1],
469            PaintingKind::Graham => [1, 2, 1],
470            PaintingKind::Match => [2, 2, 2],
471            PaintingKind::Bust => [2, 2, 2],
472            PaintingKind::Stage => [2, 2, 2],
473            PaintingKind::Void => [2, 2, 2],
474            PaintingKind::SkullAndRoses => [2, 2, 2],
475            PaintingKind::Wither => [2, 2, 2],
476            PaintingKind::Fighters => [4, 2, 4],
477            PaintingKind::Pointer => [4, 4, 4],
478            PaintingKind::Pigscene => [4, 4, 4],
479            PaintingKind::BurningSkull => [4, 4, 4],
480            PaintingKind::Skeleton => [4, 3, 4],
481            PaintingKind::Earth => [2, 2, 2],
482            PaintingKind::Wind => [2, 2, 2],
483            PaintingKind::Water => [2, 2, 2],
484            PaintingKind::Fire => [2, 2, 2],
485            PaintingKind::DonkeyKong => [4, 3, 4],
486        }
487        .into();
488
489        let mut center_pos = DVec3::splat(0.5);
490
491        let (facing_x, facing_z, cc_facing_x, cc_facing_z) =
492            match ((look.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 {
493                0 => (0, 1, 1, 0),   // South
494                1 => (-1, 0, 0, 1),  // West
495                2 => (0, -1, -1, 0), // North
496                _ => (1, 0, 0, -1),  // East
497            };
498
499        center_pos.x -= f64::from(facing_x) * 0.46875;
500        center_pos.z -= f64::from(facing_z) * 0.46875;
501
502        center_pos.x += f64::from(cc_facing_x) * if bounds.x % 2 == 0 { 0.5 } else { 0.0 };
503        center_pos.y += if bounds.y % 2 == 0 { 0.5 } else { 0.0 };
504        center_pos.z += f64::from(cc_facing_z) * if bounds.z % 2 == 0 { 0.5 } else { 0.0 };
505
506        let bounds = match (facing_x, facing_z) {
507            (1 | -1, 0) => DVec3::new(0.0625, f64::from(bounds.y), f64::from(bounds.z)),
508            _ => DVec3::new(f64::from(bounds.x), f64::from(bounds.y), 0.0625),
509        };
510
511        hitbox.0 = Aabb::new(center_pos - bounds / 2.0, center_pos + bounds / 2.0);
512    }
513}
514
515fn update_shulker_hitbox(
516    mut query: Query<
517        (
518            &mut HitboxShape,
519            &shulker::PeekAmount,
520            &shulker::AttachedFace,
521        ),
522        Or<(
523            Changed<shulker::PeekAmount>,
524            Changed<shulker::AttachedFace>,
525            Added<HitboxShape>,
526        )>,
527    >,
528) {
529    use std::f64::consts::PI;
530
531    for (mut hitbox, peek_amount, attached_face) in &mut query {
532        let pos = DVec3::splat(0.5);
533        let mut min = pos - 0.5;
534        let mut max = pos + 0.5;
535
536        let peek = 0.5 - f64::cos(f64::from(peek_amount.0) * 0.01 * PI) * 0.5;
537
538        match attached_face.0 {
539            Direction::Down => max.y += peek,
540            Direction::Up => min.y -= peek,
541            Direction::North => max.z += peek,
542            Direction::South => min.z -= peek,
543            Direction::West => max.x += peek,
544            Direction::East => min.x -= peek,
545        }
546
547        hitbox.0 = Aabb::new(min, max);
548    }
549}