diff --git a/.gitignore b/.gitignore index b808c2c..5fa0014 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,11 @@ Cargo.lock /extractor/out /extractor/classes /extractor/run +/extractor/bin rust-mc-bot .asset_cache/ /velocity flamegraph*.svg perf.data perf.data.old +/graph.gv diff --git a/crates/playground/src/playground.template.rs b/crates/playground/src/playground.template.rs index bfc8a00..1bd8dfe 100644 --- a/crates/playground/src/playground.template.rs +++ b/crates/playground/src/playground.template.rs @@ -1,5 +1,4 @@ -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; use valence::prelude::*; #[allow(unused_imports)] diff --git a/crates/valence/Cargo.toml b/crates/valence/Cargo.toml index 459f08e..4ad4e4e 100644 --- a/crates/valence/Cargo.toml +++ b/crates/valence/Cargo.toml @@ -12,6 +12,7 @@ build = "build/main.rs" authors = ["Ryan Johnson "] [dependencies] +#bevy_mod_debugdump = "0.7.0" anyhow = "1.0.65" arrayvec = "0.7.2" async-trait = "0.1.60" diff --git a/crates/valence/build/entity.rs b/crates/valence/build/entity.rs index 3972f97..1a4b38b 100644 --- a/crates/valence/build/entity.rs +++ b/crates/valence/build/entity.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; -use heck::ToPascalCase; -use proc_macro2::{Ident, TokenStream}; +use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase}; +use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; @@ -27,7 +27,6 @@ struct Field { index: u8, #[serde(flatten)] default_value: Value, - bits: Vec, } #[derive(Deserialize, Clone, Debug)] @@ -75,14 +74,8 @@ struct BlockPos { z: i32, } -#[derive(Deserialize, Clone, Debug)] -struct Bit { - name: String, - index: u8, -} - impl Value { - pub fn type_id(&self) -> i32 { + pub fn type_id(&self) -> u8 { match self { Value::Byte(_) => 0, Value::Integer(_) => 1, @@ -117,46 +110,26 @@ impl Value { Value::Integer(_) => quote!(i32), Value::Long(_) => quote!(i64), Value::Float(_) => quote!(f32), - Value::String(_) => quote!(Box), - Value::TextComponent(_) => quote!(Text), - Value::OptionalTextComponent(_) => quote!(Option), - Value::ItemStack(_) => quote!(()), // TODO + Value::String(_) => quote!(String), + Value::TextComponent(_) => quote!(crate::protocol::text::Text), + Value::OptionalTextComponent(_) => quote!(Option), + Value::ItemStack(_) => quote!(crate::protocol::item::ItemStack), Value::Boolean(_) => quote!(bool), - Value::Rotation { .. } => quote!(EulerAngle), - Value::BlockPos(_) => quote!(BlockPos), - Value::OptionalBlockPos(_) => quote!(Option), - Value::Facing(_) => quote!(Facing), - Value::OptionalUuid(_) => quote!(Option), - Value::OptionalBlockState(_) => quote!(BlockState), - Value::NbtCompound(_) => quote!(valence_nbt::Compound), - Value::Particle(_) => quote!(Particle), - Value::VillagerData { .. } => quote!(VillagerData), - Value::OptionalInt(_) => quote!(OptionalInt), - Value::EntityPose(_) => quote!(Pose), - Value::CatVariant(_) => quote!(CatKind), - Value::FrogVariant(_) => quote!(FrogKind), + Value::Rotation { .. } => quote!(crate::entity::EulerAngle), + Value::BlockPos(_) => quote!(crate::protocol::block_pos::BlockPos), + Value::OptionalBlockPos(_) => quote!(Option), + Value::Facing(_) => quote!(crate::protocol::types::Direction), + Value::OptionalUuid(_) => quote!(Option<::uuid::Uuid>), + Value::OptionalBlockState(_) => quote!(crate::protocol::block::BlockState), + Value::NbtCompound(_) => quote!(crate::nbt::Compound), + Value::Particle(_) => quote!(crate::protocol::packet::s2c::play::particle::Particle), + Value::VillagerData { .. } => quote!(crate::entity::VillagerData), + Value::OptionalInt(_) => quote!(Option), + Value::EntityPose(_) => quote!(crate::entity::Pose), + Value::CatVariant(_) => quote!(crate::entity::CatKind), + Value::FrogVariant(_) => quote!(crate::entity::FrogKind), Value::OptionalGlobalPos(_) => quote!(()), // TODO - Value::PaintingVariant(_) => quote!(PaintingKind), - } - } - - pub fn getter_return_type(&self) -> TokenStream { - match self { - Value::String(_) => quote!(&str), - Value::TextComponent(_) => quote!(&Text), - Value::OptionalTextComponent(_) => quote!(Option<&Text>), - Value::NbtCompound(_) => quote!(&valence_nbt::Compound), - _ => self.field_type(), - } - } - - pub fn getter_return_expr(&self, field_name: &Ident) -> TokenStream { - match self { - Value::String(_) | Value::TextComponent(_) | Value::NbtCompound(_) => { - quote!(&self.#field_name) - } - Value::OptionalTextComponent(_) => quote!(self.#field_name.as_ref()), - _ => quote!(self.#field_name), + Value::PaintingVariant(_) => quote!(crate::entity::PaintingKind), } } @@ -166,35 +139,53 @@ impl Value { Value::Integer(i) => quote!(#i), Value::Long(l) => quote!(#l), Value::Float(f) => quote!(#f), - Value::String(s) => quote!(#s.to_owned().into_boxed_str()), - Value::TextComponent(_) => quote!(Text::default()), // TODO + Value::String(s) => quote!(#s.to_owned()), + Value::TextComponent(txt) => { + assert!(txt.is_empty()); + quote!(crate::protocol::text::Text::default()) + } Value::OptionalTextComponent(t) => { assert!(t.is_none()); quote!(None) } - Value::ItemStack(_) => quote!(()), // TODO + Value::ItemStack(stack) => { + assert_eq!(stack, "1 air"); + quote!(crate::protocol::item::ItemStack::default()) + } Value::Boolean(b) => quote!(#b), Value::Rotation { pitch, yaw, roll } => quote! { - EulerAngle { + crate::entity::EulerAngle { pitch: #pitch, yaw: #yaw, roll: #roll, } }, Value::BlockPos(BlockPos { x, y, z }) => { - quote!(BlockPos { x: #x, y: #y, z: #z }) + quote!(crate::protocol::block_pos::BlockPos { x: #x, y: #y, z: #z }) + } + Value::OptionalBlockPos(pos) => { + assert!(pos.is_none()); + quote!(None) } - Value::OptionalBlockPos(_) => quote!(None), // TODO Value::Facing(f) => { let variant = ident(f.to_pascal_case()); - quote!(Facing::#variant) + quote!(crate::protocol::types::Direction::#variant) + } + Value::OptionalUuid(uuid) => { + assert!(uuid.is_none()); + quote!(None) + } + Value::OptionalBlockState(bs) => { + assert!(bs.is_none()); + quote!(crate::protocol::block::BlockState::default()) + } + Value::NbtCompound(s) => { + assert_eq!(s, "{}"); + quote!(crate::nbt::Compound::default()) } - Value::OptionalUuid(_) => quote!(None), // TODO - Value::OptionalBlockState(_) => quote!(BlockState::default()), // TODO - Value::NbtCompound(_) => quote!(valence_nbt::Compound::default()), // TODO Value::Particle(p) => { let variant = ident(p.to_pascal_case()); - quote!(Particle::#variant) + quote!(crate::protocol::packet::s2c::play::particle::Particle::#variant) } Value::VillagerData { typ, @@ -203,28 +194,34 @@ impl Value { } => { let typ = ident(typ.to_pascal_case()); let profession = ident(profession.to_pascal_case()); - quote!(VillagerData::new(VillagerKind::#typ, VillagerProfession::#profession, #level)) + quote! { + crate::entity::VillagerData { + kind: crate::entity::VillagerKind::#typ, + profession: crate::entity::VillagerProfession::#profession, + level: #level, + } + } } Value::OptionalInt(i) => { assert!(i.is_none()); - quote!(OptionalInt::default()) + quote!(None) } Value::EntityPose(p) => { let variant = ident(p.to_pascal_case()); - quote!(Pose::#variant) + quote!(crate::entity::Pose::#variant) } Value::CatVariant(c) => { let variant = ident(c.to_pascal_case()); - quote!(CatKind::#variant) + quote!(crate::entity::CatKind::#variant) } Value::FrogVariant(f) => { let variant = ident(f.to_pascal_case()); - quote!(FrogKind::#variant) + quote!(crate::entity::FrogKind::#variant) } Value::OptionalGlobalPos(_) => quote!(()), Value::PaintingVariant(p) => { let variant = ident(p.to_pascal_case()); - quote!(PaintingKind::#variant) + quote!(crate::entity::PaintingKind::#variant) } } } @@ -232,7 +229,9 @@ impl Value { pub fn encodable_expr(&self, self_lvalue: TokenStream) -> TokenStream { match self { Value::Integer(_) => quote!(VarInt(#self_lvalue)), - _ => self_lvalue, + Value::OptionalInt(_) => quote!(OptionalInt(#self_lvalue)), + Value::ItemStack(_) => quote!(Some(&#self_lvalue)), + _ => quote!(&#self_lvalue), } } } @@ -240,279 +239,320 @@ impl Value { type Entities = BTreeMap; pub fn build() -> anyhow::Result { - let entities = - serde_json::from_str::(include_str!("../../../extracted/entities.json"))? - .into_iter() - .map(|(k, mut v)| { - let strip = |s: String| { - if let Some(stripped) = s.strip_suffix("Entity") { - if !stripped.is_empty() { - return stripped.to_owned(); - } - } - s - }; - v.parent = v.parent.map(strip); - (strip(k), v) - }) - .collect::(); - let entity_types = serde_json::from_str::(include_str!("../../../extracted/entity_data.json"))? .types; - let concrete_entities = entities - .clone() - .into_iter() - .filter(|(_, v)| v.typ.is_some()) - .collect::(); - - let entity_kind_variants = concrete_entities.iter().map(|(name, e)| { - let name = ident(name); - let id = entity_types[e.typ.as_ref().unwrap()] as isize; - quote! { - #name = #id, - } - }); - - let concrete_entity_names = concrete_entities.keys().map(ident).collect::>(); - - let concrete_entity_structs = concrete_entities.keys().map(|struct_name| { - let fields = collect_all_fields(struct_name, &entities); - let struct_name = ident(struct_name); - - let modified_flags_type = - ident("u".to_owned() + &fields.len().next_power_of_two().max(8).to_string()); - - let struct_fields = fields.iter().map(|&field| { - let name = ident(&field.name); - let typ = field.default_value.field_type(); - quote! { - #name: #typ, - } - }); - - let field_initializers = fields.iter().map(|&field| { - let field_name = ident(&field.name); - let init = field.default_value.default_expr(); - - quote! { - #field_name: #init, - } - }); - - let getter_setters = fields.iter().map(|&field| { - let field_name = ident(&field.name); - let field_type = field.default_value.field_type(); - let field_index = field.index; - - if !field.bits.is_empty() { - field - .bits - .iter() - .map(|bit| { - let bit_name = ident(&bit.name); - let bit_index = bit.index; - let getter_name = ident(format!("get_{}", &bit.name)); - let setter_name = ident(format!("set_{}", &bit.name)); - - quote! { - pub fn #getter_name(&self) -> bool { - self.#field_name >> #bit_index as #field_type & 1 == 1 - } - - pub fn #setter_name(&mut self, #bit_name: bool) { - if self.#getter_name() != #bit_name { - self.#field_name = - (self.#field_name & !(1 << #bit_index as #field_type)) - | ((#bit_name as #field_type) << #bit_index); - - self.__modified_flags |= 1 << #field_index - } - } + let entities: Entities = + serde_json::from_str::(include_str!("../../../extracted/entities.json"))? + .into_iter() + .map(|(entity_name, mut entity)| { + let change_name = |mut name: String| { + if let Some(stripped) = name.strip_suffix("Entity") { + if !stripped.is_empty() { + name = stripped.into(); } - }) - .collect::() + } + + name + }; + + entity.parent = entity.parent.map(change_name); + (change_name(entity_name), entity) + }) + .collect(); + + let mut entity_kind_consts = TokenStream::new(); + let mut entity_kind_fmt_args = TokenStream::new(); + let mut translation_key_arms = TokenStream::new(); + let mut modules = TokenStream::new(); + let mut systems = TokenStream::new(); + let mut system_names = vec![]; + + for (entity_name, entity) in entities.clone() { + let entity_name_ident = ident(&entity_name); + let shouty_entity_name = entity_name.to_shouty_snake_case(); + let shouty_entity_name_ident = ident(&shouty_entity_name); + let snake_entity_name = entity_name.to_snake_case(); + let snake_entity_name_ident = ident(&snake_entity_name); + + let mut module_body = TokenStream::new(); + + if let Some(parent_name) = entity.parent { + let snake_parent_name = parent_name.to_snake_case(); + + let module_doc = + format!("Parent class: [`{snake_parent_name}`][super::{snake_parent_name}]."); + + module_body.extend([quote! { + #![doc = #module_doc] + }]); + } + + // Is this a concrete entity type? + if let Some(entity_type) = entity.typ { + let entity_type_id = entity_types[&entity_type]; + + entity_kind_consts.extend([quote! { + pub const #shouty_entity_name_ident: EntityKind = EntityKind(#entity_type_id); + }]); + + entity_kind_fmt_args.extend([quote! { + EntityKind::#shouty_entity_name_ident => write!(f, "{} ({})", #entity_type_id, #shouty_entity_name), + }]); + + let translation_key_expr = if let Some(key) = entity.translation_key { + quote!(Some(#key)) } else { - let getter_name = ident(format!("get_{}", &field.name)); - let setter_name = ident(format!("set_{}", &field.name)); - let getter_return_type = field.default_value.getter_return_type(); - let getter_return_expr = field.default_value.getter_return_expr(&field_name); + quote!(None) + }; - quote! { - pub fn #getter_name(&self) -> #getter_return_type { - #getter_return_expr + translation_key_arms.extend([quote! { + EntityKind::#shouty_entity_name_ident => #translation_key_expr, + }]); + + // Create bundle type. + let mut bundle_fields = TokenStream::new(); + let mut bundle_init_fields = TokenStream::new(); + + for marker_or_field in collect_bundle_fields(&entity_name, &entities) { + match marker_or_field { + MarkerOrField::Marker { entity_name } => { + let snake_entity_name_ident = ident(entity_name.to_snake_case()); + let pascal_entity_name_ident = ident(entity_name.to_pascal_case()); + + bundle_fields.extend([quote! { + pub #snake_entity_name_ident: super::#snake_entity_name_ident::#pascal_entity_name_ident, + }]); + + bundle_init_fields.extend([quote! { + #snake_entity_name_ident: Default::default(), + }]); } + MarkerOrField::Field { entity_name, field } => { + let snake_field_name = field.name.to_snake_case(); + let pascal_field_name = field.name.to_pascal_case(); + let pascal_field_name_ident = ident(&pascal_field_name); + let snake_entity_name = entity_name.to_snake_case(); + let snake_entity_name_ident = ident(&snake_entity_name); - pub fn #setter_name(&mut self, #field_name: impl Into<#field_type>) { - let #field_name = #field_name.into(); - if self.#field_name != #field_name { - self.__modified_flags |= 1 << #field_index as #modified_flags_type; - self.#field_name = #field_name; + let field_name_ident = + ident(format!("{snake_entity_name}_{snake_field_name}")); + + bundle_fields.extend([quote! { + pub #field_name_ident: super::#snake_entity_name_ident::#pascal_field_name_ident, + }]); + + bundle_init_fields.extend([quote! { + #field_name_ident: Default::default(), + }]); + } + } + } + + bundle_fields.extend([quote! { + pub kind: super::EntityKind, + pub id: super::EntityId, + pub uuid: super::UniqueId, + pub location: super::Location, + pub old_location: super::OldLocation, + pub position: super::Position, + pub old_position: super::OldPosition, + pub look: super::Look, + pub head_yaw: super::HeadYaw, + pub on_ground: super::OnGround, + pub velocity: super::Velocity, + pub statuses: super::EntityStatuses, + pub animations: super::EntityAnimations, + pub object_data: super::ObjectData, + pub tracked_data: super::TrackedData, + pub packet_byte_range: super::PacketByteRange, + }]); + + bundle_init_fields.extend([quote! { + kind: super::EntityKind::#shouty_entity_name_ident, + id: Default::default(), + uuid: Default::default(), + location: Default::default(), + old_location: Default::default(), + position: Default::default(), + old_position: Default::default(), + look: Default::default(), + head_yaw: Default::default(), + on_ground: Default::default(), + velocity: Default::default(), + statuses: Default::default(), + animations: Default::default(), + object_data: Default::default(), + tracked_data: Default::default(), + packet_byte_range: Default::default(), + }]); + + let bundle_name_ident = ident(format!("{entity_name}Bundle")); + let bundle_doc = + format!("The bundle of components for spawning `{snake_entity_name}` entities."); + + module_body.extend([quote! { + #[doc = #bundle_doc] + #[derive(bevy_ecs::bundle::Bundle, Debug)] + pub struct #bundle_name_ident { + #bundle_fields + } + + impl Default for #bundle_name_ident { + fn default() -> Self { + Self { + #bundle_init_fields } } } - } - }); + }]); + } - let initial_tracked_data_stmts = fields.iter().map(|&field| { - let field_name = ident(&field.name); - let field_index = field.index; + for field in &entity.fields { + let pascal_field_name_ident = ident(field.name.to_pascal_case()); + let snake_field_name = field.name.to_snake_case(); + let inner_type = field.default_value.field_type(); let default_expr = field.default_value.default_expr(); - let type_id = field.default_value.type_id(); - let encodable = field.default_value.encodable_expr(quote!(self.#field_name)); - quote! { - if self.#field_name != (#default_expr) { - data.push(#field_index); - VarInt(#type_id).encode(&mut *data).unwrap(); - #encodable.encode(&mut *data).unwrap(); - } - } - }); + module_body.extend([quote! { + #[derive(bevy_ecs::component::Component, PartialEq, Clone, Debug)] + pub struct #pascal_field_name_ident(pub #inner_type); - let updated_tracked_data_stmts = fields.iter().map(|&field| { - let field_name = ident(&field.name); - let field_index = field.index; - let type_id = field.default_value.type_id(); - let encodable = field.default_value.encodable_expr(quote!(self.#field_name)); - - quote! { - if (self.__modified_flags >> #field_index as #modified_flags_type) & 1 == 1 { - data.push(#field_index); - VarInt(#type_id).encode(&mut *data).unwrap(); - #encodable.encode(&mut *data).unwrap(); - } - } - }); - - quote! { - pub struct #struct_name { - /// Contains a set bit for every modified field. - __modified_flags: #modified_flags_type, - #(#struct_fields)* - } - - impl #struct_name { - pub(crate) fn new() -> Self { - Self { - __modified_flags: 0, - #(#field_initializers)* + #[allow(clippy::derivable_impls)] + impl Default for #pascal_field_name_ident { + fn default() -> Self { + Self(#default_expr) } } + }]); - pub(crate) fn initial_tracked_data(&self, data: &mut Vec) { - #(#initial_tracked_data_stmts)* - } + let system_name_ident = ident(format!("update_{snake_entity_name}_{snake_field_name}")); + let component_path = quote!(#snake_entity_name_ident::#pascal_field_name_ident); - pub(crate) fn updated_tracked_data(&self, data: &mut Vec) { - if self.__modified_flags != 0 { - #(#updated_tracked_data_stmts)* + system_names.push(quote!(#system_name_ident)); + + let data_index = field.index; + let data_type = field.default_value.type_id(); + let encodable_expr = field.default_value.encodable_expr(quote!(value.0)); + + systems.extend([quote! { + #[allow(clippy::needless_borrow)] + fn #system_name_ident( + mut query: Query<(&#component_path, &mut TrackedData), Changed<#component_path>> + ) { + for (value, mut tracked_data) in &mut query { + if *value == Default::default() { + tracked_data.remove_init_value(#data_index); + } else { + tracked_data.insert_init_value(#data_index, #data_type, #encodable_expr); + } + + if !tracked_data.is_added() { + tracked_data.append_update_value(#data_index, #data_type, #encodable_expr); + } } } + }]); + } - pub(crate) fn clear_modifications(&mut self) { - self.__modified_flags = 0; - } + let marker_doc = format!("Marker component for `{snake_entity_name}` entities."); - #(#getter_setters)* + module_body.extend([quote! { + #[doc = #marker_doc] + #[derive(bevy_ecs::component::Component, Copy, Clone, Default, Debug)] + pub struct #entity_name_ident; + }]); + + modules.extend([quote! { + #[allow(clippy::module_inception)] + pub mod #snake_entity_name_ident { + #module_body } - } - }); - - let translation_key_arms = concrete_entities.iter().map(|(k, v)| { - let name = ident(k); - let key = v - .translation_key - .as_ref() - .expect("translation key should be present for concrete entity"); - - quote! { - Self::#name => #key, - } - }); + }]); + } Ok(quote! { - /// Contains a variant for each concrete entity type. - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] - pub enum EntityKind { - #(#entity_kind_variants)* - } + #modules + + /// Identifies the type of an entity. + /// As a component, the entity kind should not be modified. + #[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] + pub struct EntityKind(i32); impl EntityKind { - pub fn translation_key(self) -> &'static str { + #entity_kind_consts + + pub const fn new(inner: i32) -> Self { + Self(inner) + } + + pub const fn get(self) -> i32 { + self.0 + } + + pub const fn translation_key(self) -> Option<&'static str> { match self { - #(#translation_key_arms)* + #translation_key_arms + _ => None, } } } - pub enum TrackedData { - #(#concrete_entity_names(#concrete_entity_names),)* - } - - impl TrackedData { - pub(super) fn new(kind: EntityKind) -> Self { - match kind { - #(EntityKind::#concrete_entity_names => Self::#concrete_entity_names(#concrete_entity_names::new()),)* - } - } - - pub fn kind(&self) -> EntityKind { - match self { - #(Self::#concrete_entity_names(_) => EntityKind::#concrete_entity_names,)* - } - } - - pub(super) fn write_initial_tracked_data(&self, buf: &mut Vec) { - buf.clear(); - - match self { - #(Self::#concrete_entity_names(e) => e.initial_tracked_data(buf),)* - } - - if !buf.is_empty() { - buf.push(0xff); - } - } - - pub(super) fn write_updated_tracked_data(&self, buf: &mut Vec) { - buf.clear(); - - match self { - #(Self::#concrete_entity_names(e) => e.updated_tracked_data(buf),)* - } - - if !buf.is_empty() { - buf.push(0xff); - } - } - - pub(super) fn clear_modifications(&mut self) { - match self { - #(Self::#concrete_entity_names(e) => e.clear_modifications(),)* + impl std::fmt::Debug for EntityKind { + #[allow(clippy::write_literal)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + #entity_kind_fmt_args + EntityKind(other) => write!(f, "{other}"), } } } - #(#concrete_entity_structs)* + fn add_tracked_data_systems(app: &mut App) { + #systems + + #( + app.add_system( + #system_names.before(WriteUpdatePacketsToInstancesSet).in_base_set(CoreSet::PostUpdate) + ); + )* + } }) } -fn collect_all_fields<'a>(entity_name: &str, entities: &'a Entities) -> Vec<&'a Field> { - fn rec<'a>(entity_name: &str, entities: &'a Entities, fields: &mut Vec<&'a Field>) { +enum MarkerOrField<'a> { + Marker { + entity_name: &'a str, + }, + Field { + entity_name: &'a str, + field: &'a Field, + }, +} + +fn collect_bundle_fields<'a>( + mut entity_name: &'a str, + entities: &'a Entities, +) -> Vec> { + let mut res = vec![]; + + loop { let e = &entities[entity_name]; - fields.extend(&e.fields); + + res.push(MarkerOrField::Marker { entity_name }); + res.extend( + e.fields + .iter() + .map(|field| MarkerOrField::Field { entity_name, field }), + ); if let Some(parent) = &e.parent { - rec(parent, entities, fields); + entity_name = parent; + } else { + break; } } - let mut fields = vec![]; - rec(entity_name, entities, &mut fields); - - fields.sort_by_key(|f| f.index); - - fields + res } diff --git a/crates/valence/examples/bench_players.rs b/crates/valence/examples/bench_players.rs index 6f3d0b7..379a97b 100644 --- a/crates/valence/examples/bench_players.rs +++ b/crates/valence/examples/bench_players.rs @@ -2,8 +2,8 @@ use std::time::Instant; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::instance::{Chunk, Instance}; use valence::prelude::*; @@ -67,26 +67,18 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, unique_id, mut pos, mut loc, mut game_mode) in &mut clients { - pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into(); - loc.0 = instances.single(); + for (entity, uuid, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, unique_id.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/biomes.rs b/crates/valence/examples/biomes.rs index bd60613..b081522 100644 --- a/crates/valence/examples/biomes.rs +++ b/crates/valence/examples/biomes.rs @@ -1,7 +1,6 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; use valence::prelude::*; const SPAWN_Y: i32 = 0; diff --git a/crates/valence/examples/block_entities.rs b/crates/valence/examples/block_entities.rs index 4ab7e67..36c2233 100644 --- a/crates/valence/examples/block_entities.rs +++ b/crates/valence/examples/block_entities.rs @@ -1,7 +1,8 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::{default_event_handler, ChatMessage, PlayerInteractBlock}; +use valence::client::event::{ChatMessage, PlayerInteractBlock}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::nbt::{compound, List}; use valence::prelude::*; use valence::protocol::types::Hand; @@ -63,29 +64,20 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Position, - &mut Look, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut pos, mut look, mut loc, mut game_mode) in &mut clients { - pos.set([1.5, FLOOR_Y as f64 + 1.0, 1.5]); - look.yaw = -90.0; - loc.0 = instances.single(); + for (entity, uuid, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([1.5, FLOOR_Y as f64 + 1.0, 1.5]), + look: Look::new(-90.0, 0.0), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/building.rs b/crates/valence/examples/building.rs index 3110fab..a1185bf 100644 --- a/crates/valence/examples/building.rs +++ b/crates/valence/examples/building.rs @@ -1,9 +1,8 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::{ - default_event_handler, PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock, -}; +use valence::client::event::{PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; use valence::protocol::types::Hand; @@ -50,28 +49,20 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { - pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into(); - loc.0 = instances.single(); + for (entity, uuid, mut client, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; client.send_message("Welcome to Valence! Build something cool.".italic()); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + uuid: *uuid, + ..Default::default() + }); } } @@ -80,7 +71,7 @@ fn toggle_gamemode_on_sneak( mut events: EventReader, ) { for event in events.iter() { - let Ok(mut mode) = clients.get_component_mut::(event.client) else { + let Ok(mut mode) = clients.get_mut(event.client) else { continue; }; *mode = match *mode { diff --git a/crates/valence/examples/chat.rs b/crates/valence/examples/chat.rs index 22c3f7b..e050721 100644 --- a/crates/valence/examples/chat.rs +++ b/crates/valence/examples/chat.rs @@ -1,9 +1,9 @@ #![allow(clippy::type_complexity)] -use bevy_app::App; use tracing::warn; -use valence::client::despawn_disconnected_clients; -use valence::client::event::{default_event_handler, ChatMessage, CommandExecution}; +use valence::client::event::{ChatMessage, CommandExecution}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const SPAWN_Y: i32 = 64; @@ -12,7 +12,7 @@ pub fn main() { tracing_subscriber::fmt().init(); App::new() - .add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline)) + .add_plugin(ServerPlugin::new(())) .add_startup_system(setup) .add_system(init_clients) .add_systems( @@ -47,29 +47,20 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { - pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); - loc.0 = instances.single(); + for (entity, uuid, mut client, mut game_mode) in &mut clients { *game_mode = GameMode::Adventure; client.send_message("Welcome to Valence! Talk about something.".italic()); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/chest.rs b/crates/valence/examples/chest.rs index d8ddf1d..9b12699 100644 --- a/crates/valence/examples/chest.rs +++ b/crates/valence/examples/chest.rs @@ -1,8 +1,9 @@ #![allow(clippy::type_complexity)] use tracing::warn; -use valence::client::despawn_disconnected_clients; -use valence::client::event::{default_event_handler, PlayerInteractBlock, StartSneaking}; +use valence::client::event::{PlayerInteractBlock, StartSneaking}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const SPAWN_Y: i32 = 64; @@ -50,26 +51,19 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { - pos.0 = [0.5, SPAWN_Y as f64 + 1.0, 0.5].into(); - loc.0 = instances.single(); + for (entity, uuid, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]), + uuid: *uuid, + ..Default::default() + }); } } @@ -78,7 +72,7 @@ fn toggle_gamemode_on_sneak( mut events: EventReader, ) { for event in events.iter() { - let Ok(mut mode) = clients.get_component_mut::(event.client) else { + let Ok(mut mode) = clients.get_mut(event.client) else { continue; }; *mode = match *mode { diff --git a/crates/valence/examples/combat.rs b/crates/valence/examples/combat.rs index 08dfd1a..3e43d45 100644 --- a/crates/valence/examples/combat.rs +++ b/crates/valence/examples/combat.rs @@ -2,10 +2,10 @@ use bevy_ecs::query::WorldQuery; use glam::Vec3Swizzles; -use valence::client::despawn_disconnected_clients; -use valence::client::event::{ - default_event_handler, PlayerInteract, StartSprinting, StopSprinting, -}; +use valence::client::event::{PlayerInteract, StartSprinting, StopSprinting}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; +use valence::entity::EntityStatuses; use valence::prelude::*; const SPAWN_Y: i32 = 64; @@ -67,20 +67,22 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query<(Entity, &UniqueId, &mut Position, &mut Location), Added>, + mut clients: Query<(Entity, &UniqueId), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut pos, mut loc) in &mut clients { - pos.set([0.0, SPAWN_Y as f64, 0.0]); - loc.0 = instances.single(); - + for (entity, uuid) in &mut clients { commands.entity(entity).insert(( CombatState { last_attacked_tick: 0, has_bonus_knockback: false, }, - McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0), + PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.5, SPAWN_Y as f64, 0.5]), + uuid: *uuid, + ..Default::default() + }, )); } } @@ -91,11 +93,11 @@ struct CombatQuery { client: &'static mut Client, pos: &'static Position, state: &'static mut CombatState, - entity: &'static mut McEntity, + statuses: &'static mut EntityStatuses, } fn handle_combat_events( - manager: Res, + manager: Res, server: Res, mut clients: Query, mut start_sprinting: EventReader, @@ -120,7 +122,7 @@ fn handle_combat_events( .. } in interact_with_entity.iter() { - let Some(victim_client) = manager.get_with_protocol_id(entity_id) else { + let Some(victim_client) = manager.get_with_id(entity_id) else { // Attacked entity doesn't exist. continue }; @@ -162,9 +164,10 @@ fn handle_combat_events( victim .client .trigger_status(EntityStatus::DamageFromGenericSource); + victim - .entity - .trigger_status(EntityStatus::DamageFromGenericSource); + .statuses + .trigger(EntityStatus::DamageFromGenericSource); } } diff --git a/crates/valence/examples/conway.rs b/crates/valence/examples/conway.rs index df2444e..c125369 100644 --- a/crates/valence/examples/conway.rs +++ b/crates/valence/examples/conway.rs @@ -2,8 +2,9 @@ use std::mem; -use valence::client::despawn_disconnected_clients; -use valence::client::event::{default_event_handler, StartDigging, StartSneaking}; +use valence::client::event::{StartDigging, StartSneaking}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const BOARD_MIN_X: i32 = -30; @@ -67,23 +68,11 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { - pos.0 = SPAWN_POS; - loc.0 = instances.single(); + for (entity, uuid, mut client, mut game_mode) in &mut clients { *game_mode = GameMode::Survival; client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); @@ -92,9 +81,13 @@ fn init_clients( life." .italic(), ); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position(SPAWN_POS), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/cow_sphere.rs b/crates/valence/examples/cow_sphere.rs index c9aaf0a..1369cf4 100644 --- a/crates/valence/examples/cow_sphere.rs +++ b/crates/valence/examples/cow_sphere.rs @@ -3,14 +3,14 @@ use std::f64::consts::TAU; use glam::{DQuat, EulerRot}; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; -use valence::util::to_yaw_and_pitch; + +type SpherePartBundle = valence::entity::cow::CowBundle; const SPHERE_CENTER: DVec3 = DVec3::new(0.5, SPAWN_POS.y as f64 + 2.0, 0.5); const SPHERE_AMOUNT: usize = 200; -const SPHERE_KIND: EntityKind = EntityKind::Cow; const SPHERE_MIN_RADIUS: f64 = 6.0; const SPHERE_MAX_RADIUS: f64 = 12.0; const SPHERE_FREQ: f64 = 0.5; @@ -48,41 +48,42 @@ fn setup(mut commands: Commands, server: Res) { let instance_id = commands.spawn(instance).id(); - commands.spawn_batch( - [0; SPHERE_AMOUNT].map(|_| (McEntity::new(SPHERE_KIND, instance_id), SpherePart)), - ); + commands.spawn_batch([0; SPHERE_AMOUNT].map(|_| { + ( + SpherePartBundle { + location: Location(instance_id), + ..Default::default() + }, + SpherePart, + ) + })); } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { - pos.set([ - SPAWN_POS.x as f64 + 0.5, - SPAWN_POS.y as f64 + 1.0, - SPAWN_POS.z as f64 + 0.5, - ]); - loc.0 = instances.single(); + for (entity, uuid, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([ + SPAWN_POS.x as f64 + 0.5, + SPAWN_POS.y as f64 + 1.0, + SPAWN_POS.z as f64 + 0.5, + ]), + uuid: *uuid, + ..Default::default() + }); } } -fn update_sphere(server: Res, mut parts: Query<&mut McEntity, With>) { +fn update_sphere( + server: Res, + mut parts: Query<(&mut Position, &mut Look, &mut HeadYaw), With>, +) { let time = server.current_tick() as f64 / server.tps() as f64; let rot_angles = DVec3::new(0.2, 0.4, 0.6) * SPHERE_FREQ * time * TAU % TAU; @@ -94,16 +95,16 @@ fn update_sphere(server: Res, mut parts: Query<&mut McEntity, With) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut HasRespawnScreen, - &mut Location, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut Client, &mut HasRespawnScreen), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut has_respawn_screen, mut loc) in &mut clients { - pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); + for (entity, uuid, mut client, mut has_respawn_screen) in &mut clients { has_respawn_screen.0 = true; - loc.0 = instances.iter().next().unwrap(); client.send_message( "Welcome to Valence! Sneak to die in the game (but not in real life).".italic(), ); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.iter().next().unwrap()), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/gamemode_switcher.rs b/crates/valence/examples/gamemode_switcher.rs index 26b1cbb..acda83a 100644 --- a/crates/valence/examples/gamemode_switcher.rs +++ b/crates/valence/examples/gamemode_switcher.rs @@ -1,7 +1,8 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::{default_event_handler, CommandExecution}; +use valence::client::event::CommandExecution; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const SPAWN_Y: i32 = 64; @@ -39,30 +40,23 @@ fn setup(mut commands: Commands, server: Res) { fn init_clients( mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut Location, - &mut GameMode, - &mut OpLevel, - ), + (Entity, &UniqueId, &mut Client, &mut GameMode, &mut OpLevel), Added, >, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut loc, mut game_mode, mut op_level) in &mut clients { - pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); - loc.0 = instances.single(); + for (entity, uuid, mut client, mut game_mode, mut op_level) in &mut clients { *game_mode = GameMode::Creative; op_level.set(2); // required to use F3+F4, eg /gamemode client.send_message("Welcome to Valence! Use F3+F4 to change gamemode.".italic()); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/parkour.rs b/crates/valence/examples/parkour.rs index 48a6f64..0e330ea 100644 --- a/crates/valence/examples/parkour.rs +++ b/crates/valence/examples/parkour.rs @@ -5,8 +5,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use rand::seq::SliceRandom; use rand::Rng; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; use valence::protocol::packet::s2c::play::TitleFadeS2c; use valence::protocol::sound::Sound; @@ -52,23 +52,12 @@ struct GameState { } fn init_clients( - mut clients: Query< - ( - Entity, - &mut Client, - &UniqueId, - &mut IsFlat, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &mut Client, &UniqueId, &mut IsFlat, &mut GameMode), Added>, server: Res, mut commands: Commands, ) { - for (entity, mut client, uuid, mut is_flat, mut loc, mut game_mode) in clients.iter_mut() { + for (entity, mut client, uuid, mut is_flat, mut game_mode) in clients.iter_mut() { is_flat.0 = true; - loc.0 = entity; *game_mode = GameMode::Adventure; client.send_message("Welcome to epic infinite parkour game!".italic()); @@ -82,9 +71,13 @@ fn init_clients( let instance = server.new_instance(DimensionId::default()); - let mcentity = McEntity::with_uuid(EntityKind::Player, entity, uuid.0); + let player = PlayerBundle { + location: Location(entity), + uuid: *uuid, + ..Default::default() + }; - commands.entity(entity).insert((state, instance, mcentity)); + commands.entity(entity).insert((state, instance, player)); } } diff --git a/crates/valence/examples/particles.rs b/crates/valence/examples/particles.rs index b5bb219..9fd14ee 100644 --- a/crates/valence/examples/particles.rs +++ b/crates/valence/examples/particles.rs @@ -2,8 +2,8 @@ use std::fmt; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const SPAWN_Y: i32 = 64; @@ -42,27 +42,19 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { - pos.set([0.5, SPAWN_Y as f64 + 1.0, 0.5]); - loc.0 = instances.single(); + for (entity, uuid, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/player_list.rs b/crates/valence/examples/player_list.rs index d428066..765d7a8 100644 --- a/crates/valence/examples/player_list.rs +++ b/crates/valence/examples/player_list.rs @@ -1,7 +1,7 @@ #![allow(clippy::type_complexity)] use rand::Rng; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; use valence::player_list::Entry; use valence::prelude::*; diff --git a/crates/valence/examples/resource_pack.rs b/crates/valence/examples/resource_pack.rs index 3749a35..b0ef177 100644 --- a/crates/valence/examples/resource_pack.rs +++ b/crates/valence/examples/resource_pack.rs @@ -1,9 +1,9 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::{ - default_event_handler, PlayerInteract, ResourcePackStatus, ResourcePackStatusChange, -}; +use valence::client::event::{PlayerInteract, ResourcePackStatus, ResourcePackStatusChange}; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; +use valence::entity::sheep::SheepBundle; use valence::prelude::*; use valence::protocol::packet::c2s::play::player_interact::Interaction; @@ -46,38 +46,31 @@ fn setup(mut commands: Commands, server: Res) { let instance_ent = commands.spawn(instance).id(); - let mut sheep = McEntity::new(EntityKind::Sheep, instance_ent); - sheep.set_position([0.0, SPAWN_Y as f64 + 1.0, 2.0]); - sheep.set_yaw(180.0); - sheep.set_head_yaw(180.0); - commands.spawn(sheep); + commands.spawn(SheepBundle { + location: Location(instance_ent), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 2.0]), + look: Look::new(180.0, 0.0), + head_yaw: HeadYaw(180.0), + ..Default::default() + }); } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut Client, - &mut Position, - &mut Location, - &mut GameMode, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { - pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); - loc.0 = instances.single(); + for (entity, uuid, mut client, mut game_mode) in &mut clients { *game_mode = GameMode::Creative; client.send_message("Hit the sheep to prompt for the resource pack.".italic()); - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/terrain.rs b/crates/valence/examples/terrain.rs index 427d220..f0a4e37 100644 --- a/crates/valence/examples/terrain.rs +++ b/crates/valence/examples/terrain.rs @@ -9,8 +9,8 @@ use std::time::SystemTime; use flume::{Receiver, Sender}; use noise::{NoiseFn, SuperSimplex}; use tracing::info; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0); @@ -108,31 +108,20 @@ fn setup(mut commands: Commands, server: Res) { } fn init_clients( - mut clients: Query< - ( - Entity, - &UniqueId, - &mut IsFlat, - &mut GameMode, - &mut Position, - &mut Location, - ), - Added, - >, + mut clients: Query<(Entity, &UniqueId, &mut IsFlat, &mut GameMode), Added>, instances: Query>, mut commands: Commands, ) { - for (entity, uuid, mut is_flat, mut game_mode, mut pos, mut loc) in &mut clients { - let instance = instances.single(); - + for (entity, uuid, mut is_flat, mut game_mode) in &mut clients { is_flat.0 = true; *game_mode = GameMode::Creative; - pos.0 = SPAWN_POS; - loc.0 = instance; - commands - .entity(entity) - .insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position(SPAWN_POS), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence/examples/text.rs b/crates/valence/examples/text.rs index 87d0809..ec2bb67 100644 --- a/crates/valence/examples/text.rs +++ b/crates/valence/examples/text.rs @@ -1,7 +1,6 @@ #![allow(clippy::type_complexity)] -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; use valence::prelude::*; use valence::protocol::translation_key; diff --git a/crates/valence/src/client.rs b/crates/valence/src/client.rs index 941fe05..e02f745 100644 --- a/crates/valence/src/client.rs +++ b/crates/valence/src/client.rs @@ -12,6 +12,7 @@ use glam::{DVec3, Vec3}; use rand::Rng; use tracing::warn; use valence_protocol::block_pos::BlockPos; +use valence_protocol::byte_angle::ByteAngle; use valence_protocol::codec::{PacketDecoder, PacketEncoder}; use valence_protocol::ident::Ident; use valence_protocol::item::ItemStack; @@ -20,11 +21,11 @@ use valence_protocol::packet::s2c::play::particle::Particle; use valence_protocol::packet::s2c::play::player_position_look::Flags as PlayerPositionLookFlags; use valence_protocol::packet::s2c::play::{ ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c, - DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, EntityTrackerUpdateS2c, - EntityVelocityUpdateS2c, GameJoinS2c, GameMessageS2c, GameStateChangeS2c, KeepAliveS2c, - OverlayMessageS2c, ParticleS2c, PlaySoundS2c, PlayerActionResponseS2c, PlayerPositionLookS2c, - PlayerRespawnS2c, PlayerSpawnPositionS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, - TitleS2c, UnloadChunkS2c, + DisconnectS2c, EntitiesDestroyS2c, EntitySetHeadYawS2c, EntitySpawnS2c, EntityStatusS2c, + EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, ExperienceOrbSpawnS2c, GameJoinS2c, + GameMessageS2c, GameStateChangeS2c, KeepAliveS2c, OverlayMessageS2c, ParticleS2c, PlaySoundS2c, + PlayerActionResponseS2c, PlayerPositionLookS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c, + PlayerSpawnS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, TitleS2c, UnloadChunkS2c, }; use valence_protocol::sound::Sound; use valence_protocol::text::Text; @@ -37,16 +38,22 @@ use crate::component::{ Properties, UniqueId, Username, }; use crate::dimension::DimensionId; -use crate::entity::{velocity_to_packet_units, EntityStatus, McEntity, TrackedData}; -use crate::instance::{Instance, UpdateInstancesPreClientSet}; +use crate::entity::{ + EntityId, EntityKind, EntityStatus, HeadYaw, ObjectData, TrackedData, Velocity, +}; +use crate::instance::{Instance, WriteUpdatePacketsToInstancesSet}; use crate::inventory::{Inventory, InventoryKind}; use crate::packet::WritePacket; use crate::prelude::ScratchBuf; use crate::server::{NewClientInfo, Server}; +use crate::util::velocity_to_packet_units; use crate::view::{ChunkPos, ChunkView}; +mod default_event_handler; pub mod event; +pub use default_event_handler::*; + /// The bundle of components needed for clients to function. All components are /// required unless otherwise stated. #[derive(Bundle)] @@ -62,7 +69,7 @@ pub(crate) struct ClientBundle { old_location: OldLocation, position: Position, old_position: OldPosition, - direction: Look, + look: Look, on_ground: OnGround, compass_pos: CompassPos, game_mode: GameMode, @@ -105,7 +112,7 @@ impl ClientBundle { old_location: OldLocation::default(), position: Position::default(), old_position: OldPosition::default(), - direction: Look::default(), + look: Look::default(), on_ground: OnGround::default(), compass_pos: CompassPos::default(), game_mode: GameMode::default(), @@ -221,10 +228,10 @@ impl Client { /// Kills the client and shows `message` on the death screen. If an entity /// killed the player, you should supply it as `killer`. - pub fn kill(&mut self, killer: Option<&McEntity>, message: impl Into) { + pub fn kill(&mut self, killer: Option, message: impl Into) { self.write_packet(&DeathMessageS2c { player_id: VarInt(0), - entity_id: killer.map_or(-1, |k| k.protocol_id()), + entity_id: killer.map(|id| id.get()).unwrap_or(-1), message: message.into().into(), }); } @@ -613,10 +620,10 @@ impl Plugin for ClientPlugin { ( initial_join, update_chunk_load_dist, - read_data_in_view - .after(UpdateInstancesPreClientSet) + read_data_in_old_view + .after(WriteUpdatePacketsToInstancesSet) .after(update_chunk_load_dist), - update_view.after(initial_join).after(read_data_in_view), + update_view.after(initial_join).after(read_data_in_old_view), respawn.after(update_view), remove_entities.after(update_view), update_spawn_position.after(update_view), @@ -624,7 +631,8 @@ impl Plugin for ClientPlugin { teleport.after(update_view), update_game_mode, send_keepalive, - update_tracked_data, + update_tracked_data.after(WriteUpdatePacketsToInstancesSet), + init_tracked_data.after(WriteUpdatePacketsToInstancesSet), update_op_level, acknowledge_player_actions, ) @@ -634,7 +642,7 @@ impl Plugin for ClientPlugin { .configure_set( FlushPacketsSet .in_base_set(CoreSet::PostUpdate) - .after(UpdateInstancesPreClientSet), + .after(WriteUpdatePacketsToInstancesSet), ) .add_system(flush_packets.in_set(FlushPacketsSet)); } @@ -783,20 +791,83 @@ fn update_chunk_load_dist( } } -fn read_data_in_view( +#[derive(WorldQuery)] +pub(crate) struct EntityInitQuery { + pub entity_id: &'static EntityId, + pub uuid: &'static UniqueId, + pub kind: &'static EntityKind, + pub look: &'static Look, + pub head_yaw: &'static HeadYaw, + pub on_ground: &'static OnGround, + pub object_data: &'static ObjectData, + pub velocity: &'static Velocity, + pub tracked_data: &'static TrackedData, +} + +impl EntityInitQueryItem<'_> { + /// Writes the appropriate packets to initialize an entity. This will spawn + /// the entity and initialize tracked data. + pub fn write_init_packets(&self, pos: DVec3, mut writer: impl WritePacket) { + match *self.kind { + EntityKind::MARKER => {} + EntityKind::EXPERIENCE_ORB => { + writer.write_packet(&ExperienceOrbSpawnS2c { + entity_id: self.entity_id.get().into(), + position: pos.to_array(), + count: self.object_data.0 as i16, + }); + } + EntityKind::PLAYER => { + writer.write_packet(&PlayerSpawnS2c { + entity_id: self.entity_id.get().into(), + player_uuid: self.uuid.0, + position: pos.to_array(), + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + }); + + // Player spawn packet doesn't include head yaw for some reason. + writer.write_packet(&EntitySetHeadYawS2c { + entity_id: self.entity_id.get().into(), + head_yaw: ByteAngle::from_degrees(self.head_yaw.0), + }); + } + _ => writer.write_packet(&EntitySpawnS2c { + entity_id: self.entity_id.get().into(), + object_uuid: self.uuid.0, + kind: self.kind.get().into(), + position: pos.to_array(), + pitch: ByteAngle::from_degrees(self.look.pitch), + yaw: ByteAngle::from_degrees(self.look.yaw), + head_yaw: ByteAngle::from_degrees(self.head_yaw.0), + data: self.object_data.0.into(), + velocity: velocity_to_packet_units(self.velocity.0), + }), + } + + if let Some(init_data) = self.tracked_data.init_data() { + writer.write_packet(&EntityTrackerUpdateS2c { + entity_id: self.entity_id.get().into(), + metadata: init_data.into(), + }); + } + } +} + +fn read_data_in_old_view( mut clients: Query<( &mut Client, - &mut ScratchBuf, &mut EntityRemoveBuf, &OldLocation, &OldPosition, &OldViewDistance, )>, instances: Query<&Instance>, - entities: Query<&McEntity>, + entities: Query<(EntityInitQuery, &OldPosition)>, + entity_ids: Query<&EntityId>, ) { clients.par_iter_mut().for_each_mut( - |(mut client, mut scratch, mut remove_buf, old_loc, old_pos, old_view_dist)| { + |(mut client, mut remove_buf, old_loc, old_pos, old_view_dist)| { let Ok(instance) = instances.get(old_loc.get()) else { return; }; @@ -829,15 +900,12 @@ fn read_data_in_view( if src_pos.map_or(true, |p| !view.contains(p)) { // The incoming entity originated from outside the view distance, so it // must be spawned. - if let Ok(entity) = entities.get(id) { - // Spawn the entity at the old position so that later relative - // entity movement packets will not - // set the entity to the wrong position. - entity.write_init_packets( - &mut client.enc, - entity.old_position(), - &mut scratch.0, - ); + if let Ok((entity, old_pos)) = entities.get(id) { + // Notice we are spawning the entity at its old position rather than + // the current position. This is because the client could also + // receive update packets for this entity this tick, which may + // include a relative entity movement. + entity.write_init_packets(old_pos.get(), &mut client.enc); } } } @@ -847,8 +915,8 @@ fn read_data_in_view( if dest_pos.map_or(true, |p| !view.contains(p)) { // The outgoing entity moved outside the view distance, so it must be // despawned. - if let Ok(entity) = entities.get(id) { - remove_buf.push(entity.protocol_id()); + if let Ok(entity_id) = entity_ids.get(id) { + remove_buf.push(entity_id.get()); } } } @@ -867,7 +935,7 @@ fn read_data_in_view( /// client's chunk position. /// /// This handles the situation when a client changes instances or chunk -/// position. It must run after [`read_data_in_view`]. +/// position. It must run after [`read_data_in_old_view`]. fn update_view( mut clients: Query< ( @@ -884,7 +952,8 @@ fn update_view( Or<(Changed, Changed, Changed)>, >, instances: Query<&Instance>, - entities: Query<&McEntity>, + entities: Query<(EntityInitQuery, &Position)>, + entity_ids: Query<&EntityId>, ) { clients.par_iter_mut().for_each_mut( |( @@ -930,8 +999,8 @@ fn update_view( // Unload all the entities in the cell. for &id in &cell.entities { - if let Ok(entity) = entities.get(id) { - remove_buf.push(entity.protocol_id()); + if let Ok(entity_id) = entity_ids.get(id) { + remove_buf.push(entity_id.get()); } } } @@ -956,12 +1025,8 @@ fn update_view( // Load all the entities in this cell. for &id in &cell.entities { - if let Ok(entity) = entities.get(id) { - entity.write_init_packets( - &mut client.enc, - entity.position(), - &mut scratch.0, - ); + if let Ok((entity, pos)) = entities.get(id) { + entity.write_init_packets(pos.get(), &mut client.enc); } } } @@ -988,8 +1053,8 @@ fn update_view( // Unload all the entities in the cell. for &id in &cell.entities { - if let Ok(entity) = entities.get(id) { - remove_buf.push(entity.protocol_id()); + if let Ok(entity_id) = entity_ids.get(id) { + remove_buf.push(entity_id.get()); } } } @@ -1011,12 +1076,8 @@ fn update_view( // Load all the entities in this cell. for &id in &cell.entities { - if let Ok(entity) = entities.get(id) { - entity.write_init_packets( - &mut client.enc, - entity.position(), - &mut scratch.0, - ); + if let Ok((entity, pos)) = entities.get(id) { + entity.write_init_packets(pos.get(), &mut client.enc); } } } @@ -1130,21 +1191,24 @@ fn flush_packets( } } -fn update_tracked_data(mut clients: Query<(&mut Client, &McEntity)>, mut scratch: Local>) { - for (mut client, entity) in &mut clients { - if let TrackedData::Player(player) = &entity.data { - scratch.clear(); - // TODO: should some fields be ignored? - player.updated_tracked_data(&mut scratch); +fn init_tracked_data(mut clients: Query<(&mut Client, &TrackedData), Added>) { + for (mut client, tracked_data) in &mut clients { + if let Some(init_data) = tracked_data.init_data() { + client.write_packet(&EntityTrackerUpdateS2c { + entity_id: VarInt(0), + metadata: init_data.into(), + }); + } + } +} - if !scratch.is_empty() { - scratch.push(0xff); - - client.enc.write_packet(&EntityTrackerUpdateS2c { - entity_id: VarInt(0), - metadata: scratch.as_slice().into(), - }) - } +fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) { + for (mut client, tracked_data) in &mut clients { + if let Some(update_data) = tracked_data.update_data() { + client.write_packet(&EntityTrackerUpdateS2c { + entity_id: VarInt(0), + metadata: update_data.into(), + }); } } } diff --git a/crates/valence/src/client/default_event_handler.rs b/crates/valence/src/client/default_event_handler.rs new file mode 100644 index 0000000..921bd1b --- /dev/null +++ b/crates/valence/src/client/default_event_handler.rs @@ -0,0 +1,116 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::query::WorldQuery; +use valence_protocol::types::Hand; + +use super::event::{ + ClientSettings, HandSwing, PlayerMove, StartSneaking, StartSprinting, StopSneaking, + StopSprinting, +}; +use super::{Client, ViewDistance}; +use crate::entity::player::PlayerModelParts; +use crate::entity::{entity, player, EntityAnimation, EntityAnimations, EntityKind, HeadYaw, Pose}; + +#[doc(hidden)] +#[derive(WorldQuery)] +#[world_query(mutable)] +pub struct DefaultEventHandlerQuery { + client: &'static mut Client, + view_dist: &'static mut ViewDistance, + head_yaw: &'static mut HeadYaw, + player_model_parts: Option<&'static mut PlayerModelParts>, + pose: &'static mut entity::Pose, + flags: &'static mut entity::Flags, + animations: Option<&'static mut EntityAnimations>, + entity_kind: Option<&'static EntityKind>, + main_arm: Option<&'static mut player::MainArm>, +} + +/// The default event handler system which handles client events in a +/// reasonable default way. +/// +/// For instance, movement events are handled by changing the entity's +/// position/rotation to match the received movement, crouching makes the +/// entity crouch, etc. +/// +/// This system's primary purpose is to reduce boilerplate code in the +/// examples, but it can be used as a quick way to get started in your own +/// code. The precise behavior of this system is left unspecified and +/// is subject to change. +/// +/// This system must be scheduled to run in the +/// [`EventLoopSchedule`](crate::client::event::EventLoopSchedule). Otherwise, +/// it may not function correctly. +#[allow(clippy::too_many_arguments)] +pub fn default_event_handler( + mut clients: Query, + mut update_settings_events: EventReader, + mut player_move: EventReader, + mut start_sneaking: EventReader, + mut stop_sneaking: EventReader, + mut start_sprinting: EventReader, + mut stop_sprinting: EventReader, + mut swing_arm: EventReader, +) { + for ClientSettings { + client, + view_distance, + displayed_skin_parts, + main_arm, + .. + } in update_settings_events.iter() + { + if let Ok(mut q) = clients.get_mut(*client) { + q.view_dist.0 = *view_distance; + + if let Some(mut parts) = q.player_model_parts { + parts.set_if_neq(PlayerModelParts(u8::from(*displayed_skin_parts))); + } + + if let Some(mut player_main_arm) = q.main_arm { + player_main_arm.0 = *main_arm as _; + } + } + } + + for PlayerMove { client, yaw, .. } in player_move.iter() { + if let Ok(mut q) = clients.get_mut(*client) { + q.head_yaw.set_if_neq(HeadYaw(*yaw)); + } + } + + for StartSneaking { client } in start_sneaking.iter() { + if let Ok(mut q) = clients.get_mut(*client) { + q.pose.set_if_neq(entity::Pose(Pose::Sneaking)); + } + } + + for StopSneaking { client } in stop_sneaking.iter() { + if let Ok(mut q) = clients.get_mut(*client) { + q.pose.set_if_neq(entity::Pose(Pose::Standing)); + } + } + + for StartSprinting { client } in start_sprinting.iter() { + if let Ok(mut q) = clients.get_mut(*client) { + q.flags.set_sprinting(true); + } + } + + for StopSprinting { client } in stop_sprinting.iter() { + if let Ok(mut q) = clients.get_mut(*client) { + q.flags.set_sprinting(false); + } + } + + for HandSwing { client, hand } in swing_arm.iter() { + if let Ok(q) = clients.get_mut(*client) { + if let (Some(mut animations), Some(&EntityKind::PLAYER)) = (q.animations, q.entity_kind) + { + animations.trigger(match hand { + Hand::Main => EntityAnimation::SwingMainHand, + Hand::Off => EntityAnimation::SwingOffHand, + }); + } + } + } +} diff --git a/crates/valence/src/client/event.rs b/crates/valence/src/client/event.rs index f9a4e16..e4e3e0e 100644 --- a/crates/valence/src/client/event.rs +++ b/crates/valence/src/client/event.rs @@ -15,9 +15,7 @@ use valence_protocol::ident::Ident; use valence_protocol::item::ItemStack; use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot}; use valence_protocol::packet::c2s::play::client_command::Action as ClientCommandAction; -use valence_protocol::packet::c2s::play::client_settings::{ - ChatMode, DisplayedSkinParts, MainHand, -}; +use valence_protocol::packet::c2s::play::client_settings::{ChatMode, DisplayedSkinParts, MainArm}; use valence_protocol::packet::c2s::play::player_action::Action as PlayerAction; use valence_protocol::packet::c2s::play::player_interact::Interaction; use valence_protocol::packet::c2s::play::recipe_category_options::RecipeBookId; @@ -30,16 +28,13 @@ use valence_protocol::packet::c2s::play::{ AdvancementTabC2s, ClientStatusC2s, ResourcePackStatusC2s, UpdatePlayerAbilitiesC2s, }; use valence_protocol::packet::C2sPlayPacket; -use valence_protocol::tracked_data::Pose; use valence_protocol::types::{Difficulty, Direction, Hand}; use super::{ CursorItem, KeepaliveState, PlayerActionSequence, PlayerInventoryState, TeleportState, - ViewDistance, }; use crate::client::Client; use crate::component::{Look, OnGround, Ping, Position}; -use crate::entity::{EntityAnimation, EntityKind, McEntity, TrackedData}; use crate::inventory::Inventory; #[derive(Clone, Debug)] @@ -98,7 +93,7 @@ pub struct ClientSettings { /// `true` if the client has chat colors enabled, `false` otherwise. pub chat_colors: bool, pub displayed_skin_parts: DisplayedSkinParts, - pub main_hand: MainHand, + pub main_arm: MainArm, pub enable_text_filtering: bool, pub allow_server_listings: bool, } @@ -829,7 +824,7 @@ fn handle_one_packet( chat_mode: p.chat_mode, chat_colors: p.chat_colors, displayed_skin_parts: p.displayed_skin_parts, - main_hand: p.main_hand, + main_arm: p.main_arm, enable_text_filtering: p.enable_text_filtering, allow_server_listings: p.allow_server_listings, }); @@ -1396,117 +1391,3 @@ fn handle_one_packet( Ok(true) } - -/// The default event handler system which handles client events in a -/// reasonable default way. -/// -/// For instance, movement events are handled by changing the entity's -/// position/rotation to match the received movement, crouching makes the -/// entity crouch, etc. -/// -/// This system's primary purpose is to reduce boilerplate code in the -/// examples, but it can be used as a quick way to get started in your own -/// code. The precise behavior of this system is left unspecified and -/// is subject to change. -/// -/// This system must be scheduled to run in the -/// [`EventLoopSchedule`]. Otherwise, it may -/// not function correctly. -#[allow(clippy::too_many_arguments)] -pub fn default_event_handler( - mut clients: Query<(&mut Client, Option<&mut McEntity>, &mut ViewDistance)>, - mut update_settings: EventReader, - mut player_move: EventReader, - mut start_sneaking: EventReader, - mut stop_sneaking: EventReader, - mut start_sprinting: EventReader, - mut stop_sprinting: EventReader, - mut swing_arm: EventReader, -) { - for ClientSettings { - client, - view_distance, - displayed_skin_parts, - main_hand, - .. - } in update_settings.iter() - { - if let Ok((_, mcentity, mut view_dist)) = clients.get_mut(*client) { - view_dist.set(*view_distance); - - if let Some(mut entity) = mcentity { - if let TrackedData::Player(player) = entity.data_mut() { - player.set_cape(displayed_skin_parts.cape()); - player.set_jacket(displayed_skin_parts.jacket()); - player.set_left_sleeve(displayed_skin_parts.left_sleeve()); - player.set_right_sleeve(displayed_skin_parts.right_sleeve()); - player.set_left_pants_leg(displayed_skin_parts.left_pants_leg()); - player.set_right_pants_leg(displayed_skin_parts.right_pants_leg()); - player.set_hat(displayed_skin_parts.hat()); - player.set_main_arm(*main_hand as u8); - } - } - } - } - - for PlayerMove { - client, - position, - yaw, - pitch, - on_ground, - .. - } in player_move.iter() - { - if let Ok((_, Some(mut mcentity), _)) = clients.get_mut(*client) { - mcentity.set_position(*position); - mcentity.set_yaw(*yaw); - mcentity.set_head_yaw(*yaw); - mcentity.set_pitch(*pitch); - mcentity.set_on_ground(*on_ground); - } - } - - for StartSneaking { client } in start_sneaking.iter() { - if let Ok((_, Some(mut entity), _)) = clients.get_mut(*client) { - if let TrackedData::Player(player) = entity.data_mut() { - player.set_pose(Pose::Sneaking); - } - }; - } - - for StopSneaking { client } in stop_sneaking.iter() { - if let Ok((_, Some(mut entity), _)) = clients.get_mut(*client) { - if let TrackedData::Player(player) = entity.data_mut() { - player.set_pose(Pose::Standing); - } - }; - } - - for StartSprinting { client } in start_sprinting.iter() { - if let Ok((_, Some(mut entity), _)) = clients.get_mut(*client) { - if let TrackedData::Player(player) = entity.data_mut() { - player.set_sprinting(true); - } - }; - } - - for StopSprinting { client } in stop_sprinting.iter() { - if let Ok((_, Some(mut entity), _)) = clients.get_mut(*client) { - if let TrackedData::Player(player) = entity.data_mut() { - player.set_sprinting(false); - } - }; - } - - for HandSwing { client, hand } in swing_arm.iter() { - if let Ok((_, Some(mut entity), _)) = clients.get_mut(*client) { - if entity.kind() == EntityKind::Player { - entity.trigger_animation(match hand { - Hand::Main => EntityAnimation::SwingMainHand, - Hand::Off => EntityAnimation::SwingOffHand, - }); - } - }; - } -} diff --git a/crates/valence/src/component.rs b/crates/valence/src/component.rs index 44c3a26..3c0aa01 100644 --- a/crates/valence/src/component.rs +++ b/crates/valence/src/component.rs @@ -10,12 +10,11 @@ use valence_protocol::types::{GameMode as ProtocolGameMode, Property}; use crate::prelude::FlushPacketsSet; use crate::util::{from_yaw_and_pitch, to_yaw_and_pitch}; use crate::view::ChunkPos; -use crate::NULL_ENTITY; /// A [`Component`] for marking entities that should be despawned at the end of /// the tick. /// -/// In Valence, some built-in components such as [`McEntity`] are not allowed to +/// In Valence, some entities such as [Minecraft entities] are not allowed to /// be removed from the [`World`] directly. Instead, you must give the entities /// you wish to despawn the `Despawned` component. At the end of the tick, /// Valence will despawn all entities with this component for you. @@ -23,13 +22,25 @@ use crate::NULL_ENTITY; /// It is legal to remove components or delete entities that Valence does not /// know about at any time. /// -/// [`McEntity`]: crate::entity::McEntity +/// [Minecraft entities]: crate::entity #[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)] pub struct Despawned; -#[derive(Component, Default, Clone, PartialEq, Eq, Debug)] +/// The universally unique identifier of an entity. Component wrapper for a +/// [`Uuid`]. +/// +/// This component is expected to remain _unique_ and _constant_ during the +/// lifetime of the entity. The [`Default`] impl generates a new random UUID. +#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] pub struct UniqueId(pub Uuid); +/// Generates a new random UUID. +impl Default for UniqueId { + fn default() -> Self { + Self(Uuid::from_bytes(rand::random())) + } +} + #[derive(Component, Clone, PartialEq, Eq, Debug)] pub struct Username(pub String); @@ -39,7 +50,7 @@ impl fmt::Display for Username { } } -#[derive(Component, Clone, PartialEq, Eq, Debug)] +#[derive(Component, Clone, PartialEq, Eq, Default, Debug)] pub struct Properties(pub Vec); impl Properties { @@ -54,7 +65,7 @@ impl Properties { } } -#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Default)] +#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] pub enum GameMode { #[default] @@ -105,10 +116,13 @@ pub struct Location(pub Entity); impl Default for Location { fn default() -> Self { - Self(NULL_ENTITY) + Self(Entity::PLACEHOLDER) } } +/// 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); @@ -124,7 +138,7 @@ impl OldLocation { impl Default for OldLocation { fn default() -> Self { - Self(NULL_ENTITY) + Self(Entity::PLACEHOLDER) } } @@ -132,11 +146,15 @@ impl Default for OldLocation { pub struct Position(pub DVec3); impl Position { + pub fn new(pos: impl Into) -> Self { + Self(pos.into()) + } + pub fn chunk_pos(&self) -> ChunkPos { ChunkPos::from_dvec3(self.0) } - pub fn get(&self) -> DVec3 { + pub fn get(self) -> DVec3 { self.0 } @@ -145,27 +163,26 @@ impl Position { } } +/// 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, Default, Debug)] pub struct OldPosition(DVec3); impl OldPosition { - pub fn new(pos: DVec3) -> Self { - Self(pos) + pub fn new(pos: impl Into) -> Self { + Self(pos.into()) } - pub fn get(&self) -> DVec3 { + pub fn get(self) -> DVec3 { self.0 } - pub fn chunk_pos(&self) -> ChunkPos { + pub fn chunk_pos(self) -> ChunkPos { ChunkPos::from_dvec3(self.0) } } -/// Velocity in m/s. -#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] -pub struct Velocity(pub Vec3); - /// Describes the direction an entity is looking using pitch and yaw angles. #[derive(Component, Copy, Clone, PartialEq, Default, Debug)] pub struct Look { @@ -176,6 +193,10 @@ pub struct Look { } 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 { from_yaw_and_pitch(self.yaw, self.pitch) diff --git a/crates/valence/src/entity.rs b/crates/valence/src/entity.rs index fbacc0d..69f7dd2 100644 --- a/crates/valence/src/entity.rs +++ b/crates/valence/src/entity.rs @@ -1,811 +1,700 @@ -use std::collections::HashMap; -use std::fmt; -use std::fmt::Formatter; +use std::num::Wrapping; use std::ops::Range; use bevy_app::{App, CoreSet, Plugin}; use bevy_ecs::prelude::*; -pub use data::{EntityKind, TrackedData}; -use glam::{DVec3, UVec3, Vec3}; +use glam::Vec3; +use paste::paste; use rustc_hash::FxHashMap; use tracing::warn; use uuid::Uuid; -use valence_protocol::byte_angle::ByteAngle; -use valence_protocol::packet::s2c::play::{ - EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntitySpawnS2c, - EntityStatusS2c as EntityEventS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, - ExperienceOrbSpawnS2c, MoveRelativeS2c, PlayerSpawnS2c, RotateAndMoveRelativeS2c, RotateS2c, -}; -use valence_protocol::tracked_data::{Facing, PaintingKind, Pose}; +pub use valence_protocol::types::Direction; use valence_protocol::var_int::VarInt; +use valence_protocol::{Decode, Encode}; -use crate::component::Despawned; -use crate::config::DEFAULT_TPS; -use crate::packet::WritePacket; -use crate::prelude::FlushPacketsSet; -use crate::util::Aabb; -use crate::NULL_ENTITY; - -pub mod data; +use crate::client::FlushPacketsSet; +use crate::component::{ + Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position, UniqueId, +}; +use crate::instance::WriteUpdatePacketsToInstancesSet; include!(concat!(env!("OUT_DIR"), "/entity_event.rs")); +include!(concat!(env!("OUT_DIR"), "/entity.rs")); + +/// 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); + +#[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(crate) Range); + +/// Cache for all the tracked data of an entity. Used for the +/// [`EntityTrackerUpdateS2c`][packet] packet. +/// +/// [packet]: valence_protocol::packet::s2c::play::EntityTrackerUpdateS2c +#[derive(Component, Default, Debug)] +pub struct TrackedData { + init_data: Vec, + /// A map of tracked data indices to the byte length of the entry in + /// `init_data`. + init_entries: Vec<(u8, u32)>, + update_data: Vec, +} + +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]: valence_protocol::packet::s2c::play::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]: valence_protocol::packet::s2c::play::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(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); + +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 { + let n = VarInt::decode(r)?.0; + + Ok(Self(if n == 0 { + None + } else { + Some(n.wrapping_sub(1)) + })) + } +} + +/// Maintains information about all spawned Minecraft entities. +#[derive(Resource, Debug)] +pub struct EntityManager { + /// Maps protocol IDs to ECS entities. + id_to_entity: FxHashMap, + uuid_to_entity: FxHashMap, + next_id: Wrapping, +} + +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_with_id(&self, entity_id: i32) -> Option { + self.id_to_entity.get(&entity_id).cloned() + } + + /// Gets the entity with the given UUID. + pub fn get_with_uuid(&self, uuid: Uuid) -> Option { + 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 u8) << $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, + } +} pub(crate) struct EntityPlugin; /// When new Minecraft entities are initialized and added to -/// [`McEntityManager`]. Systems that need all Minecraft entities to be in a +/// [`EntityManager`]. Systems that need all Minecraft entities to be in a /// valid state should run after this. #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub(crate) struct InitEntitiesSet; impl Plugin for EntityPlugin { fn build(&self, app: &mut App) { - app.insert_resource(McEntityManager::new()) + app.insert_resource(EntityManager::new()) .configure_set(InitEntitiesSet.in_base_set(CoreSet::PostUpdate)) - .add_system(init_mcentities.in_set(InitEntitiesSet)) + .add_system(init_entities.in_set(InitEntitiesSet)) + .add_system( + remove_despawned_from_manager + .in_base_set(CoreSet::PostUpdate) + .after(init_entities), + ) .add_systems( - ( - remove_despawned_from_manager.after(init_mcentities), - update_mcentities.after(FlushPacketsSet), - ) + (clear_status_changes, clear_animation_changes) + .after(WriteUpdatePacketsToInstancesSet) + .in_base_set(CoreSet::PostUpdate), + ) + .add_system( + clear_tracked_data_changes + .after(FlushPacketsSet) .in_base_set(CoreSet::PostUpdate), ); + + add_tracked_data_systems(app); } } -/// Sets the protocol ID of new mcentities and adds them to the -/// [`McEntityManager`]. -fn init_mcentities( - mut entities: Query<(Entity, &mut McEntity), Added>, - mut manager: ResMut, +fn init_entities( + mut entities: Query< + ( + Entity, + &mut EntityId, + &mut UniqueId, + &Position, + &mut OldPosition, + ), + Added, + >, + mut manager: ResMut, ) { - for (entity, mut mc_entity) in &mut entities { - if manager.next_protocol_id == 0 { - warn!("entity protocol ID overflow"); - // ID 0 is reserved for clients so we skip over it. - manager.next_protocol_id = 1; + 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(); } - mc_entity.protocol_id = manager.next_protocol_id; - manager.next_protocol_id = manager.next_protocol_id.wrapping_add(1); + 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 + ); + } - manager - .protocol_id_to_mcentity - .insert(mc_entity.protocol_id, entity); + if let Some(conflict) = manager.uuid_to_entity.insert(uuid.0, entity) { + warn!( + "entity {entity:?} has conflicting UUID of {} with entity {conflict:?}", + uuid.0 + ); + } } } -/// Removes despawned mcentities from the mcentity manager. fn remove_despawned_from_manager( - entities: Query<&mut McEntity, With>, - mut manager: ResMut, + entities: Query<(&EntityId, &UniqueId), (With, With)>, + mut manager: ResMut, ) { - for entity in &entities { - manager.protocol_id_to_mcentity.remove(&entity.protocol_id); + for (id, uuid) in &entities { + manager.id_to_entity.remove(&id.0); + manager.uuid_to_entity.remove(&uuid.0); } } -fn update_mcentities(mut mcentities: Query<&mut McEntity, Changed>) { - for mut ent in &mut mcentities { - ent.data.clear_modifications(); - ent.old_position = ent.position; - ent.old_instance = ent.instance; - ent.statuses = 0; - ent.animations = 0; - ent.yaw_or_pitch_modified = false; - ent.head_yaw_modified = false; - ent.velocity_modified = false; +fn clear_status_changes(mut statuses: Query<&mut EntityStatuses, Changed>) { + for mut statuses in &mut statuses { + statuses.0 = 0; } } -/// A [`Resource`] which maintains information about all the [`McEntity`] -/// components on the server. -#[derive(Resource, Debug)] -pub struct McEntityManager { - protocol_id_to_mcentity: FxHashMap, - next_protocol_id: i32, -} - -impl McEntityManager { - fn new() -> Self { - Self { - protocol_id_to_mcentity: HashMap::default(), - next_protocol_id: 1, - } - } - - /// Gets the [`Entity`] of the [`McEntity`] with the given protocol ID. - pub fn get_with_protocol_id(&self, id: i32) -> Option { - self.protocol_id_to_mcentity.get(&id).cloned() +fn clear_animation_changes( + mut animations: Query<&mut EntityAnimations, Changed>, +) { + for mut animations in &mut animations { + animations.0 = 0; } } -/// A component for Minecraft entities. For Valence to recognize a -/// Minecraft entity, it must have this component attached. -/// -/// ECS entities with this component are not allowed to be removed from the -/// [`World`] directly. Instead, you must mark these entities with [`Despawned`] -/// to allow deinitialization to occur. -/// -/// Every entity has common state which is accessible directly from this struct. -/// This includes position, rotation, velocity, and UUID. To access data that is -/// not common to every kind of entity, see [`Self::data`]. -#[derive(Component)] -pub struct McEntity { - pub(crate) data: TrackedData, - protocol_id: i32, - uuid: Uuid, - /// The range of bytes in the partition cell containing this entity's update - /// packets. - pub(crate) self_update_range: Range, - /// Contains a set bit for every status triggered this tick. - statuses: u64, - /// Contains a set bit for every animation triggered this tick. - animations: u8, - instance: Entity, - old_instance: Entity, - position: DVec3, - old_position: DVec3, - yaw: f32, - pitch: f32, - yaw_or_pitch_modified: bool, - head_yaw: f32, - head_yaw_modified: bool, - velocity: Vec3, - velocity_modified: bool, - on_ground: bool, -} - -impl McEntity { - /// Creates a new [`McEntity`] component with a random UUID. - /// - /// - `kind`: The type of Minecraft entity this should be. - /// - `instance`: The [`Entity`] that has an [`Instance`] that this entity - /// will be located in. - /// - /// [`Instance`]: crate::instance::Instance - pub fn new(kind: EntityKind, instance: Entity) -> Self { - Self::with_uuid(kind, instance, Uuid::from_u128(rand::random())) - } - - /// Like [`Self::new`], but allows specifying the UUID of the entity. - pub fn with_uuid(kind: EntityKind, instance: Entity, uuid: Uuid) -> Self { - Self { - data: TrackedData::new(kind), - self_update_range: 0..0, - statuses: 0, - animations: 0, - instance, - old_instance: NULL_ENTITY, - position: DVec3::ZERO, - old_position: DVec3::ZERO, - yaw: 0.0, - pitch: 0.0, - yaw_or_pitch_modified: false, - head_yaw: 0.0, - head_yaw_modified: false, - velocity: Vec3::ZERO, - velocity_modified: false, - protocol_id: 0, - uuid, - on_ground: false, - } - } - - /// Returns a reference to this entity's tracked data. - pub fn data(&self) -> &TrackedData { - &self.data - } - - /// Returns a mutable reference to this entity's tracked data. - pub fn data_mut(&mut self) -> &mut TrackedData { - &mut self.data - } - - /// Gets the [`EntityKind`] of this entity. - pub fn kind(&self) -> EntityKind { - self.data.kind() - } - - /// Returns a handle to the [`Instance`] this entity is located in. - /// - /// [`Instance`]: crate::instance::Instance - pub fn instance(&self) -> Entity { - self.instance - } - - /// Sets the [`Instance`] this entity is located in. - /// - /// [`Instance`]: crate::instance::Instance - pub fn set_instance(&mut self, instance: Entity) { - self.instance = instance; - } - - pub(crate) fn old_instance(&self) -> Entity { - self.old_instance - } - - /// Gets the UUID of this entity. - pub fn uuid(&self) -> Uuid { - self.uuid - } - - /// Returns the raw protocol ID of this entity. IDs for new entities are not - /// initialized until the end of the tick. - pub fn protocol_id(&self) -> i32 { - self.protocol_id - } - - /// Gets the position of this entity in the world it inhabits. - /// - /// The position of an entity is located on the bottom of its - /// hitbox and not the center. - pub fn position(&self) -> DVec3 { - self.position - } - - /// Sets the position of this entity in the world it inhabits. - /// - /// The position of an entity is located on the bottom of its - /// hitbox and not the center. - pub fn set_position(&mut self, pos: impl Into) { - self.position = pos.into(); - } - - /// Returns the position of this entity as it existed at the end of the - /// previous tick. - pub(crate) fn old_position(&self) -> DVec3 { - self.old_position - } - - /// Gets the yaw of this entity in degrees. - pub fn yaw(&self) -> f32 { - self.yaw - } - - /// Sets the yaw of this entity in degrees. - pub fn set_yaw(&mut self, yaw: f32) { - if self.yaw != yaw { - self.yaw = yaw; - self.yaw_or_pitch_modified = true; - } - } - - /// Gets the pitch of this entity in degrees. - pub fn pitch(&self) -> f32 { - self.pitch - } - - /// Sets the pitch of this entity in degrees. - pub fn set_pitch(&mut self, pitch: f32) { - if self.pitch != pitch { - self.pitch = pitch; - self.yaw_or_pitch_modified = true; - } - } - - /// Gets the head yaw of this entity in degrees. - pub fn head_yaw(&self) -> f32 { - self.head_yaw - } - - /// Sets the head yaw of this entity in degrees. - pub fn set_head_yaw(&mut self, head_yaw: f32) { - if self.head_yaw != head_yaw { - self.head_yaw = head_yaw; - self.head_yaw_modified = true; - } - } - - /// Gets the velocity of this entity in meters per second. - pub fn velocity(&self) -> Vec3 { - self.velocity - } - - /// Sets the velocity of this entity in meters per second. - pub fn set_velocity(&mut self, velocity: impl Into) { - let new_vel = velocity.into(); - - if self.velocity != new_vel { - self.velocity = new_vel; - self.velocity_modified = true; - } - } - - /// Gets the value of the "on ground" flag. - pub fn on_ground(&self) -> bool { - self.on_ground - } - - /// Sets the value of the "on ground" flag. - pub fn set_on_ground(&mut self, on_ground: bool) { - self.on_ground = on_ground; - // TODO: on ground modified flag? - } - - pub fn trigger_status(&mut self, status: EntityStatus) { - self.statuses |= 1 << status as u64; - } - - pub fn trigger_animation(&mut self, animation: EntityAnimation) { - self.animations |= 1 << animation as u8; - } - - /// Returns the hitbox of this entity. - /// - /// The hitbox describes the space that an entity occupies. Clients interact - /// with this space to create an [interact event]. - /// - /// The hitbox of an entity is determined by its position, entity type, and - /// other state specific to that type. - /// - /// [interact event]: crate::client::event::PlayerInteract - pub fn hitbox(&self) -> Aabb { - fn baby(is_baby: bool, adult_hitbox: [f64; 3]) -> [f64; 3] { - if is_baby { - adult_hitbox.map(|a| a / 2.0) - } else { - adult_hitbox - } - } - - fn item_frame(pos: DVec3, 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 = DVec3::from(match rotation { - 0 | 1 => [0.75, 0.0625, 0.75], - 2 | 3 => [0.75, 0.75, 0.0625], - 4 | 5 => [0.0625, 0.75, 0.75], - _ => [0.75, 0.0625, 0.75], - }); - - Aabb { - min: center_pos - bounds / 2.0, - max: center_pos + bounds / 2.0, - } - } - - let dimensions = match &self.data { - TrackedData::Allay(_) => [0.6, 0.35, 0.6], - TrackedData::ChestBoat(_) => [1.375, 0.5625, 1.375], - TrackedData::Frog(_) => [0.5, 0.5, 0.5], - TrackedData::Tadpole(_) => [0.4, 0.3, 0.4], - TrackedData::Warden(e) => match e.get_pose() { - Pose::Emerging | Pose::Digging => [0.9, 1.0, 0.9], - _ => [0.9, 2.9, 0.9], - }, - TrackedData::AreaEffectCloud(e) => [ - e.get_radius() as f64 * 2.0, - 0.5, - e.get_radius() as f64 * 2.0, - ], - TrackedData::ArmorStand(e) => { - if e.get_marker() { - [0.0, 0.0, 0.0] - } else if e.get_small() { - [0.5, 0.9875, 0.5] - } else { - [0.5, 1.975, 0.5] - } - } - TrackedData::Arrow(_) => [0.5, 0.5, 0.5], - TrackedData::Axolotl(_) => [1.3, 0.6, 1.3], - TrackedData::Bat(_) => [0.5, 0.9, 0.5], - TrackedData::Bee(e) => baby(e.get_child(), [0.7, 0.6, 0.7]), - TrackedData::Blaze(_) => [0.6, 1.8, 0.6], - TrackedData::Boat(_) => [1.375, 0.5625, 1.375], - TrackedData::Camel(e) => baby(e.get_child(), [1.7, 2.375, 1.7]), - TrackedData::Cat(_) => [0.6, 0.7, 0.6], - TrackedData::CaveSpider(_) => [0.7, 0.5, 0.7], - TrackedData::Chicken(e) => baby(e.get_child(), [0.4, 0.7, 0.4]), - TrackedData::Cod(_) => [0.5, 0.3, 0.5], - TrackedData::Cow(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), - TrackedData::Creeper(_) => [0.6, 1.7, 0.6], - TrackedData::Dolphin(_) => [0.9, 0.6, 0.9], - TrackedData::Donkey(e) => baby(e.get_child(), [1.5, 1.39648, 1.5]), - TrackedData::DragonFireball(_) => [1.0, 1.0, 1.0], - TrackedData::Drowned(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], - TrackedData::EndCrystal(_) => [2.0, 2.0, 2.0], - TrackedData::EnderDragon(_) => [16.0, 8.0, 16.0], - TrackedData::Enderman(_) => [0.6, 2.9, 0.6], - TrackedData::Endermite(_) => [0.4, 0.3, 0.4], - TrackedData::Evoker(_) => [0.6, 1.95, 0.6], - TrackedData::EvokerFangs(_) => [0.5, 0.8, 0.5], - TrackedData::ExperienceOrb(_) => [0.5, 0.5, 0.5], - TrackedData::EyeOfEnder(_) => [0.25, 0.25, 0.25], - TrackedData::FallingBlock(_) => [0.98, 0.98, 0.98], - TrackedData::FireworkRocket(_) => [0.25, 0.25, 0.25], - TrackedData::Fox(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), - TrackedData::Ghast(_) => [4.0, 4.0, 4.0], - TrackedData::Giant(_) => [3.6, 12.0, 3.6], - TrackedData::GlowItemFrame(e) => return item_frame(self.position, e.get_rotation()), - TrackedData::GlowSquid(_) => [0.8, 0.8, 0.8], - TrackedData::Goat(e) => { - if e.get_pose() == Pose::LongJumping { - baby(e.get_child(), [0.63, 0.91, 0.63]) - } else { - baby(e.get_child(), [0.9, 1.3, 0.9]) - } - } - TrackedData::Guardian(_) => [0.85, 0.85, 0.85], - TrackedData::Hoglin(e) => baby(e.get_child(), [1.39648, 1.4, 1.39648]), - TrackedData::Horse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Husk(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::Illusioner(_) => [0.6, 1.95, 0.6], - TrackedData::IronGolem(_) => [1.4, 2.7, 1.4], - TrackedData::Item(_) => [0.25, 0.25, 0.25], - TrackedData::ItemFrame(e) => return item_frame(self.position, e.get_rotation()), - TrackedData::Fireball(_) => [1.0, 1.0, 1.0], - TrackedData::LeashKnot(_) => [0.375, 0.5, 0.375], - TrackedData::Lightning(_) => [0.0, 0.0, 0.0], - TrackedData::Llama(e) => baby(e.get_child(), [0.9, 1.87, 0.9]), - TrackedData::LlamaSpit(_) => [0.25, 0.25, 0.25], - TrackedData::MagmaCube(e) => { - let s = 0.5202 * e.get_slime_size() as f64; - [s, s, s] - } - TrackedData::Marker(_) => [0.0, 0.0, 0.0], - TrackedData::Minecart(_) => [0.98, 0.7, 0.98], - TrackedData::ChestMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::FurnaceMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::HopperMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::SpawnerMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::TntMinecart(_) => [0.98, 0.7, 0.98], - TrackedData::Mule(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Mooshroom(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), - TrackedData::Ocelot(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), - TrackedData::Painting(e) => { - let bounds: UVec3 = match e.get_variant() { - PaintingKind::Kebab => [1, 1, 1], - PaintingKind::Aztec => [1, 1, 1], - PaintingKind::Alban => [1, 1, 1], - PaintingKind::Aztec2 => [1, 1, 1], - PaintingKind::Bomb => [1, 1, 1], - PaintingKind::Plant => [1, 1, 1], - PaintingKind::Wasteland => [1, 1, 1], - PaintingKind::Pool => [2, 1, 2], - PaintingKind::Courbet => [2, 1, 2], - PaintingKind::Sea => [2, 1, 2], - PaintingKind::Sunset => [2, 1, 2], - PaintingKind::Creebet => [2, 1, 2], - PaintingKind::Wanderer => [1, 2, 1], - PaintingKind::Graham => [1, 2, 1], - PaintingKind::Match => [2, 2, 2], - PaintingKind::Bust => [2, 2, 2], - PaintingKind::Stage => [2, 2, 2], - PaintingKind::Void => [2, 2, 2], - PaintingKind::SkullAndRoses => [2, 2, 2], - PaintingKind::Wither => [2, 2, 2], - PaintingKind::Fighters => [4, 2, 4], - PaintingKind::Pointer => [4, 4, 4], - PaintingKind::Pigscene => [4, 4, 4], - PaintingKind::BurningSkull => [4, 4, 4], - PaintingKind::Skeleton => [4, 3, 4], - PaintingKind::Earth => [2, 2, 2], - PaintingKind::Wind => [2, 2, 2], - PaintingKind::Water => [2, 2, 2], - PaintingKind::Fire => [2, 2, 2], - PaintingKind::DonkeyKong => [4, 3, 4], - } - .into(); - - let mut center_pos = self.position + 0.5; - - let (facing_x, facing_z, cc_facing_x, cc_facing_z) = - match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { - 0 => (0, 1, 1, 0), // South - 1 => (-1, 0, 0, 1), // West - 2 => (0, -1, -1, 0), // North - _ => (1, 0, 0, -1), // East - }; - - center_pos.x -= facing_x as f64 * 0.46875; - center_pos.z -= facing_z as f64 * 0.46875; - - center_pos.x += cc_facing_x as f64 * if bounds.x % 2 == 0 { 0.5 } else { 0.0 }; - center_pos.y += if bounds.y % 2 == 0 { 0.5 } else { 0.0 }; - center_pos.z += cc_facing_z as f64 * if bounds.z % 2 == 0 { 0.5 } else { 0.0 }; - - let bounds = match (facing_x, facing_z) { - (1, 0) | (-1, 0) => DVec3::new(0.0625, bounds.y as f64, bounds.z as f64), - _ => DVec3::new(bounds.x as f64, bounds.y as f64, 0.0625), - }; - - return Aabb { - min: center_pos - bounds / 2.0, - max: center_pos + bounds / 2.0, - }; - } - TrackedData::Panda(e) => baby(e.get_child(), [1.3, 1.25, 1.3]), - TrackedData::Parrot(_) => [0.5, 0.9, 0.5], - TrackedData::Phantom(_) => [0.9, 0.5, 0.9], - TrackedData::Pig(e) => baby(e.get_child(), [0.9, 0.9, 0.9]), - TrackedData::Piglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::PiglinBrute(_) => [0.6, 1.95, 0.6], - TrackedData::Pillager(_) => [0.6, 1.95, 0.6], - TrackedData::PolarBear(e) => baby(e.get_child(), [1.4, 1.4, 1.4]), - TrackedData::Tnt(_) => [0.98, 0.98, 0.98], - TrackedData::Pufferfish(_) => [0.7, 0.7, 0.7], - TrackedData::Rabbit(e) => baby(e.get_child(), [0.4, 0.5, 0.4]), - TrackedData::Ravager(_) => [1.95, 2.2, 1.95], - TrackedData::Salmon(_) => [0.7, 0.4, 0.7], - TrackedData::Sheep(e) => baby(e.get_child(), [0.9, 1.3, 0.9]), - TrackedData::Shulker(e) => { - const PI: f64 = std::f64::consts::PI; - - let pos = self.position + 0.5; - let mut min = pos - 0.5; - let mut max = pos + 0.5; - - let peek = 0.5 - f64::cos(e.get_peek_amount() as f64 * 0.01 * PI) * 0.5; - - match e.get_attached_face() { - Facing::Down => max.y += peek, - Facing::Up => min.y -= peek, - Facing::North => max.z += peek, - Facing::South => min.z -= peek, - Facing::West => max.x += peek, - Facing::East => min.x -= peek, - } - - return Aabb { min, max }; - } - TrackedData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], - TrackedData::Silverfish(_) => [0.4, 0.3, 0.4], - TrackedData::Skeleton(_) => [0.6, 1.99, 0.6], - TrackedData::SkeletonHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::Slime(e) => { - let s = 0.5202 * e.get_slime_size() as f64; - [s, s, s] - } - TrackedData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], - TrackedData::SnowGolem(_) => [0.7, 1.9, 0.7], - TrackedData::Snowball(_) => [0.25, 0.25, 0.25], - TrackedData::SpectralArrow(_) => [0.5, 0.5, 0.5], - TrackedData::Spider(_) => [1.4, 0.9, 1.4], - TrackedData::Squid(_) => [0.8, 0.8, 0.8], - TrackedData::Stray(_) => [0.6, 1.99, 0.6], - TrackedData::Strider(e) => baby(e.get_child(), [0.9, 1.7, 0.9]), - TrackedData::Egg(_) => [0.25, 0.25, 0.25], - TrackedData::EnderPearl(_) => [0.25, 0.25, 0.25], - TrackedData::ExperienceBottle(_) => [0.25, 0.25, 0.25], - TrackedData::Potion(_) => [0.25, 0.25, 0.25], - TrackedData::Trident(_) => [0.5, 0.5, 0.5], - TrackedData::TraderLlama(_) => [0.9, 1.87, 0.9], - TrackedData::TropicalFish(_) => [0.5, 0.4, 0.5], - TrackedData::Turtle(e) => { - if e.get_child() { - [0.36, 0.12, 0.36] - } else { - [1.2, 0.4, 1.2] - } - } - TrackedData::Vex(_) => [0.4, 0.8, 0.4], - TrackedData::Villager(e) => baby(e.get_child(), [0.6, 1.95, 0.6]), - TrackedData::Vindicator(_) => [0.6, 1.95, 0.6], - TrackedData::WanderingTrader(_) => [0.6, 1.95, 0.6], - TrackedData::Witch(_) => [0.6, 1.95, 0.6], - TrackedData::Wither(_) => [0.9, 3.5, 0.9], - TrackedData::WitherSkeleton(_) => [0.7, 2.4, 0.7], - TrackedData::WitherSkull(_) => [0.3125, 0.3125, 0.3125], - TrackedData::Wolf(e) => baby(e.get_child(), [0.6, 0.85, 0.6]), - TrackedData::Zoglin(e) => baby(e.get_baby(), [1.39648, 1.4, 1.39648]), - TrackedData::Zombie(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ZombieHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), - TrackedData::ZombieVillager(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::ZombifiedPiglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), - TrackedData::Player(e) => match e.get_pose() { - Pose::Standing => [0.6, 1.8, 0.6], - Pose::Sleeping => [0.2, 0.2, 0.2], - Pose::FallFlying => [0.6, 0.6, 0.6], - Pose::Swimming => [0.6, 0.6, 0.6], - Pose::SpinAttack => [0.6, 0.6, 0.6], - Pose::Sneaking => [0.6, 1.5, 0.6], - Pose::Dying => [0.2, 0.2, 0.2], - _ => [0.6, 1.8, 0.6], - }, - TrackedData::FishingBobber(_) => [0.25, 0.25, 0.25], - }; - - Aabb::from_bottom_size(self.position, dimensions) - } - - /// Sends the appropriate packets to initialize the entity. This will spawn - /// the entity and initialize tracked data. - pub(crate) fn write_init_packets( - &self, - mut writer: impl WritePacket, - position: DVec3, - scratch: &mut Vec, - ) { - let with_object_data = |data| EntitySpawnS2c { - entity_id: VarInt(self.protocol_id), - object_uuid: self.uuid, - kind: VarInt(self.kind() as i32), - position: position.to_array(), - 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.data { - TrackedData::Marker(_) => {} - TrackedData::ExperienceOrb(_) => writer.write_packet(&ExperienceOrbSpawnS2c { - entity_id: VarInt(self.protocol_id), - position: position.to_array(), - count: 0, // TODO - }), - TrackedData::Player(_) => { - writer.write_packet(&PlayerSpawnS2c { - entity_id: VarInt(self.protocol_id), - player_uuid: self.uuid, - position: position.to_array(), - yaw: ByteAngle::from_degrees(self.yaw), - pitch: ByteAngle::from_degrees(self.pitch), - }); - - // Player spawn packet doesn't include head yaw for some reason. - writer.write_packet(&EntitySetHeadYawS2c { - entity_id: VarInt(self.protocol_id), - head_yaw: ByteAngle::from_degrees(self.head_yaw), - }); - } - TrackedData::ItemFrame(e) => writer.write_packet(&with_object_data(e.get_rotation())), - TrackedData::GlowItemFrame(e) => { - writer.write_packet(&with_object_data(e.get_rotation())) - } - - TrackedData::Painting(_) => writer.write_packet(&with_object_data( - match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { - 0 => 3, - 1 => 4, - 2 => 2, - _ => 5, - }, - )), - // TODO: set block state ID for falling block. - TrackedData::FallingBlock(_) => writer.write_packet(&with_object_data(1)), - TrackedData::FishingBobber(e) => { - writer.write_packet(&with_object_data(e.get_hook_entity_id())) - } - TrackedData::Warden(e) => { - writer.write_packet(&with_object_data((e.get_pose() == Pose::Emerging).into())) - } - _ => writer.write_packet(&with_object_data(0)), - } - - scratch.clear(); - self.data.write_initial_tracked_data(scratch); - if !scratch.is_empty() { - writer.write_packet(&EntityTrackerUpdateS2c { - entity_id: VarInt(self.protocol_id), - metadata: scratch.as_slice().into(), - }); - } - } - - /// Writes the appropriate packets to update the entity (Position, tracked - /// data, events, animations). - pub(crate) fn write_update_packets(&self, mut writer: impl WritePacket, scratch: &mut Vec) { - let entity_id = VarInt(self.protocol_id); - - let position_delta = self.position - self.old_position; - let needs_teleport = position_delta.abs().max_element() >= 8.0; - let changed_position = self.position != self.old_position; - - if changed_position && !needs_teleport && self.yaw_or_pitch_modified { - writer.write_packet(&RotateAndMoveRelativeS2c { - entity_id, - delta: (position_delta * 4096.0).to_array().map(|v| v as i16), - yaw: ByteAngle::from_degrees(self.yaw), - pitch: ByteAngle::from_degrees(self.pitch), - on_ground: self.on_ground, - }); - } else { - if changed_position && !needs_teleport { - writer.write_packet(&MoveRelativeS2c { - entity_id, - delta: (position_delta * 4096.0).to_array().map(|v| v as i16), - on_ground: self.on_ground, - }); - } - - if self.yaw_or_pitch_modified { - writer.write_packet(&RotateS2c { - entity_id, - yaw: ByteAngle::from_degrees(self.yaw), - pitch: ByteAngle::from_degrees(self.pitch), - on_ground: self.on_ground, - }); - } - } - - if needs_teleport { - writer.write_packet(&EntityPositionS2c { - entity_id, - position: self.position.to_array(), - yaw: ByteAngle::from_degrees(self.yaw), - pitch: ByteAngle::from_degrees(self.pitch), - on_ground: self.on_ground, - }); - } - - if self.velocity_modified { - writer.write_packet(&EntityVelocityUpdateS2c { - entity_id, - velocity: velocity_to_packet_units(self.velocity), - }); - } - - if self.head_yaw_modified { - writer.write_packet(&EntitySetHeadYawS2c { - entity_id, - head_yaw: ByteAngle::from_degrees(self.head_yaw), - }); - } - - scratch.clear(); - self.data.write_updated_tracked_data(scratch); - if !scratch.is_empty() { - writer.write_packet(&EntityTrackerUpdateS2c { - entity_id, - metadata: scratch.as_slice().into(), - }); - } - - if self.statuses != 0 { - for i in 0..std::mem::size_of_val(&self.statuses) { - if (self.statuses >> i) & 1 == 1 { - writer.write_packet(&EntityEventS2c { - entity_id: entity_id.0, - entity_status: i as u8, - }); - } - } - } - - if self.animations != 0 { - for i in 0..std::mem::size_of_val(&self.animations) { - if (self.animations >> i) & 1 == 1 { - writer.write_packet(&EntityAnimationS2c { - entity_id, - animation: i as u8, - }); - } - } - } +fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed>) { + for mut tracked_data in &mut tracked_data { + tracked_data.clear_update_values(); } } -#[inline] -pub(crate) fn velocity_to_packet_units(vel: Vec3) -> [i16; 3] { - // The saturating casts to i16 are desirable. - (8000.0 / DEFAULT_TPS as f32 * vel) - .to_array() - .map(|v| v as i16) -} +#[cfg(test)] +mod tests { + use super::*; -impl fmt::Debug for McEntity { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("McEntity") - .field("kind", &self.kind()) - .field("protocol_id", &self.protocol_id) - .field("uuid", &self.uuid) - .field("position", &self.position) - .finish_non_exhaustive() + #[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); } } diff --git a/crates/valence/src/entity/data.rs b/crates/valence/src/entity/data.rs deleted file mode 100644 index dbe7a30..0000000 --- a/crates/valence/src/entity/data.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Contains the [`TrackedData`] enum and the types for each variant. - -// TODO: clean this up. -#![allow(clippy::all, missing_docs, trivial_numeric_casts, dead_code)] - -use uuid::Uuid; -use valence_protocol::block::BlockState; -use valence_protocol::block_pos::BlockPos; -use valence_protocol::text::Text; -use valence_protocol::tracked_data::*; -use valence_protocol::var_int::VarInt; -use valence_protocol::Encode; - -include!(concat!(env!("OUT_DIR"), "/entity.rs")); diff --git a/crates/valence/src/entity/hitbox.rs b/crates/valence/src/entity/hitbox.rs new file mode 100644 index 0000000..dab61ae --- /dev/null +++ b/crates/valence/src/entity/hitbox.rs @@ -0,0 +1,294 @@ +// TODO: Make a `Hitbox` component and plugin. + +/* +/// Returns the hitbox of this entity. +/// +/// The hitbox describes the space that an entity occupies. Clients interact +/// with this space to create an [interact event]. +/// +/// The hitbox of an entity is determined by its position, entity type, and +/// other state specific to that type. +/// +/// [interact event]: crate::client::event::PlayerInteract +pub fn hitbox(&self) -> Aabb { + fn baby(is_baby: bool, adult_hitbox: [f64; 3]) -> [f64; 3] { + if is_baby { + adult_hitbox.map(|a| a / 2.0) + } else { + adult_hitbox + } + } + + fn item_frame(pos: DVec3, 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 = DVec3::from(match rotation { + 0 | 1 => [0.75, 0.0625, 0.75], + 2 | 3 => [0.75, 0.75, 0.0625], + 4 | 5 => [0.0625, 0.75, 0.75], + _ => [0.75, 0.0625, 0.75], + }); + + Aabb { + min: center_pos - bounds / 2.0, + max: center_pos + bounds / 2.0, + } + } + + let dimensions = match &self.data { + TrackedData::Allay(_) => [0.6, 0.35, 0.6], + TrackedData::ChestBoat(_) => [1.375, 0.5625, 1.375], + TrackedData::Frog(_) => [0.5, 0.5, 0.5], + TrackedData::Tadpole(_) => [0.4, 0.3, 0.4], + TrackedData::Warden(e) => match e.get_pose() { + Pose::Emerging | Pose::Digging => [0.9, 1.0, 0.9], + _ => [0.9, 2.9, 0.9], + }, + TrackedData::AreaEffectCloud(e) => [ + e.get_radius() as f64 * 2.0, + 0.5, + e.get_radius() as f64 * 2.0, + ], + TrackedData::ArmorStand(e) => { + if e.get_marker() { + [0.0, 0.0, 0.0] + } else if e.get_small() { + [0.5, 0.9875, 0.5] + } else { + [0.5, 1.975, 0.5] + } + } + TrackedData::Arrow(_) => [0.5, 0.5, 0.5], + TrackedData::Axolotl(_) => [1.3, 0.6, 1.3], + TrackedData::Bat(_) => [0.5, 0.9, 0.5], + TrackedData::Bee(e) => baby(e.get_child(), [0.7, 0.6, 0.7]), + TrackedData::Blaze(_) => [0.6, 1.8, 0.6], + TrackedData::Boat(_) => [1.375, 0.5625, 1.375], + TrackedData::Camel(e) => baby(e.get_child(), [1.7, 2.375, 1.7]), + TrackedData::Cat(_) => [0.6, 0.7, 0.6], + TrackedData::CaveSpider(_) => [0.7, 0.5, 0.7], + TrackedData::Chicken(e) => baby(e.get_child(), [0.4, 0.7, 0.4]), + TrackedData::Cod(_) => [0.5, 0.3, 0.5], + TrackedData::Cow(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), + TrackedData::Creeper(_) => [0.6, 1.7, 0.6], + TrackedData::Dolphin(_) => [0.9, 0.6, 0.9], + TrackedData::Donkey(e) => baby(e.get_child(), [1.5, 1.39648, 1.5]), + TrackedData::DragonFireball(_) => [1.0, 1.0, 1.0], + TrackedData::Drowned(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::ElderGuardian(_) => [1.9975, 1.9975, 1.9975], + TrackedData::EndCrystal(_) => [2.0, 2.0, 2.0], + TrackedData::EnderDragon(_) => [16.0, 8.0, 16.0], + TrackedData::Enderman(_) => [0.6, 2.9, 0.6], + TrackedData::Endermite(_) => [0.4, 0.3, 0.4], + TrackedData::Evoker(_) => [0.6, 1.95, 0.6], + TrackedData::EvokerFangs(_) => [0.5, 0.8, 0.5], + TrackedData::ExperienceOrb(_) => [0.5, 0.5, 0.5], + TrackedData::EyeOfEnder(_) => [0.25, 0.25, 0.25], + TrackedData::FallingBlock(_) => [0.98, 0.98, 0.98], + TrackedData::FireworkRocket(_) => [0.25, 0.25, 0.25], + TrackedData::Fox(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), + TrackedData::Ghast(_) => [4.0, 4.0, 4.0], + TrackedData::Giant(_) => [3.6, 12.0, 3.6], + TrackedData::GlowItemFrame(e) => return item_frame(self.position, e.get_rotation()), + TrackedData::GlowSquid(_) => [0.8, 0.8, 0.8], + TrackedData::Goat(e) => { + if e.get_pose() == Pose::LongJumping { + baby(e.get_child(), [0.63, 0.91, 0.63]) + } else { + baby(e.get_child(), [0.9, 1.3, 0.9]) + } + } + TrackedData::Guardian(_) => [0.85, 0.85, 0.85], + TrackedData::Hoglin(e) => baby(e.get_child(), [1.39648, 1.4, 1.39648]), + TrackedData::Horse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), + TrackedData::Husk(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::Illusioner(_) => [0.6, 1.95, 0.6], + TrackedData::IronGolem(_) => [1.4, 2.7, 1.4], + TrackedData::Item(_) => [0.25, 0.25, 0.25], + TrackedData::ItemFrame(e) => return item_frame(self.position, e.get_rotation()), + TrackedData::Fireball(_) => [1.0, 1.0, 1.0], + TrackedData::LeashKnot(_) => [0.375, 0.5, 0.375], + TrackedData::Lightning(_) => [0.0, 0.0, 0.0], + TrackedData::Llama(e) => baby(e.get_child(), [0.9, 1.87, 0.9]), + TrackedData::LlamaSpit(_) => [0.25, 0.25, 0.25], + TrackedData::MagmaCube(e) => { + let s = 0.5202 * e.get_slime_size() as f64; + [s, s, s] + } + TrackedData::Marker(_) => [0.0, 0.0, 0.0], + TrackedData::Minecart(_) => [0.98, 0.7, 0.98], + TrackedData::ChestMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::CommandBlockMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::FurnaceMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::HopperMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::SpawnerMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::TntMinecart(_) => [0.98, 0.7, 0.98], + TrackedData::Mule(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), + TrackedData::Mooshroom(e) => baby(e.get_child(), [0.9, 1.4, 0.9]), + TrackedData::Ocelot(e) => baby(e.get_child(), [0.6, 0.7, 0.6]), + TrackedData::Painting(e) => { + let bounds: UVec3 = match e.get_variant() { + PaintingKind::Kebab => [1, 1, 1], + PaintingKind::Aztec => [1, 1, 1], + PaintingKind::Alban => [1, 1, 1], + PaintingKind::Aztec2 => [1, 1, 1], + PaintingKind::Bomb => [1, 1, 1], + PaintingKind::Plant => [1, 1, 1], + PaintingKind::Wasteland => [1, 1, 1], + PaintingKind::Pool => [2, 1, 2], + PaintingKind::Courbet => [2, 1, 2], + PaintingKind::Sea => [2, 1, 2], + PaintingKind::Sunset => [2, 1, 2], + PaintingKind::Creebet => [2, 1, 2], + PaintingKind::Wanderer => [1, 2, 1], + PaintingKind::Graham => [1, 2, 1], + PaintingKind::Match => [2, 2, 2], + PaintingKind::Bust => [2, 2, 2], + PaintingKind::Stage => [2, 2, 2], + PaintingKind::Void => [2, 2, 2], + PaintingKind::SkullAndRoses => [2, 2, 2], + PaintingKind::Wither => [2, 2, 2], + PaintingKind::Fighters => [4, 2, 4], + PaintingKind::Pointer => [4, 4, 4], + PaintingKind::Pigscene => [4, 4, 4], + PaintingKind::BurningSkull => [4, 4, 4], + PaintingKind::Skeleton => [4, 3, 4], + PaintingKind::Earth => [2, 2, 2], + PaintingKind::Wind => [2, 2, 2], + PaintingKind::Water => [2, 2, 2], + PaintingKind::Fire => [2, 2, 2], + PaintingKind::DonkeyKong => [4, 3, 4], + } + .into(); + + let mut center_pos = self.position + 0.5; + + let (facing_x, facing_z, cc_facing_x, cc_facing_z) = + match ((self.yaw + 45.0).rem_euclid(360.0) / 90.0) as u8 { + 0 => (0, 1, 1, 0), // South + 1 => (-1, 0, 0, 1), // West + 2 => (0, -1, -1, 0), // North + _ => (1, 0, 0, -1), // East + }; + + center_pos.x -= facing_x as f64 * 0.46875; + center_pos.z -= facing_z as f64 * 0.46875; + + center_pos.x += cc_facing_x as f64 * if bounds.x % 2 == 0 { 0.5 } else { 0.0 }; + center_pos.y += if bounds.y % 2 == 0 { 0.5 } else { 0.0 }; + center_pos.z += cc_facing_z as f64 * if bounds.z % 2 == 0 { 0.5 } else { 0.0 }; + + let bounds = match (facing_x, facing_z) { + (1, 0) | (-1, 0) => DVec3::new(0.0625, bounds.y as f64, bounds.z as f64), + _ => DVec3::new(bounds.x as f64, bounds.y as f64, 0.0625), + }; + + return Aabb { + min: center_pos - bounds / 2.0, + max: center_pos + bounds / 2.0, + }; + } + TrackedData::Panda(e) => baby(e.get_child(), [1.3, 1.25, 1.3]), + TrackedData::Parrot(_) => [0.5, 0.9, 0.5], + TrackedData::Phantom(_) => [0.9, 0.5, 0.9], + TrackedData::Pig(e) => baby(e.get_child(), [0.9, 0.9, 0.9]), + TrackedData::Piglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::PiglinBrute(_) => [0.6, 1.95, 0.6], + TrackedData::Pillager(_) => [0.6, 1.95, 0.6], + TrackedData::PolarBear(e) => baby(e.get_child(), [1.4, 1.4, 1.4]), + TrackedData::Tnt(_) => [0.98, 0.98, 0.98], + TrackedData::Pufferfish(_) => [0.7, 0.7, 0.7], + TrackedData::Rabbit(e) => baby(e.get_child(), [0.4, 0.5, 0.4]), + TrackedData::Ravager(_) => [1.95, 2.2, 1.95], + TrackedData::Salmon(_) => [0.7, 0.4, 0.7], + TrackedData::Sheep(e) => baby(e.get_child(), [0.9, 1.3, 0.9]), + TrackedData::Shulker(e) => { + const PI: f64 = std::f64::consts::PI; + + let pos = self.position + 0.5; + let mut min = pos - 0.5; + let mut max = pos + 0.5; + + let peek = 0.5 - f64::cos(e.get_peek_amount() as f64 * 0.01 * PI) * 0.5; + + match e.get_attached_face() { + Facing::Down => max.y += peek, + Facing::Up => min.y -= peek, + Facing::North => max.z += peek, + Facing::South => min.z -= peek, + Facing::West => max.x += peek, + Facing::East => min.x -= peek, + } + + return Aabb { min, max }; + } + TrackedData::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125], + TrackedData::Silverfish(_) => [0.4, 0.3, 0.4], + TrackedData::Skeleton(_) => [0.6, 1.99, 0.6], + TrackedData::SkeletonHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), + TrackedData::Slime(e) => { + let s = 0.5202 * e.get_slime_size() as f64; + [s, s, s] + } + TrackedData::SmallFireball(_) => [0.3125, 0.3125, 0.3125], + TrackedData::SnowGolem(_) => [0.7, 1.9, 0.7], + TrackedData::Snowball(_) => [0.25, 0.25, 0.25], + TrackedData::SpectralArrow(_) => [0.5, 0.5, 0.5], + TrackedData::Spider(_) => [1.4, 0.9, 1.4], + TrackedData::Squid(_) => [0.8, 0.8, 0.8], + TrackedData::Stray(_) => [0.6, 1.99, 0.6], + TrackedData::Strider(e) => baby(e.get_child(), [0.9, 1.7, 0.9]), + TrackedData::Egg(_) => [0.25, 0.25, 0.25], + TrackedData::EnderPearl(_) => [0.25, 0.25, 0.25], + TrackedData::ExperienceBottle(_) => [0.25, 0.25, 0.25], + TrackedData::Potion(_) => [0.25, 0.25, 0.25], + TrackedData::Trident(_) => [0.5, 0.5, 0.5], + TrackedData::TraderLlama(_) => [0.9, 1.87, 0.9], + TrackedData::TropicalFish(_) => [0.5, 0.4, 0.5], + TrackedData::Turtle(e) => { + if e.get_child() { + [0.36, 0.12, 0.36] + } else { + [1.2, 0.4, 1.2] + } + } + TrackedData::Vex(_) => [0.4, 0.8, 0.4], + TrackedData::Villager(e) => baby(e.get_child(), [0.6, 1.95, 0.6]), + TrackedData::Vindicator(_) => [0.6, 1.95, 0.6], + TrackedData::WanderingTrader(_) => [0.6, 1.95, 0.6], + TrackedData::Witch(_) => [0.6, 1.95, 0.6], + TrackedData::Wither(_) => [0.9, 3.5, 0.9], + TrackedData::WitherSkeleton(_) => [0.7, 2.4, 0.7], + TrackedData::WitherSkull(_) => [0.3125, 0.3125, 0.3125], + TrackedData::Wolf(e) => baby(e.get_child(), [0.6, 0.85, 0.6]), + TrackedData::Zoglin(e) => baby(e.get_baby(), [1.39648, 1.4, 1.39648]), + TrackedData::Zombie(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::ZombieHorse(e) => baby(e.get_child(), [1.39648, 1.6, 1.39648]), + TrackedData::ZombieVillager(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::ZombifiedPiglin(e) => baby(e.get_baby(), [0.6, 1.95, 0.6]), + TrackedData::Player(e) => match e.get_pose() { + Pose::Standing => [0.6, 1.8, 0.6], + Pose::Sleeping => [0.2, 0.2, 0.2], + Pose::FallFlying => [0.6, 0.6, 0.6], + Pose::Swimming => [0.6, 0.6, 0.6], + Pose::SpinAttack => [0.6, 0.6, 0.6], + Pose::Sneaking => [0.6, 1.5, 0.6], + Pose::Dying => [0.2, 0.2, 0.2], + _ => [0.6, 1.8, 0.6], + }, + TrackedData::FishingBobber(_) => [0.25, 0.25, 0.25], + }; + + Aabb::from_bottom_size(self.position, dimensions) +} +*/ \ No newline at end of file diff --git a/crates/valence/src/instance.rs b/crates/valence/src/instance.rs index c563b51..689f75d 100644 --- a/crates/valence/src/instance.rs +++ b/crates/valence/src/instance.rs @@ -2,9 +2,11 @@ use std::borrow::Cow; use std::collections::hash_map::Entry; use std::collections::BTreeSet; use std::iter::FusedIterator; +use std::mem; use bevy_app::{CoreSet, Plugin}; use bevy_ecs::prelude::*; +use bevy_ecs::query::WorldQuery; pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk}; pub use chunk_entry::*; use glam::{DVec3, Vec3}; @@ -12,19 +14,29 @@ use num::integer::div_ceil; use rustc_hash::FxHashMap; use valence_protocol::array::LengthPrefixedArray; use valence_protocol::block_pos::BlockPos; +use valence_protocol::byte_angle::ByteAngle; use valence_protocol::packet::s2c::play::particle::Particle; -use valence_protocol::packet::s2c::play::{OverlayMessageS2c, ParticleS2c, PlaySoundS2c}; +use valence_protocol::packet::s2c::play::{ + EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c, + EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, OverlayMessageS2c, + ParticleS2c, PlaySoundS2c, RotateAndMoveRelativeS2c, RotateS2c, +}; use valence_protocol::sound::Sound; use valence_protocol::text::Text; use valence_protocol::types::SoundCategory; +use valence_protocol::var_int::VarInt; use valence_protocol::Packet; -use crate::component::Despawned; +use crate::client::FlushPacketsSet; +use crate::component::{Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position}; use crate::dimension::DimensionId; -use crate::entity::{InitEntitiesSet, McEntity}; +use crate::entity::{ + EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, InitEntitiesSet, + PacketByteRange, TrackedData, Velocity, +}; use crate::packet::{PacketWriter, WritePacket}; -use crate::prelude::FlushPacketsSet; use crate::server::{Server, SharedServer}; +use crate::util::velocity_to_packet_units; use crate::view::ChunkPos; mod chunk; @@ -438,16 +450,21 @@ impl Instance { pub(crate) struct InstancePlugin; #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub(crate) struct UpdateInstancesPreClientSet; +pub(crate) struct WriteUpdatePacketsToInstancesSet; impl Plugin for InstancePlugin { fn build(&self, app: &mut bevy_app::App) { app.configure_set( - UpdateInstancesPreClientSet + WriteUpdatePacketsToInstancesSet .after(InitEntitiesSet) .in_base_set(CoreSet::PostUpdate), ) - .add_system(update_instances_pre_client.in_set(UpdateInstancesPreClientSet)) + .add_system( + update_entity_cell_positions + .before(WriteUpdatePacketsToInstancesSet) + .in_base_set(CoreSet::PostUpdate), + ) + .add_system(write_update_packets_to_instances.in_set(WriteUpdatePacketsToInstancesSet)) .add_system( update_instances_post_client .after(FlushPacketsSet) @@ -459,56 +476,63 @@ impl Plugin for InstancePlugin { } } -fn update_instances_pre_client( +/// Handles entities moving from one chunk to another. +fn update_entity_cell_positions( + entities: Query< + ( + Entity, + &Position, + &OldPosition, + &Location, + &OldLocation, + Option<&Despawned>, + ), + (With, Or<(Changed, With)>), + >, mut instances: Query<&mut Instance>, - mut entities: Query<(Entity, &mut McEntity, Option<&Despawned>)>, - server: Res, ) { - for (entity_id, entity, despawned) in &entities { - let pos = ChunkPos::at(entity.position().x, entity.position().z); - let old_pos = ChunkPos::at(entity.old_position().x, entity.old_position().z); - - let instance = entity.instance(); - let old_instance = entity.old_instance(); + for (entity, pos, old_pos, loc, old_loc, despawned) in &entities { + let pos = ChunkPos::at(pos.0.x, pos.0.z); + let old_pos = ChunkPos::at(old_pos.get().x, old_pos.get().z); if despawned.is_some() { // Entity was deleted. Remove it from the chunk it was in, if it was in a chunk // at all. - if let Ok(mut old_instance) = instances.get_mut(old_instance) { + if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity_id) { - old_cell.outgoing.push((entity_id, None)); + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, None)); } } } - } else if old_instance != instance { + } else if old_loc.get() != loc.0 { // Entity changed the instance it is in. Remove it from old cell and // insert it in the new cell. // TODO: skip marker entity? - if let Ok(mut old_instance) = instances.get_mut(old_instance) { + if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) { if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity_id) { - old_cell.outgoing.push((entity_id, None)); + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, None)); } } } - if let Ok(mut instance) = instances.get_mut(instance) { + if let Ok(mut instance) = instances.get_mut(loc.0) { match instance.partition.entry(pos) { Entry::Occupied(oe) => { let cell = oe.into_mut(); - if cell.entities.insert(entity_id) { - cell.incoming.push((entity_id, None)); + if cell.entities.insert(entity) { + cell.incoming.push((entity, None)); } } Entry::Vacant(ve) => { ve.insert(PartitionCell { chunk: None, chunk_removed: false, - entities: BTreeSet::from([entity_id]), - incoming: vec![(entity_id, None)], + entities: BTreeSet::from([entity]), + incoming: vec![(entity, None)], outgoing: vec![], packet_buf: vec![], }); @@ -521,26 +545,26 @@ fn update_instances_pre_client( // TODO: skip marker entity? - if let Ok(mut instance) = instances.get_mut(instance) { + if let Ok(mut instance) = instances.get_mut(loc.0) { if let Some(old_cell) = instance.partition.get_mut(&old_pos) { - if old_cell.entities.remove(&entity_id) { - old_cell.outgoing.push((entity_id, Some(pos))); + if old_cell.entities.remove(&entity) { + old_cell.outgoing.push((entity, Some(pos))); } } match instance.partition.entry(pos) { Entry::Occupied(oe) => { let cell = oe.into_mut(); - if cell.entities.insert(entity_id) { - cell.incoming.push((entity_id, Some(old_pos))); + if cell.entities.insert(entity) { + cell.incoming.push((entity, Some(old_pos))); } } Entry::Vacant(ve) => { ve.insert(PartitionCell { chunk: None, chunk_removed: false, - entities: BTreeSet::from([entity_id]), - incoming: vec![(entity_id, Some(old_pos))], + entities: BTreeSet::from([entity]), + incoming: vec![(entity, Some(old_pos))], outgoing: vec![], packet_buf: vec![], }); @@ -552,7 +576,15 @@ fn update_instances_pre_client( // we need to do. } } +} +/// Writes update packets from entities and chunks into each cell's packet +/// buffer. +fn write_update_packets_to_instances( + mut instances: Query<&mut Instance>, + mut entities: Query, Without)>, + server: Res, +) { let mut scratch_1 = vec![]; let mut scratch_2 = vec![]; @@ -574,15 +606,11 @@ fn update_instances_pre_client( } // Cache entity update packets into the packet buffer of this cell. - for &id in &cell.entities { - let (_, mut entity, despawned) = entities - .get_mut(id) + for &entity in &cell.entities { + let mut entity = entities + .get_mut(entity) .expect("missing entity in partition cell"); - if despawned.is_some() { - continue; - } - let start = cell.packet_buf.len(); let writer = PacketWriter::new( @@ -591,11 +619,121 @@ fn update_instances_pre_client( &mut scratch_2, ); - entity.write_update_packets(writer, &mut scratch_1); + entity.write_update_packets(writer); let end = cell.packet_buf.len(); - entity.self_update_range = start..end; + entity.packet_byte_range.0 = start..end; + } + } + } +} + +#[derive(WorldQuery)] +#[world_query(mutable)] +struct UpdateEntityQuery { + id: &'static EntityId, + pos: &'static Position, + old_pos: &'static OldPosition, + loc: &'static Location, + old_loc: &'static OldLocation, + look: Ref<'static, Look>, + head_yaw: Ref<'static, HeadYaw>, + on_ground: &'static OnGround, + velocity: Ref<'static, Velocity>, + tracked_data: &'static TrackedData, + statuses: &'static EntityStatuses, + animations: &'static EntityAnimations, + packet_byte_range: &'static mut PacketByteRange, +} + +impl UpdateEntityQueryItem<'_> { + fn write_update_packets(&self, mut writer: impl WritePacket) { + // TODO: @RJ I saw you're using UpdateEntityPosition and UpdateEntityRotation sometimes. These two packets are actually broken on the client and will erase previous position/rotation https://bugs.mojang.com/browse/MC-255263 -Moulberry + + let entity_id = VarInt(self.id.get()); + + let position_delta = self.pos.0 - self.old_pos.get(); + let needs_teleport = position_delta.abs().max_element() >= 8.0; + let changed_position = self.pos.0 != self.old_pos.get(); + + if changed_position && !needs_teleport && self.look.is_changed() { + writer.write_packet(&RotateAndMoveRelativeS2c { + entity_id, + delta: (position_delta * 4096.0).to_array().map(|v| v as i16), + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } else { + if changed_position && !needs_teleport { + writer.write_packet(&MoveRelativeS2c { + entity_id, + delta: (position_delta * 4096.0).to_array().map(|v| v as i16), + on_ground: self.on_ground.0, + }); + } + + if self.look.is_changed() { + writer.write_packet(&RotateS2c { + entity_id, + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } + } + + if needs_teleport { + writer.write_packet(&EntityPositionS2c { + entity_id, + position: self.pos.0.to_array(), + yaw: ByteAngle::from_degrees(self.look.yaw), + pitch: ByteAngle::from_degrees(self.look.pitch), + on_ground: self.on_ground.0, + }); + } + + if self.velocity.is_changed() { + writer.write_packet(&EntityVelocityUpdateS2c { + entity_id, + velocity: velocity_to_packet_units(self.velocity.0), + }); + } + + if self.head_yaw.is_changed() { + writer.write_packet(&EntitySetHeadYawS2c { + entity_id, + head_yaw: ByteAngle::from_degrees(self.head_yaw.0), + }); + } + + if let Some(update_data) = self.tracked_data.update_data() { + writer.write_packet(&EntityTrackerUpdateS2c { + entity_id, + metadata: update_data.into(), + }); + } + + if self.statuses.0 != 0 { + for i in 0..mem::size_of_val(self.statuses) { + if (self.statuses.0 >> i) & 1 == 1 { + writer.write_packet(&EntityStatusS2c { + entity_id: entity_id.0, + entity_status: i as u8, + }); + } + } + } + + if self.animations.0 != 0 { + for i in 0..mem::size_of_val(self.animations) { + if (self.animations.0 >> i) & 1 == 1 { + writer.write_packet(&EntityAnimationS2c { + entity_id, + animation: i as u8, + }); + } } } } @@ -621,7 +759,7 @@ fn update_instances_post_client(mut instances: Query<&mut Instance>) { } #[cfg(debug_assertions)] -fn check_instance_invariants(instances: Query<&Instance>, entities: Query<&McEntity>) { +fn check_instance_invariants(instances: Query<&Instance>, entities: Query<(), With>) { for instance in &instances { for (pos, cell) in &instance.partition { for &id in &cell.entities { diff --git a/crates/valence/src/lib.rs b/crates/valence/src/lib.rs index 8d56e0a..101896b 100644 --- a/crates/valence/src/lib.rs +++ b/crates/valence/src/lib.rs @@ -22,7 +22,6 @@ )] #![allow(clippy::type_complexity)] // ECS queries are often complicated. -use bevy_ecs::prelude::*; pub use { anyhow, async_trait, bevy_app, bevy_ecs, uuid, valence_nbt as nbt, valence_protocol as protocol, }; @@ -57,9 +56,7 @@ pub mod prelude { AsyncCallbacks, ConnectionMode, PlayerSampleEntry, ServerListPing, ServerPlugin, }; pub use dimension::{Dimension, DimensionId}; - pub use entity::{ - EntityAnimation, EntityKind, EntityStatus, McEntity, McEntityManager, TrackedData, - }; + pub use entity::{EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw}; pub use glam::DVec3; pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance}; pub use inventory::{Inventory, InventoryKind, OpenInventory}; @@ -79,8 +76,3 @@ pub mod prelude { use super::*; } - -/// Let's pretend that [`NULL_ENTITY`] was created by spawning an entity, -/// immediately despawning it, and then stealing its [`Entity`] ID. The user -/// doesn't need to know about this. -const NULL_ENTITY: Entity = Entity::from_bits(u64::MAX); diff --git a/crates/valence/src/util.rs b/crates/valence/src/util.rs index c54c8fc..50369c1 100644 --- a/crates/valence/src/util.rs +++ b/crates/valence/src/util.rs @@ -1,5 +1,7 @@ pub use glam::*; +use crate::config::DEFAULT_TPS; + /// An axis-aligned bounding box. `min` is expected to be <= `max` /// componentwise. #[derive(Copy, Clone, PartialEq, Default, Debug)] @@ -18,6 +20,7 @@ impl Aabb { } } + #[allow(dead_code)] pub(crate) fn from_bottom_size(bottom: impl Into, size: impl Into) -> Self { let bottom = bottom.into(); let size = size.into(); @@ -110,3 +113,11 @@ mod tests { } } } + +#[inline] +pub(crate) fn velocity_to_packet_units(vel: Vec3) -> [i16; 3] { + // The saturating casts to i16 are desirable. + (8000.0 / DEFAULT_TPS as f32 * vel) + .to_array() + .map(|v| v as i16) +} diff --git a/crates/valence/src/weather.rs b/crates/valence/src/weather.rs index 3463cb2..badcc7f 100644 --- a/crates/valence/src/weather.rs +++ b/crates/valence/src/weather.rs @@ -21,7 +21,7 @@ use bevy_ecs::prelude::*; use valence_protocol::packet::s2c::play::game_state_change::GameEventKind; use valence_protocol::packet::s2c::play::GameStateChangeS2c; -use crate::instance::UpdateInstancesPreClientSet; +use crate::instance::WriteUpdatePacketsToInstancesSet; use crate::packet::WritePacket; use crate::prelude::*; @@ -217,16 +217,14 @@ impl Plugin for WeatherPlugin { app.configure_set( UpdateWeatherPerInstanceSet .in_base_set(CoreSet::PostUpdate) - .before(UpdateInstancesPreClientSet), - ); - - app.configure_set( + .before(WriteUpdatePacketsToInstancesSet), + ) + .configure_set( UpdateWeatherPerClientSet .in_base_set(CoreSet::PostUpdate) .before(FlushPacketsSet), - ); - - app.add_systems( + ) + .add_systems( ( handle_rain_begin_per_instance, handle_rain_change_per_instance, @@ -237,9 +235,8 @@ impl Plugin for WeatherPlugin { .chain() .in_set(UpdateWeatherPerInstanceSet) .before(UpdateWeatherPerClientSet), - ); - - app.add_systems( + ) + .add_systems( ( handle_rain_begin_per_client, handle_rain_change_per_client, @@ -249,9 +246,12 @@ impl Plugin for WeatherPlugin { ) .chain() .in_set(UpdateWeatherPerClientSet), + ) + .add_system( + handle_weather_for_joined_player + .before(UpdateWeatherPerClientSet) + .in_base_set(CoreSet::PostUpdate), ); - - app.add_system(handle_weather_for_joined_player.before(UpdateWeatherPerClientSet)); } } diff --git a/crates/valence_anvil/Cargo.toml b/crates/valence_anvil/Cargo.toml index ba43d55..42e5f99 100644 --- a/crates/valence_anvil/Cargo.toml +++ b/crates/valence_anvil/Cargo.toml @@ -21,7 +21,7 @@ valence_nbt = { version = "0.5.0", path = "../valence_nbt" } [dev-dependencies] anyhow = "1.0.68" bevy_ecs = "0.10" -clap = "4.1.4" +clap = { version = "4.1.4", features = ["derive"] } criterion = "0.4.0" flume = "0.10.14" fs_extra = "1.2.0" diff --git a/crates/valence_anvil/examples/anvil_loading.rs b/crates/valence_anvil/examples/anvil_loading.rs index c22dda9..19ff96e 100644 --- a/crates/valence_anvil/examples/anvil_loading.rs +++ b/crates/valence_anvil/examples/anvil_loading.rs @@ -7,8 +7,8 @@ use clap::Parser; use flume::{Receiver, Sender}; use tracing::warn; use valence::bevy_app::AppExit; -use valence::client::despawn_disconnected_clients; -use valence::client::event::default_event_handler; +use valence::client::{default_event_handler, despawn_disconnected_clients}; +use valence::entity::player::PlayerBundle; use valence::prelude::*; use valence_anvil::{AnvilChunk, AnvilWorld}; @@ -90,28 +90,20 @@ fn setup(world: &mut World) { } fn init_clients( - mut clients: Query< - ( - &mut Position, - &mut Location, - &mut GameMode, - &mut IsFlat, - &UniqueId, - ), - Added, - >, + mut clients: Query<(Entity, &mut GameMode, &mut IsFlat, &UniqueId), Added>, instances: Query>, mut commands: Commands, ) { - for (mut pos, mut loc, mut game_mode, mut is_flat, uuid) in &mut clients { - let instance = instances.single(); - - pos.0 = SPAWN_POS; - loc.0 = instance; + for (entity, mut game_mode, mut is_flat, uuid) in &mut clients { *game_mode = GameMode::Creative; is_flat.0 = true; - commands.spawn(McEntity::with_uuid(EntityKind::Player, instance, uuid.0)); + commands.entity(entity).insert(PlayerBundle { + location: Location(instances.single()), + position: Position(SPAWN_POS), + uuid: *uuid, + ..Default::default() + }); } } diff --git a/crates/valence_protocol/src/codec.rs b/crates/valence_protocol/src/codec.rs index 574ffba..1230e87 100644 --- a/crates/valence_protocol/src/codec.rs +++ b/crates/valence_protocol/src/codec.rs @@ -504,7 +504,7 @@ mod tests { use crate::ident::Ident; use crate::item::{ItemKind, ItemStack}; use crate::text::{Text, TextFormat}; - use crate::tracked_data::PaintingKind; + use crate::types::Hand; use crate::var_long::VarLong; use crate::Decode; @@ -520,7 +520,7 @@ mod tests { d: f32, e: f64, f: BlockPos, - g: PaintingKind, + g: Hand, h: Ident<&'a str>, i: Option, j: Text, @@ -540,7 +540,7 @@ mod tests { d: 5.001, e: 1e10, f: BlockPos::new(1, 2, 3), - g: PaintingKind::DonkeyKong, + g: Hand::Off, h: Ident::new("minecraft:whatever").unwrap(), i: Some(ItemStack::new(ItemKind::WoodenSword, 12, None)), j: "my ".into_text() + "fancy".italic() + " text", diff --git a/crates/valence_protocol/src/item.rs b/crates/valence_protocol/src/item.rs index 0b23de2..002d179 100644 --- a/crates/valence_protocol/src/item.rs +++ b/crates/valence_protocol/src/item.rs @@ -40,6 +40,12 @@ impl ItemStack { } } +impl Default for ItemStack { + fn default() -> Self { + Self::new(ItemKind::Air, 1, None) + } +} + impl Encode for Option { fn encode(&self, w: impl Write) -> Result<()> { self.as_ref().encode(w) diff --git a/crates/valence_protocol/src/lib.rs b/crates/valence_protocol/src/lib.rs index 17a81d7..1adb7ba 100644 --- a/crates/valence_protocol/src/lib.rs +++ b/crates/valence_protocol/src/lib.rs @@ -94,7 +94,6 @@ pub mod packet; pub mod raw; pub mod sound; pub mod text; -pub mod tracked_data; pub mod translation_key; pub mod types; pub mod var_int; diff --git a/crates/valence_protocol/src/packet/c2s/play/client_settings.rs b/crates/valence_protocol/src/packet/c2s/play/client_settings.rs index 636c44a..fa6025e 100644 --- a/crates/valence_protocol/src/packet/c2s/play/client_settings.rs +++ b/crates/valence_protocol/src/packet/c2s/play/client_settings.rs @@ -9,7 +9,7 @@ pub struct ClientSettingsC2s<'a> { pub chat_mode: ChatMode, pub chat_colors: bool, pub displayed_skin_parts: DisplayedSkinParts, - pub main_hand: MainHand, + pub main_arm: MainArm, pub enable_text_filtering: bool, pub allow_server_listings: bool, } @@ -35,7 +35,7 @@ pub struct DisplayedSkinParts { } #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)] -pub enum MainHand { +pub enum MainArm { Left, #[default] Right, diff --git a/crates/valence_protocol/src/packet/s2c/play/particle.rs b/crates/valence_protocol/src/packet/s2c/play/particle.rs index 6f233c0..0165303 100644 --- a/crates/valence_protocol/src/packet/s2c/play/particle.rs +++ b/crates/valence_protocol/src/packet/s2c/play/particle.rs @@ -19,7 +19,7 @@ pub struct ParticleS2c<'a> { pub count: i32, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum Particle { AmbientEntityEffect, AngryVillager, @@ -232,6 +232,125 @@ impl Particle { Particle::Scrape => 91, } } + + /// Decodes the particle assuming the given particle ID. + pub fn decode_with_id(particle_id: i32, r: &mut &[u8]) -> anyhow::Result { + Ok(match particle_id { + 0 => Particle::AmbientEntityEffect, + 1 => Particle::AngryVillager, + 2 => Particle::Block(BlockState::decode(r)?), + 3 => Particle::BlockMarker(BlockState::decode(r)?), + 4 => Particle::Bubble, + 5 => Particle::Cloud, + 6 => Particle::Crit, + 7 => Particle::DamageIndicator, + 8 => Particle::DragonBreath, + 9 => Particle::DrippingLava, + 10 => Particle::FallingLava, + 11 => Particle::LandingLava, + 12 => Particle::DrippingWater, + 13 => Particle::FallingWater, + 14 => Particle::Dust { + rgb: <[f32; 3]>::decode(r)?, + scale: f32::decode(r)?, + }, + 15 => Particle::DustColorTransition { + from_rgb: <[f32; 3]>::decode(r)?, + scale: f32::decode(r)?, + to_rgb: <[f32; 3]>::decode(r)?, + }, + 16 => Particle::Effect, + 17 => Particle::ElderGuardian, + 18 => Particle::EnchantedHit, + 19 => Particle::Enchant, + 20 => Particle::EndRod, + 21 => Particle::EntityEffect, + 22 => Particle::ExplosionEmitter, + 23 => Particle::Explosion, + 24 => Particle::SonicBoom, + 25 => Particle::FallingDust(BlockState::decode(r)?), + 26 => Particle::Firework, + 27 => Particle::Fishing, + 28 => Particle::Flame, + 29 => Particle::SculkSoul, + 30 => Particle::SculkCharge { + roll: f32::decode(r)?, + }, + 31 => Particle::SculkChargePop, + 32 => Particle::SoulFireFlame, + 33 => Particle::Soul, + 34 => Particle::Flash, + 35 => Particle::HappyVillager, + 36 => Particle::Composter, + 37 => Particle::Heart, + 38 => Particle::InstantEffect, + 39 => Particle::Item(Decode::decode(r)?), + 40 => match <&str>::decode(r)? { + "block" => Particle::VibrationBlock { + block_pos: BlockPos::decode(r)?, + ticks: VarInt::decode(r)?.0, + }, + "entity" => Particle::VibrationEntity { + entity_id: VarInt::decode(r)?.0, + entity_eye_height: f32::decode(r)?, + ticks: VarInt::decode(r)?.0, + }, + invalid => bail!("invalid vibration position source of \"{invalid}\""), + }, + 41 => Particle::ItemSlime, + 42 => Particle::ItemSnowball, + 43 => Particle::LargeSmoke, + 44 => Particle::Lava, + 45 => Particle::Mycelium, + 46 => Particle::Note, + 47 => Particle::Poof, + 48 => Particle::Portal, + 49 => Particle::Rain, + 50 => Particle::Smoke, + 51 => Particle::Sneeze, + 52 => Particle::Spit, + 53 => Particle::SquidInk, + 54 => Particle::SweepAttack, + 55 => Particle::TotemOfUndying, + 56 => Particle::Underwater, + 57 => Particle::Splash, + 58 => Particle::Witch, + 59 => Particle::BubblePop, + 60 => Particle::CurrentDown, + 61 => Particle::BubbleColumnUp, + 62 => Particle::Nautilus, + 63 => Particle::Dolphin, + 64 => Particle::CampfireCosySmoke, + 65 => Particle::CampfireSignalSmoke, + 66 => Particle::DrippingHoney, + 67 => Particle::FallingHoney, + 68 => Particle::LandingHoney, + 69 => Particle::FallingNectar, + 70 => Particle::FallingSporeBlossom, + 71 => Particle::Ash, + 72 => Particle::CrimsonSpore, + 73 => Particle::WarpedSpore, + 74 => Particle::SporeBlossomAir, + 75 => Particle::DrippingObsidianTear, + 76 => Particle::FallingObsidianTear, + 77 => Particle::LandingObsidianTear, + 78 => Particle::ReversePortal, + 79 => Particle::WhiteAsh, + 80 => Particle::SmallFlame, + 81 => Particle::Snowflake, + 82 => Particle::DrippingDripstoneLava, + 83 => Particle::FallingDripstoneLava, + 84 => Particle::DrippingDripstoneWater, + 85 => Particle::FallingDripstoneWater, + 86 => Particle::GlowSquidInk, + 87 => Particle::Glow, + 88 => Particle::WaxOn, + 89 => Particle::WaxOff, + 90 => Particle::ElectricSpark, + 91 => Particle::Scrape, + id => bail!("invalid particle ID of {id}"), + }) + } } impl Encode for ParticleS2c<'_> { @@ -243,7 +362,34 @@ impl Encode for ParticleS2c<'_> { self.max_speed.encode(&mut w)?; self.count.encode(&mut w)?; - match self.particle.as_ref() { + self.particle.as_ref().encode(w) + } +} + +impl<'a> Decode<'a> for ParticleS2c<'a> { + fn decode(r: &mut &'a [u8]) -> anyhow::Result { + let particle_id = VarInt::decode(r)?.0; + let long_distance = bool::decode(r)?; + let position = <[f64; 3]>::decode(r)?; + let offset = <[f32; 3]>::decode(r)?; + let max_speed = f32::decode(r)?; + let particle_count = i32::decode(r)?; + + Ok(Self { + particle: Cow::Owned(Particle::decode_with_id(particle_id, r)?), + long_distance, + position, + offset, + max_speed, + count: particle_count, + }) + } +} + +/// Encodes the particle without an ID. +impl Encode for Particle { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { + match self { Particle::Block(block_state) => block_state.encode(w), Particle::BlockMarker(block_state) => block_state.encode(w), Particle::Dust { rgb, scale } => { @@ -281,137 +427,3 @@ impl Encode for ParticleS2c<'_> { } } } - -impl<'a> Decode<'a> for ParticleS2c<'a> { - fn decode(r: &mut &'a [u8]) -> anyhow::Result { - let particle_id = VarInt::decode(r)?.0; - let long_distance = bool::decode(r)?; - let position = <[f64; 3]>::decode(r)?; - let offset = <[f32; 3]>::decode(r)?; - let max_speed = f32::decode(r)?; - let particle_count = i32::decode(r)?; - - Ok(Self { - particle: Cow::Owned(match particle_id { - 0 => Particle::AmbientEntityEffect, - 1 => Particle::AngryVillager, - 2 => Particle::Block(BlockState::decode(r)?), - 3 => Particle::BlockMarker(BlockState::decode(r)?), - 4 => Particle::Bubble, - 5 => Particle::Cloud, - 6 => Particle::Crit, - 7 => Particle::DamageIndicator, - 8 => Particle::DragonBreath, - 9 => Particle::DrippingLava, - 10 => Particle::FallingLava, - 11 => Particle::LandingLava, - 12 => Particle::DrippingWater, - 13 => Particle::FallingWater, - 14 => Particle::Dust { - rgb: <[f32; 3]>::decode(r)?, - scale: f32::decode(r)?, - }, - 15 => Particle::DustColorTransition { - from_rgb: <[f32; 3]>::decode(r)?, - scale: f32::decode(r)?, - to_rgb: <[f32; 3]>::decode(r)?, - }, - 16 => Particle::Effect, - 17 => Particle::ElderGuardian, - 18 => Particle::EnchantedHit, - 19 => Particle::Enchant, - 20 => Particle::EndRod, - 21 => Particle::EntityEffect, - 22 => Particle::ExplosionEmitter, - 23 => Particle::Explosion, - 24 => Particle::SonicBoom, - 25 => Particle::FallingDust(BlockState::decode(r)?), - 26 => Particle::Firework, - 27 => Particle::Fishing, - 28 => Particle::Flame, - 29 => Particle::SculkSoul, - 30 => Particle::SculkCharge { - roll: f32::decode(r)?, - }, - 31 => Particle::SculkChargePop, - 32 => Particle::SoulFireFlame, - 33 => Particle::Soul, - 34 => Particle::Flash, - 35 => Particle::HappyVillager, - 36 => Particle::Composter, - 37 => Particle::Heart, - 38 => Particle::InstantEffect, - 39 => Particle::Item(Decode::decode(r)?), - 40 => match <&str>::decode(r)? { - "block" => Particle::VibrationBlock { - block_pos: BlockPos::decode(r)?, - ticks: VarInt::decode(r)?.0, - }, - "entity" => Particle::VibrationEntity { - entity_id: VarInt::decode(r)?.0, - entity_eye_height: f32::decode(r)?, - ticks: VarInt::decode(r)?.0, - }, - invalid => bail!("invalid vibration position source of \"{invalid}\""), - }, - 41 => Particle::ItemSlime, - 42 => Particle::ItemSnowball, - 43 => Particle::LargeSmoke, - 44 => Particle::Lava, - 45 => Particle::Mycelium, - 46 => Particle::Note, - 47 => Particle::Poof, - 48 => Particle::Portal, - 49 => Particle::Rain, - 50 => Particle::Smoke, - 51 => Particle::Sneeze, - 52 => Particle::Spit, - 53 => Particle::SquidInk, - 54 => Particle::SweepAttack, - 55 => Particle::TotemOfUndying, - 56 => Particle::Underwater, - 57 => Particle::Splash, - 58 => Particle::Witch, - 59 => Particle::BubblePop, - 60 => Particle::CurrentDown, - 61 => Particle::BubbleColumnUp, - 62 => Particle::Nautilus, - 63 => Particle::Dolphin, - 64 => Particle::CampfireCosySmoke, - 65 => Particle::CampfireSignalSmoke, - 66 => Particle::DrippingHoney, - 67 => Particle::FallingHoney, - 68 => Particle::LandingHoney, - 69 => Particle::FallingNectar, - 70 => Particle::FallingSporeBlossom, - 71 => Particle::Ash, - 72 => Particle::CrimsonSpore, - 73 => Particle::WarpedSpore, - 74 => Particle::SporeBlossomAir, - 75 => Particle::DrippingObsidianTear, - 76 => Particle::FallingObsidianTear, - 77 => Particle::LandingObsidianTear, - 78 => Particle::ReversePortal, - 79 => Particle::WhiteAsh, - 80 => Particle::SmallFlame, - 81 => Particle::Snowflake, - 82 => Particle::DrippingDripstoneLava, - 83 => Particle::FallingDripstoneLava, - 84 => Particle::DrippingDripstoneWater, - 85 => Particle::FallingDripstoneWater, - 86 => Particle::GlowSquidInk, - 87 => Particle::Glow, - 88 => Particle::WaxOn, - 89 => Particle::WaxOff, - 90 => Particle::ElectricSpark, - 91 => Particle::Scrape, - id => bail!("invalid particle ID of {id}"), - }), - long_distance, - position, - offset, - max_speed, - count: particle_count, - }) - } -} diff --git a/crates/valence_protocol/src/tracked_data.rs b/crates/valence_protocol/src/tracked_data.rs deleted file mode 100644 index 9b5769f..0000000 --- a/crates/valence_protocol/src/tracked_data.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Types used in the entity metadata packet. - -use crate::{Decode, Encode}; - -/// Represents an optional `u32` value excluding [`u32::MAX`]. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)] -pub struct OptionalInt(u32); - -impl OptionalInt { - /// Returns `None` iff `n` is Some(u32::MAX). - pub fn new(n: impl Into>) -> Option { - match n.into() { - None => Some(Self(0)), - Some(u32::MAX) => None, - Some(n) => Some(Self(n + 1)), - } - } - - pub fn get(self) -> Option { - self.0.checked_sub(1) - } -} - -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Encode, Decode)] -pub struct EulerAngle { - pub pitch: f32, - pub yaw: f32, - pub roll: f32, -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode)] -pub enum Facing { - Down, - Up, - North, - South, - West, - East, -} - -#[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, -} - -// TODO: remove -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Encode, Decode)] -pub enum Particle { - #[tag = 21] - EntityEffect, -} diff --git a/crates/valence_stresser/src/stresser.rs b/crates/valence_stresser/src/stresser.rs index 113fa33..8bed034 100644 --- a/crates/valence_stresser/src/stresser.rs +++ b/crates/valence_stresser/src/stresser.rs @@ -100,7 +100,7 @@ pub async fn make_session<'a>(params: &SessionParams<'a>) -> anyhow::Result<()> } } - println!("{sess_name} logined"); + println!("{sess_name} logged in"); loop { while !dec.has_next_packet()? { @@ -128,8 +128,6 @@ pub async fn make_session<'a>(params: &SessionParams<'a>) -> anyhow::Result<()> enc.append_packet(&KeepAliveC2s { id: p.id })?; conn.write_all(&enc.take()).await?; - - println!("{sess_name} keep alive") } S2cPlayPacket::PlayerPositionLookS2c(p) => { @@ -145,8 +143,6 @@ pub async fn make_session<'a>(params: &SessionParams<'a>) -> anyhow::Result<()> })?; conn.write_all(&enc.take()).await?; - - println!("{sess_name} spawned") } _ => (), }, diff --git a/extracted/entities.json b/extracted/entities.json index 39a6424..e255cad 100644 --- a/extracted/entities.json +++ b/extracted/entities.json @@ -15,8 +15,7 @@ "name": "chest", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -32,14 +31,13 @@ "name": "item", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" } ], "default_bounding_box": { - "size_x": 1.0, - "size_y": 1.0, - "size_z": 1.0 + "size_x": 0.3125, + "size_y": 0.3125, + "size_z": 0.3125 } }, "AbstractHorseEntity": { @@ -49,46 +47,19 @@ "name": "horse_flags", "index": 17, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "tamed", - "index": 1 - }, - { - "name": "saddled", - "index": 2 - }, - { - "name": "bred", - "index": 3 - }, - { - "name": "eating_grass", - "index": 4 - }, - { - "name": "angry", - "index": 5 - }, - { - "name": "eating", - "index": 6 - } - ] + "default_value": 0 }, { "name": "owner_uuid", "index": 18, "type": "optional_uuid", - "default_value": null, - "bits": [] + "default_value": null } ], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.600000023841858, - "size_z": 1.396484375 + "size_x": 0.8999999761581421, + "size_y": 1.8700000047683716, + "size_z": 0.8999999761581421 } }, "AbstractMinecartEntity": { @@ -98,43 +69,37 @@ "name": "damage_wobble_ticks", "index": 8, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "damage_wobble_side", "index": 9, "type": "integer", - "default_value": 1, - "bits": [] + "default_value": 1 }, { "name": "damage_wobble_strength", "index": 10, "type": "float", - "default_value": 0.0, - "bits": [] + "default_value": 0.0 }, { "name": "custom_block_id", "index": 11, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "custom_block_offset", "index": 12, "type": "integer", - "default_value": 6, - "bits": [] + "default_value": 6 }, { "name": "custom_block_present", "index": 13, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -150,8 +115,7 @@ "name": "immune_to_zombification", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -164,9 +128,9 @@ "parent": "HostileEntity", "fields": [], "default_bounding_box": { - "size_x": 0.699999988079071, - "size_y": 2.4000000953674316, - "size_z": 0.699999988079071 + "size_x": 0.6000000238418579, + "size_y": 1.9900000095367432, + "size_z": 0.6000000238418579 } }, "AllayEntity": { @@ -178,15 +142,13 @@ "name": "dancing", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "can_duplicate", "index": 17, "type": "boolean", - "default_value": true, - "bits": [] + "default_value": true } ], "default_bounding_box": { @@ -208,9 +170,9 @@ "parent": "PassiveEntity", "fields": [], "default_bounding_box": { - "size_x": 0.75, - "size_y": 0.41999998688697815, - "size_z": 0.75 + "size_x": 0.4000000059604645, + "size_y": 0.699999988079071, + "size_z": 0.4000000059604645 } }, "AreaEffectCloudEntity": { @@ -222,29 +184,25 @@ "name": "radius", "index": 8, "type": "float", - "default_value": 3.0, - "bits": [] + "default_value": 3.0 }, { "name": "color", "index": 9, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "waiting", "index": 10, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "particle_id", "index": 11, "type": "particle", - "default_value": "entity_effect", - "bits": [] + "default_value": "entity_effect" } ], "default_bounding_box": { @@ -262,25 +220,7 @@ "name": "armor_stand_flags", "index": 15, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "small", - "index": 0 - }, - { - "name": "show_arms", - "index": 1 - }, - { - "name": "hide_base_plate", - "index": 2 - }, - { - "name": "marker", - "index": 3 - } - ] + "default_value": 0 }, { "name": "tracker_head_rotation", @@ -290,8 +230,7 @@ "pitch": 0.0, "yaw": 0.0, "roll": 0.0 - }, - "bits": [] + } }, { "name": "tracker_body_rotation", @@ -301,8 +240,7 @@ "pitch": 0.0, "yaw": 0.0, "roll": 0.0 - }, - "bits": [] + } }, { "name": "tracker_left_arm_rotation", @@ -312,8 +250,7 @@ "pitch": -10.0, "yaw": 0.0, "roll": -10.0 - }, - "bits": [] + } }, { "name": "tracker_right_arm_rotation", @@ -323,8 +260,7 @@ "pitch": -15.0, "yaw": 0.0, "roll": 10.0 - }, - "bits": [] + } }, { "name": "tracker_left_leg_rotation", @@ -334,8 +270,7 @@ "pitch": -1.0, "yaw": 0.0, "roll": -1.0 - }, - "bits": [] + } }, { "name": "tracker_right_leg_rotation", @@ -345,8 +280,7 @@ "pitch": 1.0, "yaw": 0.0, "roll": 1.0 - }, - "bits": [] + } } ], "default_bounding_box": { @@ -364,8 +298,7 @@ "name": "color", "index": 10, "type": "integer", - "default_value": -1, - "bits": [] + "default_value": -1 } ], "default_bounding_box": { @@ -383,22 +316,19 @@ "name": "variant", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "playing_dead", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "from_bucket", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -416,13 +346,7 @@ "name": "bat_flags", "index": 16, "type": "byte", - "default_value": 1, - "bits": [ - { - "name": "hanging", - "index": 0 - } - ] + "default_value": 1 } ], "default_bounding_box": { @@ -440,28 +364,13 @@ "name": "bee_flags", "index": 17, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "near_target", - "index": 1 - }, - { - "name": "has_stung", - "index": 2 - }, - { - "name": "has_nectar", - "index": 3 - } - ] + "default_value": 0 }, { "name": "anger", "index": 18, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -479,13 +388,7 @@ "name": "blaze_flags", "index": 16, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "fire_active", - "index": 0 - } - ] + "default_value": 0 } ], "default_bounding_box": { @@ -503,50 +406,43 @@ "name": "damage_wobble_ticks", "index": 8, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "damage_wobble_side", "index": 9, "type": "integer", - "default_value": 1, - "bits": [] + "default_value": 1 }, { "name": "damage_wobble_strength", "index": 10, "type": "float", - "default_value": 0.0, - "bits": [] + "default_value": 0.0 }, { "name": "boat_type", "index": 11, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "left_paddle_moving", "index": 12, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "right_paddle_moving", "index": 13, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "bubble_wobble_ticks", "index": 14, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -564,15 +460,13 @@ "name": "dashing", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "last_pose_tick", "index": 20, "type": "long", - "default_value": -52, - "bits": [] + "default_value": -52 } ], "default_bounding_box": { @@ -590,29 +484,25 @@ "name": "cat_variant", "index": 19, "type": "cat_variant", - "default_value": "black", - "bits": [] + "default_value": "black" }, { "name": "in_sleeping_pose", "index": 20, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "head_down", "index": 21, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "collar_color", "index": 22, "type": "integer", - "default_value": 14, - "bits": [] + "default_value": 14 } ], "default_bounding_box": { @@ -685,15 +575,13 @@ "name": "command", "index": 14, "type": "string", - "default_value": "", - "bits": [] + "default_value": "" }, { "name": "last_output", "index": 15, "type": "text_component", - "default_value": "", - "bits": [] + "default_value": "" } ], "default_bounding_box": { @@ -722,22 +610,19 @@ "name": "fuse_speed", "index": 16, "type": "integer", - "default_value": -1, - "bits": [] + "default_value": -1 }, { "name": "charged", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "ignited", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -759,22 +644,19 @@ "x": 0, "y": 0, "z": 0 - }, - "bits": [] + } }, { "name": "has_fish", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "moistness", "index": 18, "type": "integer", - "default_value": 2400, - "bits": [] + "default_value": 2400 } ], "default_bounding_box": { @@ -847,15 +729,13 @@ "name": "beam_target", "index": 8, "type": "optional_block_pos", - "default_value": null, - "bits": [] + "default_value": null }, { "name": "show_bottom", "index": 9, "type": "boolean", - "default_value": true, - "bits": [] + "default_value": true } ], "default_bounding_box": { @@ -873,8 +753,7 @@ "name": "phase_type", "index": 16, "type": "integer", - "default_value": 10, - "bits": [] + "default_value": 10 } ], "default_bounding_box": { @@ -903,22 +782,19 @@ "name": "carried_block", "index": 16, "type": "optional_block_state", - "default_value": null, - "bits": [] + "default_value": null }, { "name": "angry", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "provoked", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -944,92 +820,55 @@ "name": "flags", "index": 0, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "on_fire", - "index": 0 - }, - { - "name": "sneaking", - "index": 1 - }, - { - "name": "sprinting", - "index": 3 - }, - { - "name": "swimming", - "index": 4 - }, - { - "name": "invisible", - "index": 5 - }, - { - "name": "glowing", - "index": 6 - }, - { - "name": "fall_flying", - "index": 7 - } - ] + "default_value": 0 }, { "name": "air", "index": 1, "type": "integer", - "default_value": 300, - "bits": [] + "default_value": 300 }, { "name": "custom_name", "index": 2, "type": "optional_text_component", - "default_value": null, - "bits": [] + "default_value": null }, { "name": "name_visible", "index": 3, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "silent", "index": 4, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "no_gravity", "index": 5, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "pose", "index": 6, "type": "entity_pose", - "default_value": "standing", - "bits": [] + "default_value": "standing" }, { "name": "frozen_ticks", "index": 7, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.399999976158142, - "size_z": 1.396484375 + "size_x": 1.0, + "size_y": 1.0, + "size_z": 1.0 } }, "EvokerEntity": { @@ -1080,9 +919,9 @@ "parent": "ProjectileEntity", "fields": [], "default_bounding_box": { - "size_x": 1.0, - "size_y": 1.0, - "size_z": 1.0 + "size_x": 0.3125, + "size_y": 0.3125, + "size_z": 0.3125 } }, "EyeOfEnderEntity": { @@ -1094,8 +933,7 @@ "name": "item", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" } ], "default_bounding_box": { @@ -1117,8 +955,7 @@ "x": 0, "y": 0, "z": 0 - }, - "bits": [] + } } ], "default_bounding_box": { @@ -1147,22 +984,19 @@ "name": "item", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" }, { "name": "shooter_entity_id", "index": 9, "type": "optional_int", - "default_value": null, - "bits": [] + "default_value": null }, { "name": "shot_at_angle", "index": 10, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1178,14 +1012,13 @@ "name": "from_bucket", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { - "size_x": 0.5, + "size_x": 0.699999988079071, "size_y": 0.4000000059604645, - "size_z": 0.5 + "size_z": 0.699999988079071 } }, "FishingBobberEntity": { @@ -1197,15 +1030,13 @@ "name": "hook_entity_id", "index": 8, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "caught_fish", "index": 9, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1218,9 +1049,9 @@ "parent": "MobEntity", "fields": [], "default_bounding_box": { - "size_x": 0.8999999761581421, - "size_y": 0.5, - "size_z": 0.8999999761581421 + "size_x": 4.0, + "size_y": 4.0, + "size_z": 4.0 } }, "FoxEntity": { @@ -1232,58 +1063,25 @@ "name": "type", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "fox_flags", "index": 18, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "sitting", - "index": 0 - }, - { - "name": "crouching", - "index": 2 - }, - { - "name": "rolling_head", - "index": 3 - }, - { - "name": "chasing", - "index": 4 - }, - { - "name": "sleeping", - "index": 5 - }, - { - "name": "walking", - "index": 6 - }, - { - "name": "aggressive", - "index": 7 - } - ] + "default_value": 0 }, { "name": "owner", "index": 19, "type": "optional_uuid", - "default_value": null, - "bits": [] + "default_value": null }, { "name": "other_trusted", "index": 20, "type": "optional_uuid", - "default_value": null, - "bits": [] + "default_value": null } ], "default_bounding_box": { @@ -1301,15 +1099,13 @@ "name": "variant", "index": 17, "type": "frog_variant", - "default_value": "temperate", - "bits": [] + "default_value": "temperate" }, { "name": "target", "index": 18, "type": "optional_int", - "default_value": null, - "bits": [] + "default_value": null } ], "default_bounding_box": { @@ -1327,8 +1123,7 @@ "name": "lit", "index": 14, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1346,8 +1141,7 @@ "name": "shooting", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1387,8 +1181,7 @@ "name": "dark_ticks_remaining", "index": 16, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -1406,22 +1199,19 @@ "name": "screaming", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "left_horn", "index": 18, "type": "boolean", - "default_value": true, - "bits": [] + "default_value": true }, { "name": "right_horn", "index": 19, "type": "boolean", - "default_value": true, - "bits": [] + "default_value": true } ], "default_bounding_box": { @@ -1448,21 +1238,19 @@ "name": "spikes_retracted", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "beam_target_id", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { - "size_x": 0.8500000238418579, - "size_y": 0.8500000238418579, - "size_z": 0.8500000238418579 + "size_x": 1.997499942779541, + "size_y": 1.997499942779541, + "size_z": 1.997499942779541 } }, "HoglinEntity": { @@ -1474,8 +1262,7 @@ "name": "baby", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1504,8 +1291,7 @@ "name": "variant", "index": 19, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -1518,9 +1304,9 @@ "parent": "PathAwareEntity", "fields": [], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.399999976158142, - "size_z": 1.396484375 + "size_x": 0.6000000238418579, + "size_y": 1.9900000095367432, + "size_z": 0.6000000238418579 } }, "HuskEntity": { @@ -1563,13 +1349,7 @@ "name": "iron_golem_flags", "index": 16, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "player_created", - "index": 0 - } - ] + "default_value": 0 } ], "default_bounding_box": { @@ -1587,8 +1367,7 @@ "name": "stack", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" } ], "default_bounding_box": { @@ -1606,15 +1385,13 @@ "name": "item_stack", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" }, { "name": "rotation", "index": 9, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -1652,69 +1429,49 @@ "name": "living_flags", "index": 8, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "using_item", - "index": 0 - }, - { - "name": "off_hand_active", - "index": 1 - }, - { - "name": "using_riptide", - "index": 2 - } - ] + "default_value": 0 }, { "name": "health", "index": 9, "type": "float", - "default_value": 40.0, - "bits": [] + "default_value": 30.0 }, { "name": "potion_swirls_color", "index": 10, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "potion_swirls_ambient", "index": 11, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "stuck_arrow_count", "index": 12, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "stinger_count", "index": 13, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "sleeping_position", "index": 14, "type": "optional_block_pos", - "default_value": null, - "bits": [] + "default_value": null } ], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.399999976158142, - "size_z": 1.396484375 + "size_x": 1.0, + "size_y": 1.0, + "size_z": 1.0 } }, "LlamaEntity": { @@ -1726,22 +1483,19 @@ "name": "strength", "index": 20, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "carpet_color", "index": 21, "type": "integer", - "default_value": -1, - "bits": [] + "default_value": -1 }, { "name": "variant", "index": 22, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -1790,8 +1544,7 @@ "name": "head_rolling_time_left", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -1818,27 +1571,13 @@ "name": "mob_flags", "index": 15, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "ai_disabled", - "index": 0 - }, - { - "name": "left_handed", - "index": 1 - }, - { - "name": "attacking", - "index": 2 - } - ] + "default_value": 0 } ], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.399999976158142, - "size_z": 1.396484375 + "size_x": 1.0, + "size_y": 1.0, + "size_z": 1.0 } }, "MooshroomEntity": { @@ -1850,8 +1589,7 @@ "name": "type", "index": 17, "type": "string", - "default_value": "red", - "bits": [] + "default_value": "red" } ], "default_bounding_box": { @@ -1880,8 +1618,7 @@ "name": "trusting", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -1899,8 +1636,7 @@ "name": "variant", "index": 8, "type": "painting_variant", - "default_value": "kebab", - "bits": [] + "default_value": "kebab" } ], "default_bounding_box": { @@ -1918,60 +1654,37 @@ "name": "ask_for_bamboo_ticks", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "sneeze_progress", "index": 18, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "eating_ticks", "index": 19, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "main_gene", "index": 20, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "hidden_gene", "index": 21, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "panda_flags", "index": 22, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "sneezing", - "index": 1 - }, - { - "name": "playing", - "index": 2 - }, - { - "name": "sitting", - "index": 3 - }, - { - "name": "lying_on_back", - "index": 4 - } - ] + "default_value": 0 } ], "default_bounding_box": { @@ -1989,8 +1702,7 @@ "name": "variant", "index": 19, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2006,23 +1718,22 @@ "name": "child", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { - "size_x": 0.75, - "size_y": 0.41999998688697815, - "size_z": 0.75 + "size_x": 0.4000000059604645, + "size_y": 0.699999988079071, + "size_z": 0.4000000059604645 } }, "PathAwareEntity": { "parent": "MobEntity", "fields": [], "default_bounding_box": { - "size_x": 1.396484375, - "size_y": 1.399999976158142, - "size_z": 1.396484375 + "size_x": 1.0, + "size_y": 1.0, + "size_z": 1.0 } }, "PatrolEntity": { @@ -2041,24 +1752,13 @@ "name": "projectile_flags", "index": 8, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "critical", - "index": 0 - }, - { - "name": "no_clip", - "index": 1 - } - ] + "default_value": 0 }, { "name": "pierce_level", "index": 9, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2076,8 +1776,7 @@ "name": "size", "index": 16, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2095,15 +1794,13 @@ "name": "saddled", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "boost_time", "index": 18, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2132,22 +1829,19 @@ "name": "baby", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "charging", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "dancing", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2165,8 +1859,7 @@ "name": "charging", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2184,72 +1877,37 @@ "name": "absorption_amount", "index": 15, "type": "float", - "default_value": 0.0, - "bits": [] + "default_value": 0.0 }, { "name": "score", "index": 16, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "player_model_parts", "index": 17, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "cape", - "index": 0 - }, - { - "name": "jacket", - "index": 1 - }, - { - "name": "left_sleeve", - "index": 2 - }, - { - "name": "right_sleeve", - "index": 3 - }, - { - "name": "left_pants_leg", - "index": 4 - }, - { - "name": "right_pants_leg", - "index": 5 - }, - { - "name": "hat", - "index": 6 - } - ] + "default_value": 0 }, { "name": "main_arm", "index": 18, "type": "byte", - "default_value": 1, - "bits": [] + "default_value": 1 }, { "name": "left_shoulder_entity", "index": 19, "type": "nbt_compound", - "default_value": "{}", - "bits": [] + "default_value": "{}" }, { "name": "right_shoulder_entity", "index": 20, "type": "nbt_compound", - "default_value": "{}", - "bits": [] + "default_value": "{}" } ] }, @@ -2262,8 +1920,7 @@ "name": "warning", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2287,9 +1944,9 @@ "parent": "Entity", "fields": [], "default_bounding_box": { - "size_x": 0.5, - "size_y": 0.5, - "size_z": 0.5 + "size_x": 0.25, + "size_y": 0.25, + "size_z": 0.25 } }, "PufferfishEntity": { @@ -2301,8 +1958,7 @@ "name": "puff_state", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2320,8 +1976,7 @@ "name": "rabbit_type", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2337,8 +1992,7 @@ "name": "celebrating", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2373,9 +2027,9 @@ "parent": "FishEntity", "fields": [], "default_bounding_box": { - "size_x": 0.5, + "size_x": 0.699999988079071, "size_y": 0.4000000059604645, - "size_z": 0.5 + "size_z": 0.699999988079071 } }, "SheepEntity": { @@ -2387,8 +2041,7 @@ "name": "color", "index": 17, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2417,22 +2070,19 @@ "name": "attached_face", "index": 16, "type": "facing", - "default_value": "down", - "bits": [] + "default_value": "down" }, { "name": "peek_amount", "index": 17, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "color", "index": 18, "type": "byte", - "default_value": 16, - "bits": [] + "default_value": 16 } ], "default_bounding_box": { @@ -2461,8 +2111,7 @@ "name": "converting", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2491,8 +2140,7 @@ "name": "slime_size", "index": 16, "type": "integer", - "default_value": 1, - "bits": [] + "default_value": 1 } ], "default_bounding_box": { @@ -2521,13 +2169,7 @@ "name": "snow_golem_flags", "index": 16, "type": "byte", - "default_value": 16, - "bits": [ - { - "name": "has_pumpkin", - "index": 4 - } - ] + "default_value": 16 } ], "default_bounding_box": { @@ -2576,8 +2218,7 @@ "name": "spell", "index": 17, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2595,19 +2236,13 @@ "name": "spider_flags", "index": 16, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "climbing_wall", - "index": 0 - } - ] + "default_value": 0 } ], "default_bounding_box": { - "size_x": 0.699999988079071, - "size_y": 0.5, - "size_z": 0.699999988079071 + "size_x": 1.399999976158142, + "size_y": 0.8999999761581421, + "size_z": 1.399999976158142 } }, "SquidEntity": { @@ -2650,22 +2285,19 @@ "name": "boost_time", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "cold", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "saddled", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2692,24 +2324,13 @@ "name": "tameable_flags", "index": 17, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "sitting_pose", - "index": 0 - }, - { - "name": "tamed", - "index": 2 - } - ] + "default_value": 0 }, { "name": "owner_uuid", "index": 18, "type": "optional_uuid", - "default_value": null, - "bits": [] + "default_value": null } ], "default_bounding_box": { @@ -2743,8 +2364,7 @@ "name": "item", "index": 8, "type": "item_stack", - "default_value": "1 air", - "bits": [] + "default_value": "1 air" } ], "default_bounding_box": { @@ -2762,8 +2382,7 @@ "name": "fuse", "index": 8, "type": "integer", - "default_value": 80, - "bits": [] + "default_value": 80 } ], "default_bounding_box": { @@ -2803,15 +2422,13 @@ "name": "loyalty", "index": 10, "type": "byte", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "enchanted", "index": 11, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2829,8 +2446,7 @@ "name": "variant", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2852,22 +2468,19 @@ "x": 0, "y": 0, "z": 0 - }, - "bits": [] + } }, { "name": "has_egg", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "digging_sand", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "travel_pos", @@ -2877,22 +2490,19 @@ "x": 0, "y": 0, "z": 0 - }, - "bits": [] + } }, { "name": "land_bound", "index": 21, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "actively_traveling", "index": 22, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -2910,13 +2520,7 @@ "name": "vex_flags", "index": 16, "type": "byte", - "default_value": 0, - "bits": [ - { - "name": "charging", - "index": 0 - } - ] + "default_value": 0 } ], "default_bounding_box": { @@ -2938,8 +2542,7 @@ "type": "plains", "profession": "none", "level": 1 - }, - "bits": [] + } } ], "default_bounding_box": { @@ -2979,8 +2582,7 @@ "name": "anger", "index": 16, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -2993,9 +2595,9 @@ "parent": "PathAwareEntity", "fields": [], "default_bounding_box": { - "size_x": 0.5, - "size_y": 0.4000000059604645, - "size_z": 0.5 + "size_x": 0.8999999761581421, + "size_y": 0.6000000238418579, + "size_z": 0.8999999761581421 } }, "WitchEntity": { @@ -3007,8 +2609,7 @@ "name": "drinking", "index": 17, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -3026,29 +2627,25 @@ "name": "tracked_entity_id_1", "index": 16, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "tracked_entity_id_2", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "tracked_entity_id_3", "index": 18, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "invul_timer", "index": 19, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -3077,8 +2674,7 @@ "name": "charged", "index": 8, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -3096,22 +2692,19 @@ "name": "begging", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "collar_color", "index": 20, "type": "integer", - "default_value": 14, - "bits": [] + "default_value": 14 }, { "name": "anger_time", "index": 21, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 } ], "default_bounding_box": { @@ -3129,8 +2722,7 @@ "name": "baby", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -3148,22 +2740,19 @@ "name": "baby", "index": 16, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "zombie_type", "index": 17, "type": "integer", - "default_value": 0, - "bits": [] + "default_value": 0 }, { "name": "converting_in_water", "index": 18, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false } ], "default_bounding_box": { @@ -3192,8 +2781,7 @@ "name": "converting", "index": 19, "type": "boolean", - "default_value": false, - "bits": [] + "default_value": false }, { "name": "villager_data", @@ -3201,10 +2789,9 @@ "type": "villager_data", "default_value": { "type": "plains", - "profession": "mason", + "profession": "fletcher", "level": 1 - }, - "bits": [] + } } ], "default_bounding_box": { diff --git a/extractor/src/main/java/rs/valence/extractor/extractors/Entities.java b/extractor/src/main/java/rs/valence/extractor/extractors/Entities.java index c62979b..07e0121 100644 --- a/extractor/src/main/java/rs/valence/extractor/extractors/Entities.java +++ b/extractor/src/main/java/rs/valence/extractor/extractors/Entities.java @@ -29,116 +29,6 @@ import java.lang.reflect.ParameterizedType; import java.util.*; public class Entities implements Main.Extractor { - private final static Map BIT_FIELDS = Map.ofEntries( - // @formatter:off - bits( - "flags", - bit("on_fire", 0), - bit("sneaking", 1), - bit("sprinting", 3), - bit("swimming", 4), - bit("invisible", 5), - bit("glowing", 6), - bit("fall_flying", 7) - ), - bits( - "projectile_flags", - bit("critical", 0), - bit("no_clip", 1) - ), - bits( - "living_flags", - bit("using_item", 0), - bit("off_hand_active", 1), - bit("using_riptide", 2) - ), - bits( - "player_model_parts", - bit("cape", 0), - bit("jacket", 1), - bit("left_sleeve", 2), - bit("right_sleeve", 3), - bit("left_pants_leg", 4), - bit("right_pants_leg", 5), - bit("hat", 6) - ), - bits( - "armor_stand_flags", - bit("small", 0), - bit("show_arms", 1), - bit("hide_base_plate", 2), - bit("marker", 3) - ), - bits( - "mob_flags", - bit("ai_disabled", 0), - bit("left_handed", 1), - bit("attacking", 2) - ), - bits( - "bat_flags", - bit("hanging", 0) - ), - bits( - "horse_flags", - bit("tamed", 1), - bit("saddled", 2), - bit("bred", 3), - bit("eating_grass", 4), - bit("angry", 5), - bit("eating", 6) - ), - bits( - "bee_flags", - bit("near_target", 1), - bit("has_stung", 2), - bit("has_nectar", 3) - ), - bits( - "fox_flags", - bit("sitting", 0), - bit("crouching", 2), - bit("rolling_head", 3), - bit("chasing", 4), - bit("sleeping", 5), - bit("walking", 6), - bit("aggressive", 7) - ), - bits( - "panda_flags", - bit("sneezing", 1), - bit("playing", 2), - bit("sitting", 3), - bit("lying_on_back", 4) - ), - bits( - "tameable_flags", - bit("sitting_pose", 0), - bit("tamed", 2) - ), - bits( - "iron_golem_flags", - bit("player_created", 0) - ), - bits( - "snow_golem_flags", - bit("has_pumpkin", 4) - ), - bits( - "blaze_flags", - bit("fire_active", 0) - ), - bits( - "vex_flags", - bit("charging", 0) - ), - bits( - "spider_flags", - bit("climbing_wall", 0) - ) - // @formatter:on - ); - public Entities() { } @@ -321,15 +211,6 @@ public class Entities implements Main.Extractor { fieldJson.addProperty("type", data.left()); fieldJson.add("default_value", data.right()); - var bitsJson = new JsonArray(); - for (var bit : BIT_FIELDS.getOrDefault(fieldName, new Bit[]{})) { - var bitJson = new JsonObject(); - bitJson.addProperty("name", bit.name); - bitJson.addProperty("index", bit.index); - bitsJson.add(bitJson); - } - fieldJson.add("bits", bitsJson); - fieldsJson.add(fieldJson); } }