From 8897eeacb9104e404f91b529bc0488ef313aa694 Mon Sep 17 00:00:00 2001 From: Jenya705 <51133999+Jenya705@users.noreply.github.com> Date: Tue, 9 May 2023 02:34:33 +0200 Subject: [PATCH] Hitboxes (#330) ## Description Added Hitbox component, a crate for it and systems which update hitboxes. Issue: https://github.com/valence-rs/valence/issues/299 ## Test Plan Use example "entity_hitbox" --- crates/valence/examples/entity_hitbox.rs | 146 ++++ crates/valence/src/lib.rs | 2 + crates/valence_client/src/event_loop.rs | 4 +- crates/valence_core/src/aabb.rs | 30 + crates/valence_entity/Cargo.toml | 2 +- crates/valence_entity/src/hitbox.rs | 828 +++++++++++++++-------- crates/valence_entity/src/lib.rs | 3 +- 7 files changed, 732 insertions(+), 283 deletions(-) create mode 100644 crates/valence/examples/entity_hitbox.rs diff --git a/crates/valence/examples/entity_hitbox.rs b/crates/valence/examples/entity_hitbox.rs new file mode 100644 index 0000000..d867971 --- /dev/null +++ b/crates/valence/examples/entity_hitbox.rs @@ -0,0 +1,146 @@ +use std::collections::HashMap; + +use bevy_app::App; +use bevy_ecs::prelude::Entity; +use rand::Rng; +use valence::prelude::*; +use valence_entity::entity::NameVisible; +use valence_entity::hoglin::HoglinEntityBundle; +use valence_entity::pig::PigEntityBundle; +use valence_entity::sheep::SheepEntityBundle; +use valence_entity::warden::WardenEntityBundle; +use valence_entity::zombie::ZombieEntityBundle; +use valence_entity::zombie_horse::ZombieHorseEntityBundle; +use valence_entity::{entity, Pose}; + +pub fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(init_clients) + .add_systems((spawn_entity, intersections)) + .run(); +} + +fn setup( + mut commands: Commands, + server: Res, + dimensions: Query<&DimensionType>, + biomes: Query<&Biome>, +) { + let mut instance = Instance::new(ident!("overworld"), &dimensions, &biomes, &server); + + for z in -5..5 { + for x in -5..5 { + instance.insert_chunk([x, z], Chunk::default()); + } + } + + for z in -25..25 { + for x in -25..25 { + instance.set_block([x, 64, z], BlockState::GRASS_BLOCK); + } + } + + commands.spawn(instance); +} + +fn init_clients( + mut clients: Query<(&mut Location, &mut Position, &mut GameMode, &mut Client), Added>, + instances: Query>, +) { + for (mut loc, mut pos, mut game_mode, mut client) in &mut clients { + loc.0 = instances.single(); + pos.set([0.5, 65.0, 0.5]); + *game_mode = GameMode::Creative; + client.send_message("To spawn an entity, press shift. F3 + B to activate hitboxes"); + } +} + +fn spawn_entity( + mut commands: Commands, + mut sneaking: EventReader, + client_query: Query<(&Position, &Location)>, +) { + for sneaking in sneaking.iter() { + if sneaking.state == SneakState::Start { + continue; + } + + let (position, location) = client_query.get(sneaking.client).unwrap(); + + let position = *position; + let location = *location; + + match rand::thread_rng().gen_range(0..7) { + 0 => commands.spawn(SheepEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + ..Default::default() + }), + 1 => commands.spawn(PigEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + ..Default::default() + }), + 2 => commands.spawn(ZombieEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + ..Default::default() + }), + 3 => commands.spawn(ZombieHorseEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + ..Default::default() + }), + 4 => commands.spawn(WardenEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + entity_pose: entity::Pose(Pose::Digging), + ..Default::default() + }), + 5 => commands.spawn(WardenEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + ..Default::default() + }), + 6 => commands.spawn(HoglinEntityBundle { + position, + location, + entity_name_visible: NameVisible(true), + + ..Default::default() + }), + _ => unreachable!(), + }; + } +} + +fn intersections(query: Query<(Entity, &Hitbox)>, mut name_query: Query<&mut entity::CustomName>) { + // This code only to show how hitboxes can be used + let mut intersections = HashMap::new(); + + for [(entity1, hitbox1), (entity2, hitbox2)] in query.iter_combinations() { + let aabb1 = hitbox1.get(); + let aabb2 = hitbox2.get(); + + let _ = *intersections.entry(entity1).or_insert(0); + let _ = *intersections.entry(entity2).or_insert(0); + + if aabb1.intersects(aabb2) { + *intersections.get_mut(&entity1).unwrap() += 1; + *intersections.get_mut(&entity2).unwrap() += 1; + } + } + + for (entity, value) in intersections { + let Ok(mut name) = name_query.get_mut(entity) else { continue; }; + name.0 = Some(format!("{value}").into()); + } +} diff --git a/crates/valence/src/lib.rs b/crates/valence/src/lib.rs index 7a4b914..ec41066 100644 --- a/crates/valence/src/lib.rs +++ b/crates/valence/src/lib.rs @@ -105,6 +105,7 @@ pub mod prelude { pub use valence_core::ident; // Export the `ident!` macro. pub use valence_core::uuid::UniqueId; pub use valence_core::{translation_key, CoreSettings, Server}; + pub use valence_entity::hitbox::{Hitbox, HitboxShape}; pub use super::DefaultPlugins; use super::*; @@ -127,6 +128,7 @@ impl PluginGroup for DefaultPlugins { .add(valence_biome::BiomePlugin) .add(valence_dimension::DimensionPlugin) .add(valence_entity::EntityPlugin) + .add(valence_entity::hitbox::HitboxPlugin) .add(valence_instance::InstancePlugin) .add(valence_client::ClientPlugin); diff --git a/crates/valence_client/src/event_loop.rs b/crates/valence_client/src/event_loop.rs index a6930fa..22cfe30 100644 --- a/crates/valence_client/src/event_loop.rs +++ b/crates/valence_client/src/event_loop.rs @@ -7,6 +7,7 @@ use bevy_ecs::system::SystemState; use bytes::Bytes; use tracing::{debug, warn}; use valence_core::packet::{Decode, Packet}; +use valence_entity::hitbox::HitboxUpdateSet; use crate::{Client, SpawnClientsSet}; @@ -14,7 +15,8 @@ pub(super) fn build(app: &mut App) { app.configure_set( RunEventLoopSet .in_base_set(CoreSet::PreUpdate) - .after(SpawnClientsSet), + .after(SpawnClientsSet) + .after(HitboxUpdateSet), ) .add_system(run_event_loop.in_set(RunEventLoopSet)) .add_event::(); diff --git a/crates/valence_core/src/aabb.rs b/crates/valence_core/src/aabb.rs index 80413d2..3c6949d 100644 --- a/crates/valence_core/src/aabb.rs +++ b/crates/valence_core/src/aabb.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use glam::DVec3; /// An axis-aligned bounding box. `min` is expected to be <= `max` @@ -35,4 +37,32 @@ impl Aabb { }, } } + + pub fn intersects(&self, second: Aabb) -> bool { + self.max.x >= second.min.x + && second.max.x >= self.min.x + && self.max.y >= second.min.y + && second.max.y >= self.min.y + && self.max.z >= second.min.z + && second.max.z >= self.min.z + } +} + +impl Add for Aabb { + type Output = Aabb; + + fn add(self, rhs: DVec3) -> Self::Output { + Self { + min: self.min + rhs, + max: self.max + rhs, + } + } +} + +impl Add for DVec3 { + type Output = Aabb; + + fn add(self, rhs: Aabb) -> Self::Output { + rhs + self + } } diff --git a/crates/valence_entity/Cargo.toml b/crates/valence_entity/Cargo.toml index 0d91012..967ed01 100644 --- a/crates/valence_entity/Cargo.toml +++ b/crates/valence_entity/Cargo.toml @@ -24,4 +24,4 @@ syn.workspace = true serde_json.workspace = true heck.workspace = true serde.workspace = true -valence_build_utils.workspace = true +valence_build_utils.workspace = true \ No newline at end of file diff --git a/crates/valence_entity/src/hitbox.rs b/crates/valence_entity/src/hitbox.rs index dab61ae..ae6fc5f 100644 --- a/crates/valence_entity/src/hitbox.rs +++ b/crates/valence_entity/src/hitbox.rs @@ -1,294 +1,562 @@ -// TODO: Make a `Hitbox` component and plugin. +#![allow(clippy::type_complexity)] -/* -/// Returns the hitbox of this entity. -/// -/// The hitbox describes the space that an entity occupies. Clients interact -/// with this space to create an [interact event]. -/// -/// The hitbox of an entity is determined by its position, entity type, and -/// other state specific to that type. -/// -/// [interact event]: crate::client::event::PlayerInteract -pub fn hitbox(&self) -> Aabb { - fn baby(is_baby: bool, adult_hitbox: [f64; 3]) -> [f64; 3] { - if is_baby { - adult_hitbox.map(|a| a / 2.0) - } else { - adult_hitbox +use bevy_app::{App, CoreSet, Plugin}; +use bevy_ecs::prelude::{Component, Entity, SystemSet}; +use bevy_ecs::query::{Added, Changed, Or, With}; +use bevy_ecs::schedule::{IntoSystemConfig, IntoSystemConfigs, IntoSystemSetConfig}; +use bevy_ecs::system::{Commands, Query}; +use glam::{DVec3, UVec3, Vec3Swizzles}; +use valence_core::aabb::Aabb; +use valence_core::direction::Direction; + +use crate::*; + +#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct HitboxShapeUpdateSet; + +#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct HitboxComponentsAddSet; + +#[derive(SystemSet, Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct HitboxUpdateSet; + +pub struct HitboxPlugin; + +#[derive(Resource)] +/// Settings for hitbox plugin +pub struct EntityHitboxSettings { + /// Controls if a plugin should add hitbox component on each created entity. + /// Otherwise you should add hitbox component by yourself in order to use + /// it. + pub add_hitbox_component: bool, +} + +impl Default for EntityHitboxSettings { + fn default() -> Self { + Self { + add_hitbox_component: true, } } +} - fn item_frame(pos: DVec3, rotation: i32) -> Aabb { - let mut center_pos = pos + 0.5; +impl Plugin for HitboxPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .configure_set(HitboxShapeUpdateSet.in_base_set(CoreSet::PreUpdate)) + .add_systems( + ( + update_constant_hitbox, + update_warden_hitbox, + update_area_effect_cloud_hitbox, + update_armor_stand_hitbox, + update_passive_child_hitbox, + update_zombie_hitbox, + update_piglin_hitbox, + update_zoglin_hitbox, + update_player_hitbox, + update_item_frame_hitbox, + update_slime_hitbox, + update_painting_hitbox, + update_shulker_hitbox, + ) + .in_set(HitboxShapeUpdateSet), + ) + .configure_set(HitboxComponentsAddSet.in_base_set(CoreSet::PostUpdate)) + .add_system(add_hitbox_component.in_set(HitboxComponentsAddSet)) + .configure_set( + HitboxUpdateSet + .in_base_set(CoreSet::PreUpdate) + .after(HitboxShapeUpdateSet), + ) + .add_system(update_hitbox.in_set(HitboxUpdateSet)); + } +} - match rotation { - 0 => center_pos.y += 0.46875, - 1 => center_pos.y -= 0.46875, - 2 => center_pos.z += 0.46875, - 3 => center_pos.z -= 0.46875, - 4 => center_pos.x += 0.46875, - 5 => center_pos.x -= 0.46875, - _ => center_pos.y -= 0.46875, - }; +/// Size of hitbox. The only way to manipulate it without losing it on the next +/// tick is using a marker entity. Marker entity's hitbox is never updated. +#[derive(Component, Debug, PartialEq)] +pub struct HitboxShape(pub Aabb); - let bounds = DVec3::from(match rotation { - 0 | 1 => [0.75, 0.0625, 0.75], - 2 | 3 => [0.75, 0.75, 0.0625], - 4 | 5 => [0.0625, 0.75, 0.75], - _ => [0.75, 0.0625, 0.75], - }); +#[derive(Component, Debug)] +/// Hitbox, aabb of which is calculated each tick using it's position and +/// [`Hitbox`]. In order to change size of this hitbox you need to change +/// [`Hitbox`] +pub struct Hitbox(Aabb); - Aabb { - min: center_pos - bounds / 2.0, - max: center_pos + bounds / 2.0, - } +impl HitboxShape { + pub const ZERO: HitboxShape = HitboxShape(Aabb { + min: DVec3::ZERO, + max: DVec3::ZERO, + }); + + pub fn get(&self) -> Aabb { + self.0 } - let dimensions = match &self.data { - TrackedData::Allay(_) => [0.6, 0.35, 0.6], - TrackedData::ChestBoat(_) => [1.375, 0.5625, 1.375], - TrackedData::Frog(_) => [0.5, 0.5, 0.5], - TrackedData::Tadpole(_) => [0.4, 0.3, 0.4], - TrackedData::Warden(e) => match e.get_pose() { - Pose::Emerging | Pose::Digging => [0.9, 1.0, 0.9], - _ => [0.9, 2.9, 0.9], - }, - TrackedData::AreaEffectCloud(e) => [ - e.get_radius() as f64 * 2.0, - 0.5, - e.get_radius() as f64 * 2.0, - ], - TrackedData::ArmorStand(e) => { - if e.get_marker() { - [0.0, 0.0, 0.0] - } else if e.get_small() { + pub(crate) fn centered(&mut self, size: DVec3) { + self.0 = Aabb::from_bottom_size(DVec3::ZERO, size); + } + + pub(crate) fn in_world(&self, pos: DVec3) -> Aabb { + self.0 + pos + } +} + +impl Hitbox { + pub fn get(&self) -> Aabb { + self.0 + } +} + +fn add_hitbox_component( + settings: Res, + mut commands: Commands, + query: Query<(Entity, &Position), Added>, + alt_query: Query<(Entity, &Position, &HitboxShape), Added>, +) { + if settings.add_hitbox_component { + for (entity, pos) in query.iter() { + commands + .entity(entity) + .insert(HitboxShape::ZERO) + .insert(Hitbox(HitboxShape::ZERO.in_world(pos.0))); + } + } else { + for (entity, pos, hitbox) in alt_query.iter() { + commands + .entity(entity) + .insert(Hitbox(hitbox.in_world(pos.0))); + } + } +} + +fn update_hitbox( + mut hitbox_query: Query< + (&mut Hitbox, &HitboxShape, &Position), + Or<(Changed, Changed)>, + >, +) { + for (mut in_world, hitbox, pos) in hitbox_query.iter_mut() { + in_world.0 = hitbox.in_world(pos.0); + } +} + +fn update_constant_hitbox( + mut hitbox_query: Query< + (&mut HitboxShape, &EntityKind), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, entity_kind) in hitbox_query.iter_mut() { + let size = match *entity_kind { + EntityKind::ALLAY => [0.6, 0.35, 0.6], + EntityKind::CHEST_BOAT | EntityKind::BOAT => [1.375, 0.5625, 1.375], + EntityKind::FROG => [0.5, 0.5, 0.5], + EntityKind::TADPOLE => [0.4, 0.3, 0.4], + EntityKind::SPECTRAL_ARROW | EntityKind::ARROW => [0.5, 0.5, 0.5], + EntityKind::AXOLOTL => [1.3, 0.6, 1.3], + EntityKind::BAT => [0.5, 0.9, 0.5], + EntityKind::BLAZE => [0.6, 1.8, 0.6], + EntityKind::CAT => [0.6, 0.7, 0.6], + EntityKind::CAVE_SPIDER => [0.7, 0.5, 0.7], + EntityKind::COD => [0.5, 0.3, 0.5], + EntityKind::CREEPER => [0.6, 1.7, 0.6], + EntityKind::DOLPHIN => [0.9, 0.6, 0.9], + EntityKind::DRAGON_FIREBALL => [1.0, 1.0, 1.0], + EntityKind::ELDER_GUARDIAN => [1.9975, 1.9975, 1.9975], + EntityKind::END_CRYSTAL => [2.0, 2.0, 2.0], + EntityKind::ENDER_DRAGON => [16.0, 8.0, 16.0], + EntityKind::ENDERMAN => [0.6, 2.9, 0.6], + EntityKind::ENDERMITE => [0.4, 0.3, 0.4], + EntityKind::EVOKER => [0.6, 1.95, 0.6], + EntityKind::EVOKER_FANGS => [0.5, 0.8, 0.5], + EntityKind::EXPERIENCE_ORB => [0.5, 0.5, 0.5], + EntityKind::EYE_OF_ENDER => [0.25, 0.25, 0.25], + EntityKind::FALLING_BLOCK => [0.98, 0.98, 0.98], + EntityKind::FIREWORK_ROCKET => [0.25, 0.25, 0.25], + EntityKind::GHAST => [4.0, 4.0, 4.0], + EntityKind::GIANT => [3.6, 12.0, 3.6], + EntityKind::GLOW_SQUID | EntityKind::SQUID => [0.8, 0.8, 0.8], + EntityKind::GUARDIAN => [0.85, 0.85, 0.85], + EntityKind::ILLUSIONER => [0.6, 1.95, 0.6], + EntityKind::IRON_GOLEM => [1.4, 2.7, 1.4], + EntityKind::ITEM => [0.25, 0.25, 0.25], + EntityKind::FIREBALL => [1.0, 1.0, 1.0], + EntityKind::LEASH_KNOT => [0.375, 0.5, 0.375], + EntityKind::LIGHTNING /* | EntityKind::MARKER - marker hitbox */ => [0.0; 3], + EntityKind::LLAMA_SPIT => [0.25, 0.25, 0.25], + EntityKind::MINECART + | EntityKind::CHEST_MINECART + | EntityKind::TNT_MINECART + | EntityKind::HOPPER_MINECART + | EntityKind::FURNACE_MINECART + | EntityKind::SPAWNER_MINECART + | EntityKind::COMMAND_BLOCK_MINECART => [0.98, 0.7, 0.98], + EntityKind::PARROT => [0.5, 0.9, 0.5], + EntityKind::PHANTOM => [0.9, 0.5, 0.9], + EntityKind::PIGLIN_BRUTE => [0.6, 1.95, 0.6], + EntityKind::PILLAGER => [0.6, 1.95, 0.6], + EntityKind::TNT => [0.98, 0.98, 0.98], + EntityKind::PUFFERFISH => [0.7, 0.7, 0.7], + EntityKind::RAVAGER => [1.95, 2.2, 1.95], + EntityKind::SALMON => [0.7, 0.4, 0.7], + EntityKind::SHULKER_BULLET => [0.3125, 0.3125, 0.3125], + EntityKind::SILVERFISH => [0.4, 0.3, 0.4], + EntityKind::SMALL_FIREBALL => [0.3125, 0.3125, 0.3125], + EntityKind::SNOW_GOLEM => [0.7, 1.9, 0.7], + EntityKind::SPIDER => [1.4, 0.9, 1.4], + EntityKind::STRAY => [0.6, 1.99, 0.6], + EntityKind::EGG => [0.25, 0.25, 0.25], + EntityKind::ENDER_PEARL => [0.25, 0.25, 0.25], + EntityKind::EXPERIENCE_BOTTLE => [0.25, 0.25, 0.25], + EntityKind::POTION => [0.25, 0.25, 0.25], + EntityKind::TRIDENT => [0.5, 0.5, 0.5], + EntityKind::TRADER_LLAMA => [0.9, 1.87, 0.9], + EntityKind::TROPICAL_FISH => [0.5, 0.4, 0.5], + EntityKind::VEX => [0.4, 0.8, 0.4], + EntityKind::VINDICATOR => [0.6, 1.95, 0.6], + EntityKind::WITHER => [0.9, 3.5, 0.9], + EntityKind::WITHER_SKELETON => [0.7, 2.4, 0.7], + EntityKind::WITHER_SKULL => [0.3125, 0.3125, 0.3125], + EntityKind::FISHING_BOBBER => [0.25, 0.25, 0.25], + _ => { + continue; + } + } + .into(); + hitbox.centered(size); + } +} + +fn update_warden_hitbox( + mut query: Query< + (&mut HitboxShape, &entity::Pose), + ( + Or<(Changed, Added)>, + With, + ), + >, +) { + for (mut hitbox, entity_pose) in query.iter_mut() { + hitbox.centered( + match entity_pose.0 { + Pose::Emerging | Pose::Digging => [0.9, 1.0, 0.9], + _ => [0.9, 2.9, 0.9], + } + .into(), + ); + } +} + +fn update_area_effect_cloud_hitbox( + mut query: Query< + (&mut HitboxShape, &area_effect_cloud::Radius), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, cloud_radius) in query.iter_mut() { + let diameter = cloud_radius.0 as f64 * 2.0; + hitbox.centered([diameter, 0.5, diameter].into()); + } +} + +fn update_armor_stand_hitbox( + mut query: Query< + (&mut HitboxShape, &armor_stand::ArmorStandFlags), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, stand_flags) in query.iter_mut() { + hitbox.centered( + if stand_flags.0 & 16 != 0 { + // Marker armor stand + [0.0; 3] + } else if stand_flags.0 & 1 != 0 { + // Small armor stand [0.5, 0.9875, 0.5] } else { [0.5, 1.975, 0.5] } - } - TrackedData::Arrow(_) => [0.5, 0.5, 0.5], - TrackedData::Axolotl(_) => [1.3, 0.6, 1.3], - TrackedData::Bat(_) => [0.5, 0.9, 0.5], - TrackedData::Bee(e) => baby(e.get_child(), [0.7, 0.6, 0.7]), - TrackedData::Blaze(_) => [0.6, 1.8, 0.6], - TrackedData::Boat(_) => [1.375, 0.5625, 1.375], - TrackedData::Camel(e) => baby(e.get_child(), [1.7, 2.375, 1.7]), - TrackedData::Cat(_) => [0.6, 0.7, 0.6], - TrackedData::CaveSpider(_) => [0.7, 0.5, 0.7], - TrackedData::Chicken(e) => baby(e.get_child(), [0.4, 0.7, 0.4]), - TrackedData::Cod(_) => [0.5, 0.3, 0.5], - TrackedData::Cow(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), - TrackedData::Creeper(_) => [0.6, 1.7, 0.6], - TrackedData::Dolphin(_) => [0.9, 0.6, 0.9], - TrackedData::Donkey(e) => baby(e.get_child(), [1.5, 1.39648, 1.5]), - TrackedData::DragonFireball(_) => [1.0, 1.0, 1.0], - TrackedData::Drowned(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], - TrackedData::EndCrystal(_) => [2.0, 2.0, 2.0], - TrackedData::EnderDragon(_) => [16.0, 8.0, 16.0], - TrackedData::Enderman(_) => [0.6, 2.9, 0.6], - TrackedData::Endermite(_) => [0.4, 0.3, 0.4], - TrackedData::Evoker(_) => [0.6, 1.95, 0.6], - TrackedData::EvokerFangs(_) => [0.5, 0.8, 0.5], - TrackedData::ExperienceOrb(_) => [0.5, 0.5, 0.5], - TrackedData::EyeOfEnder(_) => [0.25, 0.25, 0.25], - TrackedData::FallingBlock(_) => [0.98, 0.98, 0.98], - TrackedData::FireworkRocket(_) => [0.25, 0.25, 0.25], - TrackedData::Fox(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), - TrackedData::Ghast(_) => [4.0, 4.0, 4.0], - TrackedData::Giant(_) => [3.6, 12.0, 3.6], - TrackedData::GlowItemFrame(e) => return item_frame(self.position, e.get_rotation()), - TrackedData::GlowSquid(_) => [0.8, 0.8, 0.8], - TrackedData::Goat(e) => { - if e.get_pose() == Pose::LongJumping { - baby(e.get_child(), [0.63, 0.91, 0.63]) - } else { - baby(e.get_child(), [0.9, 1.3, 0.9]) - } - } - TrackedData::Guardian(_) => [0.85, 0.85, 0.85], - TrackedData::Hoglin(e) => baby(e.get_child(), [1.39648, 1.4, 1.39648]), - TrackedData::Horse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Husk(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::Illusioner(_) => [0.6, 1.95, 0.6], - TrackedData::IronGolem(_) => [1.4, 2.7, 1.4], - TrackedData::Item(_) => [0.25, 0.25, 0.25], - TrackedData::ItemFrame(e) => return item_frame(self.position, e.get_rotation()), - TrackedData::Fireball(_) => [1.0, 1.0, 1.0], - TrackedData::LeashKnot(_) => [0.375, 0.5, 0.375], - TrackedData::Lightning(_) => [0.0, 0.0, 0.0], - TrackedData::Llama(e) => baby(e.get_child(), [0.9, 1.87, 0.9]), - TrackedData::LlamaSpit(_) => [0.25, 0.25, 0.25], - TrackedData::MagmaCube(e) => { - let s = 0.5202 * e.get_slime_size() as f64; - [s, s, s] - } - TrackedData::Marker(_) => [0.0, 0.0, 0.0], - TrackedData::Minecart(_) => [0.98, 0.7, 0.98], - TrackedData::ChestMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::FurnaceMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::HopperMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::SpawnerMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::TntMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::Mule(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Mooshroom(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), - TrackedData::Ocelot(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), - TrackedData::Painting(e) => { - let bounds: UVec3 = match e.get_variant() { - PaintingKind::Kebab => [1, 1, 1], - PaintingKind::Aztec => [1, 1, 1], - PaintingKind::Alban => [1, 1, 1], - PaintingKind::Aztec2 => [1, 1, 1], - PaintingKind::Bomb => [1, 1, 1], - PaintingKind::Plant => [1, 1, 1], - PaintingKind::Wasteland => [1, 1, 1], - PaintingKind::Pool => [2, 1, 2], - PaintingKind::Courbet => [2, 1, 2], - PaintingKind::Sea => [2, 1, 2], - PaintingKind::Sunset => [2, 1, 2], - PaintingKind::Creebet => [2, 1, 2], - PaintingKind::Wanderer => [1, 2, 1], - PaintingKind::Graham => [1, 2, 1], - PaintingKind::Match => [2, 2, 2], - PaintingKind::Bust => [2, 2, 2], - PaintingKind::Stage => [2, 2, 2], - PaintingKind::Void => [2, 2, 2], - PaintingKind::SkullAndRoses => [2, 2, 2], - PaintingKind::Wither => [2, 2, 2], - PaintingKind::Fighters => [4, 2, 4], - PaintingKind::Pointer => [4, 4, 4], - PaintingKind::Pigscene => [4, 4, 4], - PaintingKind::BurningSkull => [4, 4, 4], - PaintingKind::Skeleton => [4, 3, 4], - PaintingKind::Earth => [2, 2, 2], - PaintingKind::Wind => [2, 2, 2], - PaintingKind::Water => [2, 2, 2], - PaintingKind::Fire => [2, 2, 2], - PaintingKind::DonkeyKong => [4, 3, 4], - } - .into(); - - let mut center_pos = self.position + 0.5; - - let (facing_x, facing_z, cc_facing_x, cc_facing_z) = - match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { - 0 => (0, 1, 1, 0), // South - 1 => (-1, 0, 0, 1), // West - 2 => (0, -1, -1, 0), // North - _ => (1, 0, 0, -1), // East - }; - - center_pos.x -= facing_x as f64 * 0.46875; - center_pos.z -= facing_z as f64 * 0.46875; - - center_pos.x += cc_facing_x as f64 * if bounds.x % 2 == 0 { 0.5 } else { 0.0 }; - center_pos.y += if bounds.y % 2 == 0 { 0.5 } else { 0.0 }; - center_pos.z += cc_facing_z as f64 * if bounds.z % 2 == 0 { 0.5 } else { 0.0 }; - - let bounds = match (facing_x, facing_z) { - (1, 0) | (-1, 0) => DVec3::new(0.0625, bounds.y as f64, bounds.z as f64), - _ => DVec3::new(bounds.x as f64, bounds.y as f64, 0.0625), - }; - - return Aabb { - min: center_pos - bounds / 2.0, - max: center_pos + bounds / 2.0, - }; - } - TrackedData::Panda(e) => baby(e.get_child(), [1.3, 1.25, 1.3]), - TrackedData::Parrot(_) => [0.5, 0.9, 0.5], - TrackedData::Phantom(_) => [0.9, 0.5, 0.9], - TrackedData::Pig(e) => baby(e.get_child(), [0.9, 0.9, 0.9]), - TrackedData::Piglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::PiglinBrute(_) => [0.6, 1.95, 0.6], - TrackedData::Pillager(_) => [0.6, 1.95, 0.6], - TrackedData::PolarBear(e) => baby(e.get_child(), [1.4, 1.4, 1.4]), - TrackedData::Tnt(_) => [0.98, 0.98, 0.98], - TrackedData::Pufferfish(_) => [0.7, 0.7, 0.7], - TrackedData::Rabbit(e) => baby(e.get_child(), [0.4, 0.5, 0.4]), - TrackedData::Ravager(_) => [1.95, 2.2, 1.95], - TrackedData::Salmon(_) => [0.7, 0.4, 0.7], - TrackedData::Sheep(e) => baby(e.get_child(), [0.9, 1.3, 0.9]), - TrackedData::Shulker(e) => { - const PI: f64 = std::f64::consts::PI; - - let pos = self.position + 0.5; - let mut min = pos - 0.5; - let mut max = pos + 0.5; - - let peek = 0.5 - f64::cos(e.get_peek_amount() as f64 * 0.01 * PI) * 0.5; - - match e.get_attached_face() { - Facing::Down => max.y += peek, - Facing::Up => min.y -= peek, - Facing::North => max.z += peek, - Facing::South => min.z -= peek, - Facing::West => max.x += peek, - Facing::East => min.x -= peek, - } - - return Aabb { min, max }; - } - TrackedData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], - TrackedData::Silverfish(_) => [0.4, 0.3, 0.4], - TrackedData::Skeleton(_) => [0.6, 1.99, 0.6], - TrackedData::SkeletonHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Slime(e) => { - let s = 0.5202 * e.get_slime_size() as f64; - [s, s, s] - } - TrackedData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], - TrackedData::SnowGolem(_) => [0.7, 1.9, 0.7], - TrackedData::Snowball(_) => [0.25, 0.25, 0.25], - TrackedData::SpectralArrow(_) => [0.5, 0.5, 0.5], - TrackedData::Spider(_) => [1.4, 0.9, 1.4], - TrackedData::Squid(_) => [0.8, 0.8, 0.8], - TrackedData::Stray(_) => [0.6, 1.99, 0.6], - TrackedData::Strider(e) => baby(e.get_child(), [0.9, 1.7, 0.9]), - TrackedData::Egg(_) => [0.25, 0.25, 0.25], - TrackedData::EnderPearl(_) => [0.25, 0.25, 0.25], - TrackedData::ExperienceBottle(_) => [0.25, 0.25, 0.25], - TrackedData::Potion(_) => [0.25, 0.25, 0.25], - TrackedData::Trident(_) => [0.5, 0.5, 0.5], - TrackedData::TraderLlama(_) => [0.9, 1.87, 0.9], - TrackedData::TropicalFish(_) => [0.5, 0.4, 0.5], - TrackedData::Turtle(e) => { - if e.get_child() { - [0.36, 0.12, 0.36] - } else { - [1.2, 0.4, 1.2] - } - } - TrackedData::Vex(_) => [0.4, 0.8, 0.4], - TrackedData::Villager(e) => baby(e.get_child(), [0.6, 1.95, 0.6]), - TrackedData::Vindicator(_) => [0.6, 1.95, 0.6], - TrackedData::WanderingTrader(_) => [0.6, 1.95, 0.6], - TrackedData::Witch(_) => [0.6, 1.95, 0.6], - TrackedData::Wither(_) => [0.9, 3.5, 0.9], - TrackedData::WitherSkeleton(_) => [0.7, 2.4, 0.7], - TrackedData::WitherSkull(_) => [0.3125, 0.3125, 0.3125], - TrackedData::Wolf(e) => baby(e.get_child(), [0.6, 0.85, 0.6]), - TrackedData::Zoglin(e) => baby(e.get_baby(), [1.39648, 1.4, 1.39648]), - TrackedData::Zombie(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ZombieHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::ZombieVillager(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ZombifiedPiglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::Player(e) => match e.get_pose() { - Pose::Standing => [0.6, 1.8, 0.6], - Pose::Sleeping => [0.2, 0.2, 0.2], - Pose::FallFlying => [0.6, 0.6, 0.6], - Pose::Swimming => [0.6, 0.6, 0.6], - Pose::SpinAttack => [0.6, 0.6, 0.6], - Pose::Sneaking => [0.6, 1.5, 0.6], - Pose::Dying => [0.2, 0.2, 0.2], - _ => [0.6, 1.8, 0.6], - }, - TrackedData::FishingBobber(_) => [0.25, 0.25, 0.25], - }; - - Aabb::from_bottom_size(self.position, dimensions) + .into(), + ); + } +} + +fn child_hitbox(child: bool, v: DVec3) -> DVec3 { + if child { + v / 2.0 + } else { + v + } +} + +fn update_passive_child_hitbox( + mut query: Query< + (Entity, &mut HitboxShape, &EntityKind, &passive::Child), + Or<(Changed, Added)>, + >, + pose_query: Query<&entity::Pose>, +) { + for (entity, mut hitbox, entity_kind, child) in query.iter_mut() { + let big_s = match *entity_kind { + EntityKind::BEE => [0.7, 0.6, 0.7], + EntityKind::CAMEL => [1.7, 2.375, 1.7], + EntityKind::CHICKEN => [0.4, 0.7, 0.4], + EntityKind::DONKEY => [1.5, 1.39648, 1.5], + EntityKind::FOX => [0.6, 0.7, 0.6], + EntityKind::GOAT => { + if pose_query + .get(entity) + .map_or(false, |v| v.0 == Pose::LongJumping) + { + [0.63, 0.91, 0.63] + } else { + [0.9, 1.3, 0.9] + } + } + EntityKind::HOGLIN => [1.39648, 1.4, 1.39648], + EntityKind::HORSE | EntityKind::SKELETON_HORSE | EntityKind::ZOMBIE_HORSE => { + [1.39648, 1.6, 1.39648] + } + EntityKind::LLAMA => [0.9, 1.87, 0.9], + EntityKind::MULE => [1.39648, 1.6, 1.39648], + EntityKind::MOOSHROOM => [0.9, 1.4, 0.9], + EntityKind::OCELOT => [0.6, 0.7, 0.6], + EntityKind::PANDA => [1.3, 1.25, 1.3], + EntityKind::PIG => [0.9, 0.9, 0.9], + EntityKind::POLAR_BEAR => [1.4, 1.4, 1.4], + EntityKind::RABBIT => [0.4, 0.5, 0.4], + EntityKind::SHEEP => [0.9, 1.3, 0.9], + EntityKind::TURTLE => { + hitbox.centered( + if child.0 { + [0.36, 0.12, 0.36] + } else { + [1.2, 0.4, 1.2] + } + .into(), + ); + continue; + } + EntityKind::VILLAGER => [0.6, 1.95, 0.6], + EntityKind::WOLF => [0.6, 0.85, 0.6], + _ => { + continue; + } + }; + hitbox.centered(child_hitbox(child.0, big_s.into())); + } +} + +fn update_zombie_hitbox( + mut query: Query< + (&mut HitboxShape, &zombie::Baby), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, baby) in query.iter_mut() { + hitbox.centered(child_hitbox(baby.0, [0.6, 1.95, 0.6].into())); + } +} + +fn update_piglin_hitbox( + mut query: Query< + (&mut HitboxShape, &piglin::Baby), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, baby) in query.iter_mut() { + hitbox.centered(child_hitbox(baby.0, [0.6, 1.95, 0.6].into())); + } +} + +fn update_zoglin_hitbox( + mut query: Query< + (&mut HitboxShape, &zoglin::Baby), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, baby) in query.iter_mut() { + hitbox.centered(child_hitbox(baby.0, [1.39648, 1.4, 1.39648].into())); + } +} + +fn update_player_hitbox( + mut query: Query< + (&mut HitboxShape, &entity::Pose), + ( + Or<(Changed, Added)>, + With, + ), + >, +) { + for (mut hitbox, pose) in query.iter_mut() { + hitbox.centered( + match pose.0 { + Pose::Sleeping | Pose::Dying => [0.2, 0.2, 0.2], + Pose::FallFlying | Pose::Swimming | Pose::SpinAttack => [0.6, 0.6, 0.6], + Pose::Sneaking => [0.6, 1.5, 0.6], + _ => [0.6, 1.8, 0.6], + } + .into(), + ); + } +} + +fn update_item_frame_hitbox( + mut query: Query< + (&mut HitboxShape, &item_frame::Rotation), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, rotation) in query.iter_mut() { + let mut center_pos = DVec3::splat(0.5); + + const A: f64 = 0.46875; + + match rotation.0 { + 0 => center_pos.y += A, + 1 => center_pos.y -= A, + 2 => center_pos.z += A, + 3 => center_pos.z -= A, + 4 => center_pos.x += A, + 5 => center_pos.x -= A, + _ => center_pos.y -= A, + } + + const BOUNDS23: DVec3 = DVec3::new(0.375, 0.375, 0.03125); + + let bounds = match rotation.0 { + 2 | 3 => BOUNDS23, + 4 | 5 => BOUNDS23.zxy(), + _ => BOUNDS23.zxy(), + }; + + hitbox.0 = Aabb { + min: center_pos - bounds, + max: center_pos + bounds, + } + } +} + +fn update_slime_hitbox( + mut query: Query< + (&mut HitboxShape, &slime::SlimeSize), + Or<(Changed, Added)>, + >, +) { + for (mut hitbox, slime_size) in query.iter_mut() { + let s = 0.5202 * slime_size.0 as f64; + hitbox.centered([s, s, s].into()); + } +} + +fn update_painting_hitbox( + mut query: Query< + (&mut HitboxShape, &painting::Variant, &Look), + Or<( + Changed, + Changed, + Added, + )>, + >, +) { + for (mut hitbox, painting_variant, look) in query.iter_mut() { + let bounds: UVec3 = match painting_variant.0 { + PaintingKind::Kebab => [1, 1, 1], + PaintingKind::Aztec => [1, 1, 1], + PaintingKind::Alban => [1, 1, 1], + PaintingKind::Aztec2 => [1, 1, 1], + PaintingKind::Bomb => [1, 1, 1], + PaintingKind::Plant => [1, 1, 1], + PaintingKind::Wasteland => [1, 1, 1], + PaintingKind::Pool => [2, 1, 2], + PaintingKind::Courbet => [2, 1, 2], + PaintingKind::Sea => [2, 1, 2], + PaintingKind::Sunset => [2, 1, 2], + PaintingKind::Creebet => [2, 1, 2], + PaintingKind::Wanderer => [1, 2, 1], + PaintingKind::Graham => [1, 2, 1], + PaintingKind::Match => [2, 2, 2], + PaintingKind::Bust => [2, 2, 2], + PaintingKind::Stage => [2, 2, 2], + PaintingKind::Void => [2, 2, 2], + PaintingKind::SkullAndRoses => [2, 2, 2], + PaintingKind::Wither => [2, 2, 2], + PaintingKind::Fighters => [4, 2, 4], + PaintingKind::Pointer => [4, 4, 4], + PaintingKind::Pigscene => [4, 4, 4], + PaintingKind::BurningSkull => [4, 4, 4], + PaintingKind::Skeleton => [4, 3, 4], + PaintingKind::Earth => [2, 2, 2], + PaintingKind::Wind => [2, 2, 2], + PaintingKind::Water => [2, 2, 2], + PaintingKind::Fire => [2, 2, 2], + PaintingKind::DonkeyKong => [4, 3, 4], + } + .into(); + + let mut center_pos = DVec3::splat(0.5); + + let (facing_x, facing_z, cc_facing_x, cc_facing_z) = + match ((look.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { + 0 => (0, 1, 1, 0), // South + 1 => (-1, 0, 0, 1), // West + 2 => (0, -1, -1, 0), // North + _ => (1, 0, 0, -1), // East + }; + + center_pos.x -= facing_x as f64 * 0.46875; + center_pos.z -= facing_z as f64 * 0.46875; + + center_pos.x += cc_facing_x as f64 * if bounds.x % 2 == 0 { 0.5 } else { 0.0 }; + center_pos.y += if bounds.y % 2 == 0 { 0.5 } else { 0.0 }; + center_pos.z += cc_facing_z as f64 * if bounds.z % 2 == 0 { 0.5 } else { 0.0 }; + + let bounds = match (facing_x, facing_z) { + (1, 0) | (-1, 0) => DVec3::new(0.0625, bounds.y as f64, bounds.z as f64), + _ => DVec3::new(bounds.x as f64, bounds.y as f64, 0.0625), + }; + + hitbox.0 = Aabb { + min: center_pos - bounds / 2.0, + max: center_pos + bounds / 2.0, + }; + } +} + +fn update_shulker_hitbox( + mut query: Query< + ( + &mut HitboxShape, + &shulker::PeekAmount, + &shulker::AttachedFace, + ), + Or<( + Changed, + Changed, + Added, + )>, + >, +) { + use std::f64::consts::PI; + + for (mut hitbox, peek_amount, attached_face) in query.iter_mut() { + let pos = DVec3::splat(0.5); + let mut min = pos - 0.5; + let mut max = pos + 0.5; + + let peek = 0.5 - f64::cos(peek_amount.0 as f64 * 0.01 * PI) * 0.5; + + match attached_face.0 { + Direction::Down => max.y += peek, + Direction::Up => min.y -= peek, + Direction::North => max.z += peek, + Direction::South => min.z -= peek, + Direction::West => max.x += peek, + Direction::East => min.x -= peek, + } + + hitbox.0 = Aabb { min, max }; + } } -*/ \ No newline at end of file diff --git a/crates/valence_entity/src/lib.rs b/crates/valence_entity/src/lib.rs index 0d35823..d01ba5f 100644 --- a/crates/valence_entity/src/lib.rs +++ b/crates/valence_entity/src/lib.rs @@ -17,6 +17,8 @@ clippy::dbg_macro )] +pub mod hitbox; + use std::num::Wrapping; use std::ops::Range; @@ -35,7 +37,6 @@ use valence_core::uuid::UniqueId; use valence_core::DEFAULT_TPS; include!(concat!(env!("OUT_DIR"), "/entity.rs")); - pub struct EntityPlugin; /// When new Minecraft entities are initialized and added to