valence/crates/valence_entity/src/lib.rs
Ryan Johnson c5557e744d
Move packets out of valence_core. (#335)
## Description

- Move all packets out of `valence_core` and into the places where
they're actually used. This has a few benefits:
- Avoids compiling code for packets that go unused when feature flags
are disabled.
- Code is distributed more uniformly across crates, improving
compilation times.
- Improves local reasoning when everything relevant to a module is
defined in the same place.
  - Easier to share code between the packet consumer and the packet.
- Tweak `Packet` macro syntax.
- Update `syn` to 2.0.
- Reorganize some code in `valence_client` (needs further work).
- Impl `WritePacket` for `Instance`.
- Remove packet enums such as `S2cPlayPacket` and `C2sPlayPacket`.
- Replace `assert_packet_count` and `assert_packet_order` macros with
non-macro methods.
To prevent this PR from getting out of hand, I've disabled the packet
inspector and stresser until they have been rewritten to account for
these changes.
2023-05-29 01:36:11 -07:00

912 lines
22 KiB
Rust

#![doc = include_str!("../README.md")]
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
rustdoc::invalid_html_tags
)]
#![warn(
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_import_braces,
unreachable_pub,
clippy::dbg_macro
)]
pub mod hitbox;
pub mod packet;
use std::num::Wrapping;
use std::ops::Range;
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::prelude::*;
use glam::{DVec3, Vec3};
use paste::paste;
use rustc_hash::FxHashMap;
use tracing::warn;
use uuid::Uuid;
use valence_core::chunk_pos::ChunkPos;
use valence_core::despawn::Despawned;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{Decode, Encode};
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
/// [`EntityManager`].
///
/// Systems that need Minecraft entities to be in a valid state should run
/// _after_ this set.
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct InitEntitiesSet;
/// When tracked data is written to the entity's [`TrackedData`] component.
/// Systems that modify tracked data should run _before_ this.
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct UpdateTrackedDataSet;
/// When entities are updated and changes from the current tick are cleared.
/// Systems that need to observe changes to entities (Such as the difference
/// between [`Position`] and [`OldPosition`]) should run _before_ this set (and
/// probably after [`InitEntitiesSet`]).
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ClearEntityChangesSet;
impl Plugin for EntityPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(EntityManager::new())
.configure_sets((
InitEntitiesSet.in_base_set(CoreSet::PostUpdate),
UpdateTrackedDataSet.in_base_set(CoreSet::PostUpdate),
ClearEntityChangesSet
.after(InitEntitiesSet)
.after(UpdateTrackedDataSet)
.in_base_set(CoreSet::PostUpdate),
))
.add_systems(
(init_entities, remove_despawned_from_manager)
.chain()
.in_set(InitEntitiesSet),
)
.add_systems(
(
clear_status_changes,
clear_animation_changes,
clear_tracked_data_changes,
update_old_position,
update_old_location,
)
.in_set(ClearEntityChangesSet),
);
add_tracked_data_systems(app);
}
}
fn update_old_position(mut query: Query<(&Position, &mut OldPosition)>) {
for (pos, mut old_pos) in &mut query {
old_pos.0 = pos.0;
}
}
fn update_old_location(mut query: Query<(&Location, &mut OldLocation)>) {
for (loc, mut old_loc) in &mut query {
old_loc.0 = loc.0;
}
}
fn init_entities(
mut entities: Query<
(
Entity,
&mut EntityId,
&mut UniqueId,
&Position,
&mut OldPosition,
),
Added<EntityKind>,
>,
mut manager: ResMut<EntityManager>,
) {
for (entity, mut id, uuid, pos, mut old_pos) in &mut entities {
*old_pos = OldPosition::new(pos.0);
if *id == EntityId::default() {
*id = manager.next_id();
}
if let Some(conflict) = manager.id_to_entity.insert(id.0, entity) {
warn!(
"entity {entity:?} has conflicting entity ID of {} with entity {conflict:?}",
id.0
);
}
if let Some(conflict) = manager.uuid_to_entity.insert(uuid.0, entity) {
warn!(
"entity {entity:?} has conflicting UUID of {} with entity {conflict:?}",
uuid.0
);
}
}
}
#[allow(clippy::type_complexity)]
fn remove_despawned_from_manager(
entities: Query<(&EntityId, &UniqueId), (With<EntityKind>, With<Despawned>)>,
mut manager: ResMut<EntityManager>,
) {
for (id, uuid) in &entities {
manager.id_to_entity.remove(&id.0);
manager.uuid_to_entity.remove(&uuid.0);
}
}
fn clear_status_changes(mut statuses: Query<&mut EntityStatuses, Changed<EntityStatuses>>) {
for mut statuses in &mut statuses {
statuses.0 = 0;
}
}
fn clear_animation_changes(
mut animations: Query<&mut EntityAnimations, Changed<EntityAnimations>>,
) {
for mut animations in &mut animations {
animations.0 = 0;
}
}
fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed<TrackedData>>) {
for mut tracked_data in &mut tracked_data {
tracked_data.clear_update_values();
}
}
/// Contains the `Instance` an entity is located in. For the coordinates
/// within the instance, see [`Position`].
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
pub struct Location(pub Entity);
impl Default for Location {
fn default() -> Self {
Self(Entity::PLACEHOLDER)
}
}
impl PartialEq<OldLocation> for Location {
fn eq(&self, other: &OldLocation) -> bool {
self.0 == other.0
}
}
/// The value of [`Location`] from the end of the previous tick.
///
/// **NOTE**: You should not modify this component after the entity is spawned.
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
pub struct OldLocation(Entity);
impl OldLocation {
pub fn new(instance: Entity) -> Self {
Self(instance)
}
pub fn get(&self) -> Entity {
self.0
}
}
impl Default for OldLocation {
fn default() -> Self {
Self(Entity::PLACEHOLDER)
}
}
impl PartialEq<Location> for OldLocation {
fn eq(&self, other: &Location) -> bool {
self.0 == other.0
}
}
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct Position(pub DVec3);
impl Position {
pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos.into())
}
pub fn chunk_pos(&self) -> ChunkPos {
ChunkPos::from_dvec3(self.0)
}
pub fn get(self) -> DVec3 {
self.0
}
pub fn set(&mut self, pos: impl Into<DVec3>) {
self.0 = pos.into();
}
}
impl PartialEq<OldPosition> for Position {
fn eq(&self, other: &OldPosition) -> bool {
self.0 == other.0
}
}
/// The value of [`Position`] from the end of the previous tick.
///
/// **NOTE**: You should not modify this component after the entity is spawned.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct OldPosition(DVec3);
impl OldPosition {
pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos.into())
}
pub fn get(self) -> DVec3 {
self.0
}
pub fn chunk_pos(self) -> ChunkPos {
ChunkPos::from_dvec3(self.0)
}
}
impl PartialEq<Position> for OldPosition {
fn eq(&self, other: &Position) -> bool {
self.0 == other.0
}
}
/// Describes the direction an entity is looking using pitch and yaw angles.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct Look {
/// The yaw angle in degrees.
pub yaw: f32,
/// The pitch angle in degrees.
pub pitch: f32,
}
impl Look {
pub const fn new(yaw: f32, pitch: f32) -> Self {
Self { yaw, pitch }
}
/// Gets a normalized direction vector from the yaw and pitch.
pub fn vec(self) -> Vec3 {
let (yaw_sin, yaw_cos) = (self.yaw + 90.0).to_radians().sin_cos();
let (pitch_sin, pitch_cos) = (-self.pitch).to_radians().sin_cos();
Vec3::new(yaw_cos * pitch_cos, pitch_sin, yaw_sin * pitch_cos)
}
/// Sets the yaw and pitch using a normalized direction vector.
pub fn set_vec(&mut self, dir: Vec3) {
debug_assert!(
dir.is_normalized(),
"the direction vector should be normalized"
);
// Preserve the current yaw if we're looking straight up or down.
if dir.x != 0.0 || dir.z != 0.0 {
self.yaw = f32::atan2(dir.z, dir.x).to_degrees() - 90.0;
}
self.pitch = -(dir.y).asin().to_degrees();
}
}
#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct OnGround(pub bool);
/// A Minecraft entity's ID according to the protocol.
///
/// IDs should be _unique_ for the duration of the server and _constant_ for
/// the lifetime of the entity. IDs of -1 (the default) will be assigned to
/// something else on the tick the entity is added. If you need to know the ID
/// ahead of time, set this component to the value returned by
/// [`EntityManager::next_id`] before spawning.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct EntityId(i32);
impl EntityId {
/// Returns the underlying entity ID as an integer.
pub fn get(self) -> i32 {
self.0
}
}
/// Returns an entity ID of -1.
impl Default for EntityId {
fn default() -> Self {
Self(-1)
}
}
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct HeadYaw(pub f32);
/// Entity velocity in m/s.
#[derive(Component, Copy, Clone, Default, Debug)]
pub struct Velocity(pub Vec3);
impl Velocity {
pub fn to_packet_units(self) -> [i16; 3] {
// The saturating casts to i16 are desirable.
(8000.0 / DEFAULT_TPS.get() as f32 * self.0)
.to_array()
.map(|v| v as i16)
}
}
#[derive(Component, Copy, Clone, Default, Debug)]
pub struct EntityStatuses(pub u64);
impl EntityStatuses {
pub fn trigger(&mut self, status: EntityStatus) {
self.set(status, true);
}
pub fn set(&mut self, status: EntityStatus, triggered: bool) {
self.0 |= (triggered as u64) << status as u64;
}
pub fn get(&self, status: EntityStatus) -> bool {
(self.0 >> status as u64) & 1 == 1
}
}
#[derive(Component, Default, Debug)]
pub struct EntityAnimations(pub u8);
impl EntityAnimations {
pub fn trigger(&mut self, anim: EntityAnimation) {
self.set(anim, true);
}
pub fn set(&mut self, anim: EntityAnimation, triggered: bool) {
self.0 |= (triggered as u8) << anim as u8;
}
pub fn get(&self, anim: EntityAnimation) -> bool {
(self.0 >> anim as u8) & 1 == 1
}
}
/// Extra integer data passed to the entity spawn packet. The meaning depends on
/// the type of entity being spawned.
///
/// Some examples:
/// - **Experience Orb**: Experience count
/// - **(Glowing) Item Frame**: Rotation
/// - **Painting**: Rotation
/// - **Falling Block**: Block state
/// - **Fishing Bobber**: Hook entity ID
/// - **Warden**: Initial pose
#[derive(Component, Default, Debug)]
pub struct ObjectData(pub i32);
/// The range of packet bytes for this entity within the cell the entity is
/// located in. For internal use only.
#[derive(Component, Default, Debug)]
pub struct PacketByteRange(pub Range<usize>);
/// Cache for all the tracked data of an entity. Used for the
/// [`EntityTrackerUpdateS2c`][packet] packet.
///
/// [packet]: crate::packet::EntityTrackerUpdateS2c
#[derive(Component, Default, Debug)]
pub struct TrackedData {
init_data: Vec<u8>,
/// A map of tracked data indices to the byte length of the entry in
/// `init_data`.
init_entries: Vec<(u8, u32)>,
update_data: Vec<u8>,
}
impl TrackedData {
/// Returns initial tracked data for the entity, ready to be sent in the
/// [`EntityTrackerUpdateS2c`][packet] packet. This is used when the entity
/// enters the view of a client.
///
/// [packet]: crate::packet::EntityTrackerUpdateS2c
pub fn init_data(&self) -> Option<&[u8]> {
if self.init_data.len() > 1 {
Some(&self.init_data)
} else {
None
}
}
/// Contains updated tracked data for the entity, ready to be sent in the
/// [`EntityTrackerUpdateS2c`][packet] packet. This is used when tracked
/// data is changed and the client is already in view of the entity.
///
/// [packet]: crate::packet::EntityTrackerUpdateS2c
pub fn update_data(&self) -> Option<&[u8]> {
if self.update_data.len() > 1 {
Some(&self.update_data)
} else {
None
}
}
pub fn insert_init_value(&mut self, index: u8, type_id: u8, value: impl Encode) {
debug_assert!(
index != 0xff,
"index of 0xff is reserved for the terminator"
);
self.remove_init_value(index);
self.init_data.pop(); // Remove terminator.
// Append the new value to the end.
let len_before = self.init_data.len();
self.init_data.extend_from_slice(&[index, type_id]);
if let Err(e) = value.encode(&mut self.init_data) {
warn!("failed to encode initial tracked data: {e:#}");
}
let len = self.init_data.len() - len_before;
self.init_entries.push((index, len as u32));
self.init_data.push(0xff); // Add terminator.
}
pub fn remove_init_value(&mut self, index: u8) -> bool {
let mut start = 0;
for (pos, &(idx, len)) in self.init_entries.iter().enumerate() {
if idx == index {
let end = start + len as usize;
self.init_data.drain(start..end);
self.init_entries.remove(pos);
return true;
}
start += len as usize;
}
false
}
pub fn append_update_value(&mut self, index: u8, type_id: u8, value: impl Encode) {
debug_assert!(
index != 0xff,
"index of 0xff is reserved for the terminator"
);
self.update_data.pop(); // Remove terminator.
self.update_data.extend_from_slice(&[index, type_id]);
if let Err(e) = value.encode(&mut self.update_data) {
warn!("failed to encode updated tracked data: {e:#}");
}
self.update_data.push(0xff); // Add terminator.
}
pub fn clear_update_values(&mut self) {
self.update_data.clear();
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)]
pub struct VillagerData {
pub kind: VillagerKind,
pub profession: VillagerProfession,
pub level: i32,
}
impl VillagerData {
pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
Self {
kind,
profession,
level,
}
}
}
impl Default for VillagerData {
fn default() -> Self {
Self {
kind: Default::default(),
profession: Default::default(),
level: 1,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum VillagerKind {
Desert,
Jungle,
#[default]
Plains,
Savanna,
Snow,
Swamp,
Taiga,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum VillagerProfession {
#[default]
None,
Armorer,
Butcher,
Cartographer,
Cleric,
Farmer,
Fisherman,
Fletcher,
Leatherworker,
Librarian,
Mason,
Nitwit,
Shepherd,
Toolsmith,
Weaponsmith,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum Pose {
#[default]
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
LongJumping,
Dying,
Croaking,
UsingTongue,
Roaring,
Sniffing,
Emerging,
Digging,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum BoatKind {
#[default]
Oak,
Spruce,
Birch,
Jungle,
Acacia,
DarkOak,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum CatKind {
Tabby,
#[default]
Black,
Red,
Siamese,
BritishShorthair,
Calico,
Persian,
Ragdoll,
White,
Jellie,
AllBlack,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum FrogKind {
#[default]
Temperate,
Warm,
Cold,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum PaintingKind {
#[default]
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,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
pub enum SnifferState {
#[default]
Idling,
FeelingHappy,
Scenting,
Sniffing,
Searching,
Digging,
Rising,
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Encode, Decode)]
pub struct EulerAngle {
pub pitch: f32,
pub yaw: f32,
pub roll: f32,
}
#[derive(Copy, Clone)]
struct OptionalInt(Option<i32>);
impl Encode for OptionalInt {
fn encode(&self, w: impl std::io::Write) -> anyhow::Result<()> {
if let Some(n) = self.0 {
VarInt(n.wrapping_add(1))
} else {
VarInt(0)
}
.encode(w)
}
}
impl Decode<'_> for OptionalInt {
fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
let n = VarInt::decode(r)?.0;
Ok(Self(if n == 0 {
None
} else {
Some(n.wrapping_sub(1))
}))
}
}
/// A [`Resource`] which maintains information about all spawned Minecraft
/// entities.
#[derive(Resource, Debug)]
pub struct EntityManager {
/// Maps protocol IDs to ECS entities.
id_to_entity: FxHashMap<i32, Entity>,
uuid_to_entity: FxHashMap<Uuid, Entity>,
next_id: Wrapping<i32>,
}
impl EntityManager {
fn new() -> Self {
Self {
id_to_entity: FxHashMap::default(),
uuid_to_entity: FxHashMap::default(),
next_id: Wrapping(1), // Skip 0.
}
}
/// Returns the next unique entity ID and increments the counter.
pub fn next_id(&mut self) -> EntityId {
if self.next_id.0 == 0 {
warn!("entity ID overflow!");
// ID 0 is reserved for clients, so skip over it.
self.next_id.0 = 1;
}
let id = EntityId(self.next_id.0);
self.next_id += 1;
id
}
/// Gets the entity with the given entity ID.
pub fn get_by_id(&self, entity_id: i32) -> Option<Entity> {
self.id_to_entity.get(&entity_id).cloned()
}
/// Gets the entity with the given UUID.
pub fn get_by_uuid(&self, uuid: Uuid) -> Option<Entity> {
self.uuid_to_entity.get(&uuid).cloned()
}
}
// TODO: should `set_if_neq` behavior be the default behavior for setters?
macro_rules! flags {
(
$(
$component:path {
$($flag:ident: $offset:literal),* $(,)?
}
)*
) => {
$(
impl $component {
$(
#[doc = "Gets the bit at offset "]
#[doc = stringify!($offset)]
#[doc = "."]
#[inline]
pub const fn $flag(&self) -> bool {
(self.0 >> $offset) & 1 == 1
}
paste! {
#[doc = "Sets the bit at offset "]
#[doc = stringify!($offset)]
#[doc = "."]
#[inline]
pub fn [< set_$flag >] (&mut self, $flag: bool) {
self.0 = (self.0 & !(1 << $offset)) | (($flag as i8) << $offset);
}
}
)*
}
)*
}
}
flags! {
entity::Flags {
on_fire: 0,
sneaking: 1,
sprinting: 3,
swimming: 4,
invisible: 5,
glowing: 6,
fall_flying: 7,
}
persistent_projectile::ProjectileFlags {
critical: 0,
no_clip: 1,
}
living::LivingFlags {
using_item: 0,
off_hand_active: 1,
using_riptide: 2,
}
player::PlayerModelParts {
cape: 0,
jacket: 1,
left_sleeve: 2,
right_sleeve: 3,
left_pants_leg: 4,
right_pants_leg: 5,
hat: 6,
}
player::MainArm {
right: 0,
}
armor_stand::ArmorStandFlags {
small: 0,
show_arms: 1,
hide_base_plate: 2,
marker: 3,
}
mob::MobFlags {
ai_disabled: 0,
left_handed: 1,
attacking: 2,
}
bat::BatFlags {
hanging: 0,
}
abstract_horse::HorseFlags {
tamed: 1,
saddled: 2,
bred: 3,
eating_grass: 4,
angry: 5,
eating: 6,
}
fox::FoxFlags {
sitting: 0,
crouching: 2,
rolling_head: 3,
chasing: 4,
sleeping: 5,
walking: 6,
aggressive: 7,
}
panda::PandaFlags {
sneezing: 1,
playing: 2,
sitting: 3,
lying_on_back: 4,
}
tameable::TameableFlags {
sitting_pose: 0,
tamed: 2,
}
iron_golem::IronGolemFlags {
player_created: 0,
}
snow_golem::SnowGolemFlags {
has_pumpkin: 4,
}
blaze::BlazeFlags {
fire_active: 0,
}
vex::VexFlags {
charging: 0,
}
spider::SpiderFlags {
climbing_wall: 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_remove_init_tracked_data() {
let mut td = TrackedData::default();
td.insert_init_value(0, 3, "foo");
td.insert_init_value(10, 6, "bar");
td.insert_init_value(5, 9, "baz");
assert!(td.remove_init_value(10));
assert!(!td.remove_init_value(10));
// Insertion overwrites value at index 0.
td.insert_init_value(0, 64, "quux");
assert!(td.remove_init_value(0));
assert!(td.remove_init_value(5));
assert!(td.init_data.as_slice().is_empty() || td.init_data.as_slice() == [0xff]);
assert!(td.init_data().is_none());
assert!(td.update_data.is_empty());
}
#[test]
fn get_set_flags() {
let mut flags = entity::Flags(0);
flags.set_on_fire(true);
let before = flags.clone();
assert_ne!(flags.0, 0);
flags.set_on_fire(true);
assert_eq!(before, flags);
flags.set_on_fire(false);
assert_eq!(flags.0, 0);
}
}