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)]
23pub struct EntityHitboxSettings {
25 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#[derive(Component, Debug, PartialEq, Deref)]
74pub struct HitboxShape(pub Aabb);
75
76#[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 => [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 [0.0; 3]
267 } else if stand_flags.0 & 1 != 0 {
268 [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), 1 => (-1, 0, 0, 1), 2 => (0, -1, -1, 0), _ => (1, 0, 0, -1), };
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}