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/classes
/extractor/run
/extractor/bin
rust-mc-bot
.asset_cache/
/velocity
flamegraph*.svg
perf.data
perf.data.old
/graph.gv

View file

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

View file

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

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use heck::ToPascalCase;
use proc_macro2::{Ident, TokenStream};
use heck::{ToPascalCase, ToShoutySnakeCase, ToSnakeCase};
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
@ -27,7 +27,6 @@ struct Field {
index: u8,
#[serde(flatten)]
default_value: Value,
bits: Vec<Bit>,
}
#[derive(Deserialize, Clone, Debug)]
@ -75,14 +74,8 @@ struct BlockPos {
z: i32,
}
#[derive(Deserialize, Clone, Debug)]
struct Bit {
name: String,
index: u8,
}
impl Value {
pub fn type_id(&self) -> i32 {
pub fn type_id(&self) -> u8 {
match self {
Value::Byte(_) => 0,
Value::Integer(_) => 1,
@ -117,46 +110,26 @@ impl Value {
Value::Integer(_) => quote!(i32),
Value::Long(_) => quote!(i64),
Value::Float(_) => quote!(f32),
Value::String(_) => quote!(Box<str>),
Value::TextComponent(_) => quote!(Text),
Value::OptionalTextComponent(_) => quote!(Option<Text>),
Value::ItemStack(_) => quote!(()), // TODO
Value::String(_) => quote!(String),
Value::TextComponent(_) => quote!(crate::protocol::text::Text),
Value::OptionalTextComponent(_) => quote!(Option<crate::protocol::text::Text>),
Value::ItemStack(_) => quote!(crate::protocol::item::ItemStack),
Value::Boolean(_) => quote!(bool),
Value::Rotation { .. } => quote!(EulerAngle),
Value::BlockPos(_) => quote!(BlockPos),
Value::OptionalBlockPos(_) => quote!(Option<BlockPos>),
Value::Facing(_) => quote!(Facing),
Value::OptionalUuid(_) => quote!(Option<Uuid>),
Value::OptionalBlockState(_) => quote!(BlockState),
Value::NbtCompound(_) => quote!(valence_nbt::Compound),
Value::Particle(_) => quote!(Particle),
Value::VillagerData { .. } => quote!(VillagerData),
Value::OptionalInt(_) => quote!(OptionalInt),
Value::EntityPose(_) => quote!(Pose),
Value::CatVariant(_) => quote!(CatKind),
Value::FrogVariant(_) => quote!(FrogKind),
Value::Rotation { .. } => quote!(crate::entity::EulerAngle),
Value::BlockPos(_) => quote!(crate::protocol::block_pos::BlockPos),
Value::OptionalBlockPos(_) => quote!(Option<crate::protocol::block_pos::BlockPos>),
Value::Facing(_) => quote!(crate::protocol::types::Direction),
Value::OptionalUuid(_) => quote!(Option<::uuid::Uuid>),
Value::OptionalBlockState(_) => quote!(crate::protocol::block::BlockState),
Value::NbtCompound(_) => quote!(crate::nbt::Compound),
Value::Particle(_) => quote!(crate::protocol::packet::s2c::play::particle::Particle),
Value::VillagerData { .. } => quote!(crate::entity::VillagerData),
Value::OptionalInt(_) => quote!(Option<i32>),
Value::EntityPose(_) => quote!(crate::entity::Pose),
Value::CatVariant(_) => quote!(crate::entity::CatKind),
Value::FrogVariant(_) => quote!(crate::entity::FrogKind),
Value::OptionalGlobalPos(_) => quote!(()), // TODO
Value::PaintingVariant(_) => quote!(PaintingKind),
}
}
pub fn getter_return_type(&self) -> TokenStream {
match self {
Value::String(_) => quote!(&str),
Value::TextComponent(_) => quote!(&Text),
Value::OptionalTextComponent(_) => quote!(Option<&Text>),
Value::NbtCompound(_) => quote!(&valence_nbt::Compound),
_ => self.field_type(),
}
}
pub fn getter_return_expr(&self, field_name: &Ident) -> TokenStream {
match self {
Value::String(_) | Value::TextComponent(_) | Value::NbtCompound(_) => {
quote!(&self.#field_name)
}
Value::OptionalTextComponent(_) => quote!(self.#field_name.as_ref()),
_ => quote!(self.#field_name),
Value::PaintingVariant(_) => quote!(crate::entity::PaintingKind),
}
}
@ -166,35 +139,53 @@ impl Value {
Value::Integer(i) => quote!(#i),
Value::Long(l) => quote!(#l),
Value::Float(f) => quote!(#f),
Value::String(s) => quote!(#s.to_owned().into_boxed_str()),
Value::TextComponent(_) => quote!(Text::default()), // TODO
Value::String(s) => quote!(#s.to_owned()),
Value::TextComponent(txt) => {
assert!(txt.is_empty());
quote!(crate::protocol::text::Text::default())
}
Value::OptionalTextComponent(t) => {
assert!(t.is_none());
quote!(None)
}
Value::ItemStack(_) => quote!(()), // TODO
Value::ItemStack(stack) => {
assert_eq!(stack, "1 air");
quote!(crate::protocol::item::ItemStack::default())
}
Value::Boolean(b) => quote!(#b),
Value::Rotation { pitch, yaw, roll } => quote! {
EulerAngle {
crate::entity::EulerAngle {
pitch: #pitch,
yaw: #yaw,
roll: #roll,
}
},
Value::BlockPos(BlockPos { x, y, z }) => {
quote!(BlockPos { x: #x, y: #y, z: #z })
quote!(crate::protocol::block_pos::BlockPos { x: #x, y: #y, z: #z })
}
Value::OptionalBlockPos(pos) => {
assert!(pos.is_none());
quote!(None)
}
Value::OptionalBlockPos(_) => quote!(None), // TODO
Value::Facing(f) => {
let variant = ident(f.to_pascal_case());
quote!(Facing::#variant)
quote!(crate::protocol::types::Direction::#variant)
}
Value::OptionalUuid(uuid) => {
assert!(uuid.is_none());
quote!(None)
}
Value::OptionalBlockState(bs) => {
assert!(bs.is_none());
quote!(crate::protocol::block::BlockState::default())
}
Value::NbtCompound(s) => {
assert_eq!(s, "{}");
quote!(crate::nbt::Compound::default())
}
Value::OptionalUuid(_) => quote!(None), // TODO
Value::OptionalBlockState(_) => quote!(BlockState::default()), // TODO
Value::NbtCompound(_) => quote!(valence_nbt::Compound::default()), // TODO
Value::Particle(p) => {
let variant = ident(p.to_pascal_case());
quote!(Particle::#variant)
quote!(crate::protocol::packet::s2c::play::particle::Particle::#variant)
}
Value::VillagerData {
typ,
@ -203,28 +194,34 @@ impl Value {
} => {
let typ = ident(typ.to_pascal_case());
let profession = ident(profession.to_pascal_case());
quote!(VillagerData::new(VillagerKind::#typ, VillagerProfession::#profession, #level))
quote! {
crate::entity::VillagerData {
kind: crate::entity::VillagerKind::#typ,
profession: crate::entity::VillagerProfession::#profession,
level: #level,
}
}
}
Value::OptionalInt(i) => {
assert!(i.is_none());
quote!(OptionalInt::default())
quote!(None)
}
Value::EntityPose(p) => {
let variant = ident(p.to_pascal_case());
quote!(Pose::#variant)
quote!(crate::entity::Pose::#variant)
}
Value::CatVariant(c) => {
let variant = ident(c.to_pascal_case());
quote!(CatKind::#variant)
quote!(crate::entity::CatKind::#variant)
}
Value::FrogVariant(f) => {
let variant = ident(f.to_pascal_case());
quote!(FrogKind::#variant)
quote!(crate::entity::FrogKind::#variant)
}
Value::OptionalGlobalPos(_) => quote!(()),
Value::PaintingVariant(p) => {
let variant = ident(p.to_pascal_case());
quote!(PaintingKind::#variant)
quote!(crate::entity::PaintingKind::#variant)
}
}
}
@ -232,7 +229,9 @@ impl Value {
pub fn encodable_expr(&self, self_lvalue: TokenStream) -> TokenStream {
match self {
Value::Integer(_) => quote!(VarInt(#self_lvalue)),
_ => self_lvalue,
Value::OptionalInt(_) => quote!(OptionalInt(#self_lvalue)),
Value::ItemStack(_) => quote!(Some(&#self_lvalue)),
_ => quote!(&#self_lvalue),
}
}
}
@ -240,279 +239,320 @@ impl Value {
type Entities = BTreeMap<String, Entity>;
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 =
serde_json::from_str::<EntityData>(include_str!("../../../extracted/entity_data.json"))?
.types;
let concrete_entities = entities
.clone()
let entities: Entities =
serde_json::from_str::<Entities>(include_str!("../../../extracted/entities.json"))?
.into_iter()
.filter(|(_, v)| v.typ.is_some())
.collect::<Entities>();
let entity_kind_variants = concrete_entities.iter().map(|(name, e)| {
let name = ident(name);
let id = entity_types[e.typ.as_ref().unwrap()] as isize;
quote! {
#name = #id,
}
});
let concrete_entity_names = concrete_entities.keys().map(ident).collect::<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
}
.map(|(entity_name, mut entity)| {
let change_name = |mut name: String| {
if let Some(stripped) = name.strip_suffix("Entity") {
if !stripped.is_empty() {
name = stripped.into();
}
}
name
};
entity.parent = entity.parent.map(change_name);
(change_name(entity_name), entity)
})
.collect::<TokenStream>()
.collect();
let mut entity_kind_consts = TokenStream::new();
let mut entity_kind_fmt_args = TokenStream::new();
let mut translation_key_arms = TokenStream::new();
let mut modules = TokenStream::new();
let mut systems = TokenStream::new();
let mut system_names = vec![];
for (entity_name, entity) in entities.clone() {
let entity_name_ident = ident(&entity_name);
let shouty_entity_name = entity_name.to_shouty_snake_case();
let shouty_entity_name_ident = ident(&shouty_entity_name);
let snake_entity_name = entity_name.to_snake_case();
let snake_entity_name_ident = ident(&snake_entity_name);
let mut module_body = TokenStream::new();
if let Some(parent_name) = entity.parent {
let snake_parent_name = parent_name.to_snake_case();
let module_doc =
format!("Parent class: [`{snake_parent_name}`][super::{snake_parent_name}].");
module_body.extend([quote! {
#![doc = #module_doc]
}]);
}
// Is this a concrete entity type?
if let Some(entity_type) = entity.typ {
let entity_type_id = entity_types[&entity_type];
entity_kind_consts.extend([quote! {
pub const #shouty_entity_name_ident: EntityKind = EntityKind(#entity_type_id);
}]);
entity_kind_fmt_args.extend([quote! {
EntityKind::#shouty_entity_name_ident => write!(f, "{} ({})", #entity_type_id, #shouty_entity_name),
}]);
let translation_key_expr = if let Some(key) = entity.translation_key {
quote!(Some(#key))
} else {
let getter_name = ident(format!("get_{}", &field.name));
let setter_name = ident(format!("set_{}", &field.name));
let getter_return_type = field.default_value.getter_return_type();
let getter_return_expr = field.default_value.getter_return_expr(&field_name);
quote!(None)
};
quote! {
pub fn #getter_name(&self) -> #getter_return_type {
#getter_return_expr
translation_key_arms.extend([quote! {
EntityKind::#shouty_entity_name_ident => #translation_key_expr,
}]);
// Create bundle type.
let mut bundle_fields = TokenStream::new();
let mut bundle_init_fields = TokenStream::new();
for marker_or_field in collect_bundle_fields(&entity_name, &entities) {
match marker_or_field {
MarkerOrField::Marker { entity_name } => {
let snake_entity_name_ident = ident(entity_name.to_snake_case());
let pascal_entity_name_ident = ident(entity_name.to_pascal_case());
bundle_fields.extend([quote! {
pub #snake_entity_name_ident: super::#snake_entity_name_ident::#pascal_entity_name_ident,
}]);
bundle_init_fields.extend([quote! {
#snake_entity_name_ident: Default::default(),
}]);
}
MarkerOrField::Field { entity_name, field } => {
let snake_field_name = field.name.to_snake_case();
let pascal_field_name = field.name.to_pascal_case();
let pascal_field_name_ident = ident(&pascal_field_name);
let snake_entity_name = entity_name.to_snake_case();
let snake_entity_name_ident = ident(&snake_entity_name);
let field_name_ident =
ident(format!("{snake_entity_name}_{snake_field_name}"));
bundle_fields.extend([quote! {
pub #field_name_ident: super::#snake_entity_name_ident::#pascal_field_name_ident,
}]);
bundle_init_fields.extend([quote! {
#field_name_ident: Default::default(),
}]);
}
}
}
pub fn #setter_name(&mut self, #field_name: impl Into<#field_type>) {
let #field_name = #field_name.into();
if self.#field_name != #field_name {
self.__modified_flags |= 1 << #field_index as #modified_flags_type;
self.#field_name = #field_name;
}
}
}
}
});
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,
}]);
let initial_tracked_data_stmts = fields.iter().map(|&field| {
let field_name = ident(&field.name);
let field_index = field.index;
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));
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(),
}]);
quote! {
if self.#field_name != (#default_expr) {
data.push(#field_index);
VarInt(#type_id).encode(&mut *data).unwrap();
#encodable.encode(&mut *data).unwrap();
}
}
});
let bundle_name_ident = ident(format!("{entity_name}Bundle"));
let bundle_doc =
format!("The bundle of components for spawning `{snake_entity_name}` entities.");
let updated_tracked_data_stmts = fields.iter().map(|&field| {
let field_name = ident(&field.name);
let field_index = field.index;
let type_id = field.default_value.type_id();
let encodable = field.default_value.encodable_expr(quote!(self.#field_name));
quote! {
if (self.__modified_flags >> #field_index as #modified_flags_type) & 1 == 1 {
data.push(#field_index);
VarInt(#type_id).encode(&mut *data).unwrap();
#encodable.encode(&mut *data).unwrap();
}
}
});
quote! {
pub struct #struct_name {
/// Contains a set bit for every modified field.
__modified_flags: #modified_flags_type,
#(#struct_fields)*
module_body.extend([quote! {
#[doc = #bundle_doc]
#[derive(bevy_ecs::bundle::Bundle, Debug)]
pub struct #bundle_name_ident {
#bundle_fields
}
impl #struct_name {
pub(crate) fn new() -> Self {
impl Default for #bundle_name_ident {
fn default() -> Self {
Self {
__modified_flags: 0,
#(#field_initializers)*
#bundle_init_fields
}
}
}
}]);
}
pub(crate) fn initial_tracked_data(&self, data: &mut Vec<u8>) {
#(#initial_tracked_data_stmts)*
for field in &entity.fields {
let pascal_field_name_ident = ident(field.name.to_pascal_case());
let snake_field_name = field.name.to_snake_case();
let inner_type = field.default_value.field_type();
let default_expr = field.default_value.default_expr();
module_body.extend([quote! {
#[derive(bevy_ecs::component::Component, PartialEq, Clone, Debug)]
pub struct #pascal_field_name_ident(pub #inner_type);
#[allow(clippy::derivable_impls)]
impl Default for #pascal_field_name_ident {
fn default() -> Self {
Self(#default_expr)
}
}
}]);
let system_name_ident = ident(format!("update_{snake_entity_name}_{snake_field_name}"));
let component_path = quote!(#snake_entity_name_ident::#pascal_field_name_ident);
system_names.push(quote!(#system_name_ident));
let data_index = field.index;
let data_type = field.default_value.type_id();
let encodable_expr = field.default_value.encodable_expr(quote!(value.0));
systems.extend([quote! {
#[allow(clippy::needless_borrow)]
fn #system_name_ident(
mut query: Query<(&#component_path, &mut TrackedData), Changed<#component_path>>
) {
for (value, mut tracked_data) in &mut query {
if *value == Default::default() {
tracked_data.remove_init_value(#data_index);
} else {
tracked_data.insert_init_value(#data_index, #data_type, #encodable_expr);
}
pub(crate) fn updated_tracked_data(&self, data: &mut Vec<u8>) {
if self.__modified_flags != 0 {
#(#updated_tracked_data_stmts)*
if !tracked_data.is_added() {
tracked_data.append_update_value(#data_index, #data_type, #encodable_expr);
}
}
}
}]);
}
pub(crate) fn clear_modifications(&mut self) {
self.__modified_flags = 0;
}
let marker_doc = format!("Marker component for `{snake_entity_name}` entities.");
#(#getter_setters)*
}
}
});
module_body.extend([quote! {
#[doc = #marker_doc]
#[derive(bevy_ecs::component::Component, Copy, Clone, Default, Debug)]
pub struct #entity_name_ident;
}]);
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,
modules.extend([quote! {
#[allow(clippy::module_inception)]
pub mod #snake_entity_name_ident {
#module_body
}
}]);
}
});
Ok(quote! {
/// Contains a variant for each concrete entity type.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum EntityKind {
#(#entity_kind_variants)*
}
#modules
/// Identifies the type of an entity.
/// As a component, the entity kind should not be modified.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct EntityKind(i32);
impl EntityKind {
pub fn translation_key(self) -> &'static str {
#entity_kind_consts
pub const fn new(inner: i32) -> Self {
Self(inner)
}
pub const fn get(self) -> i32 {
self.0
}
pub const fn translation_key(self) -> Option<&'static str> {
match self {
#(#translation_key_arms)*
#translation_key_arms
_ => None,
}
}
}
pub enum TrackedData {
#(#concrete_entity_names(#concrete_entity_names),)*
}
impl TrackedData {
pub(super) fn new(kind: EntityKind) -> Self {
match kind {
#(EntityKind::#concrete_entity_names => Self::#concrete_entity_names(#concrete_entity_names::new()),)*
}
}
pub fn kind(&self) -> EntityKind {
match self {
#(Self::#concrete_entity_names(_) => EntityKind::#concrete_entity_names,)*
}
}
pub(super) fn write_initial_tracked_data(&self, buf: &mut Vec<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(),)*
impl std::fmt::Debug for EntityKind {
#[allow(clippy::write_literal)]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
#entity_kind_fmt_args
EntityKind(other) => write!(f, "{other}"),
}
}
}
#(#concrete_entity_structs)*
fn add_tracked_data_systems(app: &mut App) {
#systems
#(
app.add_system(
#system_names.before(WriteUpdatePacketsToInstancesSet).in_base_set(CoreSet::PostUpdate)
);
)*
}
})
}
fn collect_all_fields<'a>(entity_name: &str, entities: &'a Entities) -> Vec<&'a Field> {
fn rec<'a>(entity_name: &str, entities: &'a Entities, fields: &mut Vec<&'a Field>) {
enum MarkerOrField<'a> {
Marker {
entity_name: &'a str,
},
Field {
entity_name: &'a str,
field: &'a Field,
},
}
fn collect_bundle_fields<'a>(
mut entity_name: &'a str,
entities: &'a Entities,
) -> Vec<MarkerOrField<'a>> {
let mut res = vec![];
loop {
let e = &entities[entity_name];
fields.extend(&e.fields);
res.push(MarkerOrField::Marker { entity_name });
res.extend(
e.fields
.iter()
.map(|field| MarkerOrField::Field { entity_name, field }),
);
if let Some(parent) = &e.parent {
rec(parent, entities, fields);
entity_name = parent;
} else {
break;
}
}
let mut fields = vec![];
rec(entity_name, entities, &mut fields);
fields.sort_by_key(|f| f.index);
fields
res
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,8 +5,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
use rand::seq::SliceRandom;
use rand::Rng;
use valence::client::despawn_disconnected_clients;
use valence::client::event::default_event_handler;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::entity::player::PlayerBundle;
use valence::prelude::*;
use valence::protocol::packet::s2c::play::TitleFadeS2c;
use valence::protocol::sound::Sound;
@ -52,23 +52,12 @@ struct GameState {
}
fn init_clients(
mut clients: Query<
(
Entity,
&mut Client,
&UniqueId,
&mut IsFlat,
&mut Location,
&mut GameMode,
),
Added<Client>,
>,
mut clients: Query<(Entity, &mut Client, &UniqueId, &mut IsFlat, &mut GameMode), Added<Client>>,
server: Res<Server>,
mut commands: Commands,
) {
for (entity, mut client, uuid, mut is_flat, mut loc, mut game_mode) in clients.iter_mut() {
for (entity, mut client, uuid, mut is_flat, mut game_mode) in clients.iter_mut() {
is_flat.0 = true;
loc.0 = entity;
*game_mode = GameMode::Adventure;
client.send_message("Welcome to epic infinite parkour game!".italic());
@ -82,9 +71,13 @@ fn init_clients(
let instance = server.new_instance(DimensionId::default());
let mcentity = McEntity::with_uuid(EntityKind::Player, entity, uuid.0);
let player = PlayerBundle {
location: Location(entity),
uuid: *uuid,
..Default::default()
};
commands.entity(entity).insert((state, instance, mcentity));
commands.entity(entity).insert((state, instance, player));
}
}

View file

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

View file

@ -1,7 +1,7 @@
#![allow(clippy::type_complexity)]
use rand::Rng;
use valence::client::event::default_event_handler;
use valence::client::{default_event_handler, despawn_disconnected_clients};
use valence::player_list::Entry;
use valence::prelude::*;

View file

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

View file

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

View file

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

View file

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

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::packet::c2s::play::click_slot::{ClickMode, Slot};
use valence_protocol::packet::c2s::play::client_command::Action as ClientCommandAction;
use valence_protocol::packet::c2s::play::client_settings::{
ChatMode, DisplayedSkinParts, MainHand,
};
use valence_protocol::packet::c2s::play::client_settings::{ChatMode, DisplayedSkinParts, MainArm};
use valence_protocol::packet::c2s::play::player_action::Action as PlayerAction;
use valence_protocol::packet::c2s::play::player_interact::Interaction;
use valence_protocol::packet::c2s::play::recipe_category_options::RecipeBookId;
@ -30,16 +28,13 @@ use valence_protocol::packet::c2s::play::{
AdvancementTabC2s, ClientStatusC2s, ResourcePackStatusC2s, UpdatePlayerAbilitiesC2s,
};
use valence_protocol::packet::C2sPlayPacket;
use valence_protocol::tracked_data::Pose;
use valence_protocol::types::{Difficulty, Direction, Hand};
use super::{
CursorItem, KeepaliveState, PlayerActionSequence, PlayerInventoryState, TeleportState,
ViewDistance,
};
use crate::client::Client;
use crate::component::{Look, OnGround, Ping, Position};
use crate::entity::{EntityAnimation, EntityKind, McEntity, TrackedData};
use crate::inventory::Inventory;
#[derive(Clone, Debug)]
@ -98,7 +93,7 @@ pub struct ClientSettings {
/// `true` if the client has chat colors enabled, `false` otherwise.
pub chat_colors: bool,
pub displayed_skin_parts: DisplayedSkinParts,
pub main_hand: MainHand,
pub main_arm: MainArm,
pub enable_text_filtering: bool,
pub allow_server_listings: bool,
}
@ -829,7 +824,7 @@ fn handle_one_packet(
chat_mode: p.chat_mode,
chat_colors: p.chat_colors,
displayed_skin_parts: p.displayed_skin_parts,
main_hand: p.main_hand,
main_arm: p.main_arm,
enable_text_filtering: p.enable_text_filtering,
allow_server_listings: p.allow_server_listings,
});
@ -1396,117 +1391,3 @@ fn handle_one_packet(
Ok(true)
}
/// The default event handler system which handles client events in a
/// reasonable default way.
///
/// For instance, movement events are handled by changing the entity's
/// position/rotation to match the received movement, crouching makes the
/// entity crouch, etc.
///
/// This system's primary purpose is to reduce boilerplate code in the
/// examples, but it can be used as a quick way to get started in your own
/// code. The precise behavior of this system is left unspecified and
/// is subject to change.
///
/// This system must be scheduled to run in the
/// [`EventLoopSchedule`]. Otherwise, it may
/// not function correctly.
#[allow(clippy::too_many_arguments)]
pub fn default_event_handler(
mut clients: Query<(&mut Client, Option<&mut McEntity>, &mut ViewDistance)>,
mut update_settings: EventReader<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::util::{from_yaw_and_pitch, to_yaw_and_pitch};
use crate::view::ChunkPos;
use crate::NULL_ENTITY;
/// A [`Component`] for marking entities that should be despawned at the end of
/// the tick.
///
/// In Valence, some built-in components such as [`McEntity`] are not allowed to
/// In Valence, some entities such as [Minecraft entities] are not allowed to
/// be removed from the [`World`] directly. Instead, you must give the entities
/// you wish to despawn the `Despawned` component. At the end of the tick,
/// Valence will despawn all entities with this component for you.
@ -23,13 +22,25 @@ use crate::NULL_ENTITY;
/// It is legal to remove components or delete entities that Valence does not
/// know about at any time.
///
/// [`McEntity`]: crate::entity::McEntity
/// [Minecraft entities]: crate::entity
#[derive(Component, Copy, Clone, Default, PartialEq, Eq, Debug)]
pub struct Despawned;
#[derive(Component, Default, Clone, PartialEq, Eq, Debug)]
/// The universally unique identifier of an entity. Component wrapper for a
/// [`Uuid`].
///
/// This component is expected to remain _unique_ and _constant_ during the
/// lifetime of the entity. The [`Default`] impl generates a new random UUID.
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
pub struct UniqueId(pub Uuid);
/// Generates a new random UUID.
impl Default for UniqueId {
fn default() -> Self {
Self(Uuid::from_bytes(rand::random()))
}
}
#[derive(Component, Clone, PartialEq, Eq, Debug)]
pub struct Username(pub String);
@ -39,7 +50,7 @@ impl fmt::Display for Username {
}
}
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
pub struct Properties(pub Vec<Property>);
impl Properties {
@ -54,7 +65,7 @@ impl Properties {
}
}
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Default)]
#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)]
pub enum GameMode {
#[default]
@ -105,10 +116,13 @@ pub struct Location(pub Entity);
impl Default for Location {
fn default() -> Self {
Self(NULL_ENTITY)
Self(Entity::PLACEHOLDER)
}
}
/// The value of [`Location`] from the end of the previous tick.
///
/// **NOTE**: You should not modify this component after the entity is spawned.
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
pub struct OldLocation(Entity);
@ -124,7 +138,7 @@ impl OldLocation {
impl Default for OldLocation {
fn default() -> Self {
Self(NULL_ENTITY)
Self(Entity::PLACEHOLDER)
}
}
@ -132,11 +146,15 @@ impl Default for OldLocation {
pub struct Position(pub DVec3);
impl Position {
pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos.into())
}
pub fn chunk_pos(&self) -> ChunkPos {
ChunkPos::from_dvec3(self.0)
}
pub fn get(&self) -> DVec3 {
pub fn get(self) -> DVec3 {
self.0
}
@ -145,27 +163,26 @@ impl Position {
}
}
/// The value of [`Location`] from the end of the previous tick.
///
/// **NOTE**: You should not modify this component after the entity is spawned.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct OldPosition(DVec3);
impl OldPosition {
pub fn new(pos: DVec3) -> Self {
Self(pos)
pub fn new(pos: impl Into<DVec3>) -> Self {
Self(pos.into())
}
pub fn get(&self) -> DVec3 {
pub fn get(self) -> DVec3 {
self.0
}
pub fn chunk_pos(&self) -> ChunkPos {
pub fn chunk_pos(self) -> ChunkPos {
ChunkPos::from_dvec3(self.0)
}
}
/// Velocity in m/s.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct Velocity(pub Vec3);
/// Describes the direction an entity is looking using pitch and yaw angles.
#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
pub struct Look {
@ -176,6 +193,10 @@ pub struct Look {
}
impl Look {
pub const fn new(yaw: f32, pitch: f32) -> Self {
Self { yaw, pitch }
}
/// Gets a normalized direction vector from the yaw and pitch.
pub fn vec(&self) -> Vec3 {
from_yaw_and_pitch(self.yaw, self.pitch)

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::BTreeSet;
use std::iter::FusedIterator;
use std::mem;
use bevy_app::{CoreSet, Plugin};
use bevy_ecs::prelude::*;
use bevy_ecs::query::WorldQuery;
pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk};
pub use chunk_entry::*;
use glam::{DVec3, Vec3};
@ -12,19 +14,29 @@ use num::integer::div_ceil;
use rustc_hash::FxHashMap;
use valence_protocol::array::LengthPrefixedArray;
use valence_protocol::block_pos::BlockPos;
use valence_protocol::byte_angle::ByteAngle;
use valence_protocol::packet::s2c::play::particle::Particle;
use valence_protocol::packet::s2c::play::{OverlayMessageS2c, ParticleS2c, PlaySoundS2c};
use valence_protocol::packet::s2c::play::{
EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntityStatusS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, MoveRelativeS2c, OverlayMessageS2c,
ParticleS2c, PlaySoundS2c, RotateAndMoveRelativeS2c, RotateS2c,
};
use valence_protocol::sound::Sound;
use valence_protocol::text::Text;
use valence_protocol::types::SoundCategory;
use valence_protocol::var_int::VarInt;
use valence_protocol::Packet;
use crate::component::Despawned;
use crate::client::FlushPacketsSet;
use crate::component::{Despawned, Location, Look, OldLocation, OldPosition, OnGround, Position};
use crate::dimension::DimensionId;
use crate::entity::{InitEntitiesSet, McEntity};
use crate::entity::{
EntityAnimations, EntityId, EntityKind, EntityStatuses, HeadYaw, InitEntitiesSet,
PacketByteRange, TrackedData, Velocity,
};
use crate::packet::{PacketWriter, WritePacket};
use crate::prelude::FlushPacketsSet;
use crate::server::{Server, SharedServer};
use crate::util::velocity_to_packet_units;
use crate::view::ChunkPos;
mod chunk;
@ -438,16 +450,21 @@ impl Instance {
pub(crate) struct InstancePlugin;
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct UpdateInstancesPreClientSet;
pub(crate) struct WriteUpdatePacketsToInstancesSet;
impl Plugin for InstancePlugin {
fn build(&self, app: &mut bevy_app::App) {
app.configure_set(
UpdateInstancesPreClientSet
WriteUpdatePacketsToInstancesSet
.after(InitEntitiesSet)
.in_base_set(CoreSet::PostUpdate),
)
.add_system(update_instances_pre_client.in_set(UpdateInstancesPreClientSet))
.add_system(
update_entity_cell_positions
.before(WriteUpdatePacketsToInstancesSet)
.in_base_set(CoreSet::PostUpdate),
)
.add_system(write_update_packets_to_instances.in_set(WriteUpdatePacketsToInstancesSet))
.add_system(
update_instances_post_client
.after(FlushPacketsSet)
@ -459,56 +476,63 @@ impl Plugin for InstancePlugin {
}
}
fn update_instances_pre_client(
/// Handles entities moving from one chunk to another.
fn update_entity_cell_positions(
entities: Query<
(
Entity,
&Position,
&OldPosition,
&Location,
&OldLocation,
Option<&Despawned>,
),
(With<EntityKind>, Or<(Changed<Position>, With<Despawned>)>),
>,
mut instances: Query<&mut Instance>,
mut entities: Query<(Entity, &mut McEntity, Option<&Despawned>)>,
server: Res<Server>,
) {
for (entity_id, entity, despawned) in &entities {
let pos = ChunkPos::at(entity.position().x, entity.position().z);
let old_pos = ChunkPos::at(entity.old_position().x, entity.old_position().z);
let instance = entity.instance();
let old_instance = entity.old_instance();
for (entity, pos, old_pos, loc, old_loc, despawned) in &entities {
let pos = ChunkPos::at(pos.0.x, pos.0.z);
let old_pos = ChunkPos::at(old_pos.get().x, old_pos.get().z);
if despawned.is_some() {
// Entity was deleted. Remove it from the chunk it was in, if it was in a chunk
// at all.
if let Ok(mut old_instance) = instances.get_mut(old_instance) {
if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) {
if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) {
old_cell.outgoing.push((entity_id, None));
if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity, None));
}
}
}
} else if old_instance != instance {
} else if old_loc.get() != loc.0 {
// Entity changed the instance it is in. Remove it from old cell and
// insert it in the new cell.
// TODO: skip marker entity?
if let Ok(mut old_instance) = instances.get_mut(old_instance) {
if let Ok(mut old_instance) = instances.get_mut(old_loc.get()) {
if let Some(old_cell) = old_instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) {
old_cell.outgoing.push((entity_id, None));
if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity, None));
}
}
}
if let Ok(mut instance) = instances.get_mut(instance) {
if let Ok(mut instance) = instances.get_mut(loc.0) {
match instance.partition.entry(pos) {
Entry::Occupied(oe) => {
let cell = oe.into_mut();
if cell.entities.insert(entity_id) {
cell.incoming.push((entity_id, None));
if cell.entities.insert(entity) {
cell.incoming.push((entity, None));
}
}
Entry::Vacant(ve) => {
ve.insert(PartitionCell {
chunk: None,
chunk_removed: false,
entities: BTreeSet::from([entity_id]),
incoming: vec![(entity_id, None)],
entities: BTreeSet::from([entity]),
incoming: vec![(entity, None)],
outgoing: vec![],
packet_buf: vec![],
});
@ -521,26 +545,26 @@ fn update_instances_pre_client(
// TODO: skip marker entity?
if let Ok(mut instance) = instances.get_mut(instance) {
if let Ok(mut instance) = instances.get_mut(loc.0) {
if let Some(old_cell) = instance.partition.get_mut(&old_pos) {
if old_cell.entities.remove(&entity_id) {
old_cell.outgoing.push((entity_id, Some(pos)));
if old_cell.entities.remove(&entity) {
old_cell.outgoing.push((entity, Some(pos)));
}
}
match instance.partition.entry(pos) {
Entry::Occupied(oe) => {
let cell = oe.into_mut();
if cell.entities.insert(entity_id) {
cell.incoming.push((entity_id, Some(old_pos)));
if cell.entities.insert(entity) {
cell.incoming.push((entity, Some(old_pos)));
}
}
Entry::Vacant(ve) => {
ve.insert(PartitionCell {
chunk: None,
chunk_removed: false,
entities: BTreeSet::from([entity_id]),
incoming: vec![(entity_id, Some(old_pos))],
entities: BTreeSet::from([entity]),
incoming: vec![(entity, Some(old_pos))],
outgoing: vec![],
packet_buf: vec![],
});
@ -552,7 +576,15 @@ fn update_instances_pre_client(
// we need to do.
}
}
}
/// Writes update packets from entities and chunks into each cell's packet
/// buffer.
fn write_update_packets_to_instances(
mut instances: Query<&mut Instance>,
mut entities: Query<UpdateEntityQuery, (With<EntityKind>, Without<Despawned>)>,
server: Res<Server>,
) {
let mut scratch_1 = vec![];
let mut scratch_2 = vec![];
@ -574,15 +606,11 @@ fn update_instances_pre_client(
}
// Cache entity update packets into the packet buffer of this cell.
for &id in &cell.entities {
let (_, mut entity, despawned) = entities
.get_mut(id)
for &entity in &cell.entities {
let mut entity = entities
.get_mut(entity)
.expect("missing entity in partition cell");
if despawned.is_some() {
continue;
}
let start = cell.packet_buf.len();
let writer = PacketWriter::new(
@ -591,11 +619,121 @@ fn update_instances_pre_client(
&mut scratch_2,
);
entity.write_update_packets(writer, &mut scratch_1);
entity.write_update_packets(writer);
let end = cell.packet_buf.len();
entity.self_update_range = start..end;
entity.packet_byte_range.0 = start..end;
}
}
}
}
#[derive(WorldQuery)]
#[world_query(mutable)]
struct UpdateEntityQuery {
id: &'static EntityId,
pos: &'static Position,
old_pos: &'static OldPosition,
loc: &'static Location,
old_loc: &'static OldLocation,
look: Ref<'static, Look>,
head_yaw: Ref<'static, HeadYaw>,
on_ground: &'static OnGround,
velocity: Ref<'static, Velocity>,
tracked_data: &'static TrackedData,
statuses: &'static EntityStatuses,
animations: &'static EntityAnimations,
packet_byte_range: &'static mut PacketByteRange,
}
impl UpdateEntityQueryItem<'_> {
fn write_update_packets(&self, mut writer: impl WritePacket) {
// TODO: @RJ I saw you're using UpdateEntityPosition and UpdateEntityRotation sometimes. These two packets are actually broken on the client and will erase previous position/rotation https://bugs.mojang.com/browse/MC-255263 -Moulberry
let entity_id = VarInt(self.id.get());
let position_delta = self.pos.0 - self.old_pos.get();
let needs_teleport = position_delta.abs().max_element() >= 8.0;
let changed_position = self.pos.0 != self.old_pos.get();
if changed_position && !needs_teleport && self.look.is_changed() {
writer.write_packet(&RotateAndMoveRelativeS2c {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
yaw: ByteAngle::from_degrees(self.look.yaw),
pitch: ByteAngle::from_degrees(self.look.pitch),
on_ground: self.on_ground.0,
});
} else {
if changed_position && !needs_teleport {
writer.write_packet(&MoveRelativeS2c {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
on_ground: self.on_ground.0,
});
}
if self.look.is_changed() {
writer.write_packet(&RotateS2c {
entity_id,
yaw: ByteAngle::from_degrees(self.look.yaw),
pitch: ByteAngle::from_degrees(self.look.pitch),
on_ground: self.on_ground.0,
});
}
}
if needs_teleport {
writer.write_packet(&EntityPositionS2c {
entity_id,
position: self.pos.0.to_array(),
yaw: ByteAngle::from_degrees(self.look.yaw),
pitch: ByteAngle::from_degrees(self.look.pitch),
on_ground: self.on_ground.0,
});
}
if self.velocity.is_changed() {
writer.write_packet(&EntityVelocityUpdateS2c {
entity_id,
velocity: velocity_to_packet_units(self.velocity.0),
});
}
if self.head_yaw.is_changed() {
writer.write_packet(&EntitySetHeadYawS2c {
entity_id,
head_yaw: ByteAngle::from_degrees(self.head_yaw.0),
});
}
if let Some(update_data) = self.tracked_data.update_data() {
writer.write_packet(&EntityTrackerUpdateS2c {
entity_id,
metadata: update_data.into(),
});
}
if self.statuses.0 != 0 {
for i in 0..mem::size_of_val(self.statuses) {
if (self.statuses.0 >> i) & 1 == 1 {
writer.write_packet(&EntityStatusS2c {
entity_id: entity_id.0,
entity_status: i as u8,
});
}
}
}
if self.animations.0 != 0 {
for i in 0..mem::size_of_val(self.animations) {
if (self.animations.0 >> i) & 1 == 1 {
writer.write_packet(&EntityAnimationS2c {
entity_id,
animation: i as u8,
});
}
}
}
}
@ -621,7 +759,7 @@ fn update_instances_post_client(mut instances: Query<&mut Instance>) {
}
#[cfg(debug_assertions)]
fn check_instance_invariants(instances: Query<&Instance>, entities: Query<&McEntity>) {
fn check_instance_invariants(instances: Query<&Instance>, entities: Query<(), With<EntityKind>>) {
for instance in &instances {
for (pos, cell) in &instance.partition {
for &id in &cell.entities {

View file

@ -22,7 +22,6 @@
)]
#![allow(clippy::type_complexity)] // ECS queries are often complicated.
use bevy_ecs::prelude::*;
pub use {
anyhow, async_trait, bevy_app, bevy_ecs, uuid, valence_nbt as nbt, valence_protocol as protocol,
};
@ -57,9 +56,7 @@ pub mod prelude {
AsyncCallbacks, ConnectionMode, PlayerSampleEntry, ServerListPing, ServerPlugin,
};
pub use dimension::{Dimension, DimensionId};
pub use entity::{
EntityAnimation, EntityKind, EntityStatus, McEntity, McEntityManager, TrackedData,
};
pub use entity::{EntityAnimation, EntityKind, EntityManager, EntityStatus, HeadYaw};
pub use glam::DVec3;
pub use instance::{Block, BlockMut, BlockRef, Chunk, Instance};
pub use inventory::{Inventory, InventoryKind, OpenInventory};
@ -79,8 +76,3 @@ pub mod prelude {
use super::*;
}
/// Let's pretend that [`NULL_ENTITY`] was created by spawning an entity,
/// immediately despawning it, and then stealing its [`Entity`] ID. The user
/// doesn't need to know about this.
const NULL_ENTITY: Entity = Entity::from_bits(u64::MAX);

View file

@ -1,5 +1,7 @@
pub use glam::*;
use crate::config::DEFAULT_TPS;
/// An axis-aligned bounding box. `min` is expected to be <= `max`
/// componentwise.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
@ -18,6 +20,7 @@ impl Aabb {
}
}
#[allow(dead_code)]
pub(crate) fn from_bottom_size(bottom: impl Into<DVec3>, size: impl Into<DVec3>) -> Self {
let bottom = bottom.into();
let size = size.into();
@ -110,3 +113,11 @@ mod tests {
}
}
}
#[inline]
pub(crate) fn velocity_to_packet_units(vel: Vec3) -> [i16; 3] {
// The saturating casts to i16 are desirable.
(8000.0 / DEFAULT_TPS as f32 * vel)
.to_array()
.map(|v| v as i16)
}

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

View file

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

View file

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

View file

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

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> {
fn encode(&self, w: impl Write) -> Result<()> {
self.as_ref().encode(w)

View file

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

View file

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

View file

@ -19,7 +19,7 @@ pub struct ParticleS2c<'a> {
pub count: i32,
}
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub enum Particle {
AmbientEntityEffect,
AngryVillager,
@ -232,67 +232,10 @@ impl Particle {
Particle::Scrape => 91,
}
}
}
impl Encode for ParticleS2c<'_> {
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
VarInt(self.particle.id()).encode(&mut w)?;
self.long_distance.encode(&mut w)?;
self.position.encode(&mut w)?;
self.offset.encode(&mut w)?;
self.max_speed.encode(&mut w)?;
self.count.encode(&mut w)?;
match self.particle.as_ref() {
Particle::Block(block_state) => block_state.encode(w),
Particle::BlockMarker(block_state) => block_state.encode(w),
Particle::Dust { rgb, scale } => {
rgb.encode(&mut w)?;
scale.encode(w)
}
Particle::DustColorTransition {
from_rgb,
scale,
to_rgb,
} => {
from_rgb.encode(&mut w)?;
scale.encode(&mut w)?;
to_rgb.encode(w)
}
Particle::FallingDust(block_state) => block_state.encode(w),
Particle::SculkCharge { roll } => roll.encode(w),
Particle::Item(stack) => stack.encode(w),
Particle::VibrationBlock { block_pos, ticks } => {
"block".encode(&mut w)?;
block_pos.encode(&mut w)?;
VarInt(*ticks).encode(w)
}
Particle::VibrationEntity {
entity_id,
entity_eye_height,
ticks,
} => {
"entity".encode(&mut w)?;
VarInt(*entity_id).encode(&mut w)?;
entity_eye_height.encode(&mut w)?;
VarInt(*ticks).encode(w)
}
_ => Ok(()),
}
}
}
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 {
/// 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)?),
@ -406,7 +349,34 @@ impl<'a> Decode<'a> for ParticleS2c<'a> {
90 => Particle::ElectricSpark,
91 => Particle::Scrape,
id => bail!("invalid particle ID of {id}"),
}),
})
}
}
impl Encode for ParticleS2c<'_> {
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
VarInt(self.particle.id()).encode(&mut w)?;
self.long_distance.encode(&mut w)?;
self.position.encode(&mut w)?;
self.offset.encode(&mut w)?;
self.max_speed.encode(&mut w)?;
self.count.encode(&mut w)?;
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,
@ -415,3 +385,45 @@ impl<'a> Decode<'a> for ParticleS2c<'a> {
})
}
}
/// Encodes the particle without an ID.
impl Encode for Particle {
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
match self {
Particle::Block(block_state) => block_state.encode(w),
Particle::BlockMarker(block_state) => block_state.encode(w),
Particle::Dust { rgb, scale } => {
rgb.encode(&mut w)?;
scale.encode(w)
}
Particle::DustColorTransition {
from_rgb,
scale,
to_rgb,
} => {
from_rgb.encode(&mut w)?;
scale.encode(&mut w)?;
to_rgb.encode(w)
}
Particle::FallingDust(block_state) => block_state.encode(w),
Particle::SculkCharge { roll } => roll.encode(w),
Particle::Item(stack) => stack.encode(w),
Particle::VibrationBlock { block_pos, ticks } => {
"block".encode(&mut w)?;
block_pos.encode(&mut w)?;
VarInt(*ticks).encode(w)
}
Particle::VibrationEntity {
entity_id,
entity_eye_height,
ticks,
} => {
"entity".encode(&mut w)?;
VarInt(*entity_id).encode(&mut w)?;
entity_eye_height.encode(&mut w)?;
VarInt(*ticks).encode(w)
}
_ => Ok(()),
}
}
}

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

File diff suppressed because it is too large Load diff

View file

@ -29,116 +29,6 @@ import java.lang.reflect.ParameterizedType;
import java.util.*;
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() {
}
@ -321,15 +211,6 @@ public class Entities implements Main.Extractor {
fieldJson.addProperty("type", data.left());
fieldJson.add("default_value", data.right());
var bitsJson = new JsonArray();
for (var bit : BIT_FIELDS.getOrDefault(fieldName, new Bit[]{})) {
var bitJson = new JsonObject();
bitJson.addProperty("name", bit.name);
bitJson.addProperty("index", bit.index);
bitsJson.add(bitJson);
}
fieldJson.add("bits", bitsJson);
fieldsJson.add(fieldJson);
}
}