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