Skip to main content

steel_core/entity/entities/
raw.rs

1//! NBT-preserving fallback entity.
2
3use std::sync::Weak;
4
5use glam::DVec3;
6use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
7use simdnbt::owned::NbtCompound;
8use steel_registry::entity_type::EntityTypeRef;
9use steel_utils::{DowncastType, DowncastTypeKey, UuidExt, locks::SyncMutex};
10use uuid::Uuid;
11
12use crate::entity::{Entity, EntityBase, EntityBaseLoad};
13use crate::world::World;
14
15/// Steel-specific fallback for entity types whose runtime behavior is not implemented yet.
16///
17/// Vanilla has concrete classes for every entity type. Steel uses this only to preserve
18/// worldgen and disk NBT until the corresponding typed implementation is added.
19pub struct RawEntity {
20    base: EntityBase,
21    entity_type: EntityTypeRef,
22    data: SyncMutex<NbtCompound>,
23}
24
25// SAFETY: This key identifies the Steel fallback implementation, independently
26// of the Minecraft entity registry entry stored inside it.
27unsafe impl DowncastType for RawEntity {
28    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/raw");
29}
30
31impl RawEntity {
32    /// Creates a fresh raw entity for an entity type Steel cannot behaviorally model yet.
33    #[must_use]
34    pub fn new(id: i32, position: DVec3, world: Weak<World>, entity_type: EntityTypeRef) -> Self {
35        Self {
36            base: EntityBase::new(id, position, entity_type.dimensions, world),
37            entity_type,
38            data: SyncMutex::new(NbtCompound::new()),
39        }
40    }
41
42    /// Creates a raw entity from base entity data.
43    #[must_use]
44    pub fn from_saved(load: EntityBaseLoad, entity_type: EntityTypeRef) -> Self {
45        Self {
46            base: EntityBase::from_load(load, entity_type.dimensions),
47            entity_type,
48            data: SyncMutex::new(NbtCompound::new()),
49        }
50    }
51
52    /// Sets position and rotation, matching vanilla `Entity.snapTo`.
53    ///
54    /// # Panics
55    ///
56    /// Panics if the active world entity manager rejects the snap position. This is an invariant
57    /// failure for loaded raw entities.
58    pub fn snap_to(&self, position: DVec3, yaw: f32, pitch: f32) {
59        if let Err(error) = self.base.try_set_position(position) {
60            panic!(
61                "failed to commit raw entity {} snap position: {error}",
62                self.base.id()
63            );
64        }
65        self.base.set_rotation((yaw, pitch));
66        self.set_old_position_to_current();
67    }
68
69    /// Marks a raw mob as persistent when vanilla structure generation would do so.
70    pub fn set_persistence_required(&self) {
71        self.data.lock().insert("PersistenceRequired", 1_i8);
72    }
73}
74
75impl Entity for RawEntity {
76    fn base(&self) -> &EntityBase {
77        &self.base
78    }
79
80    fn entity_type(&self) -> EntityTypeRef {
81        self.entity_type
82    }
83
84    fn tick(&self) {
85        // TODO: Replace raw entity ticking with full vanilla behavior for this entity type.
86    }
87
88    fn attackable(&self) -> bool {
89        false
90    }
91
92    fn projectile_owner_uuid(&self) -> Option<Uuid> {
93        if !self.entity_type.is_projectile {
94            return None;
95        }
96
97        self.projectile_owner_uuid_from_nbt()
98    }
99
100    fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
101        *self.data.lock() = nbt.to_owned();
102    }
103
104    fn save_additional(&self, nbt: &mut NbtCompound) {
105        *nbt = self.data.lock().clone();
106    }
107}
108
109impl RawEntity {
110    fn projectile_owner_uuid_from_nbt(&self) -> Option<Uuid> {
111        let data = self.data.lock();
112        let owner = data.int_array("Owner")?;
113        Uuid::from_int_array(owner)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use std::sync::Weak;
120
121    use glam::DVec3;
122    use simdnbt::owned::NbtTag;
123    use steel_registry::vanilla_entities;
124    use steel_utils::UuidExt;
125    use uuid::Uuid;
126
127    use crate::entity::Entity;
128
129    use super::RawEntity;
130
131    #[test]
132    fn raw_projectile_reads_vanilla_owner_uuid() {
133        let owner = Uuid::from_u128(42);
134        let entity = RawEntity::new(1, DVec3::ZERO, Weak::new(), &vanilla_entities::ENDER_PEARL);
135        entity
136            .data
137            .lock()
138            .insert("Owner", NbtTag::IntArray(owner.to_int_array().to_vec()));
139
140        assert_eq!(entity.projectile_owner_uuid(), Some(owner));
141    }
142}