1mod serializer;
28mod vanilla_serializers;
29
30pub use serializer::{
31 EntityDataSerializerEntry, EntityDataSerializerEntryRef, EntityDataSerializerRegistry,
32 EntityDataWriter,
33};
34pub use vanilla_serializers::register_vanilla_entity_data_serializers;
35
36use std::{io, str::FromStr};
37
38use steel_utils::{
39 BlockStateId, Identifier,
40 codec::VarInt,
41 serial::{ReadFrom, WriteTo},
42};
43use text_components::TextComponent;
44use uuid::Uuid;
45
46use crate::RegistryReference;
47use crate::cat_sound_variant::CatSoundVariant;
48use crate::cat_variant::CatVariant;
49use crate::chicken_sound_variant::ChickenSoundVariant;
50use crate::chicken_variant::ChickenVariant;
51use crate::cow_sound_variant::CowSoundVariant;
52use crate::cow_variant::CowVariant;
53use crate::dye_color::DyeColor;
54use crate::frog_variant::FrogVariant;
55use crate::item_stack::ItemStack;
56use crate::painting_variant::PaintingVariant;
57pub use crate::particle_type::{ColorParticleOption, ParticleData};
58use crate::pig_sound_variant::PigSoundVariant;
59use crate::pig_variant::PigVariant;
60use crate::wolf_sound_variant::WolfSoundVariant;
61use crate::wolf_variant::WolfVariant;
62use crate::zombie_nautilus_variant::ZombieNautilusVariant;
63
64pub use crate::blocks::properties::Direction;
66pub use steel_utils::BlockPos;
67
68#[derive(Debug, Clone)]
73pub struct SyncedValue<T> {
74 value: T,
75 default: T,
76 dirty: bool,
77}
78
79impl<T: Clone + PartialEq> SyncedValue<T> {
80 pub fn new(default: T) -> Self {
82 Self {
83 value: default.clone(),
84 default,
85 dirty: false,
86 }
87 }
88
89 #[inline]
91 pub const fn get(&self) -> &T {
92 &self.value
93 }
94
95 #[inline]
97 pub fn set(&mut self, value: T) {
98 if self.value != value {
99 self.value = value;
100 self.dirty = true;
101 }
102 }
103
104 #[inline]
106 pub const fn is_dirty(&self) -> bool {
107 self.dirty
108 }
109
110 #[inline]
112 pub const fn clear_dirty(&mut self) {
113 self.dirty = false;
114 }
115
116 #[inline]
118 pub fn is_default(&self) -> bool {
119 self.value == self.default
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
125#[repr(u8)]
126pub enum EntityPose {
127 #[default]
128 Standing = 0,
129 FallFlying = 1,
130 Sleeping = 2,
131 Swimming = 3,
132 SpinAttack = 4,
133 Sneaking = 5,
134 LongJumping = 6,
135 Dying = 7,
136 Croaking = 8,
137 UsingTongue = 9,
138 Sitting = 10,
139 Roaring = 11,
140 Sniffing = 12,
141 Emerging = 13,
142 Digging = 14,
143 Sliding = 15,
144 Shooting = 16,
145 Inhaling = 17,
146}
147
148impl FromStr for EntityPose {
149 type Err = ();
150
151 fn from_str(s: &str) -> Result<Self, Self::Err> {
152 match s {
153 "STANDING" => Ok(Self::Standing),
154 "FALL_FLYING" => Ok(Self::FallFlying),
155 "SLEEPING" => Ok(Self::Sleeping),
156 "SWIMMING" => Ok(Self::Swimming),
157 "SPIN_ATTACK" => Ok(Self::SpinAttack),
158 "SNEAKING" | "CROUCHING" => Ok(Self::Sneaking),
159 "LONG_JUMPING" => Ok(Self::LongJumping),
160 "DYING" => Ok(Self::Dying),
161 "CROAKING" => Ok(Self::Croaking),
162 "USING_TONGUE" => Ok(Self::UsingTongue),
163 "SITTING" => Ok(Self::Sitting),
164 "ROARING" => Ok(Self::Roaring),
165 "SNIFFING" => Ok(Self::Sniffing),
166 "EMERGING" => Ok(Self::Emerging),
167 "DIGGING" => Ok(Self::Digging),
168 "SLIDING" => Ok(Self::Sliding),
169 "SHOOTING" => Ok(Self::Shooting),
170 "INHALING" => Ok(Self::Inhaling),
171 _ => Err(()),
172 }
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq)]
178pub struct Rotations {
179 pub x: f32,
180 pub y: f32,
181 pub z: f32,
182}
183
184impl Rotations {
185 pub const ZERO: Self = Self {
186 x: 0.0,
187 y: 0.0,
188 z: 0.0,
189 };
190
191 #[must_use]
192 pub const fn new(x: f32, y: f32, z: f32) -> Self {
193 Self { x, y, z }
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
199#[repr(u8)]
200pub enum HumanoidArm {
201 Left = 0,
202 #[default]
203 Right = 1,
204}
205
206impl ReadFrom for HumanoidArm {
207 fn read(data: &mut std::io::Cursor<&[u8]>) -> io::Result<Self> {
208 match VarInt::read(data)?.0 {
209 0 => Ok(Self::Left),
210 1 => Ok(Self::Right),
211 value => Err(io::Error::new(
212 io::ErrorKind::InvalidData,
213 format!("invalid humanoid arm id {value}"),
214 )),
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub struct VillagerData {
222 pub villager_type: i32,
224 pub profession: i32,
226 pub level: i32,
228}
229
230impl VillagerData {
231 #[must_use]
232 pub const fn new(villager_type: i32, profession: i32, level: i32) -> Self {
233 Self {
234 villager_type,
235 profession,
236 level,
237 }
238 }
239}
240
241pub(crate) fn random_villager_profession_id(
243 random: &mut impl steel_utils::random::Random,
244 profession_count: usize,
245 fallback: i32,
246) -> i32 {
247 if profession_count == 0 {
248 return fallback;
249 }
250
251 let Ok(profession_count) = i32::try_from(profession_count) else {
252 panic!("villager profession registry exceeds Vanilla's signed integer range");
253 };
254 random.next_i32_bounded(profession_count)
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
259pub struct GlobalPos {
260 pub dimension: Identifier,
261 pub pos: BlockPos,
262}
263
264impl GlobalPos {
265 #[must_use]
266 pub const fn new(dimension: Identifier, pos: BlockPos) -> Self {
267 Self { dimension, pos }
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq)]
273pub struct Vector3f {
274 pub x: f32,
275 pub y: f32,
276 pub z: f32,
277}
278
279impl Vector3f {
280 pub const ZERO: Self = Self {
281 x: 0.0,
282 y: 0.0,
283 z: 0.0,
284 };
285
286 #[must_use]
287 pub const fn new(x: f32, y: f32, z: f32) -> Self {
288 Self { x, y, z }
289 }
290}
291
292#[derive(Debug, Clone, Copy, PartialEq)]
294pub struct Quaternionf {
295 pub x: f32,
296 pub y: f32,
297 pub z: f32,
298 pub w: f32,
299}
300
301impl Quaternionf {
302 pub const IDENTITY: Self = Self {
303 x: 0.0,
304 y: 0.0,
305 z: 0.0,
306 w: 1.0,
307 };
308
309 #[must_use]
310 pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
311 Self { x, y, z, w }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Default)]
317pub struct ParticleList {
318 pub particles: Vec<ParticleData>,
319}
320
321pub use crate::resolvable_profile::ResolvableProfile;
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
325#[repr(u8)]
326pub enum SnifferState {
327 #[default]
328 Idling = 0,
329 FeelingHappy = 1,
330 Scenting = 2,
331 Sniffing = 3,
332 Searching = 4,
333 Digging = 5,
334 Rising = 6,
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
339#[repr(u8)]
340pub enum ArmadilloState {
341 #[default]
342 Idle = 0,
343 RollingUp = 1,
344 Rolled = 2,
345 Unrolling = 3,
346 Scared = 4,
347}
348
349#[derive(Debug, Clone)]
356pub enum EntityData {
357 Byte(i8),
359 Int(i32),
360 Long(i64),
361 Float(f32),
362 String(String),
363 Boolean(bool),
364
365 Component(Box<TextComponent>),
367 OptionalComponent(Option<Box<TextComponent>>),
368
369 ItemStack(ItemStack),
371
372 Rotations(Rotations),
374 BlockPos(BlockPos),
375 OptionalBlockPos(Option<BlockPos>),
376 Direction(Direction),
377 OptionalLivingEntityRef(Option<Uuid>),
378 BlockState(BlockStateId),
379 OptionalBlockState(Option<BlockStateId>),
380 Vector3(Vector3f),
381 Quaternion(Quaternionf),
382
383 Pose(EntityPose),
385 SnifferState(SnifferState),
386 ArmadilloState(ArmadilloState),
387
388 Particle(ParticleData),
390 Particles(ParticleList),
391
392 VillagerData(VillagerData),
394
395 OptionalUnsignedInt(Option<u32>),
397
398 CatVariant(RegistryReference<CatVariant>),
400 CatSoundVariant(RegistryReference<CatSoundVariant>),
401 CowVariant(RegistryReference<CowVariant>),
402 CowSoundVariant(RegistryReference<CowSoundVariant>),
403 WolfVariant(RegistryReference<WolfVariant>),
404 WolfSoundVariant(RegistryReference<WolfSoundVariant>),
405 FrogVariant(RegistryReference<FrogVariant>),
406 PigVariant(RegistryReference<PigVariant>),
407 PigSoundVariant(RegistryReference<PigSoundVariant>),
408 ChickenVariant(RegistryReference<ChickenVariant>),
409 ChickenSoundVariant(RegistryReference<ChickenSoundVariant>),
410 ZombieNautilusVariant(RegistryReference<ZombieNautilusVariant>),
411 PaintingVariant(RegistryReference<PaintingVariant>),
412
413 OptionalGlobalPos(Option<GlobalPos>),
415
416 CopperGolemState(i32),
418 WeatheringCopperState(i32),
419
420 ResolvableProfile(ResolvableProfile),
422
423 HumanoidArm(HumanoidArm),
425
426 DyeColor(DyeColor),
428}
429
430#[derive(Debug, Clone)]
434pub struct DataValue {
435 pub index: u8,
437 pub serializer_id: i32,
439 pub value: EntityData,
441}
442
443impl DataValue {
444 pub fn write_to(&self, buf: &mut Vec<u8>) -> io::Result<()> {
448 use crate::REGISTRY;
449
450 self.index.write(buf)?;
451 VarInt(self.serializer_id).write(buf)?;
452
453 let writer = REGISTRY
454 .entity_data_serializers
455 .get_writer(self.serializer_id)
456 .ok_or_else(|| {
457 io::Error::other(format!(
458 "Unknown entity data serializer ID: {}",
459 self.serializer_id
460 ))
461 })?;
462
463 writer(&self.value, buf)
464 }
465}
466
467pub fn write_data_values(values: &[DataValue], buf: &mut Vec<u8>) -> io::Result<()> {
469 for value in values {
470 value.write_to(buf)?;
471 }
472 0xFFu8.write(buf)
474}
475
476#[cfg(test)]
477mod tests {
478 use steel_utils::{
479 ArgbColor, Identifier,
480 codec::VarInt,
481 random::{Random as _, legacy_random::LegacyRandom},
482 serial::WriteTo,
483 };
484
485 use crate::vanilla_entity_data::{EggEntityData, ItemEntityData, ZombieVillagerEntityData};
486 use crate::{REGISTRY, RegistryExt, init_vanilla_registry};
487
488 use super::{
489 ColorParticleOption, EntityData, EntityDataSerializerRegistry, ParticleData,
490 random_villager_profession_id, register_vanilla_entity_data_serializers,
491 };
492
493 #[test]
494 fn zombie_villager_default_selects_profession_from_registry() {
495 init_vanilla_registry();
496
497 let profession_count = REGISTRY.villager_professions.len();
498 let Ok(profession_bound) = i32::try_from(profession_count) else {
499 panic!("test villager profession registry must fit in i32");
500 };
501 let Some(nitwit_id) = REGISTRY
502 .villager_professions
503 .id_from_key(&Identifier::vanilla_static("nitwit"))
504 else {
505 panic!("nitwit villager profession must be registered");
506 };
507 let Some(plains_id) = REGISTRY
508 .villager_types
509 .id_from_key(&Identifier::vanilla_static("plains"))
510 else {
511 panic!("plains villager type must be registered");
512 };
513
514 let mut expected_random = LegacyRandom::from_seed(25);
515 let expected_profession = expected_random.next_i32_bounded(profession_bound);
516 assert_eq!(usize::try_from(expected_profession), Ok(nitwit_id));
517
518 let mut random = LegacyRandom::from_seed(25);
519 let data = ZombieVillagerEntityData::new(&mut random);
520 let villager_data = data.villager_data.get();
521
522 assert_eq!(usize::try_from(villager_data.villager_type), Ok(plains_id));
523 assert_eq!(villager_data.profession, expected_profession);
524 assert_eq!(villager_data.level, 1);
525 assert!(data.pack_all().is_empty());
526 }
527
528 #[test]
529 fn empty_villager_profession_registry_preserves_fallback_without_rng_draw() {
530 let mut random = LegacyRandom::from_seed(25);
531 let seed_before = random.get_seed();
532
533 assert_eq!(random_villager_profession_id(&mut random, 0, 7), 7);
534 assert_eq!(random.get_seed(), seed_before);
535 }
536
537 #[test]
538 fn projectile_item_stack_defaults_use_extracted_item() {
539 let data = EggEntityData::new();
540 let stack = data.throwable_item_projectile().item_stack.get();
541
542 assert_eq!(&stack.item().key, &Identifier::vanilla_static("egg"));
543 assert_eq!(stack.count(), 1);
544 assert!(data.pack_all().is_empty());
545 }
546
547 #[test]
548 fn empty_item_stack_defaults_remain_empty() {
549 let data = ItemEntityData::new();
550
551 assert!(data.item.get().is_empty());
552 assert!(data.pack_all().is_empty());
553 }
554
555 #[test]
556 fn entity_effect_particle_color_options_encode_payload() {
557 crate::init_vanilla_registry();
558 let registry = &*crate::REGISTRY;
559 let entity_effect = Identifier::vanilla_static("entity_effect");
560 let Some(particle_type_id) = registry.particle_types.id_from_key(&entity_effect) else {
561 panic!("entity_effect particle type must be registered");
562 };
563
564 let mut serializers = EntityDataSerializerRegistry::new();
565 register_vanilla_entity_data_serializers(&mut serializers);
566
567 let Some(serializer_id) = serializers.id_from_key(&Identifier::vanilla_static("particle"))
568 else {
569 panic!("particle entity-data serializer must be registered");
570 };
571
572 let Some(writer) = serializers.get_writer(serializer_id as i32) else {
573 panic!("particle entity-data serializer must have a writer");
574 };
575
576 let particle = ParticleData::new(
577 &crate::vanilla_particle_types::ENTITY_EFFECT,
578 ColorParticleOption::new(ArgbColor::new(-1)),
579 );
580 let value = EntityData::Particle(particle);
581 let mut encoded = Vec::new();
582 let result = writer(&value, &mut encoded);
583 assert!(result.is_ok(), "{result:?}");
584
585 let mut expected = Vec::new();
586 let result = VarInt(particle_type_id as i32).write(&mut expected);
587 assert!(result.is_ok(), "{result:?}");
588 let result = (-1i32).write(&mut expected);
589 assert!(result.is_ok(), "{result:?}");
590
591 assert_eq!(encoded, expected);
592 }
593}