mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
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:
parent
c9fbd1a24e
commit
4cf6e1a207
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
.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
|
||||
}
|
||||
}
|
||||
let entities: Entities =
|
||||
serde_json::from_str::<Entities>(include_str!("../../../extracted/entities.json"))?
|
||||
.into_iter()
|
||||
.map(|(entity_name, mut entity)| {
|
||||
let change_name = |mut name: String| {
|
||||
if let Some(stripped) = name.strip_suffix("Entity") {
|
||||
if !stripped.is_empty() {
|
||||
name = stripped.into();
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>()
|
||||
}
|
||||
|
||||
name
|
||||
};
|
||||
|
||||
entity.parent = entity.parent.map(change_name);
|
||||
(change_name(entity_name), entity)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut entity_kind_consts = TokenStream::new();
|
||||
let mut entity_kind_fmt_args = TokenStream::new();
|
||||
let mut translation_key_arms = TokenStream::new();
|
||||
let mut modules = TokenStream::new();
|
||||
let mut systems = TokenStream::new();
|
||||
let mut system_names = vec![];
|
||||
|
||||
for (entity_name, entity) in entities.clone() {
|
||||
let entity_name_ident = ident(&entity_name);
|
||||
let shouty_entity_name = entity_name.to_shouty_snake_case();
|
||||
let shouty_entity_name_ident = ident(&shouty_entity_name);
|
||||
let snake_entity_name = entity_name.to_snake_case();
|
||||
let snake_entity_name_ident = ident(&snake_entity_name);
|
||||
|
||||
let mut module_body = TokenStream::new();
|
||||
|
||||
if let Some(parent_name) = entity.parent {
|
||||
let snake_parent_name = parent_name.to_snake_case();
|
||||
|
||||
let module_doc =
|
||||
format!("Parent class: [`{snake_parent_name}`][super::{snake_parent_name}].");
|
||||
|
||||
module_body.extend([quote! {
|
||||
#![doc = #module_doc]
|
||||
}]);
|
||||
}
|
||||
|
||||
// Is this a concrete entity type?
|
||||
if let Some(entity_type) = entity.typ {
|
||||
let entity_type_id = entity_types[&entity_type];
|
||||
|
||||
entity_kind_consts.extend([quote! {
|
||||
pub const #shouty_entity_name_ident: EntityKind = EntityKind(#entity_type_id);
|
||||
}]);
|
||||
|
||||
entity_kind_fmt_args.extend([quote! {
|
||||
EntityKind::#shouty_entity_name_ident => write!(f, "{} ({})", #entity_type_id, #shouty_entity_name),
|
||||
}]);
|
||||
|
||||
let translation_key_expr = if let Some(key) = entity.translation_key {
|
||||
quote!(Some(#key))
|
||||
} else {
|
||||
let getter_name = ident(format!("get_{}", &field.name));
|
||||
let setter_name = ident(format!("set_{}", &field.name));
|
||||
let getter_return_type = field.default_value.getter_return_type();
|
||||
let getter_return_expr = field.default_value.getter_return_expr(&field_name);
|
||||
quote!(None)
|
||||
};
|
||||
|
||||
quote! {
|
||||
pub fn #getter_name(&self) -> #getter_return_type {
|
||||
#getter_return_expr
|
||||
translation_key_arms.extend([quote! {
|
||||
EntityKind::#shouty_entity_name_ident => #translation_key_expr,
|
||||
}]);
|
||||
|
||||
// Create bundle type.
|
||||
let mut bundle_fields = TokenStream::new();
|
||||
let mut bundle_init_fields = TokenStream::new();
|
||||
|
||||
for marker_or_field in collect_bundle_fields(&entity_name, &entities) {
|
||||
match marker_or_field {
|
||||
MarkerOrField::Marker { entity_name } => {
|
||||
let snake_entity_name_ident = ident(entity_name.to_snake_case());
|
||||
let pascal_entity_name_ident = ident(entity_name.to_pascal_case());
|
||||
|
||||
bundle_fields.extend([quote! {
|
||||
pub #snake_entity_name_ident: super::#snake_entity_name_ident::#pascal_entity_name_ident,
|
||||
}]);
|
||||
|
||||
bundle_init_fields.extend([quote! {
|
||||
#snake_entity_name_ident: Default::default(),
|
||||
}]);
|
||||
}
|
||||
MarkerOrField::Field { entity_name, field } => {
|
||||
let snake_field_name = field.name.to_snake_case();
|
||||
let pascal_field_name = field.name.to_pascal_case();
|
||||
let pascal_field_name_ident = ident(&pascal_field_name);
|
||||
let snake_entity_name = entity_name.to_snake_case();
|
||||
let snake_entity_name_ident = ident(&snake_entity_name);
|
||||
|
||||
pub fn #setter_name(&mut self, #field_name: impl Into<#field_type>) {
|
||||
let #field_name = #field_name.into();
|
||||
if self.#field_name != #field_name {
|
||||
self.__modified_flags |= 1 << #field_index as #modified_flags_type;
|
||||
self.#field_name = #field_name;
|
||||
let field_name_ident =
|
||||
ident(format!("{snake_entity_name}_{snake_field_name}"));
|
||||
|
||||
bundle_fields.extend([quote! {
|
||||
pub #field_name_ident: super::#snake_entity_name_ident::#pascal_field_name_ident,
|
||||
}]);
|
||||
|
||||
bundle_init_fields.extend([quote! {
|
||||
#field_name_ident: Default::default(),
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bundle_fields.extend([quote! {
|
||||
pub kind: super::EntityKind,
|
||||
pub id: super::EntityId,
|
||||
pub uuid: super::UniqueId,
|
||||
pub location: super::Location,
|
||||
pub old_location: super::OldLocation,
|
||||
pub position: super::Position,
|
||||
pub old_position: super::OldPosition,
|
||||
pub look: super::Look,
|
||||
pub head_yaw: super::HeadYaw,
|
||||
pub on_ground: super::OnGround,
|
||||
pub velocity: super::Velocity,
|
||||
pub statuses: super::EntityStatuses,
|
||||
pub animations: super::EntityAnimations,
|
||||
pub object_data: super::ObjectData,
|
||||
pub tracked_data: super::TrackedData,
|
||||
pub packet_byte_range: super::PacketByteRange,
|
||||
}]);
|
||||
|
||||
bundle_init_fields.extend([quote! {
|
||||
kind: super::EntityKind::#shouty_entity_name_ident,
|
||||
id: Default::default(),
|
||||
uuid: Default::default(),
|
||||
location: Default::default(),
|
||||
old_location: Default::default(),
|
||||
position: Default::default(),
|
||||
old_position: Default::default(),
|
||||
look: Default::default(),
|
||||
head_yaw: Default::default(),
|
||||
on_ground: Default::default(),
|
||||
velocity: Default::default(),
|
||||
statuses: Default::default(),
|
||||
animations: Default::default(),
|
||||
object_data: Default::default(),
|
||||
tracked_data: Default::default(),
|
||||
packet_byte_range: Default::default(),
|
||||
}]);
|
||||
|
||||
let bundle_name_ident = ident(format!("{entity_name}Bundle"));
|
||||
let bundle_doc =
|
||||
format!("The bundle of components for spawning `{snake_entity_name}` entities.");
|
||||
|
||||
module_body.extend([quote! {
|
||||
#[doc = #bundle_doc]
|
||||
#[derive(bevy_ecs::bundle::Bundle, Debug)]
|
||||
pub struct #bundle_name_ident {
|
||||
#bundle_fields
|
||||
}
|
||||
|
||||
impl Default for #bundle_name_ident {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#bundle_init_fields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}]);
|
||||
}
|
||||
|
||||
let initial_tracked_data_stmts = fields.iter().map(|&field| {
|
||||
let field_name = ident(&field.name);
|
||||
let field_index = field.index;
|
||||
for field in &entity.fields {
|
||||
let pascal_field_name_ident = ident(field.name.to_pascal_case());
|
||||
let snake_field_name = field.name.to_snake_case();
|
||||
let inner_type = field.default_value.field_type();
|
||||
let default_expr = field.default_value.default_expr();
|
||||
let type_id = field.default_value.type_id();
|
||||
let encodable = field.default_value.encodable_expr(quote!(self.#field_name));
|
||||
|
||||
quote! {
|
||||
if self.#field_name != (#default_expr) {
|
||||
data.push(#field_index);
|
||||
VarInt(#type_id).encode(&mut *data).unwrap();
|
||||
#encodable.encode(&mut *data).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
module_body.extend([quote! {
|
||||
#[derive(bevy_ecs::component::Component, PartialEq, Clone, Debug)]
|
||||
pub struct #pascal_field_name_ident(pub #inner_type);
|
||||
|
||||
let updated_tracked_data_stmts = fields.iter().map(|&field| {
|
||||
let field_name = ident(&field.name);
|
||||
let field_index = field.index;
|
||||
let type_id = field.default_value.type_id();
|
||||
let encodable = field.default_value.encodable_expr(quote!(self.#field_name));
|
||||
|
||||
quote! {
|
||||
if (self.__modified_flags >> #field_index as #modified_flags_type) & 1 == 1 {
|
||||
data.push(#field_index);
|
||||
VarInt(#type_id).encode(&mut *data).unwrap();
|
||||
#encodable.encode(&mut *data).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub struct #struct_name {
|
||||
/// Contains a set bit for every modified field.
|
||||
__modified_flags: #modified_flags_type,
|
||||
#(#struct_fields)*
|
||||
}
|
||||
|
||||
impl #struct_name {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
__modified_flags: 0,
|
||||
#(#field_initializers)*
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for #pascal_field_name_ident {
|
||||
fn default() -> Self {
|
||||
Self(#default_expr)
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
pub(crate) fn initial_tracked_data(&self, data: &mut Vec<u8>) {
|
||||
#(#initial_tracked_data_stmts)*
|
||||
}
|
||||
let system_name_ident = ident(format!("update_{snake_entity_name}_{snake_field_name}"));
|
||||
let component_path = quote!(#snake_entity_name_ident::#pascal_field_name_ident);
|
||||
|
||||
pub(crate) fn updated_tracked_data(&self, data: &mut Vec<u8>) {
|
||||
if self.__modified_flags != 0 {
|
||||
#(#updated_tracked_data_stmts)*
|
||||
system_names.push(quote!(#system_name_ident));
|
||||
|
||||
let data_index = field.index;
|
||||
let data_type = field.default_value.type_id();
|
||||
let encodable_expr = field.default_value.encodable_expr(quote!(value.0));
|
||||
|
||||
systems.extend([quote! {
|
||||
#[allow(clippy::needless_borrow)]
|
||||
fn #system_name_ident(
|
||||
mut query: Query<(&#component_path, &mut TrackedData), Changed<#component_path>>
|
||||
) {
|
||||
for (value, mut tracked_data) in &mut query {
|
||||
if *value == Default::default() {
|
||||
tracked_data.remove_init_value(#data_index);
|
||||
} else {
|
||||
tracked_data.insert_init_value(#data_index, #data_type, #encodable_expr);
|
||||
}
|
||||
|
||||
if !tracked_data.is_added() {
|
||||
tracked_data.append_update_value(#data_index, #data_type, #encodable_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
pub(crate) fn clear_modifications(&mut self) {
|
||||
self.__modified_flags = 0;
|
||||
}
|
||||
let marker_doc = format!("Marker component for `{snake_entity_name}` entities.");
|
||||
|
||||
#(#getter_setters)*
|
||||
module_body.extend([quote! {
|
||||
#[doc = #marker_doc]
|
||||
#[derive(bevy_ecs::component::Component, Copy, Clone, Default, Debug)]
|
||||
pub struct #entity_name_ident;
|
||||
}]);
|
||||
|
||||
modules.extend([quote! {
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod #snake_entity_name_ident {
|
||||
#module_body
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let translation_key_arms = concrete_entities.iter().map(|(k, v)| {
|
||||
let name = ident(k);
|
||||
let key = v
|
||||
.translation_key
|
||||
.as_ref()
|
||||
.expect("translation key should be present for concrete entity");
|
||||
|
||||
quote! {
|
||||
Self::#name => #key,
|
||||
}
|
||||
});
|
||||
}]);
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
/// Contains a variant for each concrete entity type.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum EntityKind {
|
||||
#(#entity_kind_variants)*
|
||||
}
|
||||
#modules
|
||||
|
||||
/// Identifies the type of an entity.
|
||||
/// As a component, the entity kind should not be modified.
|
||||
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct EntityKind(i32);
|
||||
|
||||
impl EntityKind {
|
||||
pub fn translation_key(self) -> &'static str {
|
||||
#entity_kind_consts
|
||||
|
||||
pub const fn new(inner: i32) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
pub const fn get(self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn translation_key(self) -> Option<&'static str> {
|
||||
match self {
|
||||
#(#translation_key_arms)*
|
||||
#translation_key_arms
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TrackedData {
|
||||
#(#concrete_entity_names(#concrete_entity_names),)*
|
||||
}
|
||||
|
||||
impl TrackedData {
|
||||
pub(super) fn new(kind: EntityKind) -> Self {
|
||||
match kind {
|
||||
#(EntityKind::#concrete_entity_names => Self::#concrete_entity_names(#concrete_entity_names::new()),)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> EntityKind {
|
||||
match self {
|
||||
#(Self::#concrete_entity_names(_) => EntityKind::#concrete_entity_names,)*
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn write_initial_tracked_data(&self, buf: &mut Vec<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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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([
|
||||
SPAWN_POS.x as f64 + 0.5,
|
||||
SPAWN_POS.y as f64 + 1.0,
|
||||
SPAWN_POS.z as f64 + 0.5,
|
||||
]);
|
||||
loc.0 = instances.single();
|
||||
for (entity, uuid, mut game_mode) in &mut clients {
|
||||
*game_mode = GameMode::Creative;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(McEntity::with_uuid(EntityKind::Player, loc.0, uuid.0));
|
||||
commands.entity(entity).insert(PlayerBundle {
|
||||
location: Location(instances.single()),
|
||||
position: Position::new([
|
||||
SPAWN_POS.x as f64 + 0.5,
|
||||
SPAWN_POS.y as f64 + 1.0,
|
||||
SPAWN_POS.z as f64 + 0.5,
|
||||
]),
|
||||
uuid: *uuid,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_sphere(server: Res<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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,21 +1191,24 @@ 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);
|
||||
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: init_data.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !scratch.is_empty() {
|
||||
scratch.push(0xff);
|
||||
|
||||
client.enc.write_packet(&EntityTrackerUpdateS2c {
|
||||
entity_id: VarInt(0),
|
||||
metadata: scratch.as_slice().into(),
|
||||
})
|
||||
}
|
||||
fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) {
|
||||
for (mut client, tracked_data) in &mut clients {
|
||||
if let Some(update_data) = tracked_data.update_data() {
|
||||
client.write_packet(&EntityTrackerUpdateS2c {
|
||||
entity_id: VarInt(0),
|
||||
metadata: update_data.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
116
crates/valence/src/client/default_event_handler.rs
Normal file
116
crates/valence/src/client/default_event_handler.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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"));
|
294
crates/valence/src/entity/hitbox.rs
Normal file
294
crates/valence/src/entity/hitbox.rs
Normal 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)
|
||||
}
|
||||
*/
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct ParticleS2c<'a> {
|
|||
pub count: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Particle {
|
||||
AmbientEntityEffect,
|
||||
AngryVillager,
|
||||
|
@ -232,6 +232,125 @@ impl Particle {
|
|||
Particle::Scrape => 91,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes the particle assuming the given particle ID.
|
||||
pub fn decode_with_id(particle_id: i32, r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||
Ok(match particle_id {
|
||||
0 => Particle::AmbientEntityEffect,
|
||||
1 => Particle::AngryVillager,
|
||||
2 => Particle::Block(BlockState::decode(r)?),
|
||||
3 => Particle::BlockMarker(BlockState::decode(r)?),
|
||||
4 => Particle::Bubble,
|
||||
5 => Particle::Cloud,
|
||||
6 => Particle::Crit,
|
||||
7 => Particle::DamageIndicator,
|
||||
8 => Particle::DragonBreath,
|
||||
9 => Particle::DrippingLava,
|
||||
10 => Particle::FallingLava,
|
||||
11 => Particle::LandingLava,
|
||||
12 => Particle::DrippingWater,
|
||||
13 => Particle::FallingWater,
|
||||
14 => Particle::Dust {
|
||||
rgb: <[f32; 3]>::decode(r)?,
|
||||
scale: f32::decode(r)?,
|
||||
},
|
||||
15 => Particle::DustColorTransition {
|
||||
from_rgb: <[f32; 3]>::decode(r)?,
|
||||
scale: f32::decode(r)?,
|
||||
to_rgb: <[f32; 3]>::decode(r)?,
|
||||
},
|
||||
16 => Particle::Effect,
|
||||
17 => Particle::ElderGuardian,
|
||||
18 => Particle::EnchantedHit,
|
||||
19 => Particle::Enchant,
|
||||
20 => Particle::EndRod,
|
||||
21 => Particle::EntityEffect,
|
||||
22 => Particle::ExplosionEmitter,
|
||||
23 => Particle::Explosion,
|
||||
24 => Particle::SonicBoom,
|
||||
25 => Particle::FallingDust(BlockState::decode(r)?),
|
||||
26 => Particle::Firework,
|
||||
27 => Particle::Fishing,
|
||||
28 => Particle::Flame,
|
||||
29 => Particle::SculkSoul,
|
||||
30 => Particle::SculkCharge {
|
||||
roll: f32::decode(r)?,
|
||||
},
|
||||
31 => Particle::SculkChargePop,
|
||||
32 => Particle::SoulFireFlame,
|
||||
33 => Particle::Soul,
|
||||
34 => Particle::Flash,
|
||||
35 => Particle::HappyVillager,
|
||||
36 => Particle::Composter,
|
||||
37 => Particle::Heart,
|
||||
38 => Particle::InstantEffect,
|
||||
39 => Particle::Item(Decode::decode(r)?),
|
||||
40 => match <&str>::decode(r)? {
|
||||
"block" => Particle::VibrationBlock {
|
||||
block_pos: BlockPos::decode(r)?,
|
||||
ticks: VarInt::decode(r)?.0,
|
||||
},
|
||||
"entity" => Particle::VibrationEntity {
|
||||
entity_id: VarInt::decode(r)?.0,
|
||||
entity_eye_height: f32::decode(r)?,
|
||||
ticks: VarInt::decode(r)?.0,
|
||||
},
|
||||
invalid => bail!("invalid vibration position source of \"{invalid}\""),
|
||||
},
|
||||
41 => Particle::ItemSlime,
|
||||
42 => Particle::ItemSnowball,
|
||||
43 => Particle::LargeSmoke,
|
||||
44 => Particle::Lava,
|
||||
45 => Particle::Mycelium,
|
||||
46 => Particle::Note,
|
||||
47 => Particle::Poof,
|
||||
48 => Particle::Portal,
|
||||
49 => Particle::Rain,
|
||||
50 => Particle::Smoke,
|
||||
51 => Particle::Sneeze,
|
||||
52 => Particle::Spit,
|
||||
53 => Particle::SquidInk,
|
||||
54 => Particle::SweepAttack,
|
||||
55 => Particle::TotemOfUndying,
|
||||
56 => Particle::Underwater,
|
||||
57 => Particle::Splash,
|
||||
58 => Particle::Witch,
|
||||
59 => Particle::BubblePop,
|
||||
60 => Particle::CurrentDown,
|
||||
61 => Particle::BubbleColumnUp,
|
||||
62 => Particle::Nautilus,
|
||||
63 => Particle::Dolphin,
|
||||
64 => Particle::CampfireCosySmoke,
|
||||
65 => Particle::CampfireSignalSmoke,
|
||||
66 => Particle::DrippingHoney,
|
||||
67 => Particle::FallingHoney,
|
||||
68 => Particle::LandingHoney,
|
||||
69 => Particle::FallingNectar,
|
||||
70 => Particle::FallingSporeBlossom,
|
||||
71 => Particle::Ash,
|
||||
72 => Particle::CrimsonSpore,
|
||||
73 => Particle::WarpedSpore,
|
||||
74 => Particle::SporeBlossomAir,
|
||||
75 => Particle::DrippingObsidianTear,
|
||||
76 => Particle::FallingObsidianTear,
|
||||
77 => Particle::LandingObsidianTear,
|
||||
78 => Particle::ReversePortal,
|
||||
79 => Particle::WhiteAsh,
|
||||
80 => Particle::SmallFlame,
|
||||
81 => Particle::Snowflake,
|
||||
82 => Particle::DrippingDripstoneLava,
|
||||
83 => Particle::FallingDripstoneLava,
|
||||
84 => Particle::DrippingDripstoneWater,
|
||||
85 => Particle::FallingDripstoneWater,
|
||||
86 => Particle::GlowSquidInk,
|
||||
87 => Particle::Glow,
|
||||
88 => Particle::WaxOn,
|
||||
89 => Particle::WaxOff,
|
||||
90 => Particle::ElectricSpark,
|
||||
91 => Particle::Scrape,
|
||||
id => bail!("invalid particle ID of {id}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for ParticleS2c<'_> {
|
||||
|
@ -243,7 +362,34 @@ impl Encode for ParticleS2c<'_> {
|
|||
self.max_speed.encode(&mut w)?;
|
||||
self.count.encode(&mut w)?;
|
||||
|
||||
match self.particle.as_ref() {
|
||||
self.particle.as_ref().encode(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for ParticleS2c<'a> {
|
||||
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
||||
let particle_id = VarInt::decode(r)?.0;
|
||||
let long_distance = bool::decode(r)?;
|
||||
let position = <[f64; 3]>::decode(r)?;
|
||||
let offset = <[f32; 3]>::decode(r)?;
|
||||
let max_speed = f32::decode(r)?;
|
||||
let particle_count = i32::decode(r)?;
|
||||
|
||||
Ok(Self {
|
||||
particle: Cow::Owned(Particle::decode_with_id(particle_id, r)?),
|
||||
long_distance,
|
||||
position,
|
||||
offset,
|
||||
max_speed,
|
||||
count: particle_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the particle without an ID.
|
||||
impl Encode for Particle {
|
||||
fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Particle::Block(block_state) => block_state.encode(w),
|
||||
Particle::BlockMarker(block_state) => block_state.encode(w),
|
||||
Particle::Dust { rgb, scale } => {
|
||||
|
@ -281,137 +427,3 @@ impl Encode for ParticleS2c<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Decode<'a> for ParticleS2c<'a> {
|
||||
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
|
||||
let particle_id = VarInt::decode(r)?.0;
|
||||
let long_distance = bool::decode(r)?;
|
||||
let position = <[f64; 3]>::decode(r)?;
|
||||
let offset = <[f32; 3]>::decode(r)?;
|
||||
let max_speed = f32::decode(r)?;
|
||||
let particle_count = i32::decode(r)?;
|
||||
|
||||
Ok(Self {
|
||||
particle: Cow::Owned(match particle_id {
|
||||
0 => Particle::AmbientEntityEffect,
|
||||
1 => Particle::AngryVillager,
|
||||
2 => Particle::Block(BlockState::decode(r)?),
|
||||
3 => Particle::BlockMarker(BlockState::decode(r)?),
|
||||
4 => Particle::Bubble,
|
||||
5 => Particle::Cloud,
|
||||
6 => Particle::Crit,
|
||||
7 => Particle::DamageIndicator,
|
||||
8 => Particle::DragonBreath,
|
||||
9 => Particle::DrippingLava,
|
||||
10 => Particle::FallingLava,
|
||||
11 => Particle::LandingLava,
|
||||
12 => Particle::DrippingWater,
|
||||
13 => Particle::FallingWater,
|
||||
14 => Particle::Dust {
|
||||
rgb: <[f32; 3]>::decode(r)?,
|
||||
scale: f32::decode(r)?,
|
||||
},
|
||||
15 => Particle::DustColorTransition {
|
||||
from_rgb: <[f32; 3]>::decode(r)?,
|
||||
scale: f32::decode(r)?,
|
||||
to_rgb: <[f32; 3]>::decode(r)?,
|
||||
},
|
||||
16 => Particle::Effect,
|
||||
17 => Particle::ElderGuardian,
|
||||
18 => Particle::EnchantedHit,
|
||||
19 => Particle::Enchant,
|
||||
20 => Particle::EndRod,
|
||||
21 => Particle::EntityEffect,
|
||||
22 => Particle::ExplosionEmitter,
|
||||
23 => Particle::Explosion,
|
||||
24 => Particle::SonicBoom,
|
||||
25 => Particle::FallingDust(BlockState::decode(r)?),
|
||||
26 => Particle::Firework,
|
||||
27 => Particle::Fishing,
|
||||
28 => Particle::Flame,
|
||||
29 => Particle::SculkSoul,
|
||||
30 => Particle::SculkCharge {
|
||||
roll: f32::decode(r)?,
|
||||
},
|
||||
31 => Particle::SculkChargePop,
|
||||
32 => Particle::SoulFireFlame,
|
||||
33 => Particle::Soul,
|
||||
34 => Particle::Flash,
|
||||
35 => Particle::HappyVillager,
|
||||
36 => Particle::Composter,
|
||||
37 => Particle::Heart,
|
||||
38 => Particle::InstantEffect,
|
||||
39 => Particle::Item(Decode::decode(r)?),
|
||||
40 => match <&str>::decode(r)? {
|
||||
"block" => Particle::VibrationBlock {
|
||||
block_pos: BlockPos::decode(r)?,
|
||||
ticks: VarInt::decode(r)?.0,
|
||||
},
|
||||
"entity" => Particle::VibrationEntity {
|
||||
entity_id: VarInt::decode(r)?.0,
|
||||
entity_eye_height: f32::decode(r)?,
|
||||
ticks: VarInt::decode(r)?.0,
|
||||
},
|
||||
invalid => bail!("invalid vibration position source of \"{invalid}\""),
|
||||
},
|
||||
41 => Particle::ItemSlime,
|
||||
42 => Particle::ItemSnowball,
|
||||
43 => Particle::LargeSmoke,
|
||||
44 => Particle::Lava,
|
||||
45 => Particle::Mycelium,
|
||||
46 => Particle::Note,
|
||||
47 => Particle::Poof,
|
||||
48 => Particle::Portal,
|
||||
49 => Particle::Rain,
|
||||
50 => Particle::Smoke,
|
||||
51 => Particle::Sneeze,
|
||||
52 => Particle::Spit,
|
||||
53 => Particle::SquidInk,
|
||||
54 => Particle::SweepAttack,
|
||||
55 => Particle::TotemOfUndying,
|
||||
56 => Particle::Underwater,
|
||||
57 => Particle::Splash,
|
||||
58 => Particle::Witch,
|
||||
59 => Particle::BubblePop,
|
||||
60 => Particle::CurrentDown,
|
||||
61 => Particle::BubbleColumnUp,
|
||||
62 => Particle::Nautilus,
|
||||
63 => Particle::Dolphin,
|
||||
64 => Particle::CampfireCosySmoke,
|
||||
65 => Particle::CampfireSignalSmoke,
|
||||
66 => Particle::DrippingHoney,
|
||||
67 => Particle::FallingHoney,
|
||||
68 => Particle::LandingHoney,
|
||||
69 => Particle::FallingNectar,
|
||||
70 => Particle::FallingSporeBlossom,
|
||||
71 => Particle::Ash,
|
||||
72 => Particle::CrimsonSpore,
|
||||
73 => Particle::WarpedSpore,
|
||||
74 => Particle::SporeBlossomAir,
|
||||
75 => Particle::DrippingObsidianTear,
|
||||
76 => Particle::FallingObsidianTear,
|
||||
77 => Particle::LandingObsidianTear,
|
||||
78 => Particle::ReversePortal,
|
||||
79 => Particle::WhiteAsh,
|
||||
80 => Particle::SmallFlame,
|
||||
81 => Particle::Snowflake,
|
||||
82 => Particle::DrippingDripstoneLava,
|
||||
83 => Particle::FallingDripstoneLava,
|
||||
84 => Particle::DrippingDripstoneWater,
|
||||
85 => Particle::FallingDripstoneWater,
|
||||
86 => Particle::GlowSquidInk,
|
||||
87 => Particle::Glow,
|
||||
88 => Particle::WaxOn,
|
||||
89 => Particle::WaxOff,
|
||||
90 => Particle::ElectricSpark,
|
||||
91 => Particle::Scrape,
|
||||
id => bail!("invalid particle ID of {id}"),
|
||||
}),
|
||||
long_distance,
|
||||
position,
|
||||
offset,
|
||||
max_speed,
|
||||
count: particle_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue