From 9ec1df197868883eeb242b61792426b224bfad75 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Sat, 17 Sep 2022 23:14:48 -0700 Subject: [PATCH] Entity Hitbox Improvements (#70) Solves #45. With help from @guac420 --- examples/entity_raycast.rs | 417 +++++++++++++++++++++++++++++++++++++ examples/raycast.rs | 206 ------------------ src/entity.rs | 262 ++++++++++++++++++----- src/entity/types.rs | 31 ++- 4 files changed, 658 insertions(+), 258 deletions(-) create mode 100644 examples/entity_raycast.rs delete mode 100644 examples/raycast.rs diff --git a/examples/entity_raycast.rs b/examples/entity_raycast.rs new file mode 100644 index 0000000..450baa1 --- /dev/null +++ b/examples/entity_raycast.rs @@ -0,0 +1,417 @@ +use std::net::SocketAddr; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use log::LevelFilter; +use uuid::Uuid; +use valence::async_trait; +use valence::block::{BlockPos, BlockState}; +use valence::chunk::UnloadedChunk; +use valence::client::{handle_event_default, GameMode}; +use valence::config::{Config, ServerListPing}; +use valence::dimension::DimensionId; +use valence::entity::types::{Facing, PaintingKind, Pose}; +use valence::entity::{EntityId, EntityKind, TrackedData}; +use valence::player_list::PlayerListId; +use valence::server::{Server, SharedServer, ShutdownResult}; +use valence::spatial_index::RaycastHit; +use valence::text::{Color, TextFormat}; +use valence::util::from_yaw_and_pitch; +use vek::Vec3; + +pub fn main() -> ShutdownResult { + env_logger::Builder::new() + .filter_module("valence", LevelFilter::Trace) + .parse_default_env() + .init(); + + valence::start_server( + Game { + player_count: AtomicUsize::new(0), + }, + None, + ) +} + +struct Game { + player_count: AtomicUsize, +} + +#[derive(Default)] +struct ClientState { + player: EntityId, + shulker_bullet: EntityId, +} + +const MAX_PLAYERS: usize = 10; + +const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -5); + +const PLAYER_EYE_HEIGHT: f64 = 1.62; + +// TODO +// const PLAYER_SNEAKING_EYE_HEIGHT: f64 = 1.495; + +#[async_trait] +impl Config for Game { + type ServerState = Option; + type ClientState = ClientState; + type EntityState = (); + type WorldState = (); + type ChunkState = (); + type PlayerListState = (); + + fn max_connections(&self) -> usize { + // We want status pings to be successful even if the server is full. + MAX_PLAYERS + 64 + } + + async fn server_list_ping( + &self, + _server: &SharedServer, + _remote_addr: SocketAddr, + _protocol_version: i32, + ) -> ServerListPing { + ServerListPing::Respond { + online_players: self.player_count.load(Ordering::SeqCst) as i32, + max_players: MAX_PLAYERS as i32, + player_sample: Default::default(), + description: "Hello Valence!".color(Color::AQUA), + favicon_png: Some(include_bytes!("../assets/logo-64x64.png").as_slice().into()), + } + } + + fn init(&self, server: &mut Server) { + let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); + let (player_list_id, player_list) = server.player_lists.insert(()); + server.state = Some(player_list_id); + + let size = 5; + for z in -size..size { + for x in -size..size { + world.chunks.insert([x, z], UnloadedChunk::default(), ()); + } + } + + world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK); + + // ==== Item Frames ==== // + let (_, e) = server.entities.insert(EntityKind::ItemFrame, ()); + if let TrackedData::ItemFrame(i) = e.data_mut() { + i.set_rotation(Facing::North as i32); + } + e.set_world(world_id); + e.set_position([2.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::ItemFrame, ()); + if let TrackedData::ItemFrame(i) = e.data_mut() { + i.set_rotation(Facing::East as i32); + } + e.set_world(world_id); + e.set_position([3.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::GlowItemFrame, ()); + if let TrackedData::GlowItemFrame(i) = e.data_mut() { + i.set_rotation(Facing::South as i32); + } + e.set_world(world_id); + e.set_position([4.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::GlowItemFrame, ()); + if let TrackedData::GlowItemFrame(i) = e.data_mut() { + i.set_rotation(Facing::West as i32); + } + e.set_world(world_id); + e.set_position([5.0, 102.0, 0.0]); + + // ==== Paintings ==== // + let (_, e) = server.entities.insert(EntityKind::Painting, ()); + if let TrackedData::Painting(p) = e.data_mut() { + p.set_variant(PaintingKind::Pigscene); + } + e.set_world(world_id); + e.set_yaw(180.0); + e.set_position([0.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::Painting, ()); + if let TrackedData::Painting(p) = e.data_mut() { + p.set_variant(PaintingKind::DonkeyKong); + } + e.set_world(world_id); + e.set_yaw(90.0); + e.set_position([-4.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::Painting, ()); + if let TrackedData::Painting(p) = e.data_mut() { + p.set_variant(PaintingKind::Void); + } + e.set_world(world_id); + e.set_position([-6.0, 102.0, 0.0]); + + let (_, e) = server.entities.insert(EntityKind::Painting, ()); + if let TrackedData::Painting(p) = e.data_mut() { + p.set_variant(PaintingKind::Aztec); + } + e.set_yaw(270.0); + e.set_world(world_id); + e.set_position([-7.0, 102.0, 0.0]); + + // ==== Shulkers ==== // + let (_, e) = server.entities.insert(EntityKind::Shulker, ()); + if let TrackedData::Shulker(s) = e.data_mut() { + s.set_peek_amount(100); + s.set_attached_face(Facing::West); + } + e.set_world(world_id); + e.set_position([-4.0, 102.0, -8.0]); + + let (_, e) = server.entities.insert(EntityKind::Shulker, ()); + if let TrackedData::Shulker(s) = e.data_mut() { + s.set_peek_amount(75); + s.set_attached_face(Facing::Up); + } + e.set_world(world_id); + e.set_position([-1.0, 102.0, -8.0]); + + let (_, e) = server.entities.insert(EntityKind::Shulker, ()); + if let TrackedData::Shulker(s) = e.data_mut() { + s.set_peek_amount(50); + s.set_attached_face(Facing::Down); + } + e.set_world(world_id); + e.set_position([2.0, 102.0, -8.0]); + + let (_, e) = server.entities.insert(EntityKind::Shulker, ()); + if let TrackedData::Shulker(s) = e.data_mut() { + s.set_peek_amount(25); + s.set_attached_face(Facing::East); + } + e.set_world(world_id); + e.set_position([5.0, 102.0, -8.0]); + + let (_, e) = server.entities.insert(EntityKind::Shulker, ()); + if let TrackedData::Shulker(s) = e.data_mut() { + s.set_peek_amount(0); + s.set_attached_face(Facing::North); + } + e.set_world(world_id); + e.set_position([8.0, 102.0, -8.0]); + + // ==== Slimes ==== // + let (_, e) = server.entities.insert(EntityKind::Slime, ()); + if let TrackedData::Slime(s) = e.data_mut() { + s.set_slime_size(30); + } + e.set_world(world_id); + e.set_yaw(180.0); + e.set_head_yaw(180.0); + e.set_position([12.0, 102.0, 10.0]); + + let (_, e) = server.entities.insert(EntityKind::MagmaCube, ()); + if let TrackedData::MagmaCube(m) = e.data_mut() { + m.set_slime_size(30); + } + e.set_world(world_id); + e.set_yaw(180.0); + e.set_head_yaw(180.0); + e.set_position([-12.0, 102.0, 10.0]); + + // ==== Sheep ==== // + let (_, e) = server.entities.insert(EntityKind::Sheep, ()); + if let TrackedData::Sheep(s) = e.data_mut() { + s.set_color(6); + s.set_child(true); + } + e.set_world(world_id); + e.set_position([-5.0, 101.0, -4.5]); + e.set_yaw(270.0); + e.set_head_yaw(270.0); + + let (_, e) = server.entities.insert(EntityKind::Sheep, ()); + if let TrackedData::Sheep(s) = e.data_mut() { + s.set_color(6); + } + e.set_world(world_id); + e.set_position([5.0, 101.0, -4.5]); + e.set_yaw(90.0); + e.set_head_yaw(90.0); + + // ==== Players ==== // + let player_poses = [ + Pose::Standing, + Pose::Sneaking, + Pose::FallFlying, + Pose::Sleeping, + Pose::Swimming, + Pose::SpinAttack, + Pose::Dying, + ]; + + for (i, pose) in player_poses.into_iter().enumerate() { + player_list.insert( + Uuid::from_u128(i as u128), + format!("fake_player_{i}"), + None, + GameMode::Survival, + 0, + None, + ); + + let (_, e) = server + .entities + .insert_with_uuid(EntityKind::Player, Uuid::from_u128(i as u128), ()) + .unwrap(); + if let TrackedData::Player(p) = e.data_mut() { + p.set_pose(pose); + } + e.set_world(world_id); + e.set_position([-3.0 + i as f64 * 2.0, 104.0, -9.0]); + } + + // ==== Warden ==== // + let (_, e) = server.entities.insert(EntityKind::Warden, ()); + e.set_world(world_id); + e.set_position([-7.0, 102.0, -4.5]); + e.set_yaw(270.0); + e.set_head_yaw(270.0); + + let (_, e) = server.entities.insert(EntityKind::Warden, ()); + if let TrackedData::Warden(w) = e.data_mut() { + w.set_pose(Pose::Emerging); + } + e.set_world(world_id); + e.set_position([-7.0, 102.0, -6.5]); + e.set_yaw(270.0); + e.set_head_yaw(270.0); + + // ==== Goat ==== // + let (_, e) = server.entities.insert(EntityKind::Goat, ()); + e.set_world(world_id); + e.set_position([5.0, 103.0, -4.5]); + e.set_yaw(270.0); + e.set_head_yaw(90.0); + + let (_, e) = server.entities.insert(EntityKind::Goat, ()); + if let TrackedData::Goat(g) = e.data_mut() { + g.set_pose(Pose::LongJumping); + } + e.set_world(world_id); + e.set_position([5.0, 103.0, -3.5]); + e.set_yaw(270.0); + e.set_head_yaw(90.0); + } + + fn update(&self, server: &mut Server) { + let (world_id, world) = server.worlds.iter_mut().next().unwrap(); + + server.clients.retain(|_, client| { + if client.created_this_tick() { + if self + .player_count + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { + (count < MAX_PLAYERS).then_some(count + 1) + }) + .is_err() + { + client.disconnect("The server is full!".color(Color::RED)); + return false; + } + + match server + .entities + .insert_with_uuid(EntityKind::Player, client.uuid(), ()) + { + Some((id, _)) => client.state.player = id, + None => { + client.disconnect("Conflicting UUID"); + return false; + } + } + + client.spawn(world_id); + client.set_flat(true); + client.set_game_mode(GameMode::Creative); + client.teleport( + [ + SPAWN_POS.x as f64 + 0.5, + SPAWN_POS.y as f64 + 1.0, + SPAWN_POS.z as f64 + 0.5, + ], + 0.0, + 0.0, + ); + client.set_player_list(server.state.clone()); + + if let Some(id) = &server.state { + server.player_lists.get_mut(id).insert( + client.uuid(), + client.username(), + client.textures().cloned(), + client.game_mode(), + 0, + None, + ); + } + + client.send_message( + "Press ".italic() + + "F3 + B".italic().color(Color::AQUA) + + " to show hitboxes.".italic(), + ); + } + + if client.is_disconnected() { + self.player_count.fetch_sub(1, Ordering::SeqCst); + if let Some(id) = &server.state { + server.player_lists.get_mut(id).remove(client.uuid()); + } + server.entities.remove(client.state.player); + + return false; + } + + let client_pos = client.position(); + + let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z); + let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64); + let not_self_or_bullet = |hit: &RaycastHit| { + hit.entity != client.state.player && hit.entity != client.state.shulker_bullet + }; + + if let Some(hit) = world + .spatial_index + .raycast(origin, direction, not_self_or_bullet) + { + let bullet = + if let Some(bullet) = server.entities.get_mut(client.state.shulker_bullet) { + bullet + } else { + let (id, bullet) = server.entities.insert(EntityKind::ShulkerBullet, ()); + client.state.shulker_bullet = id; + bullet.set_world(world_id); + bullet + }; + + let mut hit_pos = origin + direction * hit.near; + let hitbox = bullet.hitbox(); + + hit_pos.y -= (hitbox.max.y - hitbox.min.y) / 2.0; + + bullet.set_position(hit_pos); + + client.set_action_bar("Intersection".color(Color::GREEN)); + } else { + server.entities.remove(client.state.shulker_bullet); + client.set_action_bar("No Intersection".color(Color::RED)); + } + + while handle_event_default( + client, + server.entities.get_mut(client.state.player).unwrap(), + ) + .is_some() + {} + + true + }); + } +} diff --git a/examples/raycast.rs b/examples/raycast.rs deleted file mode 100644 index 50e4ef9..0000000 --- a/examples/raycast.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::net::SocketAddr; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use log::LevelFilter; -use valence::async_trait; -use valence::block::{BlockPos, BlockState}; -use valence::chunk::UnloadedChunk; -use valence::client::{handle_event_default, GameMode}; -use valence::config::{Config, ServerListPing}; -use valence::dimension::DimensionId; -use valence::entity::{EntityId, EntityKind, TrackedData}; -use valence::player_list::PlayerListId; -use valence::server::{Server, SharedServer, ShutdownResult}; -use valence::spatial_index::RaycastHit; -use valence::text::{Color, TextFormat}; -use valence::util::from_yaw_and_pitch; -use vek::Vec3; - -pub fn main() -> ShutdownResult { - env_logger::Builder::new() - .filter_module("valence", LevelFilter::Trace) - .parse_default_env() - .init(); - - valence::start_server( - Game { - player_count: AtomicUsize::new(0), - }, - None, - ) -} - -struct Game { - player_count: AtomicUsize, -} - -const MAX_PLAYERS: usize = 10; - -const SPAWN_POS: BlockPos = BlockPos::new(0, 100, -5); - -const PLAYER_EYE_HEIGHT: f64 = 1.62; - -// TODO -// const PLAYER_SNEAKING_EYE_HEIGHT: f64 = 1.495; - -#[async_trait] -impl Config for Game { - type ServerState = Option; - type ClientState = EntityId; - /// `true` for entities that have been intersected with. - type EntityState = bool; - type WorldState = (); - type ChunkState = (); - type PlayerListState = (); - - fn max_connections(&self) -> usize { - // We want status pings to be successful even if the server is full. - MAX_PLAYERS + 64 - } - - async fn server_list_ping( - &self, - _server: &SharedServer, - _remote_addr: SocketAddr, - _protocol_version: i32, - ) -> ServerListPing { - ServerListPing::Respond { - online_players: self.player_count.load(Ordering::SeqCst) as i32, - max_players: MAX_PLAYERS as i32, - player_sample: Default::default(), - description: "Hello Valence!".color(Color::AQUA), - favicon_png: Some(include_bytes!("../assets/logo-64x64.png").as_slice().into()), - } - } - - fn init(&self, server: &mut Server) { - let (world_id, world) = server.worlds.insert(DimensionId::default(), ()); - server.state = Some(server.player_lists.insert(()).0); - - let size = 5; - for z in -size..size { - for x in -size..size { - world.chunks.insert([x, z], UnloadedChunk::default(), ()); - } - } - - world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK); - - const SHEEP_COUNT: usize = 10; - for i in 0..SHEEP_COUNT { - let offset = (i as f64 - (SHEEP_COUNT - 1) as f64 / 2.0) * 1.25; - - let (_, sheep) = server.entities.insert(EntityKind::Sheep, false); - sheep.set_world(world_id); - sheep.set_position([offset + 0.5, SPAWN_POS.y as f64 + 1.0, 0.0]); - sheep.set_yaw(180.0); - sheep.set_head_yaw(180.0); - } - } - - fn update(&self, server: &mut Server) { - let (world_id, world) = server.worlds.iter_mut().next().unwrap(); - - server.clients.retain(|_, client| { - if client.created_this_tick() { - if self - .player_count - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| { - (count < MAX_PLAYERS).then_some(count + 1) - }) - .is_err() - { - client.disconnect("The server is full!".color(Color::RED)); - return false; - } - - match server - .entities - .insert_with_uuid(EntityKind::Player, client.uuid(), false) - { - Some((id, _)) => client.state = id, - None => { - client.disconnect("Conflicting UUID"); - return false; - } - } - - client.spawn(world_id); - client.set_flat(true); - client.set_game_mode(GameMode::Creative); - client.teleport( - [ - SPAWN_POS.x as f64 + 0.5, - SPAWN_POS.y as f64 + 1.0, - SPAWN_POS.z as f64 + 0.5, - ], - 0.0, - 0.0, - ); - client.set_player_list(server.state.clone()); - - if let Some(id) = &server.state { - server.player_lists.get_mut(id).insert( - client.uuid(), - client.username(), - client.textures().cloned(), - client.game_mode(), - 0, - None, - ); - } - - client.send_message( - "Look at a sheep to change its ".italic() - + "color".italic().color(Color::GREEN) - + ".", - ); - } - - if client.is_disconnected() { - self.player_count.fetch_sub(1, Ordering::SeqCst); - if let Some(id) = &server.state { - server.player_lists.get_mut(id).remove(client.uuid()); - } - server.entities.remove(client.state); - - return false; - } - - let client_pos = client.position(); - - let origin = Vec3::new(client_pos.x, client_pos.y + PLAYER_EYE_HEIGHT, client_pos.z); - let direction = from_yaw_and_pitch(client.yaw() as f64, client.pitch() as f64); - let only_sheep = |hit: &RaycastHit| { - server - .entities - .get(hit.entity) - .map_or(false, |e| e.kind() == EntityKind::Sheep) - }; - - if let Some(hit) = world.spatial_index.raycast(origin, direction, only_sheep) { - if let Some(e) = server.entities.get_mut(hit.entity) { - e.state = true; - } - } - - while handle_event_default(client, server.entities.get_mut(client.state).unwrap()) - .is_some() - {} - - true - }); - - for (_, e) in server.entities.iter_mut() { - let intersected = e.state; - if let TrackedData::Sheep(sheep) = &mut e.data_mut() { - if intersected { - sheep.set_color(5); - } else { - sheep.set_color(0); - } - } - e.state = false; - } - } -} diff --git a/src/entity.rs b/src/entity.rs index 4b65a2f..f40a707 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use vek::{Aabb, Vec3}; use crate::config::Config; +use crate::entity::types::{Facing, PaintingKind, Pose}; use crate::protocol::packets::s2c::play::{ S2cPlayPacket, SetEntityMetadata, SpawnEntity, SpawnExperienceOrb, SpawnPlayer, }; @@ -422,12 +423,49 @@ impl Entity { /// /// [interact event]: crate::client::ClientEvent::InteractWithEntity pub fn hitbox(&self) -> Aabb { - let dims = match &self.variants { + fn baby(is_baby: bool, adult_hitbox: [f64; 3]) -> [f64; 3] { + if is_baby { + adult_hitbox.map(|a| a / 2.0) + } else { + adult_hitbox + } + } + + fn item_frame(pos: Vec3, rotation: i32) -> Aabb { + let mut center_pos = pos + 0.5; + + 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, + }; + + let bounds = Vec3::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], + }); + + Aabb { + min: center_pos - bounds / 2.0, + max: center_pos + bounds / 2.0, + } + } + + let dimensions = match &self.variants { 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(_) => [0.9, 2.9, 0.9], + 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, @@ -445,19 +483,19 @@ impl Entity { 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(_) => [0.7, 0.6, 0.7], // TODO: baby size? + 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::Cat(_) => [0.6, 0.7, 0.6], TrackedData::CaveSpider(_) => [0.7, 0.5, 0.7], - TrackedData::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size? + TrackedData::Chicken(e) => baby(e.get_child(), [0.4, 0.7, 0.4]), TrackedData::Cod(_) => [0.5, 0.3, 0.5], - TrackedData::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size? + 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(_) => [1.5, 1.39648, 1.5], // TODO: baby size? + TrackedData::Donkey(e) => baby(e.get_child(), [1.5, 1.39648, 1.5]), TrackedData::DragonFireball(_) => [1.0, 1.0, 1.0], - TrackedData::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size? + 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], @@ -469,27 +507,35 @@ impl Entity { 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(_) => [0.6, 0.7, 0.6], // TODO: baby size? + 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(_) => todo!("account for rotation"), + TrackedData::GlowItemFrame(e) => { + return item_frame(self.new_position, e.get_rotation()) + } TrackedData::GlowSquid(_) => [0.8, 0.8, 0.8], - TrackedData::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size? + 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(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? - TrackedData::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - TrackedData::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size? + 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(_) => todo!("account for rotation"), + TrackedData::ItemFrame(e) => return item_frame(self.new_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(_) => [0.9, 1.87, 0.9], // TODO: baby size? + 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 = e.get_slime_size() as f64 * 0.51000005; + let s = 0.5202 * e.get_slime_size() as f64; [s, s, s] } TrackedData::Marker(_) => [0.0, 0.0, 0.0], @@ -500,31 +546,111 @@ impl Entity { 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(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - TrackedData::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size? - TrackedData::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size? - TrackedData::Painting(_) => todo!("account for rotation and type"), - TrackedData::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size? + 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: Vec3 = 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.new_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) => bounds.as_().with_x(0.0625), + _ => bounds.as_().with_z(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(_) => [0.9, 0.9, 0.9], // TODO: baby size? - TrackedData::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? + 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(_) => [1.4, 1.4, 1.4], // TODO: baby size? + 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(_) => [0.4, 0.5, 0.4], // TODO: baby size? + 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(_) => [0.9, 1.3, 0.9], // TODO: baby size? - TrackedData::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated? + 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.new_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(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? + TrackedData::SkeletonHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), TrackedData::Slime(e) => { - let s = 0.51000005 * e.get_slime_size() as f64; + let s = 0.5202 * e.get_slime_size() as f64; [s, s, s] } TrackedData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], @@ -534,7 +660,7 @@ impl Entity { 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(_) => [0.9, 1.7, 0.9], // TODO: baby size? + 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], @@ -542,26 +668,41 @@ impl Entity { 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(_) => [1.2, 0.4, 1.2], // TODO: baby size? + 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(_) => [0.6, 1.95, 0.6], // TODO: baby size? + 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(_) => [0.6, 0.85, 0.6], // TODO: baby size? - TrackedData::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size? - TrackedData::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size? - TrackedData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size? - TrackedData::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size? - TrackedData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size? - TrackedData::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose. + 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() { + types::Pose::Standing => [0.6, 1.8, 0.6], + types::Pose::Sleeping => [0.2, 0.2, 0.2], + types::Pose::FallFlying => [0.6, 0.6, 0.6], + types::Pose::Swimming => [0.6, 0.6, 0.6], + types::Pose::SpinAttack => [0.6, 0.6, 0.6], + types::Pose::Sneaking => [0.6, 1.5, 0.6], + types::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_and_size(self.new_position, dims.into()) + aabb_from_bottom_and_size(self.new_position, dimensions.into()) } /// Gets the tracked data packet to send to clients after this entity has @@ -597,6 +738,20 @@ impl Entity { } pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option { + let with_object_data = |data| { + Some(EntitySpawnPacket::Entity(SpawnEntity { + entity_id: VarInt(this_id.to_network_id()), + object_uuid: self.uuid, + kind: VarInt(self.kind() as i32), + position: self.new_position, + pitch: ByteAngle::from_degrees(self.pitch), + yaw: ByteAngle::from_degrees(self.yaw), + head_yaw: ByteAngle::from_degrees(self.head_yaw), + data: VarInt(data), + velocity: velocity_to_packet_units(self.velocity), + })) + }; + match &self.variants { TrackedData::Marker(_) => None, TrackedData::ExperienceOrb(_) => { @@ -613,17 +768,22 @@ impl Entity { yaw: ByteAngle::from_degrees(self.yaw), pitch: ByteAngle::from_degrees(self.pitch), })), - _ => Some(EntitySpawnPacket::Entity(SpawnEntity { - entity_id: VarInt(this_id.to_network_id()), - object_uuid: self.uuid, - kind: VarInt(self.kind() as i32), - position: self.new_position, - pitch: ByteAngle::from_degrees(self.pitch), - yaw: ByteAngle::from_degrees(self.yaw), - head_yaw: ByteAngle::from_degrees(self.head_yaw), - data: VarInt(1), // TODO - velocity: velocity_to_packet_units(self.velocity), - })), + TrackedData::ItemFrame(e) => with_object_data(e.get_rotation()), + TrackedData::GlowItemFrame(e) => with_object_data(e.get_rotation()), + TrackedData::Painting(_) => { + with_object_data(match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { + 0 => 3, + 1 => 4, + 2 => 2, + _ => 5, + }) + } + TrackedData::FallingBlock(_) => with_object_data(1), // TODO: set block state ID. + TrackedData::FishingBobber(e) => with_object_data(e.get_hook_entity_id()), + TrackedData::Warden(e) => { + with_object_data((e.get_pose() == types::Pose::Emerging).into()) + } + _ => with_object_data(0), } } } diff --git a/src/entity/types.rs b/src/entity/types.rs index ff64592..bf7ba8f 100644 --- a/src/entity/types.rs +++ b/src/entity/types.rs @@ -234,7 +234,36 @@ impl Encode for FrogKind { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub enum PaintingKind { #[default] - Kebab, // TODO + Kebab, + Aztec, + Alban, + Aztec2, + Bomb, + Plant, + Wasteland, + Pool, + Courbet, + Sea, + Sunset, + Creebet, + Wanderer, + Graham, + Match, + Bust, + Stage, + Void, + SkullAndRoses, + Wither, + Fighters, + Pointer, + Pigscene, + BurningSkull, + Skeleton, + Earth, + Wind, + Water, + Fire, + DonkeyKong, } impl Encode for PaintingKind {