Skip to main content

steel_core/entity/
synced_data.rs

1use bitflags::bitflags;
2use steel_registry::{
3    entity_data::{DataValue, EntityPose},
4    vanilla_entity_data::{VanillaEntityData, VanillaLivingEntityData},
5};
6use steel_utils::BlockPos;
7use steel_utils::locks::SyncMutex;
8use text_components::TextComponent;
9
10bitflags! {
11    /// Vanilla base entity shared-flags metadata byte.
12    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
13    struct EntitySharedFlags: u8 {
14        const ON_FIRE = 1 << 0;
15        const SHIFT_KEY_DOWN = 1 << 1;
16        const SPRINTING = 1 << 3;
17        const SWIMMING = 1 << 4;
18        const INVISIBLE = 1 << 5;
19        const GLOWING = 1 << 6;
20        const FALL_FLYING = 1 << 7;
21    }
22}
23
24impl EntitySharedFlags {
25    #[must_use]
26    const fn from_metadata_byte(byte: i8) -> Self {
27        Self::from_bits_retain(byte as u8)
28    }
29
30    #[must_use]
31    const fn metadata_byte(self) -> i8 {
32        self.bits() as i8
33    }
34}
35
36/// Thread-safe access to an entity's vanilla synchronized data.
37pub trait EntitySyncedData: Send + Sync {
38    /// Packs dirty values for network sync, clearing dirty flags.
39    fn pack_dirty(&self) -> Option<Vec<DataValue>>;
40
41    /// Packs all non-default values for initial entity spawn.
42    fn pack_all(&self) -> Vec<DataValue>;
43
44    /// Returns the shared vanilla `NoGravity` flag.
45    fn is_no_gravity(&self) -> bool;
46
47    /// Sets synchronized vanilla air supply.
48    fn set_air_supply(&self, air_supply: i32);
49
50    /// Sets synchronized vanilla custom name.
51    fn set_custom_name(&self, custom_name: Option<TextComponent>);
52
53    /// Sets synchronized vanilla custom-name visibility.
54    fn set_custom_name_visible(&self, visible: bool);
55
56    /// Sets synchronized vanilla silent flag.
57    fn set_silent(&self, silent: bool);
58
59    /// Sets the shared vanilla `NoGravity` flag.
60    fn set_no_gravity(&self, no_gravity: bool);
61
62    /// Sets synchronized vanilla pose.
63    fn set_pose(&self, pose: EntityPose);
64
65    /// Returns the shared vanilla shift-key-down flag.
66    fn is_shift_key_down(&self) -> bool;
67
68    /// Returns the shared vanilla swimming flag.
69    fn is_swimming(&self) -> bool;
70
71    /// Returns the shared vanilla invisible flag.
72    fn is_base_invisible_flag(&self) -> bool;
73
74    /// Sets the shared vanilla shift-key-down flag.
75    fn set_shift_key_down(&self, shift_key_down: bool);
76
77    /// Sets the shared vanilla swimming flag.
78    fn set_swimming(&self, swimming: bool);
79
80    /// Sets the shared vanilla sprinting flag.
81    fn set_sprinting(&self, sprinting: bool);
82
83    /// Sets the shared vanilla fall-flying flag.
84    fn set_fall_flying(&self, fall_flying: bool);
85
86    /// Sets the shared vanilla on-fire flag.
87    fn set_base_on_fire_flag(&self, on_fire: bool);
88
89    /// Sets the shared vanilla invisible flag.
90    fn set_base_invisible_flag(&self, invisible: bool);
91
92    /// Sets the shared vanilla glowing flag.
93    fn set_base_glowing_flag(&self, glowing: bool);
94
95    /// Sets synchronized vanilla frozen ticks.
96    fn set_base_ticks_frozen(&self, ticks_frozen: i32);
97}
98
99/// Thread-safe access to synchronized data declared by vanilla `LivingEntity`.
100pub trait LivingEntitySyncedData: EntitySyncedData {
101    /// Sets synchronized vanilla sleeping position.
102    fn set_sleeping_pos(&self, sleeping_pos: BlockPos);
103
104    /// Clears synchronized vanilla sleeping position.
105    fn clear_sleeping_pos(&self);
106}
107
108impl<T> LivingEntitySyncedData for SyncMutex<T>
109where
110    T: VanillaLivingEntityData + Send + Sync,
111{
112    fn set_sleeping_pos(&self, sleeping_pos: BlockPos) {
113        self.lock()
114            .living_entity_mut()
115            .sleeping_pos
116            .set(Some(sleeping_pos));
117    }
118
119    fn clear_sleeping_pos(&self) {
120        self.lock().living_entity_mut().sleeping_pos.set(None);
121    }
122}
123
124impl<T> EntitySyncedData for SyncMutex<T>
125where
126    T: VanillaEntityData + Send + Sync,
127{
128    fn pack_dirty(&self) -> Option<Vec<DataValue>> {
129        VanillaEntityData::pack_dirty(&mut *self.lock())
130    }
131
132    fn pack_all(&self) -> Vec<DataValue> {
133        VanillaEntityData::pack_all(&*self.lock())
134    }
135
136    fn is_no_gravity(&self) -> bool {
137        *VanillaEntityData::base(&*self.lock()).no_gravity.get()
138    }
139
140    fn set_air_supply(&self, air_supply: i32) {
141        VanillaEntityData::base_mut(&mut *self.lock())
142            .air_supply
143            .set(air_supply);
144    }
145
146    fn set_custom_name(&self, custom_name: Option<TextComponent>) {
147        VanillaEntityData::base_mut(&mut *self.lock())
148            .custom_name
149            .set(custom_name.map(Box::new));
150    }
151
152    fn set_custom_name_visible(&self, visible: bool) {
153        VanillaEntityData::base_mut(&mut *self.lock())
154            .custom_name_visible
155            .set(visible);
156    }
157
158    fn set_silent(&self, silent: bool) {
159        VanillaEntityData::base_mut(&mut *self.lock())
160            .silent
161            .set(silent);
162    }
163
164    fn set_no_gravity(&self, no_gravity: bool) {
165        VanillaEntityData::base_mut(&mut *self.lock())
166            .no_gravity
167            .set(no_gravity);
168    }
169
170    fn set_pose(&self, pose: EntityPose) {
171        VanillaEntityData::base_mut(&mut *self.lock())
172            .pose
173            .set(pose);
174    }
175
176    fn is_shift_key_down(&self) -> bool {
177        EntitySharedFlags::from_metadata_byte(
178            *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
179        )
180        .contains(EntitySharedFlags::SHIFT_KEY_DOWN)
181    }
182
183    fn is_swimming(&self) -> bool {
184        EntitySharedFlags::from_metadata_byte(
185            *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
186        )
187        .contains(EntitySharedFlags::SWIMMING)
188    }
189
190    fn is_base_invisible_flag(&self) -> bool {
191        EntitySharedFlags::from_metadata_byte(
192            *VanillaEntityData::base(&*self.lock()).shared_flags.get(),
193        )
194        .contains(EntitySharedFlags::INVISIBLE)
195    }
196
197    fn set_shift_key_down(&self, shift_key_down: bool) {
198        self.set_shared_flag(EntitySharedFlags::SHIFT_KEY_DOWN, shift_key_down);
199    }
200
201    fn set_swimming(&self, swimming: bool) {
202        self.set_shared_flag(EntitySharedFlags::SWIMMING, swimming);
203    }
204
205    fn set_sprinting(&self, sprinting: bool) {
206        self.set_shared_flag(EntitySharedFlags::SPRINTING, sprinting);
207    }
208
209    fn set_fall_flying(&self, fall_flying: bool) {
210        self.set_shared_flag(EntitySharedFlags::FALL_FLYING, fall_flying);
211    }
212
213    fn set_base_on_fire_flag(&self, on_fire: bool) {
214        self.set_shared_flag(EntitySharedFlags::ON_FIRE, on_fire);
215    }
216
217    fn set_base_invisible_flag(&self, invisible: bool) {
218        self.set_shared_flag(EntitySharedFlags::INVISIBLE, invisible);
219    }
220
221    fn set_base_glowing_flag(&self, glowing: bool) {
222        self.set_shared_flag(EntitySharedFlags::GLOWING, glowing);
223    }
224
225    fn set_base_ticks_frozen(&self, ticks_frozen: i32) {
226        VanillaEntityData::base_mut(&mut *self.lock())
227            .ticks_frozen
228            .set(ticks_frozen);
229    }
230}
231
232trait SharedFlagSetter {
233    fn set_shared_flag(&self, flag: EntitySharedFlags, value: bool);
234}
235
236impl<T> SharedFlagSetter for SyncMutex<T>
237where
238    T: VanillaEntityData + Send + Sync,
239{
240    fn set_shared_flag(&self, flag: EntitySharedFlags, value: bool) {
241        let mut entity_data = self.lock();
242        let base = VanillaEntityData::base_mut(&mut *entity_data);
243        let mut flags = EntitySharedFlags::from_metadata_byte(*base.shared_flags.get());
244        flags.set(flag, value);
245        base.shared_flags.set(flags.metadata_byte());
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use steel_registry::{
252        entity_data::EntityData,
253        vanilla_entity_data::{ItemEntityData, PlayerEntityData},
254    };
255    use text_components::TextComponent;
256
257    use super::*;
258
259    #[test]
260    fn synced_data_reads_no_gravity_from_generated_base_layer() {
261        let data = SyncMutex::new(ItemEntityData::new());
262        assert!(!EntitySyncedData::is_no_gravity(&data));
263
264        EntitySyncedData::set_no_gravity(&data, true);
265
266        assert!(EntitySyncedData::is_no_gravity(&data));
267        let Some(values) = EntitySyncedData::pack_dirty(&data) else {
268            panic!("expected dirty no-gravity metadata");
269        };
270        assert_eq!(values.len(), 1);
271        assert_eq!(values[0].index, 5);
272        assert_eq!(values[0].serializer_id, 8);
273        assert!(matches!(values[0].value, EntityData::Boolean(true)));
274        assert!(EntitySyncedData::pack_dirty(&data).is_none());
275    }
276
277    #[test]
278    fn synced_data_reads_shift_key_down_from_generated_base_layer() {
279        let data = SyncMutex::new(ItemEntityData::new());
280        assert!(!EntitySyncedData::is_shift_key_down(&data));
281
282        data.lock()
283            .base_mut()
284            .shared_flags
285            .set(EntitySharedFlags::SHIFT_KEY_DOWN.metadata_byte());
286
287        assert!(EntitySyncedData::is_shift_key_down(&data));
288    }
289
290    #[test]
291    fn synced_data_reads_swimming_from_generated_base_layer() {
292        let data = SyncMutex::new(ItemEntityData::new());
293        assert!(!EntitySyncedData::is_swimming(&data));
294
295        data.lock()
296            .base_mut()
297            .shared_flags
298            .set(EntitySharedFlags::SWIMMING.metadata_byte());
299
300        assert!(EntitySyncedData::is_swimming(&data));
301    }
302
303    #[test]
304    fn synced_data_reads_invisible_from_generated_base_layer() {
305        let data = SyncMutex::new(ItemEntityData::new());
306        assert!(!EntitySyncedData::is_base_invisible_flag(&data));
307
308        EntitySyncedData::set_base_invisible_flag(&data, true);
309
310        assert!(EntitySyncedData::is_base_invisible_flag(&data));
311    }
312
313    #[test]
314    fn synced_data_writes_individual_shared_flags_without_stomping() {
315        let data = SyncMutex::new(ItemEntityData::new());
316
317        EntitySyncedData::set_shift_key_down(&data, true);
318        EntitySyncedData::set_swimming(&data, true);
319        EntitySyncedData::set_sprinting(&data, true);
320        EntitySyncedData::set_fall_flying(&data, true);
321
322        let flags = EntitySharedFlags::from_metadata_byte(*data.lock().base().shared_flags.get());
323        assert!(flags.contains(EntitySharedFlags::SHIFT_KEY_DOWN));
324        assert!(flags.contains(EntitySharedFlags::SWIMMING));
325        assert!(flags.contains(EntitySharedFlags::SPRINTING));
326        assert!(flags.contains(EntitySharedFlags::FALL_FLYING));
327
328        EntitySyncedData::set_swimming(&data, false);
329
330        let flags = EntitySharedFlags::from_metadata_byte(*data.lock().base().shared_flags.get());
331        assert!(flags.contains(EntitySharedFlags::SHIFT_KEY_DOWN));
332        assert!(!flags.contains(EntitySharedFlags::SWIMMING));
333        assert!(flags.contains(EntitySharedFlags::SPRINTING));
334        assert!(flags.contains(EntitySharedFlags::FALL_FLYING));
335    }
336
337    #[test]
338    fn synced_data_writes_fire_and_freeze_base_layer() {
339        let data = SyncMutex::new(ItemEntityData::new());
340
341        data.set_base_on_fire_flag(true);
342        data.set_base_ticks_frozen(12);
343
344        let values =
345            EntitySyncedData::pack_dirty(&data).expect("expected dirty base fire/freeze metadata");
346        assert_eq!(values.len(), 2);
347        assert!(matches!(values[0].value, EntityData::Byte(1)));
348        assert!(matches!(values[1].value, EntityData::Int(12)));
349
350        assert!(EntitySyncedData::pack_dirty(&data).is_none());
351    }
352
353    #[test]
354    fn synced_data_writes_shared_save_base_layer() {
355        let data = SyncMutex::new(ItemEntityData::new());
356
357        data.set_air_supply(42);
358        data.set_custom_name(Some(TextComponent::plain("Steel")));
359        data.set_custom_name_visible(true);
360        data.set_silent(true);
361        data.set_base_glowing_flag(true);
362
363        let values =
364            EntitySyncedData::pack_dirty(&data).expect("expected dirty shared save metadata");
365
366        assert_eq!(values.len(), 5);
367        assert_eq!(values[0].index, 0);
368        assert!(matches!(
369            values[0].value,
370            EntityData::Byte(value)
371                if EntitySharedFlags::from_metadata_byte(value)
372                    .contains(EntitySharedFlags::GLOWING)
373        ));
374        assert_eq!(values[1].index, 1);
375        assert!(matches!(values[1].value, EntityData::Int(42)));
376        assert_eq!(values[2].index, 2);
377        assert!(matches!(
378            values[2].value,
379            EntityData::OptionalComponent(Some(_))
380        ));
381        assert_eq!(values[3].index, 3);
382        assert!(matches!(values[3].value, EntityData::Boolean(true)));
383        assert_eq!(values[4].index, 4);
384        assert!(matches!(values[4].value, EntityData::Boolean(true)));
385    }
386
387    #[test]
388    fn living_synced_data_writes_sleeping_pos_layer() {
389        let data = SyncMutex::new(PlayerEntityData::new());
390        let bed_pos = BlockPos::new(1, 64, 2);
391
392        LivingEntitySyncedData::set_sleeping_pos(&data, bed_pos);
393
394        let values = EntitySyncedData::pack_dirty(&data)
395            .expect("expected dirty living sleeping-pos metadata");
396        assert_eq!(values.len(), 1);
397        assert_eq!(values[0].index, 14);
398        assert_eq!(values[0].serializer_id, 11);
399        assert!(matches!(
400            values[0].value,
401            EntityData::OptionalBlockPos(Some(pos)) if pos == bed_pos
402        ));
403
404        LivingEntitySyncedData::clear_sleeping_pos(&data);
405
406        let values = EntitySyncedData::pack_dirty(&data)
407            .expect("expected dirty cleared sleeping-pos metadata");
408        assert_eq!(values.len(), 1);
409        assert_eq!(values[0].index, 14);
410        assert!(matches!(
411            values[0].value,
412            EntityData::OptionalBlockPos(None)
413        ));
414    }
415}