//! See: use std::collections::{BTreeMap, HashMap}; use anyhow::Context; use heck::{ToPascalCase, ToSnakeCase}; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; use crate::{ident, write_to_out_path}; struct Class { name: &'static str, inherit: Option<&'static Class>, fields: &'static [Field], } struct Field { name: &'static str, typ: Type, } /// Each variant contains the default value for the field. enum Type { BitFields(&'static [BitField]), Byte(u8), VarInt(i32), Float(f32), String(&'static str), Text, OptText(Option<&'static str>), Slot, Bool(bool), ArmorStandRotations(f32, f32, f32), BlockPos(i32, i32, i32), OptBlockPos(Option<(i32, i32, i32)>), Direction, OptUuid, BlockState, Nbt, Particle, VillagerData, Pose, // ==== Specialized ==== // OptEntityId, BoatVariant, MainHand, } impl Type { pub fn default_expr(&self) -> TokenStream { match self { Type::BitFields(bfs) => { let mut default = 0; for bf in *bfs { default = (bf.default as u8) << bf.offset; } quote! { #default } } Type::Byte(d) => quote! { #d }, Type::VarInt(d) => quote! { VarInt(#d) }, Type::Float(d) => quote! { #d }, Type::String(d) => quote! { #d.into() }, Type::Text => quote! { Default::default() }, Type::OptText(d) => match d { Some(d) => quote! { Some(Box::new(Text::from(#d))) }, None => quote! { None }, }, Type::Slot => quote! { () }, // TODO Type::Bool(d) => quote! { #d }, Type::ArmorStandRotations(x, y, z) => { quote! { ArmorStandRotations::new(#x, #y, #z) } } Type::BlockPos(x, y, z) => quote! { BlockPos::new(#x, #y, #z) }, Type::OptBlockPos(d) => match d { Some((x, y, z)) => quote! { Some(BlockPos::new(#x, #y, #z)) }, None => quote! { None }, }, Type::Direction => quote! { Direction::Down }, Type::OptUuid => quote! { None }, Type::BlockState => quote! { BlockState::AIR }, Type::Nbt => quote! { nbt::Blob::new() }, Type::Particle => quote! { () }, // TODO Type::VillagerData => quote! { VillagerData::default() }, Type::Pose => quote! { Pose::default() }, Type::OptEntityId => quote! { None }, Type::BoatVariant => quote! { BoatVariant::default() }, Type::MainHand => quote! { MainHand::default() }, } } pub fn type_id(&self) -> i32 { match self { Type::BitFields(_) => 0, Type::Byte(_) => 0, Type::VarInt(_) => 1, Type::Float(_) => 2, Type::String(_) => 3, Type::Text => 4, Type::OptText(_) => 5, Type::Slot => 6, Type::Bool(_) => 7, Type::ArmorStandRotations(_, _, _) => 8, Type::BlockPos(_, _, _) => 9, Type::OptBlockPos(_) => 10, Type::Direction => 11, Type::OptUuid => 12, Type::BlockState => 13, Type::Nbt => 14, Type::Particle => 15, Type::VillagerData => 16, Type::Pose => 18, Type::OptEntityId => 17, Type::BoatVariant => 1, Type::MainHand => 0, } } } struct BitField { name: &'static str, offset: u8, default: bool, } const BASE_ENTITY: Class = Class { name: "base_entity", inherit: None, fields: &[ Field { name: "base_entity_flags", typ: Type::BitFields(&[ BitField { name: "on_fire", offset: 0, default: false, }, BitField { name: "crouching", offset: 1, default: false, }, BitField { name: "sprinting", offset: 3, // Skipping unused default: false, }, BitField { name: "swimming", offset: 4, default: false, }, BitField { name: "invisible", offset: 5, default: false, }, BitField { name: "glowing", offset: 6, default: false, }, BitField { name: "elytra_flying", offset: 7, default: false, }, ]), }, Field { name: "air_ticks", typ: Type::VarInt(300), }, Field { name: "custom_name", typ: Type::OptText(None), }, Field { name: "custom_name_visible", typ: Type::Bool(false), }, Field { name: "silent", typ: Type::Bool(false), }, Field { name: "no_gravity", typ: Type::Bool(false), }, Field { name: "pose", typ: Type::Pose, }, Field { name: "frozen_ticks", typ: Type::VarInt(0), }, ], }; const ABSTRACT_ARROW: Class = Class { name: "abstract_arrow", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "abstract_arrow_flags", typ: Type::BitFields(&[ BitField { name: "critical", offset: 0, default: false, }, BitField { name: "noclip", offset: 1, default: false, }, ]), }, Field { name: "piercing_level", typ: Type::Byte(0), }, ], }; const LIVING_ENTITY: Class = Class { name: "living_entity", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "living_entity_flags", typ: Type::BitFields(&[ BitField { name: "hand_active", offset: 0, default: false, }, BitField { name: "active_hand", offset: 1, default: false, }, BitField { name: "riptide_spin_attack", offset: 2, default: false, }, ]), }, Field { name: "health", typ: Type::Float(1.0), }, Field { name: "potion_effect_color", typ: Type::VarInt(0), // TODO: potion effect color type }, Field { name: "potion_effect_ambient", typ: Type::Bool(false), }, Field { name: "arrow_count", typ: Type::VarInt(0), }, Field { name: "bee_stinger_count", typ: Type::VarInt(0), }, Field { name: "bed_sleeping_position", typ: Type::OptBlockPos(None), }, ], }; const MOB: Class = Class { name: "mob", inherit: Some(&LIVING_ENTITY), fields: &[Field { name: "mob_flags", typ: Type::BitFields(&[ BitField { name: "ai_disabled", offset: 0, default: false, }, BitField { name: "left_handed", offset: 1, default: false, }, BitField { name: "aggressive", offset: 2, default: false, }, ]), }], }; const AMBIENT_CREATURE: Class = Class { name: "ambient_creature", inherit: Some(&MOB), fields: &[], }; const PATHFINDER_MOB: Class = Class { name: "pathfinder_mob", inherit: Some(&MOB), fields: &[], }; const WATER_ANIMAL: Class = Class { name: "water_animal", inherit: Some(&PATHFINDER_MOB), fields: &[], }; const ABSTRACT_FISH: Class = Class { name: "abstract_fish", inherit: Some(&WATER_ANIMAL), fields: &[Field { name: "from_bucket", typ: Type::Bool(false), }], }; const AGEABLE_MOB: Class = Class { name: "ageable_mob", inherit: Some(&PATHFINDER_MOB), fields: &[Field { name: "is_baby", typ: Type::Bool(false), }], }; const ANIMAL: Class = Class { name: "animal", inherit: Some(&PATHFINDER_MOB), fields: &[], }; const ABSTRACT_HORSE: Class = Class { name: "abstract_horse", inherit: Some(&ANIMAL), fields: &[ Field { name: "horse_flags", typ: Type::BitFields(&[ BitField { name: "tame", offset: 1, // Skip unused default: false, }, BitField { name: "saddled", offset: 2, default: false, }, BitField { name: "bred", offset: 3, default: false, }, BitField { name: "eating", offset: 4, default: false, }, BitField { name: "rearing", offset: 5, default: false, }, BitField { name: "mouth_open", offset: 6, default: false, }, ]), }, Field { name: "owner", typ: Type::OptUuid, }, ], }; const CHESTED_HORSE: Class = Class { name: "chested_horse", inherit: Some(&ABSTRACT_HORSE), fields: &[Field { name: "has_chest", typ: Type::Bool(false), }], }; const COW: Class = Class { name: "cow", inherit: Some(&ANIMAL), fields: &[], }; const TAMEABLE_ANIMAL: Class = Class { name: "tameable_animal", inherit: Some(&ANIMAL), fields: &[Field { name: "tameable_animal_flags", typ: Type::BitFields(&[ BitField { name: "sitting", offset: 0, default: false, }, BitField { name: "tamed", offset: 2, // Skip unused. default: false, }, ]), }], }; const ABSTRACT_VILLAGER: Class = Class { name: "abstract_villager", inherit: Some(&AGEABLE_MOB), fields: &[Field { name: "head_shake_timer", typ: Type::VarInt(0), }], }; const ABSTRACT_GOLEM: Class = Class { name: "abstract_golem", inherit: Some(&PATHFINDER_MOB), fields: &[], }; const MONSTER: Class = Class { name: "monster", inherit: Some(&PATHFINDER_MOB), fields: &[], }; const BASE_PIGLIN: Class = Class { name: "base_piglin", inherit: Some(&MONSTER), fields: &[Field { name: "zombification_immune", typ: Type::Bool(false), }], }; const GUARDIAN: Class = Class { name: "guardian", inherit: Some(&MONSTER), fields: &[ Field { name: "retracting_spikes", typ: Type::Bool(false), }, Field { name: "target", typ: Type::OptEntityId, }, ], }; const RAIDER: Class = Class { name: "raider", inherit: Some(&MONSTER), fields: &[Field { name: "celebrating", typ: Type::Bool(false), }], }; const ABSTRACT_ILLAGER: Class = Class { name: "abstract_illager", inherit: Some(&RAIDER), fields: &[], }; const SPELLCASTER_ILLAGER: Class = Class { name: "spellcaster_illager", inherit: Some(&ABSTRACT_ILLAGER), fields: &[Field { name: "spellcaster_state", typ: Type::Byte(0), /* TODO: Spell (0: none, 1: summon vex, 2: attack, 3: wololo, 4: * disappear, 5: blindness) */ }], }; const ABSTRACT_SKELETON: Class = Class { name: "abstract_skeleton", inherit: Some(&MONSTER), fields: &[], }; const SPIDER: Class = Class { name: "spider", inherit: Some(&MONSTER), fields: &[Field { name: "spider_flags", typ: Type::BitFields(&[BitField { name: "climbing", offset: 0, default: false, }]), }], }; const ZOMBIE: Class = Class { name: "zombie", inherit: Some(&MONSTER), fields: &[Field { name: "baby", typ: Type::Bool(false), }], }; const FLYING: Class = Class { name: "flying", inherit: Some(&MOB), fields: &[], }; const ABSTRACT_MINECART: Class = Class { name: "abstract minecart", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "shaking_power", typ: Type::VarInt(0), }, Field { name: "shaking_direction", typ: Type::VarInt(1), // TODO: Refined type? }, Field { name: "shaking_multiplier", typ: Type::Float(0.0), }, Field { name: "custom_block_id", typ: Type::VarInt(0), // TODO: is this a BlockState? }, Field { name: "custom_block_y_pos", typ: Type::VarInt(6), // TODO: measured in 16ths of a block. Refined type? }, Field { name: "show_custom_block", typ: Type::Bool(false), }, ], }; const ABSTRACT_MINECART_CONTAINER: Class = Class { name: "abstract_minecart_container", inherit: Some(&ABSTRACT_MINECART), fields: &[], }; const ENTITIES: &[Class] = &[ Class { // TODO: how is this defined? name: "leash_knot", inherit: None, fields: &[], }, Class { // TODO: how is this defined? name: "lightning_bolt", inherit: None, fields: &[], }, Class { name: "experience_orb", inherit: None, fields: &[], }, Class { name: "painting", inherit: None, fields: &[], }, Class { name: "marker", inherit: None, fields: &[], }, Class { name: "item", inherit: Some(&BASE_ENTITY), fields: &[], // TODO: what are the fields? }, Class { name: "egg", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "ender_pearl", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "experience_bottle", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "potion", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "potion", typ: Type::Slot, }], }, Class { name: "snowball", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "eye_of_ender", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "falling_block", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "spawn_position", typ: Type::BlockPos(0, 0, 0), }], }, Class { name: "area_effect_cloud", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "radius", typ: Type::Float(0.5), }, Field { name: "color", typ: Type::VarInt(0), }, Field { name: "ignore_radius", typ: Type::Bool(false), }, Field { name: "particle", typ: Type::Particle, }, ], }, Class { name: "fishing_bobber", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "hooked_entity", typ: Type::OptEntityId, }, Field { name: "catchable", typ: Type::Bool(false), }, ], }, Class { name: "arrow", inherit: Some(&ABSTRACT_ARROW), fields: &[Field { name: "color", typ: Type::VarInt(-1), // TODO: custom type }], }, Class { name: "spectral_arrow", inherit: Some(&ABSTRACT_ARROW), fields: &[], }, Class { name: "trident", inherit: Some(&ABSTRACT_ARROW), fields: &[ Field { name: "loyalty_level", typ: Type::VarInt(0), }, Field { name: "enchantment_glint", typ: Type::Bool(false), }, ], }, Class { name: "boat", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "last_hit_ticks", typ: Type::VarInt(0), }, Field { name: "forward_direction", typ: Type::VarInt(1), // TODO: direction enum? }, Field { name: "damage_taken", typ: Type::Float(0.0), }, Field { name: "typ", typ: Type::BoatVariant, }, Field { name: "left_paddle_turning", typ: Type::Bool(false), }, Field { name: "right_paddle_turning", typ: Type::Bool(false), }, Field { name: "splash_timer", typ: Type::VarInt(0), }, ], }, Class { name: "end_crystal", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "beam_target", typ: Type::OptBlockPos(None), }, Field { name: "show_bottom", typ: Type::Bool(true), }, ], }, Class { name: "dragon_fireball", inherit: Some(&BASE_ENTITY), fields: &[], }, Class { name: "small_fireball", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "fireball", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "item", typ: Type::Slot, }], }, Class { name: "wither_skull", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "invulnerable", typ: Type::Bool(false), }], }, Class { name: "firework_rocket", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "info", typ: Type::Slot, }, Field { name: "used_by", typ: Type::OptEntityId, }, Field { name: "shot_at_angle", typ: Type::Bool(false), }, ], }, Class { name: "item_frame", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "item", typ: Type::Slot, }, Field { name: "rotation", typ: Type::VarInt(0), // TODO: Direction enum? }, ], }, Class { // TODO: How is glow item frame defined? This is a guess. name: "glow_item_frame", inherit: Some(&BASE_ENTITY), fields: &[ Field { name: "item", typ: Type::Slot, }, Field { name: "rotation", typ: Type::VarInt(0), // TODO: Direction enum? }, ], }, Class { name: "player", inherit: Some(&LIVING_ENTITY), fields: &[ Field { name: "additional_hearts", typ: Type::Float(0.0), }, Field { name: "score", typ: Type::VarInt(0), }, Field { name: "displayed_skin_parts", typ: Type::BitFields(&[ BitField { name: "cape_enabled", offset: 0, default: false, }, BitField { name: "jacket_enabled", offset: 1, default: false, }, BitField { name: "left_sleeve_enabled", offset: 2, default: false, }, BitField { name: "right_sleeve_enabled", offset: 3, default: false, }, BitField { name: "left_pants_leg_enabled", offset: 4, default: false, }, BitField { name: "right_pants_leg_enabled", offset: 5, default: false, }, BitField { name: "hat_enabled", offset: 6, default: false, }, ]), }, Field { name: "main_hand", typ: Type::MainHand, }, Field { name: "left_shoulder_entity_data", typ: Type::Nbt, }, Field { name: "right_shoulder_entity_data", typ: Type::Nbt, }, ], }, Class { name: "armor_stand", inherit: Some(&LIVING_ENTITY), fields: &[ Field { name: "armor_stand_flags", typ: Type::BitFields(&[ BitField { name: "small", offset: 0, default: false, }, BitField { name: "arms", offset: 1, default: false, }, BitField { name: "no_baseplate", offset: 2, default: false, }, BitField { name: "marker", offset: 3, default: false, }, ]), }, Field { name: "head_rotation", typ: Type::ArmorStandRotations(0.0, 0.0, 0.0), }, Field { name: "body_rotation", typ: Type::ArmorStandRotations(0.0, 0.0, 0.0), }, Field { name: "left_arm_rotation", typ: Type::ArmorStandRotations(-10.0, 0.0, -10.0), }, Field { name: "right_arm_rotation", typ: Type::ArmorStandRotations(-15.0, 0.0, -10.0), }, Field { name: "left_leg_rotation", typ: Type::ArmorStandRotations(-1.0, 0.0, -1.0), }, Field { name: "right_leg_rotation", typ: Type::ArmorStandRotations(1.0, 0.0, 1.0), }, ], }, Class { name: "bat", inherit: Some(&AMBIENT_CREATURE), fields: &[Field { name: "bat_flags", typ: Type::BitFields(&[BitField { name: "hanging", offset: 0, default: false, }]), }], }, Class { name: "squid", inherit: Some(&WATER_ANIMAL), fields: &[], }, Class { // TODO: How is glow squid defined? This is a guess. name: "glow_squid", inherit: Some(&WATER_ANIMAL), fields: &[], }, Class { name: "dolphin", inherit: Some(&WATER_ANIMAL), fields: &[ Field { name: "treasure_position", typ: Type::BlockPos(0, 0, 0), }, Field { name: "has_fish", typ: Type::Bool(false), }, Field { name: "moisture_level", typ: Type::VarInt(2400), }, ], }, Class { name: "cod", inherit: Some(&ABSTRACT_FISH), fields: &[], }, Class { name: "pufferfish", inherit: Some(&ABSTRACT_FISH), fields: &[Field { name: "puff_state", typ: Type::VarInt(0), // TODO: PuffState in the range [0, 2]. (Bounded int?) }], }, Class { name: "salmon", inherit: Some(&ABSTRACT_FISH), fields: &[], }, Class { name: "tropical_fish", inherit: Some(&ABSTRACT_FISH), fields: &[Field { name: "variant", typ: Type::VarInt(0), // TODO: TropicalFishVariant enum }], }, Class { name: "horse", inherit: Some(&ABSTRACT_HORSE), fields: &[Field { name: "variant", typ: Type::VarInt(0), // TODO: HorseVariant enum }], }, Class { name: "zombie_horse", inherit: Some(&ABSTRACT_HORSE), fields: &[], }, Class { name: "skeleton_horse", inherit: Some(&ABSTRACT_HORSE), fields: &[], }, Class { name: "donkey", inherit: Some(&CHESTED_HORSE), fields: &[], }, Class { name: "llama", inherit: Some(&CHESTED_HORSE), fields: &[ Field { name: "strength", typ: Type::VarInt(0), // TODO: upper bound? }, Field { name: "carpet_color", typ: Type::VarInt(-1), // TODO: Carpet color enum. }, Field { name: "variant", typ: Type::VarInt(0), // TODO: Llama variant enum. }, ], }, Class { name: "trader_llama", inherit: None, // TODO: really? fields: &[], }, Class { name: "mule", inherit: Some(&CHESTED_HORSE), fields: &[], }, Class { name: "axolotl", inherit: Some(&ANIMAL), fields: &[ Field { name: "variant", typ: Type::VarInt(0), // TODO: AxolotlVariant enum. }, Field { name: "playing_dead", typ: Type::Bool(false), }, Field { name: "from_bucket", typ: Type::Bool(false), }, ], }, Class { name: "bee", inherit: Some(&ANIMAL), fields: &[ Field { name: "bee_flags", typ: Type::BitFields(&[ BitField { name: "angry", offset: 1, // Skip unused. default: false, }, BitField { name: "stung", offset: 2, default: false, }, BitField { name: "nectar", offset: 3, default: false, }, ]), }, Field { name: "anger_ticks", typ: Type::VarInt(0), }, ], }, Class { name: "fox", inherit: Some(&ANIMAL), fields: &[ Field { name: "variant", typ: Type::VarInt(0), // TODO: 0 for red, 1 for snow }, Field { name: "fox_flags", typ: Type::BitFields(&[ BitField { name: "sitting", offset: 0, default: false, }, BitField { name: "fox_crouching", offset: 2, // Skip unused default: false, }, BitField { name: "interested", offset: 3, default: false, }, BitField { name: "pouncing", offset: 4, default: false, }, BitField { name: "sleeping", offset: 5, default: false, }, BitField { name: "faceplanted", offset: 6, default: false, }, BitField { name: "defending", offset: 7, default: false, }, ]), }, // TODO: what are these UUIDs? Field { name: "first_uuid", typ: Type::OptUuid, }, Field { name: "second_uuid", typ: Type::OptUuid, }, ], }, Class { name: "ocelot", inherit: Some(&ANIMAL), fields: &[Field { name: "trusting", typ: Type::Bool(false), }], }, Class { name: "panda", inherit: Some(&ANIMAL), fields: &[ Field { name: "breed_timer", typ: Type::VarInt(0), }, Field { name: "sneeze_timer", typ: Type::VarInt(0), }, Field { name: "eat_timer", typ: Type::VarInt(0), }, Field { name: "main_gene", typ: Type::Byte(0), }, Field { name: "hidden_gene", typ: Type::Byte(0), }, Field { name: "panda_flags", typ: Type::BitFields(&[ BitField { name: "sneezing", offset: 1, // Skip unused. default: false, }, BitField { name: "rolling", offset: 2, default: false, }, BitField { name: "sitting", offset: 3, default: false, }, BitField { name: "on_back", offset: 4, default: false, }, ]), }, ], }, Class { name: "pig", inherit: Some(&ANIMAL), fields: &[ Field { name: "has_saddle", typ: Type::Bool(false), }, Field { name: "boost_timer", typ: Type::VarInt(0), }, ], }, Class { name: "rabbit", inherit: Some(&ANIMAL), fields: &[Field { name: "variant", typ: Type::VarInt(0), // TODO: rabbit variant enum. }], }, Class { name: "turtle", inherit: Some(&ANIMAL), fields: &[ Field { name: "home_position", typ: Type::BlockPos(0, 0, 0), }, Field { name: "has_egg", typ: Type::Bool(false), }, Field { name: "laying_egg", typ: Type::Bool(false), }, Field { name: "travel_pos", typ: Type::BlockPos(0, 0, 0), }, Field { name: "going_home", typ: Type::Bool(false), }, Field { name: "travelling", typ: Type::Bool(false), }, ], }, Class { name: "polar_bear", inherit: Some(&ANIMAL), fields: &[Field { name: "standing_up", typ: Type::Bool(true), }], }, Class { name: "chicken", inherit: Some(&ANIMAL), fields: &[], }, COW, Class { name: "hoglin", inherit: Some(&ANIMAL), fields: &[Field { name: "zombification_immune", typ: Type::Bool(false), }], }, Class { name: "mooshroom", inherit: Some(&COW), fields: &[Field { name: "variant", typ: Type::String("red"), // TODO: "red" or "brown" enum. }], }, Class { name: "sheep", inherit: Some(&ANIMAL), fields: &[Field { name: "sheep_state", typ: Type::Byte(0), // TODO: sheep state type. }], }, Class { name: "goat", inherit: Some(&ANIMAL), fields: &[], // TODO: What are the goat fields? }, Class { name: "strider", inherit: Some(&ANIMAL), fields: &[ Field { name: "boost_timer", typ: Type::VarInt(0), }, Field { name: "shaking", typ: Type::Bool(false), }, Field { name: "saddle", typ: Type::Bool(false), }, ], }, Class { name: "cat", inherit: Some(&TAMEABLE_ANIMAL), fields: &[ Field { name: "variant", typ: Type::VarInt(1), // TODO: cat variant enum. }, Field { name: "lying", typ: Type::Bool(false), }, Field { name: "relaxed", typ: Type::Bool(false), }, Field { name: "collar_color", typ: Type::VarInt(14), // TODO: dye color enum. }, ], }, Class { name: "wolf", inherit: Some(&TAMEABLE_ANIMAL), fields: &[ Field { name: "begging", typ: Type::Bool(false), }, Field { name: "collar_color", // TODO: dye color enum typ: Type::VarInt(14), }, Field { name: "anger_timer", typ: Type::VarInt(0), }, ], }, Class { name: "parrot", inherit: Some(&TAMEABLE_ANIMAL), fields: &[Field { name: "variant", typ: Type::VarInt(0), // TODO: parrot variant enum. }], }, Class { name: "villager", inherit: Some(&ABSTRACT_VILLAGER), fields: &[Field { name: "villager_data", typ: Type::VillagerData, }], }, Class { name: "wandering_trader", inherit: Some(&ABSTRACT_VILLAGER), fields: &[], }, Class { name: "iron_golem", inherit: Some(&ABSTRACT_GOLEM), fields: &[Field { name: "iron_golem_flags", typ: Type::BitFields(&[BitField { name: "player_created", offset: 0, default: false, }]), }], }, Class { name: "snow_golem", inherit: Some(&ABSTRACT_GOLEM), fields: &[Field { name: "snow_golem_flags", typ: Type::BitFields(&[BitField { name: "pumpkin_hat", offset: 4, default: true, }]), }], }, Class { name: "shulker", inherit: Some(&ABSTRACT_GOLEM), fields: &[ Field { name: "attach_face", typ: Type::Direction, }, Field { name: "attachment_position", typ: Type::OptBlockPos(None), }, Field { name: "shield_height", typ: Type::Byte(0), }, Field { name: "color", typ: Type::Byte(10), // TODO: dye color enum }, ], }, Class { // TODO: how is this defined? name: "shulker_bullet", inherit: Some(&BASE_ENTITY), fields: &[], }, Class { name: "piglin", inherit: Some(&BASE_PIGLIN), fields: &[ Field { name: "baby", typ: Type::Bool(false), }, Field { name: "charging_crossbow", typ: Type::Bool(false), }, Field { name: "dancing", typ: Type::Bool(false), }, ], }, Class { name: "piglin_brute", inherit: Some(&BASE_PIGLIN), fields: &[], }, Class { name: "blaze", inherit: Some(&MONSTER), fields: &[Field { name: "blaze_flags", typ: Type::BitFields(&[BitField { name: "blaze_on_fire", // TODO: better name for this? offset: 0, default: false, }]), }], }, Class { name: "creeper", inherit: Some(&MONSTER), fields: &[ Field { name: "creeper_state", typ: Type::VarInt(-1), // TODO -1 for idle, +1 for fuse. }, Field { name: "charged", typ: Type::Bool(false), }, Field { name: "ignited", typ: Type::Bool(false), }, ], }, Class { name: "endermite", inherit: Some(&MONSTER), fields: &[], }, Class { name: "giant", inherit: Some(&MONSTER), fields: &[], }, GUARDIAN, Class { name: "elder_guardian", inherit: Some(&GUARDIAN), fields: &[], }, Class { name: "silverfish", inherit: Some(&MONSTER), fields: &[], }, Class { name: "vindicator", inherit: Some(&ABSTRACT_ILLAGER), fields: &[], }, Class { name: "pillager", inherit: Some(&ABSTRACT_ILLAGER), fields: &[Field { name: "charging", typ: Type::Bool(false), }], }, Class { name: "evoker", inherit: Some(&SPELLCASTER_ILLAGER), fields: &[], }, Class { name: "illusioner", inherit: Some(&SPELLCASTER_ILLAGER), fields: &[], }, Class { name: "ravager", inherit: Some(&RAIDER), fields: &[], }, Class { name: "evoker_fangs", inherit: Some(&BASE_ENTITY), fields: &[], }, Class { name: "witch", inherit: Some(&RAIDER), fields: &[Field { name: "drinking_potion", typ: Type::Bool(false), }], }, Class { name: "vex", inherit: Some(&MONSTER), fields: &[Field { name: "vex_flags", typ: Type::BitFields(&[BitField { name: "attacking", offset: 0, default: false, }]), }], }, Class { name: "skeleton", inherit: Some(&ABSTRACT_SKELETON), fields: &[], }, Class { name: "wither_skeleton", inherit: Some(&ABSTRACT_SKELETON), fields: &[], }, Class { name: "stray", inherit: Some(&ABSTRACT_SKELETON), fields: &[], }, SPIDER, Class { name: "cave_spider", inherit: Some(&SPIDER), // TODO: does cave_spider inherit from spider? fields: &[], }, Class { name: "wither", inherit: Some(&MONSTER), fields: &[ // TODO: are these actually OptEntityId, or something else? Field { name: "center_head_target", typ: Type::OptEntityId, }, Field { name: "left_head_target", typ: Type::OptEntityId, }, Field { name: "right_head_target", typ: Type::OptEntityId, }, Field { name: "invulnerable_time", typ: Type::VarInt(0), }, ], }, Class { name: "zoglin", inherit: Some(&MONSTER), fields: &[Field { name: "baby", typ: Type::Bool(false), }], }, ZOMBIE, Class { name: "zombie_villager", inherit: Some(&ZOMBIE), fields: &[ Field { name: "converting", typ: Type::Bool(false), }, Field { name: "villager_data", typ: Type::VillagerData, }, ], }, Class { name: "husk", inherit: Some(&ZOMBIE), fields: &[], }, Class { name: "drowned", inherit: Some(&ZOMBIE), fields: &[], }, Class { name: "zombified_piglin", inherit: Some(&ZOMBIE), fields: &[], }, Class { name: "enderman", inherit: Some(&MONSTER), fields: &[ Field { name: "carried_block", typ: Type::BlockState, }, Field { name: "screaming", typ: Type::Bool(false), }, Field { name: "staring", typ: Type::Bool(false), }, ], }, Class { name: "ender_dragon", inherit: Some(&MOB), fields: &[Field { name: "phase", typ: Type::VarInt(10), // TODO: dragon phase enum }], }, Class { name: "ghast", inherit: Some(&FLYING), fields: &[Field { name: "attacking", typ: Type::Bool(false), }], }, Class { name: "phantom", inherit: Some(&FLYING), fields: &[Field { name: "size", typ: Type::VarInt(0), }], }, Class { name: "slime", inherit: Some(&MOB), fields: &[Field { name: "size", typ: Type::VarInt(1), // TODO: bounds? }], }, Class { name: "magma_cube", inherit: Some(&MOB), fields: &[Field { name: "size", typ: Type::VarInt(1), }], }, Class { name: "llama_spit", inherit: Some(&BASE_ENTITY), fields: &[], }, Class { name: "minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[], }, Class { name: "hopper_minecart", inherit: Some(&ABSTRACT_MINECART_CONTAINER), fields: &[], }, Class { name: "chest_minecart", inherit: Some(&ABSTRACT_MINECART_CONTAINER), fields: &[], }, Class { name: "furnace_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[Field { name: "has_fuel", typ: Type::Bool(false), }], }, Class { name: "tnt_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[], }, Class { name: "spawner_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[], }, Class { name: "command_block_minecart", inherit: Some(&ABSTRACT_MINECART), fields: &[ Field { name: "command", typ: Type::String(""), }, Field { name: "last_output", typ: Type::Text, }, ], }, Class { name: "tnt", inherit: Some(&BASE_ENTITY), fields: &[Field { name: "fuse_timer", typ: Type::VarInt(80), }], }, ]; pub fn build() -> anyhow::Result<()> { // Sort the entities in ID order, where the IDs are obtained from entities.json. let entities = { let entities: HashMap<_, _> = ENTITIES.iter().map(|c| (c.name, c)).collect(); #[derive(Deserialize)] struct JsonEntity { id: usize, name: String, } let json_entities: Vec = serde_json::from_str(include_str!("../data/entities.json"))?; let mut res = Vec::new(); for (i, e) in json_entities.iter().enumerate() { assert_eq!(e.id, i); let name = e.name.as_str(); res.push( *entities .get(name) .with_context(|| format!("entity \"{name}\" was not defined"))?, ); } assert_eq!(json_entities.len(), entities.len()); res }; let mut all_classes = BTreeMap::new(); for mut class in entities.iter().cloned() { while let None = all_classes.insert(class.name, class) { match class.inherit { Some(parent) => class = parent, None => break, } } } let entity_type_variants = entities .iter() .map(|c| ident(c.name.to_pascal_case())) .collect::>(); /* let set_type_arms = entities.iter().map(|&entity| { let entity_name = ident(entity.name.to_pascal_case()); let mut old_fields = Vec::new(); collect_class_fields(entity, &mut old_fields); let new_type_arms = entities.iter().map(|&new_entity| { let new_entity_name = ident(new_entity.name.to_pascal_case()); let mut new_fields = Vec::new(); collect_class_fields(new_entity, &mut new_fields); let assign_fields = new_fields .iter() .cloned() .filter(|&new_field| old_fields.iter().any(|&f| f.name == new_field.name)) .map(|new_field| { let name = ident(new_field.name.to_snake_case()); quote! { new.#name = old.#name; } }); quote! { EntityType::#new_entity_name => { let mut new = #new_entity_name::new(); #(#assign_fields)* *self = Self::#new_entity_name(new); } } }); quote! { Self::#entity_name(old) => match new_type { #(#new_type_arms)* }, } }); */ let entity_structs = entities.iter().map(|&class| { let mut fields = Vec::new(); collect_class_fields(class, &mut fields); let name = ident(class.name.to_pascal_case()); let struct_fields = fields.iter().map(|&f| { let name = ident(f.name.to_snake_case()); let typ = match f.typ { Type::BitFields(_) => quote! { u8 }, Type::Byte(_) => quote! { u8 }, Type::VarInt(_) => quote! { VarInt }, Type::Float(_) => quote! { f32 }, Type::String(_) => quote! { Box }, Type::Text => quote! { Box }, Type::OptText(_) => quote! { Option> }, Type::Slot => quote! { () }, // TODO Type::Bool(_) => quote! { bool }, Type::ArmorStandRotations(_, _, _) => quote! { ArmorStandRotations }, Type::BlockPos(_, _, _) => quote! { BlockPos }, Type::OptBlockPos(_) => quote! { Option }, Type::Direction => quote! { Direction }, Type::OptUuid => quote! { Option }, Type::BlockState => quote! { BlockState }, Type::Nbt => quote! { nbt::Blob }, Type::Particle => quote! { () }, // TODO Type::VillagerData => quote! { VillagerData }, Type::Pose => quote! { Pose }, Type::OptEntityId => quote! { Option }, Type::BoatVariant => quote! { BoatVariant }, Type::MainHand => quote! { MainHand }, }; quote! { #name: #typ, } }); let constructor_fields = fields.iter().map(|field| { let name = ident(field.name.to_snake_case()); let val = field.typ.default_expr(); quote! { #name: #val, } }); let getter_setters = fields .iter() .enumerate() .map(|(field_offset, field)| { let name = ident(field.name.to_snake_case()); let getter_name = ident(format!("get_{}", name.to_string())); let setter_name = ident(format!("set_{}", name.to_string())); let field_offset = field_offset as u32; // TODO: documentation on methods. let standard_getter_setter = |type_name: TokenStream| quote! { pub fn #getter_name(&self) -> #type_name { self.#name } pub fn #setter_name(&mut self, #name: #type_name) { if self.#name != #name { self.modified_flags |= 1 << #field_offset; } self.#name = #name; } }; match field.typ { Type::BitFields(bfs) => bfs .iter() .map(|bf| { let bit_name = ident(bf.name.to_snake_case()); let getter_name = ident(format!("get_{}", bit_name.to_string())); let setter_name = ident(format!("set_{}", bit_name.to_string())); let offset = bf.offset; quote! { pub fn #getter_name(&self) -> bool { (self.#name >> #offset) & 1 == 1 } pub fn #setter_name(&mut self, #bit_name: bool) { if self.#getter_name() != #bit_name { self.#name = (self.#name & !(1 << #offset)) | ((#bit_name as u8) << #offset); self.modified_flags |= 1 << #field_offset; } } } }) .collect(), Type::Byte(_) => standard_getter_setter(quote!(u8)), Type::VarInt(_) => quote! { pub fn #getter_name(&self) -> i32 { self.#name.0 } pub fn #setter_name(&mut self, #name: i32) { if self.#name.0 != #name { self.modified_flags |= 1 << #field_offset; } self.#name = VarInt(#name); } }, Type::Float(_) => standard_getter_setter(quote!(f32)), Type::String(_) => quote! { pub fn #getter_name(&self) -> &str { &self.#name } pub fn #setter_name(&mut self, #name: impl Into>) { let #name = #name.into(); if self.#name != #name { self.modified_flags |= 1 << #field_offset; } self.#name = #name; } }, Type::Text => quote! { pub fn #getter_name(&self) -> &Text { &self.#name } pub fn #setter_name(&mut self, #name: impl Into) { let #name = Box::new(#name.into()); if self.#name != #name { self.modified_flags |= 1 << #field_offset; } self.#name = #name; } }, Type::OptText(_) => quote! { pub fn #getter_name(&self) -> Option<&Text> { self.#name.as_deref() } pub fn #setter_name(&mut self, #name: Option>) { let #name = #name.map(|x| Box::new(x.into())); if self.#name != #name { self.modified_flags |= 1 << #field_offset; } self.#name = #name; } }, Type::Slot => quote! {}, // TODO Type::Bool(_) => standard_getter_setter(quote!(bool)), Type::ArmorStandRotations(_, _, _) => standard_getter_setter(quote!(ArmorStandRotations)), Type::BlockPos(_, _, _) => standard_getter_setter(quote!(BlockPos)), Type::OptBlockPos(_) => standard_getter_setter(quote!(Option)), Type::Direction => standard_getter_setter(quote!(Direction)), Type::OptUuid => standard_getter_setter(quote!(Option)), Type::BlockState => standard_getter_setter(quote!(BlockState)), Type::Nbt => quote! { pub fn #getter_name(&self) -> &nbt::Blob { &self.#name } pub fn #setter_name(&mut self, #name: nbt::Blob) { if self.#name != #name { self.modified_flags |= 1 << #field_offset; } self.#name = #name; } }, Type::Particle => quote! {}, // TODO Type::VillagerData => standard_getter_setter(quote!(VillagerData)), Type::Pose => standard_getter_setter(quote!(Pose)), Type::OptEntityId => standard_getter_setter(quote!(Option)), Type::BoatVariant => standard_getter_setter(quote!(BoatVariant)), Type::MainHand => standard_getter_setter(quote!(MainHand)), } }) .collect::(); quote! { pub struct #name { /// Contains a set bit for each modified field. modified_flags: u32, #(#struct_fields)* } impl #name { pub(super) fn new() -> Self { Self { modified_flags: 0, #(#constructor_fields)* } } #getter_setters } } }); let initial_metadata_arms = entities.iter().map(|&entity| { let name = ident(entity.name.to_pascal_case()); let mut fields = Vec::new(); collect_class_fields(entity, &mut fields); let check_fields = fields.into_iter().enumerate().map(|(idx, f)| { let name = ident(f.name.to_snake_case()); let default = f.typ.default_expr(); let index: u8 = idx.try_into().unwrap(); let type_id = f.typ.type_id(); quote! { if m.#name != #default { data.push(#index); VarInt(#type_id).encode(&mut data).unwrap(); m.#name.encode(&mut data).unwrap(); } } }); quote! { Self::#name(m) => { #(#check_fields)* } } }); let updated_metadata_arms = entities.iter().map(|&entity| { let name = ident(entity.name.to_pascal_case()); let mut fields = Vec::new(); collect_class_fields(entity, &mut fields); let update_fields = fields.into_iter().enumerate().map(|(idx, f)| { let name = ident(f.name.to_snake_case()); let u8_index: u8 = idx.try_into().unwrap(); let u32_index = idx as u32; let type_id = f.typ.type_id(); quote! { if (m.modified_flags >> #u32_index) & 1 == 1 { data.push(#u8_index); VarInt(#type_id).encode(&mut data).unwrap(); m.#name.encode(&mut data).unwrap(); } } }); quote! { Self::#name(m) => { if m.modified_flags == 0 { return None; } #(#update_fields)* } } }); let finished = quote! { /// Identifies a type of entity, such as `chicken`, `zombie` or `item`. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum EntityType { #(#entity_type_variants,)* } impl Default for EntityType { fn default() -> Self { Self::Marker } } #(#entity_structs)* /// An enum encoding the type of n entity along with its metadata. /// /// Metadata encompases most of an entity's state, except for some /// basic pieces of information such as position and rotation. pub enum EntityMeta { #(#entity_type_variants(#entity_type_variants),)* } impl EntityMeta { pub(super) fn new(typ: EntityType) -> Self { match typ { #(EntityType::#entity_type_variants => Self::#entity_type_variants(#entity_type_variants::new()),)* } } pub(super) fn typ(&self) -> EntityType { match self { #(Self::#entity_type_variants(_) => EntityType::#entity_type_variants,)* } } pub(super) fn initial_metadata(&self) -> Option> { let mut data = Vec::new(); match self { #(#initial_metadata_arms)* } if data.is_empty() { None } else { data.push(0xff); Some(data) } } pub(super) fn updated_metadata(&self) -> Option> { let mut data = Vec::new(); match self { #(#updated_metadata_arms)* } if data.is_empty() { None } else { data.push(0xff); Some(data) } } pub(super) fn clear_modifications(&mut self) { match self { #(Self::#entity_type_variants(m) => m.modified_flags = 0,)* } } } }; write_to_out_path("entity.rs", &finished.to_string()) } fn collect_class_fields(class: &Class, fields: &mut Vec<&'static Field>) { if let Some(parent) = class.inherit { collect_class_fields(parent, fields); } fields.extend(class.fields); }