Entity Rework (#294)

## Description

Closes #269
Closes #199 

- Removes `McEntity` and replaces it with bundles of components, one for
each entity type.
- Tracked data types are now separate components rather than stuffing
everything into a `TrackedData` enum.
- Tracked data is now cached in binary form within each entity,
eliminating some work when entities enter the view of clients.
- Complete redesign of entity code generator.
- More docs for some components.
- Field bits are moved out of the entity extractor and into the valence
entity module.
- Moved hitbox code to separate module.
- Refactor instance update systems to improve parallelism.

### TODOs
- [x] Update examples.
- [x] Update `default_event_handler`.
- [x] Fix bugs.

## Test Plan

Steps:
1. Check out the entity module docs with `cargo d --open`.
2. Run examples.
This commit is contained in:
Ryan Johnson 2023-03-21 23:29:38 -07:00 committed by GitHub
parent c9fbd1a24e
commit 4cf6e1a207
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2375 additions and 2754 deletions

2
.gitignore vendored
View file

@ -8,9 +8,11 @@ Cargo.lock
/extractor/out /extractor/out
/extractor/classes /extractor/classes
/extractor/run /extractor/run
/extractor/bin
rust-mc-bot rust-mc-bot
.asset_cache/ .asset_cache/
/velocity /velocity
flamegraph*.svg flamegraph*.svg
perf.data perf.data
perf.data.old perf.data.old
/graph.gv

View file

@ -1,5 +1,4 @@
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler;
use valence::prelude::*; use valence::prelude::*;
#[allow(unused_imports)] #[allow(unused_imports)]

View file

@ -12,6 +12,7 @@ build = "build/main.rs"
authors = ["Ryan Johnson <ryanj00a@gmail.com>"] authors = ["Ryan Johnson <ryanj00a@gmail.com>"]
[dependencies] [dependencies]
#bevy_mod_debugdump = "0.7.0"
anyhow = "1.0.65" anyhow = "1.0.65"
arrayvec = "0.7.2" arrayvec = "0.7.2"
async-trait = "0.1.60" async-trait = "0.1.60"

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use heck::ToPascalCase; use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
use proc_macro2::{Ident, TokenStream}; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use serde::Deserialize; use serde::Deserialize;
@ -27,7 +27,6 @@ struct Field {
index: u8, index: u8,
#[serde(flatten)] #[serde(flatten)]
default_value: Value, default_value: Value,
bits: Vec<Bit>,
} }
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
@ -75,14 +74,8 @@ struct BlockPos {
z: i32, z: i32,
} }
#[derive(Deserialize, Clone, Debug)]
struct Bit {
name: String,
index: u8,
}
impl Value { impl Value {
pub fn type_id(&self) -> i32 { pub fn type_id(&self) -> u8 {
match self { match self {
Value::Byte(_) => 0, Value::Byte(_) => 0,
Value::Integer(_) => 1, Value::Integer(_) => 1,
@ -117,46 +110,26 @@ impl Value {
Value::Integer(_) => quote!(i32), Value::Integer(_) => quote!(i32),
Value::Long(_) => quote!(i64), Value::Long(_) => quote!(i64),
Value::Float(_) => quote!(f32), Value::Float(_) => quote!(f32),
Value::String(_) => quote!(Box<str>), Value::String(_) => quote!(String),
Value::TextComponent(_) => quote!(Text), Value::TextComponent(_) => quote!(crate::protocol::text::Text),
Value::OptionalTextComponent(_) => quote!(Option<Text>), Value::OptionalTextComponent(_) => quote!(Option<crate::protocol::text::Text>),
Value::ItemStack(_) => quote!(()), // TODO Value::ItemStack(_) => quote!(crate::protocol::item::ItemStack),
Value::Boolean(_) => quote!(bool), Value::Boolean(_) => quote!(bool),
Value::Rotation { .. } => quote!(EulerAngle), Value::Rotation { .. } => quote!(crate::entity::EulerAngle),
Value::BlockPos(_) => quote!(BlockPos), Value::BlockPos(_) => quote!(crate::protocol::block_pos::BlockPos),
Value::OptionalBlockPos(_) => quote!(Option<BlockPos>), Value::OptionalBlockPos(_) => quote!(Option<crate::protocol::block_pos::BlockPos>),
Value::Facing(_) => quote!(Facing), Value::Facing(_) => quote!(crate::protocol::types::Direction),
Value::OptionalUuid(_) => quote!(Option<Uuid>), Value::OptionalUuid(_) => quote!(Option<::uuid::Uuid>),
Value::OptionalBlockState(_) => quote!(BlockState), Value::OptionalBlockState(_) => quote!(crate::protocol::block::BlockState),
Value::NbtCompound(_) => quote!(valence_nbt::Compound), Value::NbtCompound(_) => quote!(crate::nbt::Compound),
Value::Particle(_) => quote!(Particle), Value::Particle(_) => quote!(crate::protocol::packet::s2c::play::particle::Particle),
Value::VillagerData { .. } => quote!(VillagerData), Value::VillagerData { .. } => quote!(crate::entity::VillagerData),
Value::OptionalInt(_) => quote!(OptionalInt), Value::OptionalInt(_) => quote!(Option<i32>),
Value::EntityPose(_) => quote!(Pose), Value::EntityPose(_) => quote!(crate::entity::Pose),
Value::CatVariant(_) => quote!(CatKind), Value::CatVariant(_) => quote!(crate::entity::CatKind),
Value::FrogVariant(_) => quote!(FrogKind), Value::FrogVariant(_) => quote!(crate::entity::FrogKind),
Value::OptionalGlobalPos(_) => quote!(()), // TODO Value::OptionalGlobalPos(_) => quote!(()), // TODO
Value::PaintingVariant(_) => quote!(PaintingKind), Value::PaintingVariant(_) => quote!(crate::entity::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),
} }
} }
@ -166,35 +139,53 @@ impl Value {
Value::Integer(i) => quote!(#i), Value::Integer(i) => quote!(#i),
Value::Long(l) => quote!(#l), Value::Long(l) => quote!(#l),
Value::Float(f) => quote!(#f), Value::Float(f) => quote!(#f),
Value::String(s) => quote!(#s.to_owned().into_boxed_str()), Value::String(s) => quote!(#s.to_owned()),
Value::TextComponent(_) => quote!(Text::default()), // TODO Value::TextComponent(txt) => {
assert!(txt.is_empty());
quote!(crate::protocol::text::Text::default())
}
Value::OptionalTextComponent(t) => { Value::OptionalTextComponent(t) => {
assert!(t.is_none()); assert!(t.is_none());
quote!(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::Boolean(b) => quote!(#b),
Value::Rotation { pitch, yaw, roll } => quote! { Value::Rotation { pitch, yaw, roll } => quote! {
EulerAngle { crate::entity::EulerAngle {
pitch: #pitch, pitch: #pitch,
yaw: #yaw, yaw: #yaw,
roll: #roll, roll: #roll,
} }
}, },
Value::BlockPos(BlockPos { x, y, z }) => { 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) => { Value::Facing(f) => {
let variant = ident(f.to_pascal_case()); 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) => { Value::Particle(p) => {
let variant = ident(p.to_pascal_case()); let variant = ident(p.to_pascal_case());
quote!(Particle::#variant) quote!(crate::protocol::packet::s2c::play::particle::Particle::#variant)
} }
Value::VillagerData { Value::VillagerData {
typ, typ,
@ -203,28 +194,34 @@ impl Value {
} => { } => {
let typ = ident(typ.to_pascal_case()); let typ = ident(typ.to_pascal_case());
let profession = ident(profession.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) => { Value::OptionalInt(i) => {
assert!(i.is_none()); assert!(i.is_none());
quote!(OptionalInt::default()) quote!(None)
} }
Value::EntityPose(p) => { Value::EntityPose(p) => {
let variant = ident(p.to_pascal_case()); let variant = ident(p.to_pascal_case());
quote!(Pose::#variant) quote!(crate::entity::Pose::#variant)
} }
Value::CatVariant(c) => { Value::CatVariant(c) => {
let variant = ident(c.to_pascal_case()); let variant = ident(c.to_pascal_case());
quote!(CatKind::#variant) quote!(crate::entity::CatKind::#variant)
} }
Value::FrogVariant(f) => { Value::FrogVariant(f) => {
let variant = ident(f.to_pascal_case()); let variant = ident(f.to_pascal_case());
quote!(FrogKind::#variant) quote!(crate::entity::FrogKind::#variant)
} }
Value::OptionalGlobalPos(_) => quote!(()), Value::OptionalGlobalPos(_) => quote!(()),
Value::PaintingVariant(p) => { Value::PaintingVariant(p) => {
let variant = ident(p.to_pascal_case()); 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 { pub fn encodable_expr(&self, self_lvalue: TokenStream) -> TokenStream {
match self { match self {
Value::Integer(_) => quote!(VarInt(#self_lvalue)), 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<String, Entity>; type Entities = BTreeMap<String, Entity>;
pub fn build() -> anyhow::Result<TokenStream> { pub fn build() -> anyhow::Result<TokenStream> {
let entities =
serde_json::from_str::<Entities>(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::<Entities>();
let entity_types = let entity_types =
serde_json::from_str::<EntityData>(include_str!("../../../extracted/entity_data.json"))? serde_json::from_str::<EntityData>(include_str!("../../../extracted/entity_data.json"))?
.types; .types;
let concrete_entities = entities let entities: Entities =
.clone() serde_json::from_str::<Entities>(include_str!("../../../extracted/entities.json"))?
.into_iter() .into_iter()
.filter(|(_, v)| v.typ.is_some()) .map(|(entity_name, mut entity)| {
.collect::<Entities>(); let change_name = |mut name: String| {
if let Some(stripped) = name.strip_suffix("Entity") {
let entity_kind_variants = concrete_entities.iter().map(|(name, e)| { if !stripped.is_empty() {
let name = ident(name); name = stripped.into();
let id = entity_types[e.typ.as_ref().unwrap()] as isize;
quote! {
#name = #id,
}
});
let concrete_entity_names = concrete_entities.keys().map(ident).collect::<Vec<_>>();
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
}
}
} }
}) }
.collect::<TokenStream>()
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 { } else {
let getter_name = ident(format!("get_{}", &field.name)); quote!(None)
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! { translation_key_arms.extend([quote! {
pub fn #getter_name(&self) -> #getter_return_type { EntityKind::#shouty_entity_name_ident => #translation_key_expr,
#getter_return_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_ident =
let #field_name = #field_name.into(); ident(format!("{snake_entity_name}_{snake_field_name}"));
if self.#field_name != #field_name {
self.__modified_flags |= 1 << #field_index as #modified_flags_type; bundle_fields.extend([quote! {
self.#field_name = #field_name; 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| { for field in &entity.fields {
let field_name = ident(&field.name); let pascal_field_name_ident = ident(field.name.to_pascal_case());
let field_index = field.index; 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 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! { module_body.extend([quote! {
if self.#field_name != (#default_expr) { #[derive(bevy_ecs::component::Component, PartialEq, Clone, Debug)]
data.push(#field_index); pub struct #pascal_field_name_ident(pub #inner_type);
VarInt(#type_id).encode(&mut *data).unwrap();
#encodable.encode(&mut *data).unwrap();
}
}
});
let updated_tracked_data_stmts = fields.iter().map(|&field| { #[allow(clippy::derivable_impls)]
let field_name = ident(&field.name); impl Default for #pascal_field_name_ident {
let field_index = field.index; fn default() -> Self {
let type_id = field.default_value.type_id(); Self(#default_expr)
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)*
} }
} }
}]);
pub(crate) fn initial_tracked_data(&self, data: &mut Vec<u8>) { let system_name_ident = ident(format!("update_{snake_entity_name}_{snake_field_name}"));
#(#initial_tracked_data_stmts)* let component_path = quote!(#snake_entity_name_ident::#pascal_field_name_ident);
}
pub(crate) fn updated_tracked_data(&self, data: &mut Vec<u8>) { system_names.push(quote!(#system_name_ident));
if self.__modified_flags != 0 {
#(#updated_tracked_data_stmts)* 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) { let marker_doc = format!("Marker component for `{snake_entity_name}` entities.");
self.__modified_flags = 0;
}
#(#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! { Ok(quote! {
/// Contains a variant for each concrete entity type. #modules
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum EntityKind { /// Identifies the type of an entity.
#(#entity_kind_variants)* /// 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 { 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 { match self {
#(#translation_key_arms)* #translation_key_arms
_ => None,
} }
} }
} }
pub enum TrackedData { impl std::fmt::Debug for EntityKind {
#(#concrete_entity_names(#concrete_entity_names),)* #[allow(clippy::write_literal)]
} fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
impl TrackedData { #entity_kind_fmt_args
pub(super) fn new(kind: EntityKind) -> Self { EntityKind(other) => write!(f, "{other}"),
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<u8>) {
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<u8>) {
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(),)*
} }
} }
} }
#(#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> { enum MarkerOrField<'a> {
fn rec<'a>(entity_name: &str, entities: &'a Entities, fields: &mut Vec<&'a Field>) { 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<MarkerOrField<'a>> {
let mut res = vec![];
loop {
let e = &entities[entity_name]; 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 { if let Some(parent) = &e.parent {
rec(parent, entities, fields); entity_name = parent;
} else {
break;
} }
} }
let mut fields = vec![]; res
rec(entity_name, entities, &mut fields);
fields.sort_by_key(|f| f.index);
fields
} }

View file

@ -2,8 +2,8 @@
use std::time::Instant; use std::time::Instant;
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::instance::{Chunk, Instance}; use valence::instance::{Chunk, Instance};
use valence::prelude::*; use valence::prelude::*;
@ -67,26 +67,18 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, unique_id, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut game_mode) in &mut clients {
pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into();
loc.0 = instances.single();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, unique_id.0)); position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,7 +1,6 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 0; const SPAWN_Y: i32 = 0;

View file

@ -1,7 +1,8 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::event::{ChatMessage, PlayerInteractBlock};
use valence::client::event::{default_event_handler, ChatMessage, PlayerInteractBlock}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::nbt::{compound, List}; use valence::nbt::{compound, List};
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::types::Hand; use valence::protocol::types::Hand;
@ -63,29 +64,20 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Position,
&mut Look,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut pos, mut look, mut loc, mut game_mode) in &mut clients { for (entity, uuid, 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();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([1.5, FLOOR_Y as f64 + 1.0, 1.5]),
look: Look::new(-90.0, 0.0),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,9 +1,8 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::event::{PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock};
use valence::client::event::{ use valence::client::{default_event_handler, despawn_disconnected_clients};
default_event_handler, PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock, use valence::entity::player::PlayerBundle;
};
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::types::Hand; use valence::protocol::types::Hand;
@ -50,28 +49,20 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut client, mut game_mode) in &mut clients {
pos.0 = [0.0, SPAWN_Y as f64 + 1.0, 0.0].into();
loc.0 = instances.single();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
client.send_message("Welcome to Valence! Build something cool.".italic()); client.send_message("Welcome to Valence! Build something cool.".italic());
commands
.entity(entity) commands.entity(entity).insert(PlayerBundle {
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); 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<StartSneaking>, mut events: EventReader<StartSneaking>,
) { ) {
for event in events.iter() { for event in events.iter() {
let Ok(mut mode) = clients.get_component_mut::<GameMode>(event.client) else { let Ok(mut mode) = clients.get_mut(event.client) else {
continue; continue;
}; };
*mode = match *mode { *mode = match *mode {

View file

@ -1,9 +1,9 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use bevy_app::App;
use tracing::warn; use tracing::warn;
use valence::client::despawn_disconnected_clients; use valence::client::event::{ChatMessage, CommandExecution};
use valence::client::event::{default_event_handler, ChatMessage, CommandExecution}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -12,7 +12,7 @@ pub fn main() {
tracing_subscriber::fmt().init(); tracing_subscriber::fmt().init();
App::new() App::new()
.add_plugin(ServerPlugin::new(()).with_connection_mode(ConnectionMode::Offline)) .add_plugin(ServerPlugin::new(()))
.add_startup_system(setup) .add_startup_system(setup)
.add_system(init_clients) .add_system(init_clients)
.add_systems( .add_systems(
@ -47,29 +47,20 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut client, mut game_mode) in &mut clients {
pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]);
loc.0 = instances.single();
*game_mode = GameMode::Adventure; *game_mode = GameMode::Adventure;
client.send_message("Welcome to Valence! Talk about something.".italic()); client.send_message("Welcome to Valence! Talk about something.".italic());
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,8 +1,9 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use tracing::warn; use tracing::warn;
use valence::client::despawn_disconnected_clients; use valence::client::event::{PlayerInteractBlock, StartSneaking};
use valence::client::event::{default_event_handler, PlayerInteractBlock, StartSneaking}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -50,26 +51,19 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut game_mode) in &mut clients {
pos.0 = [0.5, SPAWN_Y as f64 + 1.0, 0.5].into();
loc.0 = instances.single();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
commands
.entity(entity) commands.entity(entity).insert(PlayerBundle {
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); 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<StartSneaking>, mut events: EventReader<StartSneaking>,
) { ) {
for event in events.iter() { for event in events.iter() {
let Ok(mut mode) = clients.get_component_mut::<GameMode>(event.client) else { let Ok(mut mode) = clients.get_mut(event.client) else {
continue; continue;
}; };
*mode = match *mode { *mode = match *mode {

View file

@ -2,10 +2,10 @@
use bevy_ecs::query::WorldQuery; use bevy_ecs::query::WorldQuery;
use glam::Vec3Swizzles; use glam::Vec3Swizzles;
use valence::client::despawn_disconnected_clients; use valence::client::event::{PlayerInteract, StartSprinting, StopSprinting};
use valence::client::event::{ use valence::client::{default_event_handler, despawn_disconnected_clients};
default_event_handler, PlayerInteract, StartSprinting, StopSprinting, use valence::entity::player::PlayerBundle;
}; use valence::entity::EntityStatuses;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -67,20 +67,22 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query<(Entity, &UniqueId, &mut Position, &mut Location), Added<Client>>, mut clients: Query<(Entity, &UniqueId), Added<Client>>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut pos, mut loc) in &mut clients { for (entity, uuid) in &mut clients {
pos.set([0.0, SPAWN_Y as f64, 0.0]);
loc.0 = instances.single();
commands.entity(entity).insert(( commands.entity(entity).insert((
CombatState { CombatState {
last_attacked_tick: 0, last_attacked_tick: 0,
has_bonus_knockback: false, 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, client: &'static mut Client,
pos: &'static Position, pos: &'static Position,
state: &'static mut CombatState, state: &'static mut CombatState,
entity: &'static mut McEntity, statuses: &'static mut EntityStatuses,
} }
fn handle_combat_events( fn handle_combat_events(
manager: Res<McEntityManager>, manager: Res<EntityManager>,
server: Res<Server>, server: Res<Server>,
mut clients: Query<CombatQuery>, mut clients: Query<CombatQuery>,
mut start_sprinting: EventReader<StartSprinting>, mut start_sprinting: EventReader<StartSprinting>,
@ -120,7 +122,7 @@ fn handle_combat_events(
.. ..
} in interact_with_entity.iter() } 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. // Attacked entity doesn't exist.
continue continue
}; };
@ -162,9 +164,10 @@ fn handle_combat_events(
victim victim
.client .client
.trigger_status(EntityStatus::DamageFromGenericSource); .trigger_status(EntityStatus::DamageFromGenericSource);
victim victim
.entity .statuses
.trigger_status(EntityStatus::DamageFromGenericSource); .trigger(EntityStatus::DamageFromGenericSource);
} }
} }

View file

@ -2,8 +2,9 @@
use std::mem; use std::mem;
use valence::client::despawn_disconnected_clients; use valence::client::event::{StartDigging, StartSneaking};
use valence::client::event::{default_event_handler, StartDigging, StartSneaking}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const BOARD_MIN_X: i32 = -30; const BOARD_MIN_X: i32 = -30;
@ -67,23 +68,11 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut client, mut game_mode) in &mut clients {
pos.0 = SPAWN_POS;
loc.0 = instances.single();
*game_mode = GameMode::Survival; *game_mode = GameMode::Survival;
client.send_message("Welcome to Conway's game of life in Minecraft!".italic()); client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
@ -92,9 +81,13 @@ fn init_clients(
life." life."
.italic(), .italic(),
); );
commands
.entity(entity) commands.entity(entity).insert(PlayerBundle {
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); location: Location(instances.single()),
position: Position(SPAWN_POS),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -3,14 +3,14 @@
use std::f64::consts::TAU; use std::f64::consts::TAU;
use glam::{DQuat, EulerRot}; use glam::{DQuat, EulerRot};
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::prelude::*; 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_CENTER: DVec3 = DVec3::new(0.5, SPAWN_POS.y as f64 + 2.0, 0.5);
const SPHERE_AMOUNT: usize = 200; const SPHERE_AMOUNT: usize = 200;
const SPHERE_KIND: EntityKind = EntityKind::Cow;
const SPHERE_MIN_RADIUS: f64 = 6.0; const SPHERE_MIN_RADIUS: f64 = 6.0;
const SPHERE_MAX_RADIUS: f64 = 12.0; const SPHERE_MAX_RADIUS: f64 = 12.0;
const SPHERE_FREQ: f64 = 0.5; const SPHERE_FREQ: f64 = 0.5;
@ -48,41 +48,42 @@ fn setup(mut commands: Commands, server: Res<Server>) {
let instance_id = commands.spawn(instance).id(); let instance_id = commands.spawn(instance).id();
commands.spawn_batch( commands.spawn_batch([0; SPHERE_AMOUNT].map(|_| {
[0; SPHERE_AMOUNT].map(|_| (McEntity::new(SPHERE_KIND, instance_id), SpherePart)), (
); SpherePartBundle {
location: Location(instance_id),
..Default::default()
},
SpherePart,
)
}));
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, 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();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); 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<Server>, mut parts: Query<&mut McEntity, With<SpherePart>>) { fn update_sphere(
server: Res<Server>,
mut parts: Query<(&mut Position, &mut Look, &mut HeadYaw), With<SpherePart>>,
) {
let time = server.current_tick() as f64 / server.tps() as f64; 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; 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<Server>, mut parts: Query<&mut McEntity, With<Spher
((time * SPHERE_FREQ * TAU).sin() + 1.0) / 2.0, ((time * SPHERE_FREQ * TAU).sin() + 1.0) / 2.0,
); );
for (mut entity, p) in parts.iter_mut().zip(fibonacci_spiral(SPHERE_AMOUNT)) { for ((mut pos, mut look, mut head_yaw), p) in
parts.iter_mut().zip(fibonacci_spiral(SPHERE_AMOUNT))
{
debug_assert!(p.is_normalized()); debug_assert!(p.is_normalized());
let dir = rot * p; let dir = rot * p;
let (yaw, pitch) = to_yaw_and_pitch(dir.as_vec3());
entity.set_position(SPHERE_CENTER + dir * radius); pos.0 = SPHERE_CENTER + dir * radius;
entity.set_yaw(yaw); look.set_vec(dir.as_vec3());
entity.set_head_yaw(yaw); head_yaw.0 = look.yaw;
entity.set_pitch(pitch);
} }
} }

View file

@ -1,7 +1,8 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::event::{PerformRespawn, StartSneaking};
use valence::client::event::{default_event_handler, PerformRespawn, StartSneaking}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -42,31 +43,22 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut Client, &mut HasRespawnScreen), Added<Client>>,
(
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut HasRespawnScreen,
&mut Location,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut has_respawn_screen, mut loc) in &mut clients { for (entity, uuid, mut client, mut has_respawn_screen) in &mut clients {
pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]);
has_respawn_screen.0 = true; has_respawn_screen.0 = true;
loc.0 = instances.iter().next().unwrap();
client.send_message( client.send_message(
"Welcome to Valence! Sneak to die in the game (but not in real life).".italic(), "Welcome to Valence! Sneak to die in the game (but not in real life).".italic(),
); );
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.iter().next().unwrap()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,7 +1,8 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::event::CommandExecution;
use valence::client::event::{default_event_handler, CommandExecution}; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -39,30 +40,23 @@ fn setup(mut commands: Commands, server: Res<Server>) {
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<
( (Entity, &UniqueId, &mut Client, &mut GameMode, &mut OpLevel),
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut Location,
&mut GameMode,
&mut OpLevel,
),
Added<Client>, Added<Client>,
>, >,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut loc, mut game_mode, mut op_level) in &mut clients { for (entity, uuid, mut client, 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();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
op_level.set(2); // required to use F3+F4, eg /gamemode op_level.set(2); // required to use F3+F4, eg /gamemode
client.send_message("Welcome to Valence! Use F3+F4 to change gamemode.".italic()); client.send_message("Welcome to Valence! Use F3+F4 to change gamemode.".italic());
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -5,8 +5,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rand::Rng; use rand::Rng;
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::packet::s2c::play::TitleFadeS2c; use valence::protocol::packet::s2c::play::TitleFadeS2c;
use valence::protocol::sound::Sound; use valence::protocol::sound::Sound;
@ -52,23 +52,12 @@ struct GameState {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &mut Client, &UniqueId, &mut IsFlat, &mut GameMode), Added<Client>>,
(
Entity,
&mut Client,
&UniqueId,
&mut IsFlat,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
server: Res<Server>, server: Res<Server>,
mut commands: Commands, 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; is_flat.0 = true;
loc.0 = entity;
*game_mode = GameMode::Adventure; *game_mode = GameMode::Adventure;
client.send_message("Welcome to epic infinite parkour game!".italic()); 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 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));
} }
} }

View file

@ -2,8 +2,8 @@
use std::fmt; use std::fmt;
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -42,27 +42,19 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut game_mode) in &mut clients {
pos.set([0.5, SPAWN_Y as f64 + 1.0, 0.5]);
loc.0 = instances.single();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([0.5, SPAWN_Y as f64 + 1.0, 0.5]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,7 +1,7 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use rand::Rng; 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::player_list::Entry;
use valence::prelude::*; use valence::prelude::*;

View file

@ -1,9 +1,9 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::event::{PlayerInteract, ResourcePackStatus, ResourcePackStatusChange};
use valence::client::event::{ use valence::client::{default_event_handler, despawn_disconnected_clients};
default_event_handler, PlayerInteract, ResourcePackStatus, ResourcePackStatusChange, use valence::entity::player::PlayerBundle;
}; use valence::entity::sheep::SheepBundle;
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::packet::c2s::play::player_interact::Interaction; use valence::protocol::packet::c2s::play::player_interact::Interaction;
@ -46,38 +46,31 @@ fn setup(mut commands: Commands, server: Res<Server>) {
let instance_ent = commands.spawn(instance).id(); let instance_ent = commands.spawn(instance).id();
let mut sheep = McEntity::new(EntityKind::Sheep, instance_ent); commands.spawn(SheepBundle {
sheep.set_position([0.0, SPAWN_Y as f64 + 1.0, 2.0]); location: Location(instance_ent),
sheep.set_yaw(180.0); position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 2.0]),
sheep.set_head_yaw(180.0); look: Look::new(180.0, 0.0),
commands.spawn(sheep); head_yaw: HeadYaw(180.0),
..Default::default()
});
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut Client, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut Client,
&mut Position,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut client, mut pos, mut loc, mut game_mode) in &mut clients { for (entity, uuid, mut client, mut game_mode) in &mut clients {
pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]);
loc.0 = instances.single();
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
client.send_message("Hit the sheep to prompt for the resource pack.".italic()); client.send_message("Hit the sheep to prompt for the resource pack.".italic());
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position::new([0.0, SPAWN_Y as f64 + 1.0, 0.0]),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -9,8 +9,8 @@ use std::time::SystemTime;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use noise::{NoiseFn, SuperSimplex}; use noise::{NoiseFn, SuperSimplex};
use tracing::info; use tracing::info;
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0); const SPAWN_POS: DVec3 = DVec3::new(0.0, 200.0, 0.0);
@ -108,31 +108,20 @@ fn setup(mut commands: Commands, server: Res<Server>) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &UniqueId, &mut IsFlat, &mut GameMode), Added<Client>>,
(
Entity,
&UniqueId,
&mut IsFlat,
&mut GameMode,
&mut Position,
&mut Location,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, uuid, mut is_flat, mut game_mode, mut pos, mut loc) in &mut clients { for (entity, uuid, mut is_flat, mut game_mode) in &mut clients {
let instance = instances.single();
is_flat.0 = true; is_flat.0 = true;
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
pos.0 = SPAWN_POS;
loc.0 = instance;
commands commands.entity(entity).insert(PlayerBundle {
.entity(entity) location: Location(instances.single()),
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0)); position: Position(SPAWN_POS),
uuid: *uuid,
..Default::default()
});
} }
} }

View file

@ -1,7 +1,6 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler;
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::translation_key; use valence::protocol::translation_key;

View file

@ -12,6 +12,7 @@ use glam::{DVec3, Vec3};
use rand::Rng; use rand::Rng;
use tracing::warn; use tracing::warn;
use valence_protocol::block_pos::BlockPos; use valence_protocol::block_pos::BlockPos;
use valence_protocol::byte_angle::ByteAngle;
use valence_protocol::codec::{PacketDecoder, PacketEncoder}; use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::ident::Ident; use valence_protocol::ident::Ident;
use valence_protocol::item::ItemStack; 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::player_position_look::Flags as PlayerPositionLookFlags;
use valence_protocol::packet::s2c::play::{ use valence_protocol::packet::s2c::play::{
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c, ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c,
DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, EntityTrackerUpdateS2c, DisconnectS2c, EntitiesDestroyS2c, EntitySetHeadYawS2c, EntitySpawnS2c, EntityStatusS2c,
EntityVelocityUpdateS2c, GameJoinS2c, GameMessageS2c, GameStateChangeS2c, KeepAliveS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, ExperienceOrbSpawnS2c, GameJoinS2c,
OverlayMessageS2c, ParticleS2c, PlaySoundS2c, PlayerActionResponseS2c, PlayerPositionLookS2c, GameMessageS2c, GameStateChangeS2c, KeepAliveS2c, OverlayMessageS2c, ParticleS2c, PlaySoundS2c,
PlayerRespawnS2c, PlayerSpawnPositionS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, PlayerActionResponseS2c, PlayerPositionLookS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c,
TitleS2c, UnloadChunkS2c, PlayerSpawnS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, TitleS2c, UnloadChunkS2c,
}; };
use valence_protocol::sound::Sound; use valence_protocol::sound::Sound;
use valence_protocol::text::Text; use valence_protocol::text::Text;
@ -37,16 +38,22 @@ use crate::component::{
Properties, UniqueId, Username, Properties, UniqueId, Username,
}; };
use crate::dimension::DimensionId; use crate::dimension::DimensionId;
use crate::entity::{velocity_to_packet_units, EntityStatus, McEntity, TrackedData}; use crate::entity::{
use crate::instance::{Instance, UpdateInstancesPreClientSet}; EntityId, EntityKind, EntityStatus, HeadYaw, ObjectData, TrackedData, Velocity,
};
use crate::instance::{Instance, WriteUpdatePacketsToInstancesSet};
use crate::inventory::{Inventory, InventoryKind}; use crate::inventory::{Inventory, InventoryKind};
use crate::packet::WritePacket; use crate::packet::WritePacket;
use crate::prelude::ScratchBuf; use crate::prelude::ScratchBuf;
use crate::server::{NewClientInfo, Server}; use crate::server::{NewClientInfo, Server};
use crate::util::velocity_to_packet_units;
use crate::view::{ChunkPos, ChunkView}; use crate::view::{ChunkPos, ChunkView};
mod default_event_handler;
pub mod event; pub mod event;
pub use default_event_handler::*;
/// The bundle of components needed for clients to function. All components are /// The bundle of components needed for clients to function. All components are
/// required unless otherwise stated. /// required unless otherwise stated.
#[derive(Bundle)] #[derive(Bundle)]
@ -62,7 +69,7 @@ pub(crate) struct ClientBundle {
old_location: OldLocation, old_location: OldLocation,
position: Position, position: Position,
old_position: OldPosition, old_position: OldPosition,
direction: Look, look: Look,
on_ground: OnGround, on_ground: OnGround,
compass_pos: CompassPos, compass_pos: CompassPos,
game_mode: GameMode, game_mode: GameMode,
@ -105,7 +112,7 @@ impl ClientBundle {
old_location: OldLocation::default(), old_location: OldLocation::default(),
position: Position::default(), position: Position::default(),
old_position: OldPosition::default(), old_position: OldPosition::default(),
direction: Look::default(), look: Look::default(),
on_ground: OnGround::default(), on_ground: OnGround::default(),
compass_pos: CompassPos::default(), compass_pos: CompassPos::default(),
game_mode: GameMode::default(), game_mode: GameMode::default(),
@ -221,10 +228,10 @@ impl Client {
/// Kills the client and shows `message` on the death screen. If an entity /// Kills the client and shows `message` on the death screen. If an entity
/// killed the player, you should supply it as `killer`. /// killed the player, you should supply it as `killer`.
pub fn kill(&mut self, killer: Option<&McEntity>, message: impl Into<Text>) { pub fn kill(&mut self, killer: Option<EntityId>, message: impl Into<Text>) {
self.write_packet(&DeathMessageS2c { self.write_packet(&DeathMessageS2c {
player_id: VarInt(0), 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(), message: message.into().into(),
}); });
} }
@ -613,10 +620,10 @@ impl Plugin for ClientPlugin {
( (
initial_join, initial_join,
update_chunk_load_dist, update_chunk_load_dist,
read_data_in_view read_data_in_old_view
.after(UpdateInstancesPreClientSet) .after(WriteUpdatePacketsToInstancesSet)
.after(update_chunk_load_dist), .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), respawn.after(update_view),
remove_entities.after(update_view), remove_entities.after(update_view),
update_spawn_position.after(update_view), update_spawn_position.after(update_view),
@ -624,7 +631,8 @@ impl Plugin for ClientPlugin {
teleport.after(update_view), teleport.after(update_view),
update_game_mode, update_game_mode,
send_keepalive, send_keepalive,
update_tracked_data, update_tracked_data.after(WriteUpdatePacketsToInstancesSet),
init_tracked_data.after(WriteUpdatePacketsToInstancesSet),
update_op_level, update_op_level,
acknowledge_player_actions, acknowledge_player_actions,
) )
@ -634,7 +642,7 @@ impl Plugin for ClientPlugin {
.configure_set( .configure_set(
FlushPacketsSet FlushPacketsSet
.in_base_set(CoreSet::PostUpdate) .in_base_set(CoreSet::PostUpdate)
.after(UpdateInstancesPreClientSet), .after(WriteUpdatePacketsToInstancesSet),
) )
.add_system(flush_packets.in_set(FlushPacketsSet)); .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 clients: Query<(
&mut Client, &mut Client,
&mut ScratchBuf,
&mut EntityRemoveBuf, &mut EntityRemoveBuf,
&OldLocation, &OldLocation,
&OldPosition, &OldPosition,
&OldViewDistance, &OldViewDistance,
)>, )>,
instances: Query<&Instance>, instances: Query<&Instance>,
entities: Query<&McEntity>, entities: Query<(EntityInitQuery, &OldPosition)>,
entity_ids: Query<&EntityId>,
) { ) {
clients.par_iter_mut().for_each_mut( 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 { let Ok(instance) = instances.get(old_loc.get()) else {
return; return;
}; };
@ -829,15 +900,12 @@ fn read_data_in_view(
if src_pos.map_or(true, |p| !view.contains(p)) { if src_pos.map_or(true, |p| !view.contains(p)) {
// The incoming entity originated from outside the view distance, so it // The incoming entity originated from outside the view distance, so it
// must be spawned. // must be spawned.
if let Ok(entity) = entities.get(id) { if let Ok((entity, old_pos)) = entities.get(id) {
// Spawn the entity at the old position so that later relative // Notice we are spawning the entity at its old position rather than
// entity movement packets will not // the current position. This is because the client could also
// set the entity to the wrong position. // receive update packets for this entity this tick, which may
entity.write_init_packets( // include a relative entity movement.
&mut client.enc, entity.write_init_packets(old_pos.get(), &mut client.enc);
entity.old_position(),
&mut scratch.0,
);
} }
} }
} }
@ -847,8 +915,8 @@ fn read_data_in_view(
if dest_pos.map_or(true, |p| !view.contains(p)) { if dest_pos.map_or(true, |p| !view.contains(p)) {
// The outgoing entity moved outside the view distance, so it must be // The outgoing entity moved outside the view distance, so it must be
// despawned. // despawned.
if let Ok(entity) = entities.get(id) { if let Ok(entity_id) = entity_ids.get(id) {
remove_buf.push(entity.protocol_id()); remove_buf.push(entity_id.get());
} }
} }
} }
@ -867,7 +935,7 @@ fn read_data_in_view(
/// client's chunk position. /// client's chunk position.
/// ///
/// This handles the situation when a client changes instances or chunk /// 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( fn update_view(
mut clients: Query< mut clients: Query<
( (
@ -884,7 +952,8 @@ fn update_view(
Or<(Changed<Location>, Changed<Position>, Changed<ViewDistance>)>, Or<(Changed<Location>, Changed<Position>, Changed<ViewDistance>)>,
>, >,
instances: Query<&Instance>, instances: Query<&Instance>,
entities: Query<&McEntity>, entities: Query<(EntityInitQuery, &Position)>,
entity_ids: Query<&EntityId>,
) { ) {
clients.par_iter_mut().for_each_mut( clients.par_iter_mut().for_each_mut(
|( |(
@ -930,8 +999,8 @@ fn update_view(
// Unload all the entities in the cell. // Unload all the entities in the cell.
for &id in &cell.entities { for &id in &cell.entities {
if let Ok(entity) = entities.get(id) { if let Ok(entity_id) = entity_ids.get(id) {
remove_buf.push(entity.protocol_id()); remove_buf.push(entity_id.get());
} }
} }
} }
@ -956,12 +1025,8 @@ fn update_view(
// Load all the entities in this cell. // Load all the entities in this cell.
for &id in &cell.entities { for &id in &cell.entities {
if let Ok(entity) = entities.get(id) { if let Ok((entity, pos)) = entities.get(id) {
entity.write_init_packets( entity.write_init_packets(pos.get(), &mut client.enc);
&mut client.enc,
entity.position(),
&mut scratch.0,
);
} }
} }
} }
@ -988,8 +1053,8 @@ fn update_view(
// Unload all the entities in the cell. // Unload all the entities in the cell.
for &id in &cell.entities { for &id in &cell.entities {
if let Ok(entity) = entities.get(id) { if let Ok(entity_id) = entity_ids.get(id) {
remove_buf.push(entity.protocol_id()); remove_buf.push(entity_id.get());
} }
} }
} }
@ -1011,12 +1076,8 @@ fn update_view(
// Load all the entities in this cell. // Load all the entities in this cell.
for &id in &cell.entities { for &id in &cell.entities {
if let Ok(entity) = entities.get(id) { if let Ok((entity, pos)) = entities.get(id) {
entity.write_init_packets( entity.write_init_packets(pos.get(), &mut client.enc);
&mut client.enc,
entity.position(),
&mut scratch.0,
);
} }
} }
} }
@ -1130,21 +1191,24 @@ fn flush_packets(
} }
} }
fn update_tracked_data(mut clients: Query<(&mut Client, &McEntity)>, mut scratch: Local<Vec<u8>>) { fn init_tracked_data(mut clients: Query<(&mut Client, &TrackedData), Added<TrackedData>>) {
for (mut client, entity) in &mut clients { for (mut client, tracked_data) in &mut clients {
if let TrackedData::Player(player) = &entity.data { if let Some(init_data) = tracked_data.init_data() {
scratch.clear(); client.write_packet(&EntityTrackerUpdateS2c {
// TODO: should some fields be ignored? entity_id: VarInt(0),
player.updated_tracked_data(&mut scratch); metadata: init_data.into(),
});
}
}
}
if !scratch.is_empty() { fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) {
scratch.push(0xff); for (mut client, tracked_data) in &mut clients {
if let Some(update_data) = tracked_data.update_data() {
client.enc.write_packet(&EntityTrackerUpdateS2c { client.write_packet(&EntityTrackerUpdateS2c {
entity_id: VarInt(0), entity_id: VarInt(0),
metadata: scratch.as_slice().into(), metadata: update_data.into(),
}) });
}
} }
} }
} }

View file

@ -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<DefaultEventHandlerQuery>,
mut update_settings_events: EventReader<ClientSettings>,
mut player_move: EventReader<PlayerMove>,
mut start_sneaking: EventReader<StartSneaking>,
mut stop_sneaking: EventReader<StopSneaking>,
mut start_sprinting: EventReader<StartSprinting>,
mut stop_sprinting: EventReader<StopSprinting>,
mut swing_arm: EventReader<HandSwing>,
) {
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,
});
}
}
}
}

View file

@ -15,9 +15,7 @@ use valence_protocol::ident::Ident;
use valence_protocol::item::ItemStack; use valence_protocol::item::ItemStack;
use valence_protocol::packet::c2s::play::click_slot::{ClickMode, Slot}; 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_command::Action as ClientCommandAction;
use valence_protocol::packet::c2s::play::client_settings::{ use valence_protocol::packet::c2s::play::client_settings::{ChatMode, DisplayedSkinParts, MainArm};
ChatMode, DisplayedSkinParts, MainHand,
};
use valence_protocol::packet::c2s::play::player_action::Action as PlayerAction; 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::player_interact::Interaction;
use valence_protocol::packet::c2s::play::recipe_category_options::RecipeBookId; use valence_protocol::packet::c2s::play::recipe_category_options::RecipeBookId;
@ -30,16 +28,13 @@ use valence_protocol::packet::c2s::play::{
AdvancementTabC2s, ClientStatusC2s, ResourcePackStatusC2s, UpdatePlayerAbilitiesC2s, AdvancementTabC2s, ClientStatusC2s, ResourcePackStatusC2s, UpdatePlayerAbilitiesC2s,
}; };
use valence_protocol::packet::C2sPlayPacket; use valence_protocol::packet::C2sPlayPacket;
use valence_protocol::tracked_data::Pose;
use valence_protocol::types::{Difficulty, Direction, Hand}; use valence_protocol::types::{Difficulty, Direction, Hand};
use super::{ use super::{
CursorItem, KeepaliveState, PlayerActionSequence, PlayerInventoryState, TeleportState, CursorItem, KeepaliveState, PlayerActionSequence, PlayerInventoryState, TeleportState,
ViewDistance,
}; };
use crate::client::Client; use crate::client::Client;
use crate::component::{Look, OnGround, Ping, Position}; use crate::component::{Look, OnGround, Ping, Position};
use crate::entity::{EntityAnimation, EntityKind, McEntity, TrackedData};
use crate::inventory::Inventory; use crate::inventory::Inventory;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -98,7 +93,7 @@ pub struct ClientSettings {
/// `true` if the client has chat colors enabled, `false` otherwise. /// `true` if the client has chat colors enabled, `false` otherwise.
pub chat_colors: bool, pub chat_colors: bool,
pub displayed_skin_parts: DisplayedSkinParts, pub displayed_skin_parts: DisplayedSkinParts,
pub main_hand: MainHand, pub main_arm: MainArm,
pub enable_text_filtering: bool, pub enable_text_filtering: bool,
pub allow_server_listings: bool, pub allow_server_listings: bool,
} }
@ -829,7 +824,7 @@ fn handle_one_packet(
chat_mode: p.chat_mode, chat_mode: p.chat_mode,
chat_colors: p.chat_colors, chat_colors: p.chat_colors,
displayed_skin_parts: p.displayed_skin_parts, displayed_skin_parts: p.displayed_skin_parts,
main_hand: p.main_hand, main_arm: p.main_arm,
enable_text_filtering: p.enable_text_filtering, enable_text_filtering: p.enable_text_filtering,
allow_server_listings: p.allow_server_listings, allow_server_listings: p.allow_server_listings,
}); });
@ -1396,117 +1391,3 @@ fn handle_one_packet(
Ok(true) 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<ClientSettings>,
mut player_move: EventReader<PlayerMove>,
mut start_sneaking: EventReader<StartSneaking>,
mut stop_sneaking: EventReader<StopSneaking>,
mut start_sprinting: EventReader<StartSprinting>,
mut stop_sprinting: EventReader<StopSprinting>,
mut swing_arm: EventReader<HandSwing>,
) {
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,
});
}
};
}
}

View file

@ -10,12 +10,11 @@ use valence_protocol::types::{GameMode as ProtocolGameMode, Property};
use crate::prelude::FlushPacketsSet; use crate::prelude::FlushPacketsSet;
use crate::util::{from_yaw_and_pitch, to_yaw_and_pitch}; use crate::util::{from_yaw_and_pitch, to_yaw_and_pitch};
use crate::view::ChunkPos; use crate::view::ChunkPos;
use crate::NULL_ENTITY;
/// A [`Component`] for marking entities that should be despawned at the end of /// A [`Component`] for marking entities that should be despawned at the end of
/// the tick. /// 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 /// 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, /// you wish to despawn the `Despawned` component. At the end of the tick,
/// Valence will despawn all entities with this component for you. /// 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 /// It is legal to remove components or delete entities that Valence does not
/// know about at any time. /// know about at any time.
/// ///
/// [`McEntity`]: crate::entity::McEntity /// [Minecraft entities]: crate::entity
#[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)] #[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct Despawned; 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); 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)] #[derive(Component, Clone, PartialEq, Eq, Debug)]
pub struct Username(pub String); 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<Property>); pub struct Properties(pub Vec<Property>);
impl Properties { 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 { pub enum GameMode {
#[default] #[default]
@ -105,10 +116,13 @@ pub struct Location(pub Entity);
impl Default for Location { impl Default for Location {
fn default() -> Self { 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)] #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
pub struct OldLocation(Entity); pub struct OldLocation(Entity);
@ -124,7 +138,7 @@ impl OldLocation {
impl Default for OldLocation { impl Default for OldLocation {
fn default() -> Self { fn default() -> Self {
Self(NULL_ENTITY) Self(Entity::PLACEHOLDER)
} }
} }
@ -132,11 +146,15 @@ impl Default for OldLocation {
pub struct Position(pub DVec3); pub struct Position(pub DVec3);
impl Position { impl Position {
pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos.into())
}
pub fn chunk_pos(&self) -> ChunkPos { pub fn chunk_pos(&self) -> ChunkPos {
ChunkPos::from_dvec3(self.0) ChunkPos::from_dvec3(self.0)
} }
pub fn get(&self) -> DVec3 { pub fn get(self) -> DVec3 {
self.0 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)] #[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct OldPosition(DVec3); pub struct OldPosition(DVec3);
impl OldPosition { impl OldPosition {
pub fn new(pos: DVec3) -> Self { pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos) Self(pos.into())
} }
pub fn get(&self) -> DVec3 { pub fn get(self) -> DVec3 {
self.0 self.0
} }
pub fn chunk_pos(&self) -> ChunkPos { pub fn chunk_pos(self) -> ChunkPos {
ChunkPos::from_dvec3(self.0) 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. /// Describes the direction an entity is looking using pitch and yaw angles.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)] #[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct Look { pub struct Look {
@ -176,6 +193,10 @@ pub struct Look {
} }
impl 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. /// Gets a normalized direction vector from the yaw and pitch.
pub fn vec(&self) -> Vec3 { pub fn vec(&self) -> Vec3 {
from_yaw_and_pitch(self.yaw, self.pitch) from_yaw_and_pitch(self.yaw, self.pitch)

File diff suppressed because it is too large Load diff

View file

@ -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"));

View file

@ -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)
}
*/

View file

@ -2,9 +2,11 @@ use std::borrow::Cow;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::iter::FusedIterator; use std::iter::FusedIterator;
use std::mem;
use bevy_app::{CoreSet, Plugin}; use bevy_app::{CoreSet, Plugin};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::query::WorldQuery;
pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk}; pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk};
pub use chunk_entry::*; pub use chunk_entry::*;
use glam::{DVec3, Vec3}; use glam::{DVec3, Vec3};
@ -12,19 +14,29 @@ use num::integer::div_ceil;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use valence_protocol::array::LengthPrefixedArray; use valence_protocol::array::LengthPrefixedArray;
use valence_protocol::block_pos::BlockPos; 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::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::sound::Sound;
use valence_protocol::text::Text; use valence_protocol::text::Text;
use valence_protocol::types::SoundCategory; use valence_protocol::types::SoundCategory;
use valence_protocol::var_int::VarInt;
use valence_protocol::Packet; 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::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::packet::{PacketWriter, WritePacket};
use crate::prelude::FlushPacketsSet;
use crate::server::{Server, SharedServer}; use crate::server::{Server, SharedServer};
use crate::util::velocity_to_packet_units;
use crate::view::ChunkPos; use crate::view::ChunkPos;
mod chunk; mod chunk;
@ -438,16 +450,21 @@ impl Instance {
pub(crate) struct InstancePlugin; pub(crate) struct InstancePlugin;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct UpdateInstancesPreClientSet; pub(crate) struct WriteUpdatePacketsToInstancesSet;
impl Plugin for InstancePlugin { impl Plugin for InstancePlugin {
fn build(&self, app: &mut bevy_app::App) { fn build(&self, app: &mut bevy_app::App) {
app.configure_set( app.configure_set(
UpdateInstancesPreClientSet WriteUpdatePacketsToInstancesSet
.after(InitEntitiesSet) .after(InitEntitiesSet)
.in_base_set(CoreSet::PostUpdate), .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( .add_system(
update_instances_post_client update_instances_post_client
.after(FlushPacketsSet) .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<EntityKind>, Or<(Changed<Position>, With<Despawned>)>),
>,
mut instances: Query<&mut Instance>, mut instances: Query<&mut Instance>,
mut entities: Query<(Entity, &mut McEntity, Option<&Despawned>)>,
server: Res<Server>,
) { ) {
for (entity_id, entity, despawned) in &entities { for (entity, pos, old_pos, loc, old_loc, despawned) in &entities {
let pos = ChunkPos::at(entity.position().x, entity.position().z); let pos = ChunkPos::at(pos.0.x, pos.0.z);
let old_pos = ChunkPos::at(entity.old_position().x, entity.old_position().z); let old_pos = ChunkPos::at(old_pos.get().x, old_pos.get().z);
let instance = entity.instance();
let old_instance = entity.old_instance();
if despawned.is_some() { if despawned.is_some() {
// Entity was deleted. Remove it from the chunk it was in, if it was in a chunk // Entity was deleted. Remove it from the chunk it was in, if it was in a chunk
// at all. // 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 let Some(old_cell) = old_instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) { if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity_id, None)); 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 // Entity changed the instance it is in. Remove it from old cell and
// insert it in the new cell. // insert it in the new cell.
// TODO: skip marker entity? // 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 let Some(old_cell) = old_instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) { if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity_id, None)); 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) { match instance.partition.entry(pos) {
Entry::Occupied(oe) => { Entry::Occupied(oe) => {
let cell = oe.into_mut(); let cell = oe.into_mut();
if cell.entities.insert(entity_id) { if cell.entities.insert(entity) {
cell.incoming.push((entity_id, None)); cell.incoming.push((entity, None));
} }
} }
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
ve.insert(PartitionCell { ve.insert(PartitionCell {
chunk: None, chunk: None,
chunk_removed: false, chunk_removed: false,
entities: BTreeSet::from([entity_id]), entities: BTreeSet::from([entity]),
incoming: vec![(entity_id, None)], incoming: vec![(entity, None)],
outgoing: vec![], outgoing: vec![],
packet_buf: vec![], packet_buf: vec![],
}); });
@ -521,26 +545,26 @@ fn update_instances_pre_client(
// TODO: skip marker entity? // 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 let Some(old_cell) = instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) { if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity_id, Some(pos))); old_cell.outgoing.push((entity, Some(pos)));
} }
} }
match instance.partition.entry(pos) { match instance.partition.entry(pos) {
Entry::Occupied(oe) => { Entry::Occupied(oe) => {
let cell = oe.into_mut(); let cell = oe.into_mut();
if cell.entities.insert(entity_id) { if cell.entities.insert(entity) {
cell.incoming.push((entity_id, Some(old_pos))); cell.incoming.push((entity, Some(old_pos)));
} }
} }
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
ve.insert(PartitionCell { ve.insert(PartitionCell {
chunk: None, chunk: None,
chunk_removed: false, chunk_removed: false,
entities: BTreeSet::from([entity_id]), entities: BTreeSet::from([entity]),
incoming: vec![(entity_id, Some(old_pos))], incoming: vec![(entity, Some(old_pos))],
outgoing: vec![], outgoing: vec![],
packet_buf: vec![], packet_buf: vec![],
}); });
@ -552,7 +576,15 @@ fn update_instances_pre_client(
// we need to do. // 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<UpdateEntityQuery, (With<EntityKind>, Without<Despawned>)>,
server: Res<Server>,
) {
let mut scratch_1 = vec![]; let mut scratch_1 = vec![];
let mut scratch_2 = 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. // Cache entity update packets into the packet buffer of this cell.
for &id in &cell.entities { for &entity in &cell.entities {
let (_, mut entity, despawned) = entities let mut entity = entities
.get_mut(id) .get_mut(entity)
.expect("missing entity in partition cell"); .expect("missing entity in partition cell");
if despawned.is_some() {
continue;
}
let start = cell.packet_buf.len(); let start = cell.packet_buf.len();
let writer = PacketWriter::new( let writer = PacketWriter::new(
@ -591,11 +619,121 @@ fn update_instances_pre_client(
&mut scratch_2, &mut scratch_2,
); );
entity.write_update_packets(writer, &mut scratch_1); entity.write_update_packets(writer);
let end = cell.packet_buf.len(); 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)] #[cfg(debug_assertions)]
fn check_instance_invariants(instances: Query<&Instance>, entities: Query<&McEntity>) { fn check_instance_invariants(instances: Query<&Instance>, entities: Query<(), With<EntityKind>>) {
for instance in &instances { for instance in &instances {
for (pos, cell) in &instance.partition { for (pos, cell) in &instance.partition {
for &id in &cell.entities { for &id in &cell.entities {

View file

@ -22,7 +22,6 @@
)] )]
#![allow(clippy::type_complexity)] // ECS queries are often complicated. #![allow(clippy::type_complexity)] // ECS queries are often complicated.
use bevy_ecs::prelude::*;
pub use { pub use {
anyhow, async_trait, bevy_app, bevy_ecs, uuid, valence_nbt as nbt, valence_protocol as protocol, 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, AsyncCallbacks, ConnectionMode, PlayerSampleEntry, ServerListPing, ServerPlugin,
}; };
pub use dimension::{Dimension, DimensionId}; pub use dimension::{Dimension, DimensionId};
pub use entity::{ pub use entity::{EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw};
EntityAnimation, EntityKind, EntityStatus, McEntity, McEntityManager, TrackedData,
};
pub use glam::DVec3; pub use glam::DVec3;
pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance}; pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance};
pub use inventory::{Inventory, InventoryKind, OpenInventory}; pub use inventory::{Inventory, InventoryKind, OpenInventory};
@ -79,8 +76,3 @@ pub mod prelude {
use super::*; 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);

View file

@ -1,5 +1,7 @@
pub use glam::*; pub use glam::*;
use crate::config::DEFAULT_TPS;
/// An axis-aligned bounding box. `min` is expected to be <= `max` /// An axis-aligned bounding box. `min` is expected to be <= `max`
/// componentwise. /// componentwise.
#[derive(Copy, Clone, PartialEq, Default, Debug)] #[derive(Copy, Clone, PartialEq, Default, Debug)]
@ -18,6 +20,7 @@ impl Aabb {
} }
} }
#[allow(dead_code)]
pub(crate) fn from_bottom_size(bottom: impl Into<DVec3>, size: impl Into<DVec3>) -> Self { pub(crate) fn from_bottom_size(bottom: impl Into<DVec3>, size: impl Into<DVec3>) -> Self {
let bottom = bottom.into(); let bottom = bottom.into();
let size = size.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)
}

View file

@ -21,7 +21,7 @@ use bevy_ecs::prelude::*;
use valence_protocol::packet::s2c::play::game_state_change::GameEventKind; use valence_protocol::packet::s2c::play::game_state_change::GameEventKind;
use valence_protocol::packet::s2c::play::GameStateChangeS2c; use valence_protocol::packet::s2c::play::GameStateChangeS2c;
use crate::instance::UpdateInstancesPreClientSet; use crate::instance::WriteUpdatePacketsToInstancesSet;
use crate::packet::WritePacket; use crate::packet::WritePacket;
use crate::prelude::*; use crate::prelude::*;
@ -217,16 +217,14 @@ impl Plugin for WeatherPlugin {
app.configure_set( app.configure_set(
UpdateWeatherPerInstanceSet UpdateWeatherPerInstanceSet
.in_base_set(CoreSet::PostUpdate) .in_base_set(CoreSet::PostUpdate)
.before(UpdateInstancesPreClientSet), .before(WriteUpdatePacketsToInstancesSet),
); )
.configure_set(
app.configure_set(
UpdateWeatherPerClientSet UpdateWeatherPerClientSet
.in_base_set(CoreSet::PostUpdate) .in_base_set(CoreSet::PostUpdate)
.before(FlushPacketsSet), .before(FlushPacketsSet),
); )
.add_systems(
app.add_systems(
( (
handle_rain_begin_per_instance, handle_rain_begin_per_instance,
handle_rain_change_per_instance, handle_rain_change_per_instance,
@ -237,9 +235,8 @@ impl Plugin for WeatherPlugin {
.chain() .chain()
.in_set(UpdateWeatherPerInstanceSet) .in_set(UpdateWeatherPerInstanceSet)
.before(UpdateWeatherPerClientSet), .before(UpdateWeatherPerClientSet),
); )
.add_systems(
app.add_systems(
( (
handle_rain_begin_per_client, handle_rain_begin_per_client,
handle_rain_change_per_client, handle_rain_change_per_client,
@ -249,9 +246,12 @@ impl Plugin for WeatherPlugin {
) )
.chain() .chain()
.in_set(UpdateWeatherPerClientSet), .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));
} }
} }

View file

@ -21,7 +21,7 @@ valence_nbt = { version = "0.5.0", path = "../valence_nbt" }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
bevy_ecs = "0.10" bevy_ecs = "0.10"
clap = "4.1.4" clap = { version = "4.1.4", features = ["derive"] }
criterion = "0.4.0" criterion = "0.4.0"
flume = "0.10.14" flume = "0.10.14"
fs_extra = "1.2.0" fs_extra = "1.2.0"

View file

@ -7,8 +7,8 @@ use clap::Parser;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use tracing::warn; use tracing::warn;
use valence::bevy_app::AppExit; use valence::bevy_app::AppExit;
use valence::client::despawn_disconnected_clients; use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::client::event::default_event_handler; use valence::entity::player::PlayerBundle;
use valence::prelude::*; use valence::prelude::*;
use valence_anvil::{AnvilChunk, AnvilWorld}; use valence_anvil::{AnvilChunk, AnvilWorld};
@ -90,28 +90,20 @@ fn setup(world: &mut World) {
} }
fn init_clients( fn init_clients(
mut clients: Query< mut clients: Query<(Entity, &mut GameMode, &mut IsFlat, &UniqueId), Added<Client>>,
(
&mut Position,
&mut Location,
&mut GameMode,
&mut IsFlat,
&UniqueId,
),
Added<Client>,
>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (mut pos, mut loc, mut game_mode, mut is_flat, uuid) in &mut clients { for (entity, mut game_mode, mut is_flat, uuid) in &mut clients {
let instance = instances.single();
pos.0 = SPAWN_POS;
loc.0 = instance;
*game_mode = GameMode::Creative; *game_mode = GameMode::Creative;
is_flat.0 = true; 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()
});
} }
} }

View file

@ -504,7 +504,7 @@ mod tests {
use crate::ident::Ident; use crate::ident::Ident;
use crate::item::{ItemKind, ItemStack}; use crate::item::{ItemKind, ItemStack};
use crate::text::{Text, TextFormat}; use crate::text::{Text, TextFormat};
use crate::tracked_data::PaintingKind; use crate::types::Hand;
use crate::var_long::VarLong; use crate::var_long::VarLong;
use crate::Decode; use crate::Decode;
@ -520,7 +520,7 @@ mod tests {
d: f32, d: f32,
e: f64, e: f64,
f: BlockPos, f: BlockPos,
g: PaintingKind, g: Hand,
h: Ident<&'a str>, h: Ident<&'a str>,
i: Option<ItemStack>, i: Option<ItemStack>,
j: Text, j: Text,
@ -540,7 +540,7 @@ mod tests {
d: 5.001, d: 5.001,
e: 1e10, e: 1e10,
f: BlockPos::new(1, 2, 3), f: BlockPos::new(1, 2, 3),
g: PaintingKind::DonkeyKong, g: Hand::Off,
h: Ident::new("minecraft:whatever").unwrap(), h: Ident::new("minecraft:whatever").unwrap(),
i: Some(ItemStack::new(ItemKind::WoodenSword, 12, None)), i: Some(ItemStack::new(ItemKind::WoodenSword, 12, None)),
j: "my ".into_text() + "fancy".italic() + " text", j: "my ".into_text() + "fancy".italic() + " text",

View file

@ -40,6 +40,12 @@ impl ItemStack {
} }
} }
impl Default for ItemStack {
fn default() -> Self {
Self::new(ItemKind::Air, 1, None)
}
}
impl Encode for Option<ItemStack> { impl Encode for Option<ItemStack> {
fn encode(&self, w: impl Write) -> Result<()> { fn encode(&self, w: impl Write) -> Result<()> {
self.as_ref().encode(w) self.as_ref().encode(w)

View file

@ -94,7 +94,6 @@ pub mod packet;
pub mod raw; pub mod raw;
pub mod sound; pub mod sound;
pub mod text; pub mod text;
pub mod tracked_data;
pub mod translation_key; pub mod translation_key;
pub mod types; pub mod types;
pub mod var_int; pub mod var_int;

View file

@ -9,7 +9,7 @@ pub struct ClientSettingsC2s<'a> {
pub chat_mode: ChatMode, pub chat_mode: ChatMode,
pub chat_colors: bool, pub chat_colors: bool,
pub displayed_skin_parts: DisplayedSkinParts, pub displayed_skin_parts: DisplayedSkinParts,
pub main_hand: MainHand, pub main_arm: MainArm,
pub enable_text_filtering: bool, pub enable_text_filtering: bool,
pub allow_server_listings: bool, pub allow_server_listings: bool,
} }
@ -35,7 +35,7 @@ pub struct DisplayedSkinParts {
} }
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
pub enum MainHand { pub enum MainArm {
Left, Left,
#[default] #[default]
Right, Right,

View file

@ -19,7 +19,7 @@ pub struct ParticleS2c<'a> {
pub count: i32, pub count: i32,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum Particle { pub enum Particle {
AmbientEntityEffect, AmbientEntityEffect,
AngryVillager, AngryVillager,
@ -232,6 +232,125 @@ impl Particle {
Particle::Scrape => 91, Particle::Scrape => 91,
} }
} }
/// Decodes the particle assuming the given particle ID.
pub fn decode_with_id(particle_id: i32, r: &mut &[u8]) -> anyhow::Result<Self> {
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<'_> { impl Encode for ParticleS2c<'_> {
@ -243,7 +362,34 @@ impl Encode for ParticleS2c<'_> {
self.max_speed.encode(&mut w)?; self.max_speed.encode(&mut w)?;
self.count.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<Self> {
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::Block(block_state) => block_state.encode(w),
Particle::BlockMarker(block_state) => block_state.encode(w), Particle::BlockMarker(block_state) => block_state.encode(w),
Particle::Dust { rgb, scale } => { 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<Self> {
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,
})
}
}

View file

@ -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<u32>>) -> Option<Self> {
match n.into() {
None => Some(Self(0)),
Some(u32::MAX) => None,
Some(n) => Some(Self(n + 1)),
}
}
pub fn get(self) -> Option<u32> {
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,
}

View file

@ -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 { loop {
while !dec.has_next_packet()? { 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 })?; enc.append_packet(&KeepAliveC2s { id: p.id })?;
conn.write_all(&enc.take()).await?; conn.write_all(&enc.take()).await?;
println!("{sess_name} keep alive")
} }
S2cPlayPacket::PlayerPositionLookS2c(p) => { S2cPlayPacket::PlayerPositionLookS2c(p) => {
@ -145,8 +143,6 @@ pub async fn make_session<'a>(params: &SessionParams<'a>) -> anyhow::Result<()>
})?; })?;
conn.write_all(&enc.take()).await?; conn.write_all(&enc.take()).await?;
println!("{sess_name} spawned")
} }
_ => (), _ => (),
}, },

File diff suppressed because it is too large Load diff

View file

@ -29,116 +29,6 @@ import java.lang.reflect.ParameterizedType;
import java.util.*; import java.util.*;
public class Entities implements Main.Extractor { public class Entities implements Main.Extractor {
private final static Map<String, Bit[]> 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() { public Entities() {
} }
@ -321,15 +211,6 @@ public class Entities implements Main.Extractor {
fieldJson.addProperty("type", data.left()); fieldJson.addProperty("type", data.left());
fieldJson.add("default_value", data.right()); 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); fieldsJson.add(fieldJson);
} }
} }