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 #[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
36pub trait EntitySyncedData: Send + Sync {
38 fn pack_dirty(&self) -> Option<Vec<DataValue>>;
40
41 fn pack_all(&self) -> Vec<DataValue>;
43
44 fn is_no_gravity(&self) -> bool;
46
47 fn set_air_supply(&self, air_supply: i32);
49
50 fn set_custom_name(&self, custom_name: Option<TextComponent>);
52
53 fn set_custom_name_visible(&self, visible: bool);
55
56 fn set_silent(&self, silent: bool);
58
59 fn set_no_gravity(&self, no_gravity: bool);
61
62 fn set_pose(&self, pose: EntityPose);
64
65 fn is_shift_key_down(&self) -> bool;
67
68 fn is_swimming(&self) -> bool;
70
71 fn is_base_invisible_flag(&self) -> bool;
73
74 fn set_shift_key_down(&self, shift_key_down: bool);
76
77 fn set_swimming(&self, swimming: bool);
79
80 fn set_sprinting(&self, sprinting: bool);
82
83 fn set_fall_flying(&self, fall_flying: bool);
85
86 fn set_base_on_fire_flag(&self, on_fire: bool);
88
89 fn set_base_invisible_flag(&self, invisible: bool);
91
92 fn set_base_glowing_flag(&self, glowing: bool);
94
95 fn set_base_ticks_frozen(&self, ticks_frozen: i32);
97}
98
99pub trait LivingEntitySyncedData: EntitySyncedData {
101 fn set_sleeping_pos(&self, sleeping_pos: BlockPos);
103
104 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}