use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::iter::FusedIterator;
use std::num::NonZeroU32;
use bitfield_struct::bitfield;
pub use data::{EntityKind, TrackedData};
use rayon::iter::ParallelIterator;
use uuid::Uuid;
use vek::{Aabb, Vec3};
use crate::config::Config;
use crate::protocol::packets::s2c::play::{
EntitySpawn, EntityTrackerUpdate, ExperienceOrbSpawn, PlayerSpawn, S2cPlayPacket,
};
use crate::protocol::{ByteAngle, RawBytes, VarInt};
use crate::slab_versioned::{Key, VersionedSlab};
use crate::util::aabb_from_bottom_and_size;
use crate::world::WorldId;
use crate::STANDARD_TPS;
pub mod data;
pub mod types;
include!(concat!(env!("OUT_DIR"), "/entity_event.rs"));
pub struct Entities<C: Config> {
slab: VersionedSlab<Entity<C>>,
uuid_to_entity: HashMap<Uuid, EntityId>,
network_id_to_entity: HashMap<NonZeroU32, u32>,
}
impl<C: Config> Entities<C> {
pub(crate) fn new() -> Self {
Self {
slab: VersionedSlab::new(),
uuid_to_entity: HashMap::new(),
network_id_to_entity: HashMap::new(),
}
}
pub fn insert(
&mut self,
kind: EntityKind,
state: C::EntityState,
) -> (EntityId, &mut Entity<C>) {
self.insert_with_uuid(kind, Uuid::from_bytes(rand::random()), state)
.expect("UUID collision")
}
pub fn insert_with_uuid(
&mut self,
kind: EntityKind,
uuid: Uuid,
data: C::EntityState,
) -> Option<(EntityId, &mut Entity<C>)> {
match self.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None,
Entry::Vacant(ve) => {
let (k, e) = self.slab.insert(Entity {
state: data,
variants: TrackedData::new(kind),
events: Vec::new(),
bits: EntityBits::new(),
world: WorldId::NULL,
new_position: Vec3::default(),
old_position: Vec3::default(),
yaw: 0.0,
pitch: 0.0,
head_yaw: 0.0,
velocity: Vec3::default(),
uuid,
});
self.network_id_to_entity.insert(k.version(), k.index());
ve.insert(EntityId(k));
Some((EntityId(k), e))
}
}
}
pub fn remove(&mut self, entity: EntityId) -> Option<C::EntityState> {
self.slab.remove(entity.0).map(|e| {
self.uuid_to_entity
.remove(&e.uuid)
.expect("UUID should have been in UUID map");
self.network_id_to_entity
.remove(&entity.0.version())
.expect("network ID should have been in the network ID map");
e.state
})
}
pub fn retain(&mut self, mut f: impl FnMut(EntityId, &mut Entity<C>) -> bool) {
self.slab.retain(|k, v| {
if f(EntityId(k), v) {
true
} else {
self.uuid_to_entity
.remove(&v.uuid)
.expect("UUID should have been in UUID map");
self.network_id_to_entity
.remove(&k.version())
.expect("network ID should have been in the network ID map");
false
}
});
}
pub fn len(&self) -> usize {
self.slab.len()
}
pub fn is_empty(&self) -> bool {
self.slab.len() == 0
}
pub fn get_with_uuid(&self, uuid: Uuid) -> Option<EntityId> {
self.uuid_to_entity.get(&uuid).cloned()
}
pub fn get(&self, entity: EntityId) -> Option<&Entity<C>> {
self.slab.get(entity.0)
}
pub fn get_mut(&mut self, entity: EntityId) -> Option<&mut Entity<C>> {
self.slab.get_mut(entity.0)
}
pub(crate) fn get_with_network_id(&self, network_id: i32) -> Option<EntityId> {
let version = NonZeroU32::new(network_id as u32)?;
let index = *self.network_id_to_entity.get(&version)?;
Some(EntityId(Key::new(index, version)))
}
pub fn iter(
&self,
) -> impl ExactSizeIterator<Item = (EntityId, &Entity<C>)> + FusedIterator + Clone + '_ {
self.slab.iter().map(|(k, v)| (EntityId(k), v))
}
pub fn iter_mut(
&mut self,
) -> impl ExactSizeIterator<Item = (EntityId, &mut Entity<C>)> + FusedIterator + '_ {
self.slab.iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter(&self) -> impl ParallelIterator<Item = (EntityId, &Entity<C>)> + Clone + '_ {
self.slab.par_iter().map(|(k, v)| (EntityId(k), v))
}
pub fn par_iter_mut(
&mut self,
) -> impl ParallelIterator<Item = (EntityId, &mut Entity<C>)> + '_ {
self.slab.par_iter_mut().map(|(k, v)| (EntityId(k), v))
}
pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() {
e.old_position = e.new_position;
e.variants.clear_modifications();
e.events.clear();
e.bits.set_yaw_or_pitch_modified(false);
e.bits.set_head_yaw_modified(false);
e.bits.set_velocity_modified(false);
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct EntityId(Key);
impl EntityId {
pub const NULL: Self = Self(Key::NULL);
pub(crate) fn to_network_id(self) -> i32 {
self.0.version().get() as i32
}
}
pub struct Entity<C: Config> {
pub state: C::EntityState,
variants: TrackedData,
bits: EntityBits,
events: Vec<EntityEvent>,
world: WorldId,
new_position: Vec3<f64>,
old_position: Vec3<f64>,
yaw: f32,
pitch: f32,
head_yaw: f32,
velocity: Vec3<f32>,
uuid: Uuid,
}
#[bitfield(u8)]
pub(crate) struct EntityBits {
pub yaw_or_pitch_modified: bool,
pub head_yaw_modified: bool,
pub velocity_modified: bool,
pub on_ground: bool,
#[bits(4)]
_pad: u8,
}
impl<C: Config> Entity<C> {
pub(crate) fn bits(&self) -> EntityBits {
self.bits
}
pub fn data(&self) -> &TrackedData {
&self.variants
}
pub fn data_mut(&mut self) -> &mut TrackedData {
&mut self.variants
}
pub fn kind(&self) -> EntityKind {
self.variants.kind()
}
pub fn push_event(&mut self, event: EntityEvent) {
self.events.push(event);
}
pub(crate) fn events(&self) -> &[EntityEvent] {
&self.events
}
pub fn world(&self) -> WorldId {
self.world
}
pub fn set_world(&mut self, world: WorldId) {
self.world = world;
}
pub fn position(&self) -> Vec3<f64> {
self.new_position
}
pub fn set_position(&mut self, pos: impl Into<Vec3<f64>>) {
self.new_position = pos.into();
}
pub(crate) fn old_position(&self) -> Vec3<f64> {
self.old_position
}
pub fn yaw(&self) -> f32 {
self.yaw
}
pub fn set_yaw(&mut self, yaw: f32) {
if self.yaw != yaw {
self.yaw = yaw;
self.bits.set_yaw_or_pitch_modified(true);
}
}
pub fn pitch(&self) -> f32 {
self.pitch
}
pub fn set_pitch(&mut self, pitch: f32) {
if self.pitch != pitch {
self.pitch = pitch;
self.bits.set_yaw_or_pitch_modified(true);
}
}
pub fn head_yaw(&self) -> f32 {
self.head_yaw
}
pub fn set_head_yaw(&mut self, head_yaw: f32) {
if self.head_yaw != head_yaw {
self.head_yaw = head_yaw;
self.bits.set_head_yaw_modified(true);
}
}
pub fn velocity(&self) -> Vec3<f32> {
self.velocity
}
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
let new_vel = velocity.into();
if self.velocity != new_vel {
self.velocity = new_vel;
self.bits.set_velocity_modified(true);
}
}
pub fn on_ground(&self) -> bool {
self.bits.on_ground()
}
pub fn set_on_ground(&mut self, on_ground: bool) {
self.bits.set_on_ground(on_ground);
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn hitbox(&self) -> Aabb<f64> {
let dims = 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::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() {
[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(_) => [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], TrackedData::Cod(_) => [0.5, 0.3, 0.5],
TrackedData::Cow(_) => [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], TrackedData::DragonFireball(_) => [1.0, 1.0, 1.0],
TrackedData::Drowned(_) => [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(_) => [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::GlowSquid(_) => [0.8, 0.8, 0.8],
TrackedData::Goat(_) => [1.3, 0.9, 1.3], TrackedData::Guardian(_) => [0.85, 0.85, 0.85],
TrackedData::Hoglin(_) => [1.39648, 1.4, 1.39648], TrackedData::Horse(_) => [1.39648, 1.6, 1.39648], TrackedData::Husk(_) => [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::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], TrackedData::LlamaSpit(_) => [0.25, 0.25, 0.25],
TrackedData::MagmaCube(e) => {
let s = e.get_slime_size() as f64 * 0.51000005;
[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(_) => [1.39648, 1.6, 1.39648], TrackedData::Mooshroom(_) => [0.9, 1.4, 0.9], TrackedData::Ocelot(_) => [0.6, 0.7, 0.6], TrackedData::Painting(_) => todo!("account for rotation and type"),
TrackedData::Panda(_) => [0.6, 0.7, 0.6], TrackedData::Parrot(_) => [0.5, 0.9, 0.5],
TrackedData::Phantom(_) => [0.9, 0.5, 0.9],
TrackedData::Pig(_) => [0.9, 0.9, 0.9], TrackedData::Piglin(_) => [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], TrackedData::Tnt(_) => [0.98, 0.98, 0.98],
TrackedData::Pufferfish(_) => [0.7, 0.7, 0.7],
TrackedData::Rabbit(_) => [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], TrackedData::Shulker(_) => [1.0, 1.0, 1.0], 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], TrackedData::Slime(e) => {
let s = 0.51000005 * 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(_) => [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(_) => [1.2, 0.4, 1.2], TrackedData::Vex(_) => [0.4, 0.8, 0.4],
TrackedData::Villager(_) => [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], TrackedData::Zoglin(_) => [1.39648, 1.4, 1.39648], TrackedData::Zombie(_) => [0.6, 1.95, 0.6], TrackedData::ZombieHorse(_) => [1.39648, 1.6, 1.39648], TrackedData::ZombieVillager(_) => [0.6, 1.95, 0.6], TrackedData::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], TrackedData::Player(_) => [0.6, 1.8, 0.6], TrackedData::FishingBobber(_) => [0.25, 0.25, 0.25],
};
aabb_from_bottom_and_size(self.new_position, dims.into())
}
pub(crate) fn initial_tracked_data_packet(
&self,
this_id: EntityId,
) -> Option<EntityTrackerUpdate> {
self.variants
.initial_tracked_data()
.map(|meta| EntityTrackerUpdate {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
}
pub(crate) fn updated_tracked_data_packet(
&self,
this_id: EntityId,
) -> Option<EntityTrackerUpdate> {
self.variants
.updated_tracked_data()
.map(|meta| EntityTrackerUpdate {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
}
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
match &self.variants {
TrackedData::Marker(_) => None,
TrackedData::ExperienceOrb(_) => {
Some(EntitySpawnPacket::ExperienceOrb(ExperienceOrbSpawn {
entity_id: VarInt(this_id.to_network_id()),
position: self.new_position,
count: 0, }))
}
TrackedData::Player(_) => Some(EntitySpawnPacket::Player(PlayerSpawn {
entity_id: VarInt(this_id.to_network_id()),
player_uuid: self.uuid,
position: self.new_position,
yaw: ByteAngle::from_degrees(self.yaw),
pitch: ByteAngle::from_degrees(self.pitch),
})),
_ => Some(EntitySpawnPacket::Entity(EntitySpawn {
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), velocity: velocity_to_packet_units(self.velocity),
})),
}
}
}
pub(crate) fn velocity_to_packet_units(vel: Vec3<f32>) -> Vec3<i16> {
(8000.0 / STANDARD_TPS as f32 * vel).as_()
}
pub(crate) enum EntitySpawnPacket {
Entity(EntitySpawn),
ExperienceOrb(ExperienceOrbSpawn),
Player(PlayerSpawn),
}
impl From<EntitySpawnPacket> for S2cPlayPacket {
fn from(pkt: EntitySpawnPacket) -> Self {
match pkt {
EntitySpawnPacket::Entity(pkt) => pkt.into(),
EntitySpawnPacket::ExperienceOrb(pkt) => pkt.into(),
EntitySpawnPacket::Player(pkt) => pkt.into(),
}
}
}