Entity Hitbox Improvements (#70)

Solves #45. With help from @guac420
This commit is contained in:
Ryan Johnson 2022-09-17 23:14:48 -07:00 committed by GitHub
parent dcd4a2b5ed
commit 9ec1df1978
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 658 additions and 258 deletions

417
examples/entity_raycast.rs Normal file
View file

@ -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<PlayerListId>;
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<Self>,
_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<Self>) {
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<Self>) {
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
});
}
}

View file

@ -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<PlayerListId>;
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<Self>,
_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<Self>) {
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<Self>) {
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;
}
}
}

View file

@ -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<C: Config> Entity<C> {
///
/// [interact event]: crate::client::ClientEvent::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> {
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<f64>, rotation: i32) -> Aabb<f64> {
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<C: Config> Entity<C> {
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<C: Config> Entity<C> {
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<C: Config> Entity<C> {
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<u32> = 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<C: Config> Entity<C> {
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<C: Config> Entity<C> {
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<C: Config> Entity<C> {
}
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
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<C: Config> Entity<C> {
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),
}
}
}

View file

@ -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 {