Rewrite the entity generator

This commit is contained in:
Ryan 2022-07-27 07:10:35 -07:00
parent f97c67d42a
commit 2e22946ffc
17 changed files with 3761 additions and 2693 deletions

View file

@ -7,9 +7,9 @@ use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
use crate::{ident, write_to_out_path};
use crate::ident;
pub fn build() -> anyhow::Result<()> {
pub fn build() -> anyhow::Result<TokenStream> {
let blocks = parse_blocks_json()?;
let max_block_state = blocks.iter().map(|b| b.max_state_id).max().unwrap();
@ -297,7 +297,7 @@ pub fn build() -> anyhow::Result<()> {
let property_name_count = prop_values.len();
let finished = quote! {
Ok(quote! {
/// Represents the state of a block, not including block entity data such as
/// the text on a sign, the design on a banner, or the content of a spawner.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
@ -551,9 +551,7 @@ pub fn build() -> anyhow::Result<()> {
Self::from_bool(b)
}
}
};
write_to_out_path("block.rs", &finished.to_string())
})
}
struct Block {

File diff suppressed because it is too large Load diff

66
build/entity_event.rs Normal file
View file

@ -0,0 +1,66 @@
use std::collections::BTreeMap;
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::quote;
use serde::Deserialize;
use crate::ident;
#[derive(Deserialize, Clone, Debug)]
struct EntityData {
statuses: BTreeMap<String, u8>,
animations: BTreeMap<String, u8>,
}
pub fn build() -> anyhow::Result<TokenStream> {
let entity_data: EntityData =
serde_json::from_str(include_str!("../extracted/entity_data.json"))?;
let mut statuses: Vec<_> = entity_data.statuses.into_iter().collect();
statuses.sort_by_key(|(_, id)| *id);
let mut animations: Vec<_> = entity_data.animations.into_iter().collect();
animations.sort_by_key(|(_, id)| *id);
let event_variants = statuses
.iter()
.chain(animations.iter())
.map(|(name, _)| ident(name.to_pascal_case()));
let status_arms = statuses.iter().map(|(name, code)| {
let name = ident(name.to_pascal_case());
quote! {
Self::#name => StatusOrAnimation::Status(#code),
}
});
let animation_arms = animations.iter().map(|(name, code)| {
let name = ident(name.to_pascal_case());
quote! {
Self::#name => StatusOrAnimation::Animation(#code),
}
});
Ok(quote! {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Event {
#(#event_variants,)*
}
impl Event {
pub(crate) fn status_or_animation(self) -> StatusOrAnimation {
match self {
#(#status_arms)*
#(#animation_arms)*
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum StatusOrAnimation {
Status(u8),
Animation(u8),
}
})
}

View file

@ -7,35 +7,38 @@ use proc_macro2::{Ident, Span};
mod block;
mod entity;
mod entity_event;
pub fn main() -> anyhow::Result<()> {
for file in ["blocks.json", "entities.json"] {
println!("cargo:rerun-if-changed=data/{file}");
}
let generators = [
(entity::build as fn() -> _, "entity.rs"),
(entity_event::build, "entity_event.rs"),
(block::build, "block.rs"),
];
block::build()?;
entity::build()?;
let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?;
for (g, file_name) in generators {
println!("cargo:rerun-if-changed=extracted/{file_name}");
let path = Path::new(&out_dir).join(file_name);
let code = g()?.to_string();
fs::write(&path, &code)?;
// Format the output for debugging purposes.
// Doesn't matter if rustfmt is unavailable.
let _ = Command::new("rustfmt").arg(path).output();
}
Ok(())
}
fn ident(s: impl AsRef<str>) -> Ident {
let s = s.as_ref().trim();
if s.starts_with(char::is_numeric) {
Ident::new(&format!("_{s}"), Span::call_site())
} else {
Ident::new(s, Span::call_site())
match s.as_bytes() {
// TODO: check for the other rust keywords.
[b'0'..=b'9', ..] | b"type" => Ident::new(&format!("_{s}"), Span::call_site()),
_ => Ident::new(s, Span::call_site()),
}
}
fn write_to_out_path(file_name: impl AsRef<str>, content: impl AsRef<str>) -> anyhow::Result<()> {
let out_dir = env::var_os("OUT_DIR").context("can't get OUT_DIR env var")?;
let path = Path::new(&out_dir).join(file_name.as_ref());
fs::write(&path, &content.as_ref())?;
// Format the output for debugging purposes.
// Doesn't matter if rustfmt is unavailable.
let _ = Command::new("rustfmt").arg(path).output();
Ok(())
}

View file

@ -3,12 +3,11 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use log::LevelFilter;
use valence::block::{BlockPos, BlockState};
use valence::client::Event::{self};
use valence::client::{ClientId, GameMode, Hand, InteractWithEntityKind};
use valence::client::{ClientId, Event, GameMode, Hand, InteractWithEntityKind};
use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId;
use valence::entity::state::Pose;
use valence::entity::{EntityId, EntityKind, EntityState};
use valence::entity::data::Pose;
use valence::entity::{EntityEnum, EntityId, EntityKind, Event as EntityEvent};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat};
use valence::{async_trait, Ticks};
@ -37,7 +36,7 @@ struct ClientData {
/// The client's player entity.
player: EntityId,
/// The extra knockback on the first hit while sprinting.
has_extra_knockback: bool,
extra_knockback: bool,
}
#[derive(Default)]
@ -67,7 +66,6 @@ impl Config for Game {
}
fn online_mode(&self) -> bool {
// You'll want this to be true on real servers.
false
}
@ -160,7 +158,7 @@ impl Config for Game {
.unwrap();
client.data.player = player_id;
client.data.has_extra_knockback = true;
client.data.extra_knockback = true;
player.data.client = client_id;
player.data.last_attack_time = 0;
@ -181,7 +179,7 @@ impl Config for Game {
while let Some(event) = client.pop_event() {
match event {
Event::StartSprinting => {
client.data.has_extra_knockback = true;
client.data.extra_knockback = true;
}
Event::InteractWithEntity {
id,
@ -195,21 +193,18 @@ impl Config for Game {
{
target.data.attacked = true;
target.data.attacker_pos = client.position();
target.data.extra_knockback = client.data.has_extra_knockback;
target.data.extra_knockback = client.data.extra_knockback;
target.data.last_attack_time = current_tick;
client.data.has_extra_knockback = false;
client.data.extra_knockback = false;
}
}
}
Event::ArmSwing(hand) => {
let player = server.entities.get_mut(client.data.player).unwrap();
if let EntityState::Player(e) = &mut player.state {
match hand {
Hand::Main => e.trigger_swing_main_arm(),
Hand::Off => e.trigger_swing_offhand(),
}
Hand::Main => player.trigger_event(EntityEvent::SwingMainHand),
Hand::Off => player.trigger_event(EntityEvent::SwingOffHand),
}
}
_ => (),
@ -237,7 +232,7 @@ impl Config for Game {
player.set_pitch(client.pitch());
player.set_on_ground(client.on_ground());
if let EntityState::Player(player) = &mut player.state {
if let EntityEnum::Player(player) = player.view_mut() {
if client.is_sneaking() {
player.set_pose(Pose::Sneaking);
} else {
@ -266,12 +261,10 @@ impl Config for Game {
victim.set_velocity(victim.velocity() / 2.0 + vel.as_());
if let EntityState::Player(e) = &mut e.state {
e.trigger_take_damage();
e.trigger_hurt();
}
victim.player_mut().trigger_take_damage();
victim.player_mut().trigger_hurt();
e.trigger_event(EntityEvent::DamageFromGenericSource);
e.trigger_event(EntityEvent::Damage);
victim.trigger_entity_event(EntityEvent::DamageFromGenericSource);
victim.trigger_entity_event(EntityEvent::Damage);
}
}
}

View file

@ -10,8 +10,8 @@ use valence::block::BlockState;
use valence::client::{Event, Hand};
use valence::config::{Config, ServerListPing};
use valence::dimension::{Dimension, DimensionId};
use valence::entity::state::Pose;
use valence::entity::{EntityId, EntityKind, EntityState};
use valence::entity::data::Pose;
use valence::entity::{EntityEnum, EntityId, EntityKind, Event as EntityEvent};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::text::{Color, TextFormat};
use valence::{async_trait, ident};
@ -179,14 +179,10 @@ impl Config for Game {
true;
}
}
Event::ArmSwing(hand) => {
if let EntityState::Player(e) = &mut player.state {
match hand {
Hand::Main => e.trigger_swing_main_arm(),
Hand::Off => e.trigger_swing_offhand(),
}
}
}
Event::ArmSwing(hand) => match hand {
Hand::Main => player.trigger_event(EntityEvent::SwingMainHand),
Hand::Off => player.trigger_event(EntityEvent::SwingOffHand),
},
_ => {}
}
}
@ -198,7 +194,7 @@ impl Config for Game {
player.set_pitch(client.pitch());
player.set_on_ground(client.on_ground());
if let EntityState::Player(player) = &mut player.state {
if let EntityEnum::Player(player) = player.view_mut() {
if client.is_sneaking() {
player.set_pose(Pose::Sneaking);
} else {

View file

@ -7,7 +7,7 @@ use valence::block::{BlockPos, BlockState};
use valence::client::GameMode;
use valence::config::{Config, ServerListPing};
use valence::dimension::DimensionId;
use valence::entity::{EntityKind, EntityState};
use valence::entity::{EntityEnum, EntityKind};
use valence::server::{Server, SharedServer, ShutdownResult};
use valence::spatial_index::RaycastHit;
use valence::text::{Color, TextFormat};
@ -166,11 +166,12 @@ impl Config for Game {
});
for (_, e) in server.entities.iter_mut() {
if let EntityState::Sheep(sheep) = &mut e.state {
if e.data {
sheep.set_sheep_state(5);
let intersected = e.data;
if let EntityEnum::Sheep(sheep) = &mut e.view_mut() {
if intersected {
sheep.set_color(5);
} else {
sheep.set_sheep_state(0);
sheep.set_color(0);
}
}
e.data = false;

View file

@ -117,10 +117,8 @@ impl Config for Game {
);
client.send_message("Welcome to the terrain example!".italic());
client.send_message(
"This demonstrates how to create infinite procedurally generated terrain."
.italic(),
);
client
.send_message("Explore this infinite procedurally generated terrain.".italic());
}
if client.is_disconnected() {

2436
extracted/entities.json Normal file

File diff suppressed because it is too large Load diff

509
extracted/entity_data.json Normal file
View file

@ -0,0 +1,509 @@
{
"types": {
"allay": 0,
"area_effect_cloud": 1,
"armor_stand": 2,
"arrow": 3,
"axolotl": 4,
"bat": 5,
"bee": 6,
"blaze": 7,
"boat": 8,
"chest_boat": 9,
"cat": 10,
"cave_spider": 11,
"chicken": 12,
"cod": 13,
"cow": 14,
"creeper": 15,
"dolphin": 16,
"donkey": 17,
"dragon_fireball": 18,
"drowned": 19,
"elder_guardian": 20,
"end_crystal": 21,
"ender_dragon": 22,
"enderman": 23,
"endermite": 24,
"evoker": 25,
"evoker_fangs": 26,
"experience_orb": 27,
"eye_of_ender": 28,
"falling_block": 29,
"firework_rocket": 30,
"fox": 31,
"frog": 32,
"ghast": 33,
"giant": 34,
"glow_item_frame": 35,
"glow_squid": 36,
"goat": 37,
"guardian": 38,
"hoglin": 39,
"horse": 40,
"husk": 41,
"illusioner": 42,
"iron_golem": 43,
"item": 44,
"item_frame": 45,
"fireball": 46,
"leash_knot": 47,
"lightning_bolt": 48,
"llama": 49,
"llama_spit": 50,
"magma_cube": 51,
"marker": 52,
"minecart": 53,
"chest_minecart": 54,
"command_block_minecart": 55,
"furnace_minecart": 56,
"hopper_minecart": 57,
"spawner_minecart": 58,
"tnt_minecart": 59,
"mule": 60,
"mooshroom": 61,
"ocelot": 62,
"painting": 63,
"panda": 64,
"parrot": 65,
"phantom": 66,
"pig": 67,
"piglin": 68,
"piglin_brute": 69,
"pillager": 70,
"polar_bear": 71,
"tnt": 72,
"pufferfish": 73,
"rabbit": 74,
"ravager": 75,
"salmon": 76,
"sheep": 77,
"shulker": 78,
"shulker_bullet": 79,
"silverfish": 80,
"skeleton": 81,
"skeleton_horse": 82,
"slime": 83,
"small_fireball": 84,
"snow_golem": 85,
"snowball": 86,
"spectral_arrow": 87,
"spider": 88,
"squid": 89,
"stray": 90,
"strider": 91,
"tadpole": 92,
"egg": 93,
"ender_pearl": 94,
"experience_bottle": 95,
"potion": 96,
"trident": 97,
"trader_llama": 98,
"tropical_fish": 99,
"turtle": 100,
"vex": 101,
"villager": 102,
"vindicator": 103,
"wandering_trader": 104,
"warden": 105,
"witch": 106,
"wither": 107,
"wither_skeleton": 108,
"wither_skull": 109,
"wolf": 110,
"zoglin": 111,
"zombie": 112,
"zombie_horse": 113,
"zombie_villager": 114,
"zombified_piglin": 115,
"player": 116,
"fishing_bobber": 117
},
"statuses": {
"add_sprinting_particles_or_reset_spawner_minecart_spawn_delay": 1,
"damage_from_generic_source": 2,
"play_death_sound_or_add_projectile_hit_particles": 3,
"play_attack_sound": 4,
"stop_attack": 5,
"add_negative_player_reaction_particles": 6,
"add_positive_player_reaction_particles": 7,
"shake_off_water": 8,
"consume_item": 9,
"set_sheep_eat_grass_timer_or_prime_tnt_minecart": 10,
"look_at_villager": 11,
"add_villager_heart_particles": 12,
"add_villager_angry_particles": 13,
"add_villager_happy_particles": 14,
"add_witch_particles": 15,
"play_cure_zombie_villager_sound": 16,
"explode_firework_client": 17,
"add_breeding_particles": 18,
"reset_squid_thrust_timer": 19,
"play_spawn_effects": 20,
"play_guardian_attack_sound": 21,
"use_reduced_debug_info": 22,
"use_full_debug_info": 23,
"set_op_level_0": 24,
"set_op_level_1": 25,
"set_op_level_2": 26,
"set_op_level_3": 27,
"set_op_level_4": 28,
"block_with_shield": 29,
"break_shield": 30,
"pull_hooked_entity": 31,
"hit_armor_stand": 32,
"damage_from_thorns": 33,
"stop_looking_at_villager": 34,
"use_totem_of_undying": 35,
"damage_from_drowning": 36,
"damage_from_fire": 37,
"add_dolphin_happy_villager_particles": 38,
"stun_ravager": 39,
"tame_ocelot_failed": 40,
"tame_ocelot_success": 41,
"add_splash_particles": 42,
"add_cloud_particles": 43,
"damage_from_berry_bush": 44,
"create_eating_particles": 45,
"add_portal_particles": 46,
"break_mainhand": 47,
"break_offhand": 48,
"break_head": 49,
"break_chest": 50,
"break_legs": 51,
"break_feet": 52,
"drip_honey": 53,
"drip_rich_honey": 54,
"swap_hands": 55,
"reset_wolf_shake": 56,
"damage_from_freezing": 57,
"prepare_ram": 58,
"finish_ram": 59,
"add_death_particles": 60,
"ears_twitch": 61,
"sonic_boom": 62
},
"animations": {
"swing_main_hand": 0,
"damage": 1,
"wake_up": 2,
"swing_off_hand": 3,
"crit": 4,
"enchanted_hit": 5
},
"villager_types": {
"desert": 0,
"jungle": 1,
"plains": 2,
"savanna": 3,
"snow": 4,
"swamp": 5,
"taiga": 6
},
"villager_professions": {
"none": 0,
"armorer": 1,
"butcher": 2,
"cartographer": 3,
"cleric": 4,
"farmer": 5,
"fisherman": 6,
"fletcher": 7,
"leatherworker": 8,
"librarian": 9,
"mason": 10,
"nitwit": 11,
"shepherd": 12,
"toolsmith": 13,
"weaponsmith": 14
},
"cat_variants": {
"tabby": 0,
"black": 1,
"red": 2,
"siamese": 3,
"british_shorthair": 4,
"calico": 5,
"persian": 6,
"ragdoll": 7,
"white": 8,
"jellie": 9,
"all_black": 10
},
"frog_variants": {
"temperate": 0,
"warm": 1,
"cold": 2
},
"painting_variants": {
"kebab": {
"id": 0,
"width": 16,
"height": 16
},
"aztec": {
"id": 1,
"width": 16,
"height": 16
},
"alban": {
"id": 2,
"width": 16,
"height": 16
},
"aztec2": {
"id": 3,
"width": 16,
"height": 16
},
"bomb": {
"id": 4,
"width": 16,
"height": 16
},
"plant": {
"id": 5,
"width": 16,
"height": 16
},
"wasteland": {
"id": 6,
"width": 16,
"height": 16
},
"pool": {
"id": 7,
"width": 32,
"height": 16
},
"courbet": {
"id": 8,
"width": 32,
"height": 16
},
"sea": {
"id": 9,
"width": 32,
"height": 16
},
"sunset": {
"id": 10,
"width": 32,
"height": 16
},
"creebet": {
"id": 11,
"width": 32,
"height": 16
},
"wanderer": {
"id": 12,
"width": 16,
"height": 32
},
"graham": {
"id": 13,
"width": 16,
"height": 32
},
"match": {
"id": 14,
"width": 32,
"height": 32
},
"bust": {
"id": 15,
"width": 32,
"height": 32
},
"stage": {
"id": 16,
"width": 32,
"height": 32
},
"void": {
"id": 17,
"width": 32,
"height": 32
},
"skull_and_roses": {
"id": 18,
"width": 32,
"height": 32
},
"wither": {
"id": 19,
"width": 32,
"height": 32
},
"fighters": {
"id": 20,
"width": 64,
"height": 32
},
"pointer": {
"id": 21,
"width": 64,
"height": 64
},
"pigscene": {
"id": 22,
"width": 64,
"height": 64
},
"burning_skull": {
"id": 23,
"width": 64,
"height": 64
},
"skeleton": {
"id": 24,
"width": 64,
"height": 48
},
"earth": {
"id": 25,
"width": 32,
"height": 32
},
"wind": {
"id": 26,
"width": 32,
"height": 32
},
"water": {
"id": 27,
"width": 32,
"height": 32
},
"fire": {
"id": 28,
"width": 32,
"height": 32
},
"donkey_kong": {
"id": 29,
"width": 64,
"height": 48
}
},
"facing": {
"down": 0,
"up": 1,
"north": 2,
"south": 3,
"west": 4,
"east": 5
},
"poses": {
"standing": 0,
"fall_flying": 1,
"sleeping": 2,
"swimming": 3,
"spin_attack": 4,
"crouching": 5,
"long_jumping": 6,
"dying": 7,
"croaking": 8,
"using_tongue": 9,
"roaring": 10,
"sniffing": 11,
"emerging": 12,
"digging": 13
},
"particle_types": {
"ambient_entity_effect": 0,
"angry_villager": 1,
"block": 2,
"block_marker": 3,
"bubble": 4,
"cloud": 5,
"crit": 6,
"damage_indicator": 7,
"dragon_breath": 8,
"dripping_lava": 9,
"falling_lava": 10,
"landing_lava": 11,
"dripping_water": 12,
"falling_water": 13,
"dust": 14,
"dust_color_transition": 15,
"effect": 16,
"elder_guardian": 17,
"enchanted_hit": 18,
"enchant": 19,
"end_rod": 20,
"entity_effect": 21,
"explosion_emitter": 22,
"explosion": 23,
"sonic_boom": 24,
"falling_dust": 25,
"firework": 26,
"fishing": 27,
"flame": 28,
"sculk_soul": 29,
"sculk_charge": 30,
"sculk_charge_pop": 31,
"soul_fire_flame": 32,
"soul": 33,
"flash": 34,
"happy_villager": 35,
"composter": 36,
"heart": 37,
"instant_effect": 38,
"item": 39,
"vibration": 40,
"item_slime": 41,
"item_snowball": 42,
"large_smoke": 43,
"lava": 44,
"mycelium": 45,
"note": 46,
"poof": 47,
"portal": 48,
"rain": 49,
"smoke": 50,
"sneeze": 51,
"spit": 52,
"squid_ink": 53,
"sweep_attack": 54,
"totem_of_undying": 55,
"underwater": 56,
"splash": 57,
"witch": 58,
"bubble_pop": 59,
"current_down": 60,
"bubble_column_up": 61,
"nautilus": 62,
"dolphin": 63,
"campfire_cosy_smoke": 64,
"campfire_signal_smoke": 65,
"dripping_honey": 66,
"falling_honey": 67,
"landing_honey": 68,
"falling_nectar": 69,
"falling_spore_blossom": 70,
"ash": 71,
"crimson_spore": 72,
"warped_spore": 73,
"spore_blossom_air": 74,
"dripping_obsidian_tear": 75,
"falling_obsidian_tear": 76,
"landing_obsidian_tear": 77,
"reverse_portal": 78,
"white_ash": 79,
"small_flame": 80,
"snowflake": 81,
"dripping_dripstone_lava": 82,
"falling_dripstone_lava": 83,
"dripping_dripstone_water": 84,
"falling_dripstone_water": 85,
"glow_squid_ink": 86,
"glow": 87,
"wax_on": 88,
"wax_off": 89,
"electric_spark": 90,
"scrape": 91,
"shriek": 92
}
}

View file

@ -290,9 +290,16 @@ public class Entities implements Main.Extractor {
var parent = entityClass.getSuperclass();
var hasParent = parent != null && Entity.class.isAssignableFrom(parent);
entityJson.addProperty("parent", hasParent ? parent.getSimpleName() : null);
entityJson.add("translation_key", entityType != null ? new JsonPrimitive(entityType.getTranslationKey()) : null);
if (hasParent) {
entityJson.addProperty("parent", parent.getSimpleName());
}
if (entityType != null) {
entityJson.addProperty("type", Registry.ENTITY_TYPE.getId(entityType).getPath());
entityJson.add("translation_key", new JsonPrimitive(entityType.getTranslationKey()));
}
var fieldsJson = new JsonArray();
for (var entityField : entityClass.getDeclaredFields()) {

View file

@ -22,6 +22,12 @@ public class EntityData implements Main.Extractor {
public JsonElement extract() throws Exception {
var dataJson = new JsonObject();
var typesJson = new JsonObject();
for (var type : Registry.ENTITY_TYPE) {
typesJson.addProperty(Registry.ENTITY_TYPE.getId(type).getPath(), Registry.ENTITY_TYPE.getRawId(type));
}
dataJson.add("types", typesJson);
var statusesJson = new JsonObject();
for (var field : EntityStatuses.class.getDeclaredFields()) {
if (field.canAccess(null) && field.get(null) instanceof Byte code) {

View file

@ -1,7 +1,5 @@
//! Connections to the server after logging in.
/// Contains the [`Event`] enum and related data types.
mod event;
use std::collections::{HashSet, VecDeque};
use std::iter::FusedIterator;
use std::time::Duration;
@ -18,8 +16,11 @@ use crate::block_pos::BlockPos;
use crate::chunk_pos::ChunkPos;
use crate::config::Config;
use crate::dimension::DimensionId;
use crate::entity::types::Player;
use crate::entity::{velocity_to_packet_units, Entities, Entity, EntityId, EntityKind};
use crate::entity::kinds::Player;
use crate::entity::{
velocity_to_packet_units, Entities, EntityId, EntityKind, Event as EntityEvent,
StatusOrAnimation,
};
use crate::player_textures::SignedPlayerTextures;
use crate::protocol_inner::packets::play::c2s::{
C2sPlayPacket, DiggingStatus, InteractKind, PlayerCommandId,
@ -28,12 +29,12 @@ pub use crate::protocol_inner::packets::play::s2c::SetTitleAnimationTimes as Tit
use crate::protocol_inner::packets::play::s2c::{
Animate, BiomeRegistry, BlockChangeAck, ChatType, ChatTypeChat, ChatTypeNarration,
ChatTypeRegistry, ChatTypeRegistryEntry, ClearTitles, DimensionTypeRegistry,
DimensionTypeRegistryEntry, Disconnect, EntityEvent, ForgetLevelChunk, GameEvent,
DimensionTypeRegistryEntry, Disconnect, EntityStatus, ForgetLevelChunk, GameEvent,
GameEventReason, KeepAlive, Login, MoveEntityPosition, MoveEntityPositionAndRotation,
MoveEntityRotation, PlayerPosition, PlayerPositionFlags, RegistryCodec, RemoveEntities,
Respawn, RotateHead, S2cPlayPacket, SetChunkCacheCenter, SetChunkCacheRadius,
SetEntityMetadata, SetEntityMotion, SetSubtitleText, SetTitleText, SpawnPosition, SystemChat,
TeleportEntity, UpdateAttributes, UpdateAttributesProperty, ENTITY_EVENT_MAX_BOUND,
TeleportEntity, UpdateAttributes, UpdateAttributesProperty,
};
use crate::protocol_inner::{BoundedInt, ByteAngle, Nbt, RawBytes, VarInt};
use crate::server::{C2sPacketChannels, NewClientData, SharedServer};
@ -43,6 +44,9 @@ use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
use crate::world::{WorldId, Worlds};
use crate::{ident, Ticks, LIBRARY_NAMESPACE, STANDARD_TPS};
/// Contains the [`Event`] enum and related data types.
mod event;
/// A container for all [`Client`]s on a [`Server`](crate::server::Server).
///
/// New clients are automatically inserted into this container but
@ -211,6 +215,7 @@ pub struct Client<C: Config> {
flags: ClientFlags,
/// The data for the client's own player entity.
player_data: Player,
entity_events: Vec<EntityEvent>,
}
#[bitfield(u16)]
@ -280,6 +285,7 @@ impl<C: Config> Client<C> {
.with_modified_spawn_position(true)
.with_got_keepalive(true),
player_data: Player::new(),
entity_events: Vec::new(),
}
}
@ -522,6 +528,10 @@ impl<C: Config> Client<C> {
self.events.pop_front()
}
pub fn trigger_entity_event(&mut self, event: EntityEvent) {
self.entity_events.push(event);
}
/// The current view distance of this client measured in chunks.
pub fn view_distance(&self) -> u8 {
self.settings
@ -1193,7 +1203,7 @@ impl<C: Config> Client<C> {
if let Some(entity) = entities.get(id) {
debug_assert!(entity.kind() != EntityKind::Marker);
if self.new_position.distance(entity.position()) <= view_dist as f64 * 16.0 {
if let Some(meta) = entity.updated_metadata_packet(id) {
if let Some(meta) = entity.updated_tracked_data_packet(id) {
send_packet(&mut self.send, meta);
}
@ -1273,7 +1283,7 @@ impl<C: Config> Client<C> {
)
}
send_entity_events(&mut self.send, id, entity);
send_entity_events(&mut self.send, id.to_network_id(), entity.events());
return true;
}
@ -1291,7 +1301,7 @@ impl<C: Config> Client<C> {
// Update the client's own player metadata.
let mut data = Vec::new();
self.player_data.updated_metadata(&mut data);
self.player_data.updated_tracked_data(&mut data);
if !data.is_empty() {
data.push(0xff);
@ -1320,29 +1330,18 @@ impl<C: Config> Client<C> {
.expect("should not be a marker entity"),
);
if let Some(meta) = entity.initial_metadata_packet(id) {
if let Some(meta) = entity.initial_tracked_data_packet(id) {
self.send_packet(meta);
}
send_entity_events(&mut self.send, id, entity);
send_entity_events(&mut self.send, id.to_network_id(), entity.events());
}
None
},
);
for &code in self.player_data.event_codes() {
if code <= ENTITY_EVENT_MAX_BOUND as u8 {
send_packet(
&mut self.send,
EntityEvent {
entity_id: 0,
entity_status: BoundedInt(code),
},
);
}
// Don't bother sending animations for self since it shouldn't have
// any effect.
}
send_entity_events(&mut self.send, 0, &self.entity_events);
self.entity_events.clear();
self.player_data.clear_modifications();
self.old_position = self.new_position;
@ -1366,24 +1365,23 @@ fn send_packet(send_opt: &mut SendOpt, pkt: impl Into<S2cPlayPacket>) {
}
}
fn send_entity_events<C: Config>(send_opt: &mut SendOpt, id: EntityId, entity: &Entity<C>) {
for &code in entity.state.event_codes() {
if code <= ENTITY_EVENT_MAX_BOUND as u8 {
send_packet(
fn send_entity_events(send_opt: &mut SendOpt, entity_id: i32, events: &[EntityEvent]) {
for &event in events {
match event.status_or_animation() {
StatusOrAnimation::Status(code) => send_packet(
send_opt,
EntityEvent {
entity_id: id.to_network_id(),
entity_status: BoundedInt(code),
EntityStatus {
entity_id,
entity_status: code,
},
);
} else {
send_packet(
),
StatusOrAnimation::Animation(code) => send_packet(
send_opt,
Animate {
entity_id: VarInt(id.to_network_id()),
animation: BoundedInt(code - ENTITY_EVENT_MAX_BOUND as u8 - 1),
entity_id: VarInt(entity_id),
animation: code,
},
)
),
}
}
}

View file

@ -1,16 +1,13 @@
//! Dynamic actors in a world.
pub mod state;
pub mod types;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::iter::FusedIterator;
use std::num::NonZeroU32;
use bitfield_struct::bitfield;
pub use kinds::{EntityEnum, EntityKind};
use rayon::iter::ParallelIterator;
pub use types::{EntityKind, EntityState};
use uuid::Uuid;
use vek::{Aabb, Vec3};
@ -24,6 +21,11 @@ use crate::util::aabb_from_bottom_and_size;
use crate::world::WorldId;
use crate::STANDARD_TPS;
pub mod data;
pub mod kinds;
include!(concat!(env!("OUT_DIR"), "/entity_event.rs"));
/// A container for all [`Entity`]s on a [`Server`](crate::server::Server).
///
/// # Spawning Player Entities
@ -72,7 +74,8 @@ impl<C: Config> Entities<C> {
Entry::Vacant(ve) => {
let (k, e) = self.sm.insert(Entity {
data,
state: EntityState::new(kind),
variants: EntityEnum::new(kind),
events: Vec::new(),
flags: EntityFlags(0),
world: WorldId::NULL,
new_position: Vec3::default(),
@ -197,7 +200,8 @@ impl<C: Config> Entities<C> {
pub(crate) fn update(&mut self) {
for (_, e) in self.iter_mut() {
e.old_position = e.new_position;
e.state.clear_modifications();
e.variants.clear_modifications();
e.events.clear();
e.flags.set_yaw_or_pitch_modified(false);
e.flags.set_head_yaw_modified(false);
@ -239,9 +243,9 @@ impl EntityId {
pub struct Entity<C: Config> {
/// Custom data.
pub data: C::EntityData,
/// Kind-specific state for this entity.
pub state: EntityState,
variants: EntityEnum,
flags: EntityFlags,
events: Vec<Event>,
world: WorldId,
new_position: Vec3<f64>,
old_position: Vec3<f64>,
@ -267,9 +271,25 @@ impl<C: Config> Entity<C> {
self.flags
}
pub fn view(&self) -> &EntityEnum {
&self.variants
}
pub fn view_mut(&mut self) -> &mut EntityEnum {
&mut self.variants
}
/// Gets the [`EntityKind`] of this entity.
pub fn kind(&self) -> EntityKind {
self.state.kind()
self.variants.kind()
}
pub fn trigger_event(&mut self, event: Event) {
self.events.push(event);
}
pub(crate) fn events(&self) -> &[Event] {
&self.events
}
/// Gets the [`WorldId`](crate::world::WorldId) of the world this entity is
@ -387,18 +407,18 @@ impl<C: Config> Entity<C> {
///
/// [interact event]: crate::client::Event::InteractWithEntity
pub fn hitbox(&self) -> Aabb<f64> {
let dims = match &self.state {
EntityState::Allay(_) => [0.6, 0.35, 0.6],
EntityState::ChestBoat(_) => [1.375, 0.5625, 1.375],
EntityState::Frog(_) => [0.5, 0.5, 0.5],
EntityState::Tadpole(_) => [0.4, 0.3, 0.4],
EntityState::Warden(_) => [0.9, 2.9, 0.9],
EntityState::AreaEffectCloud(e) => [
let dims = match &self.variants {
EntityEnum::Allay(_) => [0.6, 0.35, 0.6],
EntityEnum::ChestBoat(_) => [1.375, 0.5625, 1.375],
EntityEnum::Frog(_) => [0.5, 0.5, 0.5],
EntityEnum::Tadpole(_) => [0.4, 0.3, 0.4],
EntityEnum::Warden(_) => [0.9, 2.9, 0.9],
EntityEnum::AreaEffectCloud(e) => [
e.get_radius() as f64 * 2.0,
0.5,
e.get_radius() as f64 * 2.0,
],
EntityState::ArmorStand(e) => {
EntityEnum::ArmorStand(e) => {
if e.get_marker() {
[0.0, 0.0, 0.0]
} else if e.get_small() {
@ -407,160 +427,171 @@ impl<C: Config> Entity<C> {
[0.5, 1.975, 0.5]
}
}
EntityState::Arrow(_) => [0.5, 0.5, 0.5],
EntityState::Axolotl(_) => [1.3, 0.6, 1.3],
EntityState::Bat(_) => [0.5, 0.9, 0.5],
EntityState::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityState::Blaze(_) => [0.6, 1.8, 0.6],
EntityState::Boat(_) => [1.375, 0.5625, 1.375],
EntityState::Cat(_) => [0.6, 0.7, 0.6],
EntityState::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityState::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityState::Cod(_) => [0.5, 0.3, 0.5],
EntityState::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityState::Creeper(_) => [0.6, 1.7, 0.6],
EntityState::Dolphin(_) => [0.9, 0.6, 0.9],
EntityState::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityState::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityState::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityState::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityState::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityState::Enderman(_) => [0.6, 2.9, 0.6],
EntityState::Endermite(_) => [0.4, 0.3, 0.4],
EntityState::Evoker(_) => [0.6, 1.95, 0.6],
EntityState::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityState::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityState::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityState::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityState::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityState::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Ghast(_) => [4.0, 4.0, 4.0],
EntityState::Giant(_) => [3.6, 12.0, 3.6],
EntityState::GlowItemFrame(_) => todo!("account for rotation"),
EntityState::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityState::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityState::Guardian(_) => [0.85, 0.85, 0.85],
EntityState::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityState::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Illusioner(_) => [0.6, 1.95, 0.6],
EntityState::IronGolem(_) => [1.4, 2.7, 1.4],
EntityState::Item(_) => [0.25, 0.25, 0.25],
EntityState::ItemFrame(_) => todo!("account for rotation"),
EntityState::Fireball(_) => [1.0, 1.0, 1.0],
EntityState::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityState::LightningBolt(_) => [0.0, 0.0, 0.0],
EntityState::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityState::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityState::MagmaCube(e) => {
let s = e.get_size() as f64 * 0.51000005;
EntityEnum::Arrow(_) => [0.5, 0.5, 0.5],
EntityEnum::Axolotl(_) => [1.3, 0.6, 1.3],
EntityEnum::Bat(_) => [0.5, 0.9, 0.5],
EntityEnum::Bee(_) => [0.7, 0.6, 0.7], // TODO: baby size?
EntityEnum::Blaze(_) => [0.6, 1.8, 0.6],
EntityEnum::Boat(_) => [1.375, 0.5625, 1.375],
EntityEnum::Cat(_) => [0.6, 0.7, 0.6],
EntityEnum::CaveSpider(_) => [0.7, 0.5, 0.7],
EntityEnum::Chicken(_) => [0.4, 0.7, 0.4], // TODO: baby size?
EntityEnum::Cod(_) => [0.5, 0.3, 0.5],
EntityEnum::Cow(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityEnum::Creeper(_) => [0.6, 1.7, 0.6],
EntityEnum::Dolphin(_) => [0.9, 0.6, 0.9],
EntityEnum::Donkey(_) => [1.5, 1.39648, 1.5], // TODO: baby size?
EntityEnum::DragonFireball(_) => [1.0, 1.0, 1.0],
EntityEnum::Drowned(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::ElderGuardian(_) => [1.9975, 1.9975, 1.9975],
EntityEnum::EndCrystal(_) => [2.0, 2.0, 2.0],
EntityEnum::EnderDragon(_) => [16.0, 8.0, 16.0],
EntityEnum::Enderman(_) => [0.6, 2.9, 0.6],
EntityEnum::Endermite(_) => [0.4, 0.3, 0.4],
EntityEnum::Evoker(_) => [0.6, 1.95, 0.6],
EntityEnum::EvokerFangs(_) => [0.5, 0.8, 0.5],
EntityEnum::ExperienceOrb(_) => [0.5, 0.5, 0.5],
EntityEnum::EyeOfEnder(_) => [0.25, 0.25, 0.25],
EntityEnum::FallingBlock(_) => [0.98, 0.98, 0.98],
EntityEnum::FireworkRocket(_) => [0.25, 0.25, 0.25],
EntityEnum::Fox(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityEnum::Ghast(_) => [4.0, 4.0, 4.0],
EntityEnum::Giant(_) => [3.6, 12.0, 3.6],
EntityEnum::GlowItemFrame(_) => todo!("account for rotation"),
EntityEnum::GlowSquid(_) => [0.8, 0.8, 0.8],
EntityEnum::Goat(_) => [1.3, 0.9, 1.3], // TODO: baby size?
EntityEnum::Guardian(_) => [0.85, 0.85, 0.85],
EntityEnum::Hoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityEnum::Horse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityEnum::Husk(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::Illusioner(_) => [0.6, 1.95, 0.6],
EntityEnum::IronGolem(_) => [1.4, 2.7, 1.4],
EntityEnum::Item(_) => [0.25, 0.25, 0.25],
EntityEnum::ItemFrame(_) => todo!("account for rotation"),
EntityEnum::Fireball(_) => [1.0, 1.0, 1.0],
EntityEnum::LeashKnot(_) => [0.375, 0.5, 0.375],
EntityEnum::Lightning(_) => [0.0, 0.0, 0.0],
EntityEnum::Llama(_) => [0.9, 1.87, 0.9], // TODO: baby size?
EntityEnum::LlamaSpit(_) => [0.25, 0.25, 0.25],
EntityEnum::MagmaCube(e) => {
let s = e.get_slime_size() as f64 * 0.51000005;
[s, s, s]
}
EntityState::Marker(_) => [0.0, 0.0, 0.0],
EntityState::Minecart(_) => [0.98, 0.7, 0.98],
EntityState::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityState::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityState::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityState::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityState::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityState::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityState::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityState::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Painting(_) => todo!("account for rotation and type"),
EntityState::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityState::Parrot(_) => [0.5, 0.9, 0.5],
EntityState::Phantom(_) => [0.9, 0.5, 0.9],
EntityState::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityState::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityState::Pillager(_) => [0.6, 1.95, 0.6],
EntityState::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityState::Tnt(_) => [0.98, 0.98, 0.98],
EntityState::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityState::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityState::Ravager(_) => [1.95, 2.2, 1.95],
EntityState::Salmon(_) => [0.7, 0.4, 0.7],
EntityState::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityState::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityState::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityState::Silverfish(_) => [0.4, 0.3, 0.4],
EntityState::Skeleton(_) => [0.6, 1.99, 0.6],
EntityState::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::Slime(e) => {
let s = 0.51000005 * e.get_size() as f64;
EntityEnum::Marker(_) => [0.0, 0.0, 0.0],
EntityEnum::Minecart(_) => [0.98, 0.7, 0.98],
EntityEnum::ChestMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::CommandBlockMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::FurnaceMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::HopperMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::SpawnerMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::TntMinecart(_) => [0.98, 0.7, 0.98],
EntityEnum::Mule(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityEnum::Mooshroom(_) => [0.9, 1.4, 0.9], // TODO: baby size?
EntityEnum::Ocelot(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityEnum::Painting(_) => todo!("account for rotation and type"),
EntityEnum::Panda(_) => [0.6, 0.7, 0.6], // TODO: baby size?
EntityEnum::Parrot(_) => [0.5, 0.9, 0.5],
EntityEnum::Phantom(_) => [0.9, 0.5, 0.9],
EntityEnum::Pig(_) => [0.9, 0.9, 0.9], // TODO: baby size?
EntityEnum::Piglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::PiglinBrute(_) => [0.6, 1.95, 0.6],
EntityEnum::Pillager(_) => [0.6, 1.95, 0.6],
EntityEnum::PolarBear(_) => [1.4, 1.4, 1.4], // TODO: baby size?
EntityEnum::Tnt(_) => [0.98, 0.98, 0.98],
EntityEnum::Pufferfish(_) => [0.7, 0.7, 0.7],
EntityEnum::Rabbit(_) => [0.4, 0.5, 0.4], // TODO: baby size?
EntityEnum::Ravager(_) => [1.95, 2.2, 1.95],
EntityEnum::Salmon(_) => [0.7, 0.4, 0.7],
EntityEnum::Sheep(_) => [0.9, 1.3, 0.9], // TODO: baby size?
EntityEnum::Shulker(_) => [1.0, 1.0, 1.0], // TODO: how is height calculated?
EntityEnum::ShulkerBullet(_) => [0.3125, 0.3125, 0.3125],
EntityEnum::Silverfish(_) => [0.4, 0.3, 0.4],
EntityEnum::Skeleton(_) => [0.6, 1.99, 0.6],
EntityEnum::SkeletonHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityEnum::Slime(e) => {
let s = 0.51000005 * e.get_slime_size() as f64;
[s, s, s]
}
EntityState::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityState::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityState::Snowball(_) => [0.25, 0.25, 0.25],
EntityState::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityState::Spider(_) => [1.4, 0.9, 1.4],
EntityState::Squid(_) => [0.8, 0.8, 0.8],
EntityState::Stray(_) => [0.6, 1.99, 0.6],
EntityState::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityState::Egg(_) => [0.25, 0.25, 0.25],
EntityState::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityState::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityState::Potion(_) => [0.25, 0.25, 0.25],
EntityState::Trident(_) => [0.5, 0.5, 0.5],
EntityState::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityState::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityState::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityState::Vex(_) => [0.4, 0.8, 0.4],
EntityState::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Vindicator(_) => [0.6, 1.95, 0.6],
EntityState::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityState::Witch(_) => [0.6, 1.95, 0.6],
EntityState::Wither(_) => [0.9, 3.5, 0.9],
EntityState::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityState::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityState::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityState::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityState::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityState::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityState::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityState::FishingBobber(_) => [0.25, 0.25, 0.25],
EntityEnum::SmallFireball(_) => [0.3125, 0.3125, 0.3125],
EntityEnum::SnowGolem(_) => [0.7, 1.9, 0.7],
EntityEnum::Snowball(_) => [0.25, 0.25, 0.25],
EntityEnum::SpectralArrow(_) => [0.5, 0.5, 0.5],
EntityEnum::Spider(_) => [1.4, 0.9, 1.4],
EntityEnum::Squid(_) => [0.8, 0.8, 0.8],
EntityEnum::Stray(_) => [0.6, 1.99, 0.6],
EntityEnum::Strider(_) => [0.9, 1.7, 0.9], // TODO: baby size?
EntityEnum::Egg(_) => [0.25, 0.25, 0.25],
EntityEnum::EnderPearl(_) => [0.25, 0.25, 0.25],
EntityEnum::ExperienceBottle(_) => [0.25, 0.25, 0.25],
EntityEnum::Potion(_) => [0.25, 0.25, 0.25],
EntityEnum::Trident(_) => [0.5, 0.5, 0.5],
EntityEnum::TraderLlama(_) => [0.9, 1.87, 0.9],
EntityEnum::TropicalFish(_) => [0.5, 0.4, 0.5],
EntityEnum::Turtle(_) => [1.2, 0.4, 1.2], // TODO: baby size?
EntityEnum::Vex(_) => [0.4, 0.8, 0.4],
EntityEnum::Villager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::Vindicator(_) => [0.6, 1.95, 0.6],
EntityEnum::WanderingTrader(_) => [0.6, 1.95, 0.6],
EntityEnum::Witch(_) => [0.6, 1.95, 0.6],
EntityEnum::Wither(_) => [0.9, 3.5, 0.9],
EntityEnum::WitherSkeleton(_) => [0.7, 2.4, 0.7],
EntityEnum::WitherSkull(_) => [0.3125, 0.3125, 0.3125],
EntityEnum::Wolf(_) => [0.6, 0.85, 0.6], // TODO: baby size?
EntityEnum::Zoglin(_) => [1.39648, 1.4, 1.39648], // TODO: baby size?
EntityEnum::Zombie(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::ZombieHorse(_) => [1.39648, 1.6, 1.39648], // TODO: baby size?
EntityEnum::ZombieVillager(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::ZombifiedPiglin(_) => [0.6, 1.95, 0.6], // TODO: baby size?
EntityEnum::Player(_) => [0.6, 1.8, 0.6], // TODO: changes depending on the pose.
EntityEnum::FishingBobber(_) => [0.25, 0.25, 0.25],
};
aabb_from_bottom_and_size(self.new_position, dims.into())
}
/// Gets the metadata packet to send to clients after this entity has been
/// spawned.
/// Gets the tracked data packet to send to clients after this entity has
/// been spawned.
///
/// Is `None` if there is no initial metadata.
pub(crate) fn initial_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.state.initial_metadata().map(|meta| SetEntityMetadata {
/// Returns `None` if all the tracked data is at its default values.
pub(crate) fn initial_tracked_data_packet(
&self,
this_id: EntityId,
) -> Option<SetEntityMetadata> {
self.variants
.initial_tracked_data()
.map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
}
/// Gets the metadata packet to send to clients when the entity is modified.
/// Gets the tracked data packet to send to clients when the entity is
/// modified.
///
/// Is `None` if this entity's metadata has not been modified.
pub(crate) fn updated_metadata_packet(&self, this_id: EntityId) -> Option<SetEntityMetadata> {
self.state.updated_metadata().map(|meta| SetEntityMetadata {
/// Returns `None` if this entity's tracked data has not been modified.
pub(crate) fn updated_tracked_data_packet(
&self,
this_id: EntityId,
) -> Option<SetEntityMetadata> {
self.variants
.updated_tracked_data()
.map(|meta| SetEntityMetadata {
entity_id: VarInt(this_id.to_network_id()),
metadata: RawBytes(meta),
})
}
pub(crate) fn spawn_packet(&self, this_id: EntityId) -> Option<EntitySpawnPacket> {
match &self.state {
EntityState::Marker(_) => None,
EntityState::ExperienceOrb(_) => {
match &self.variants {
EntityEnum::Marker(_) => None,
EntityEnum::ExperienceOrb(_) => {
Some(EntitySpawnPacket::ExperienceOrb(AddExperienceOrb {
entity_id: VarInt(this_id.to_network_id()),
position: self.new_position,
count: 0, // TODO
}))
}
EntityState::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
EntityEnum::Player(_) => Some(EntitySpawnPacket::Player(AddPlayer {
entity_id: VarInt(this_id.to_network_id()),
player_uuid: self.uuid,
position: self.new_position,

View file

@ -1,35 +1,63 @@
//! Types used in [`EntityData`](crate::entity::EntityData).
//! Primitive types used in getters and setters on entities.
use std::io::Write;
use std::io::{Read, Write};
use crate::protocol_inner::{Encode, VarInt};
use crate::protocol_inner::{Decode, Encode, VarInt};
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct ArmorStandRotations {
/// Rotation on the X axis in degrees.
pub x: f32,
/// Rotation on the Y axis in degrees.
pub y: f32,
/// Rotation on the Z axis in degrees.
pub z: f32,
}
/// Represents an optional `u32` value excluding [`u32::MAX`].
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct OptionalInt(u32);
impl ArmorStandRotations {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
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)
}
}
impl Encode for ArmorStandRotations {
impl Encode for OptionalInt {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.x.encode(w)?;
self.y.encode(w)?;
self.z.encode(w)
VarInt(self.0 as i32).encode(w)
}
}
impl Decode for OptionalInt {
fn decode(r: &mut impl Read) -> anyhow::Result<Self> {
Ok(Self(VarInt::decode(r)?.0 as u32))
}
}
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct EulerAngle {
pub pitch: f32,
pub yaw: f32,
pub roll: f32,
}
impl EulerAngle {
pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self {
Self { pitch, yaw, roll }
}
}
impl Encode for EulerAngle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
self.pitch.encode(w)?;
self.yaw.encode(w)?;
self.roll.encode(w)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Direction {
pub enum Facing {
Down,
Up,
North,
@ -38,7 +66,7 @@ pub enum Direction {
East,
}
impl Encode for Direction {
impl Encode for Facing {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
@ -206,7 +234,7 @@ impl Encode for FrogKind {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub enum PaintingKind {
#[default]
Default, // TODO
Kebab, // TODO
}
impl Encode for PaintingKind {
@ -214,3 +242,15 @@ impl Encode for PaintingKind {
VarInt(*self as i32).encode(w)
}
}
// TODO
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Particle {
EntityEffect = 21,
}
impl Encode for Particle {
fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> {
VarInt(*self as i32).encode(w)
}
}

View file

@ -1,10 +1,9 @@
//! Contains a struct for each variant in [`EntityKind`].
//! Contains the [`EntityEnum`] and the types for each variant.
#![allow(clippy::all, missing_docs)]
#![allow(clippy::all, missing_docs, trivial_numeric_casts)]
use crate::block::{BlockPos, BlockState};
use crate::entity::state::*;
use crate::entity::EntityId;
use crate::entity::data::*;
use crate::protocol_inner::{Encode, VarInt};
use crate::text::Text;
use crate::uuid::Uuid;

View file

@ -618,7 +618,7 @@ pub mod play {
def_struct! {
Animate 0x03 {
entity_id: VarInt,
animation: BoundedInt<u8, 0, 5>,
animation: u8,
}
}
@ -735,12 +735,10 @@ pub mod play {
}
}
pub const ENTITY_EVENT_MAX_BOUND: i64 = 62;
def_struct! {
EntityEvent 0x18 {
EntityStatus 0x18 {
entity_id: i32,
entity_status: BoundedInt<u8, 1, ENTITY_EVENT_MAX_BOUND>,
entity_status: u8,
}
}
@ -1270,7 +1268,7 @@ pub mod play {
BossEvent,
ClearTitles,
Disconnect,
EntityEvent,
EntityStatus,
ForgetLevelChunk,
GameEvent,
KeepAlive,