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::{BlockStateId, Identifier, codec::VarInt, serial::WriteTo};
39use text_components::TextComponent;
40use uuid::Uuid;
41
42use crate::item_stack::ItemStack;
43
44pub use crate::blocks::properties::Direction;
46pub use steel_utils::BlockPos;
47
48#[derive(Debug, Clone)]
53pub struct SyncedValue<T> {
54 value: T,
55 default: T,
56 dirty: bool,
57}
58
59impl<T: Clone + PartialEq> SyncedValue<T> {
60 pub fn new(default: T) -> Self {
62 Self {
63 value: default.clone(),
64 default,
65 dirty: false,
66 }
67 }
68
69 #[inline]
71 pub fn get(&self) -> &T {
72 &self.value
73 }
74
75 #[inline]
77 pub fn set(&mut self, value: T) {
78 if self.value != value {
79 self.value = value;
80 self.dirty = true;
81 }
82 }
83
84 #[inline]
86 pub fn is_dirty(&self) -> bool {
87 self.dirty
88 }
89
90 #[inline]
92 pub fn clear_dirty(&mut self) {
93 self.dirty = false;
94 }
95
96 #[inline]
98 pub fn is_default(&self) -> bool {
99 self.value == self.default
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
105#[repr(u8)]
106pub enum EntityPose {
107 #[default]
108 Standing = 0,
109 FallFlying = 1,
110 Sleeping = 2,
111 Swimming = 3,
112 SpinAttack = 4,
113 Sneaking = 5,
114 LongJumping = 6,
115 Dying = 7,
116 Croaking = 8,
117 UsingTongue = 9,
118 Sitting = 10,
119 Roaring = 11,
120 Sniffing = 12,
121 Emerging = 13,
122 Digging = 14,
123 Sliding = 15,
124 Shooting = 16,
125 Inhaling = 17,
126}
127
128impl FromStr for EntityPose {
129 type Err = ();
130
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 match s {
133 "STANDING" => Ok(Self::Standing),
134 "FALL_FLYING" => Ok(Self::FallFlying),
135 "SLEEPING" => Ok(Self::Sleeping),
136 "SWIMMING" => Ok(Self::Swimming),
137 "SPIN_ATTACK" => Ok(Self::SpinAttack),
138 "SNEAKING" | "CROUCHING" => Ok(Self::Sneaking),
139 "LONG_JUMPING" => Ok(Self::LongJumping),
140 "DYING" => Ok(Self::Dying),
141 "CROAKING" => Ok(Self::Croaking),
142 "USING_TONGUE" => Ok(Self::UsingTongue),
143 "SITTING" => Ok(Self::Sitting),
144 "ROARING" => Ok(Self::Roaring),
145 "SNIFFING" => Ok(Self::Sniffing),
146 "EMERGING" => Ok(Self::Emerging),
147 "DIGGING" => Ok(Self::Digging),
148 "SLIDING" => Ok(Self::Sliding),
149 "SHOOTING" => Ok(Self::Shooting),
150 "INHALING" => Ok(Self::Inhaling),
151 _ => Err(()),
152 }
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq)]
158pub struct Rotations {
159 pub x: f32,
160 pub y: f32,
161 pub z: f32,
162}
163
164impl Rotations {
165 pub const ZERO: Self = Self {
166 x: 0.0,
167 y: 0.0,
168 z: 0.0,
169 };
170
171 pub fn new(x: f32, y: f32, z: f32) -> Self {
172 Self { x, y, z }
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
178#[repr(u8)]
179pub enum HumanoidArm {
180 Left = 0,
181 #[default]
182 Right = 1,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
187pub struct VillagerData {
188 pub villager_type: i32,
190 pub profession: i32,
192 pub level: i32,
194}
195
196impl VillagerData {
197 pub fn new(villager_type: i32, profession: i32, level: i32) -> Self {
198 Self {
199 villager_type,
200 profession,
201 level,
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Hash)]
208pub struct GlobalPos {
209 pub dimension: Identifier,
210 pub pos: BlockPos,
211}
212
213impl GlobalPos {
214 pub fn new(dimension: Identifier, pos: BlockPos) -> Self {
215 Self { dimension, pos }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq)]
221pub struct Vector3f {
222 pub x: f32,
223 pub y: f32,
224 pub z: f32,
225}
226
227impl Vector3f {
228 pub const ZERO: Self = Self {
229 x: 0.0,
230 y: 0.0,
231 z: 0.0,
232 };
233
234 pub fn new(x: f32, y: f32, z: f32) -> Self {
235 Self { x, y, z }
236 }
237}
238
239#[derive(Debug, Clone, Copy, PartialEq)]
241pub struct Quaternionf {
242 pub x: f32,
243 pub y: f32,
244 pub z: f32,
245 pub w: f32,
246}
247
248impl Quaternionf {
249 pub const IDENTITY: Self = Self {
250 x: 0.0,
251 y: 0.0,
252 z: 0.0,
253 w: 1.0,
254 };
255
256 pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
257 Self { x, y, z, w }
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
263pub enum ParticleOptions {
264 None,
265 Color { color: i32 },
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
270pub struct ParticleData {
271 pub particle_type: i32,
273 pub options: ParticleOptions,
274}
275
276impl ParticleData {
277 #[must_use]
278 pub fn new(particle_type: i32, options: ParticleOptions) -> Self {
279 Self {
280 particle_type,
281 options,
282 }
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Default)]
288pub struct ParticleList {
289 pub particles: Vec<ParticleData>,
290}
291
292#[derive(Debug, Clone, PartialEq, Default)]
295pub struct ResolvableProfile {
296 }
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
301#[repr(u8)]
302pub enum SnifferState {
303 #[default]
304 Idling = 0,
305 FeelingHappy = 1,
306 Scenting = 2,
307 Sniffing = 3,
308 Searching = 4,
309 Digging = 5,
310 Rising = 6,
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
315#[repr(u8)]
316pub enum ArmadilloState {
317 #[default]
318 Idle = 0,
319 RollingUp = 1,
320 Rolled = 2,
321 Unrolling = 3,
322 Scared = 4,
323}
324
325#[derive(Debug, Clone)]
332pub enum EntityData {
333 Byte(i8),
335 Int(i32),
336 Long(i64),
337 Float(f32),
338 String(String),
339 Boolean(bool),
340
341 Component(Box<TextComponent>),
343 OptionalComponent(Option<Box<TextComponent>>),
344
345 ItemStack(ItemStack),
347
348 Rotations(Rotations),
350 BlockPos(BlockPos),
351 OptionalBlockPos(Option<BlockPos>),
352 Direction(Direction),
353 OptionalLivingEntityRef(Option<Uuid>),
354 BlockState(BlockStateId),
355 OptionalBlockState(Option<BlockStateId>),
356 Vector3(Vector3f),
357 Quaternion(Quaternionf),
358
359 Pose(EntityPose),
361 SnifferState(SnifferState),
362 ArmadilloState(ArmadilloState),
363
364 Particle(ParticleData),
366 Particles(ParticleList),
367
368 VillagerData(VillagerData),
370
371 OptionalUnsignedInt(Option<u32>),
373
374 CatVariant(i32),
376 CatSoundVariant(i32),
377 CowVariant(i32),
378 CowSoundVariant(i32),
379 WolfVariant(i32),
380 WolfSoundVariant(i32),
381 FrogVariant(i32),
382 PigVariant(i32),
383 PigSoundVariant(i32),
384 ChickenVariant(i32),
385 ChickenSoundVariant(i32),
386 ZombieNautilusVariant(i32),
387 PaintingVariant(i32),
388
389 OptionalGlobalPos(Option<GlobalPos>),
391
392 CopperGolemState(i32),
394 WeatheringCopperState(i32),
395
396 ResolvableProfile(ResolvableProfile),
398
399 HumanoidArm(HumanoidArm),
401}
402
403#[derive(Debug, Clone)]
407pub struct DataValue {
408 pub index: u8,
410 pub serializer_id: i32,
412 pub value: EntityData,
414}
415
416impl DataValue {
417 pub fn write_to(&self, buf: &mut Vec<u8>) -> io::Result<()> {
421 use crate::REGISTRY;
422
423 self.index.write(buf)?;
424 VarInt(self.serializer_id).write(buf)?;
425
426 let writer = REGISTRY
427 .entity_data_serializers
428 .get_writer(self.serializer_id)
429 .ok_or_else(|| {
430 io::Error::other(format!(
431 "Unknown entity data serializer ID: {}",
432 self.serializer_id
433 ))
434 })?;
435
436 writer(&self.value, buf)
437 }
438}
439
440pub fn write_data_values(values: &[DataValue], buf: &mut Vec<u8>) -> io::Result<()> {
442 for value in values {
443 value.write_to(buf)?;
444 }
445 0xFFu8.write(buf)
447}
448
449#[cfg(test)]
450mod tests {
451 use steel_utils::{Identifier, codec::VarInt, serial::WriteTo};
452
453 use crate::vanilla_entity_data::{EggEntityData, ItemEntityData};
454 use crate::{Registry, RegistryExt};
455
456 use super::{
457 EntityData, EntityDataSerializerRegistry, ParticleData, ParticleOptions,
458 register_vanilla_entity_data_serializers,
459 };
460
461 #[test]
462 fn projectile_item_stack_defaults_use_extracted_item() {
463 let data = EggEntityData::new();
464 let stack = data.throwable_item_projectile().item_stack.get();
465
466 assert_eq!(&stack.item().key, &Identifier::vanilla_static("egg"));
467 assert_eq!(stack.count(), 1);
468 assert!(data.pack_all().is_empty());
469 }
470
471 #[test]
472 fn empty_item_stack_defaults_remain_empty() {
473 let data = ItemEntityData::new();
474
475 assert!(data.item.get().is_empty());
476 assert!(data.pack_all().is_empty());
477 }
478
479 #[test]
480 fn entity_effect_particle_color_options_encode_payload() {
481 let registry = Registry::new_vanilla();
482 let entity_effect = Identifier::vanilla_static("entity_effect");
483 let Some(particle_type_id) = registry.particle_types.id_from_key(&entity_effect) else {
484 panic!("entity_effect particle type must be registered");
485 };
486
487 let mut serializers = EntityDataSerializerRegistry::new();
488 register_vanilla_entity_data_serializers(&mut serializers);
489
490 let Some(serializer_id) = serializers.id_from_key(&Identifier::vanilla_static("particle"))
491 else {
492 panic!("particle entity-data serializer must be registered");
493 };
494
495 let Some(writer) = serializers.get_writer(serializer_id as i32) else {
496 panic!("particle entity-data serializer must have a writer");
497 };
498
499 let particle = ParticleData::new(
500 particle_type_id as i32,
501 ParticleOptions::Color { color: -1 },
502 );
503 let value = EntityData::Particle(particle);
504 let mut encoded = Vec::new();
505 let result = writer(&value, &mut encoded);
506 assert!(result.is_ok(), "{result:?}");
507
508 let mut expected = Vec::new();
509 let result = VarInt(particle_type_id as i32).write(&mut expected);
510 assert!(result.is_ok(), "{result:?}");
511 let result = (-1i32).write(&mut expected);
512 assert!(result.is_ok(), "{result:?}");
513
514 assert_eq!(encoded, expected);
515 }
516}