Skip to main content

steel_core/entity/entities/
block_display.rs

1//! Block display entity implementation.
2//!
3//! Display entities render a block, item, or text without collision.
4//! They're commonly used for visual effects, holograms, and decorations.
5
6use std::sync::Weak;
7
8use glam::DVec3;
9use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
10use simdnbt::owned::NbtCompound;
11use steel_macros::entity_behavior;
12use steel_registry::entity_type::EntityTypeRef;
13use steel_registry::vanilla_entity_data::BlockDisplayEntityData;
14use steel_utils::BlockStateId;
15use steel_utils::locks::SyncMutex;
16use steel_utils::{DowncastType, DowncastTypeKey};
17use uuid::Uuid;
18
19use crate::entity::{Entity, EntityBase, EntityBaseLoad, EntitySyncedData};
20use crate::world::World;
21
22/// A block display entity that renders a block state at its position.
23///
24/// Block displays are purely visual entities with no collision.
25/// They support transformation (translation, rotation, scale) and
26/// interpolation for smooth animations.
27#[entity_behavior(class = "BlockDisplay")]
28pub struct BlockDisplayEntity {
29    /// Common entity fields (id, uuid, position, etc.).
30    base: EntityBase,
31    /// Vanilla entity type registered for this implementation.
32    entity_type: EntityTypeRef,
33    /// Synced entity data for network serialization.
34    entity_data: SyncMutex<BlockDisplayEntityData>,
35}
36
37// SAFETY: This key is owned by Steel and uniquely identifies `BlockDisplayEntity`.
38unsafe impl DowncastType for BlockDisplayEntity {
39    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/block_display");
40}
41
42impl BlockDisplayEntity {
43    /// Creates a new block display entity.
44    ///
45    /// The `id` should be obtained from `next_entity_id()`.
46    #[must_use]
47    pub fn new(entity_type: EntityTypeRef, id: i32, position: DVec3, world: Weak<World>) -> Self {
48        Self {
49            base: EntityBase::new(id, position, entity_type.dimensions, world),
50            entity_type,
51            entity_data: SyncMutex::new(BlockDisplayEntityData::new()),
52        }
53    }
54
55    /// Creates a new block display entity with a specific UUID.
56    ///
57    /// The `id` should be obtained from `next_entity_id()`.
58    #[must_use]
59    pub fn with_uuid(
60        entity_type: EntityTypeRef,
61        id: i32,
62        position: DVec3,
63        uuid: Uuid,
64        world: Weak<World>,
65    ) -> Self {
66        Self {
67            base: EntityBase::with_uuid(id, uuid, position, entity_type.dimensions, world),
68            entity_type,
69            entity_data: SyncMutex::new(BlockDisplayEntityData::new()),
70        }
71    }
72
73    /// Creates a block display entity from saved data.
74    ///
75    /// Display entities have no physical collision, but vanilla base state is
76    /// still persisted and should round-trip through the shared base.
77    #[must_use]
78    pub fn from_saved(entity_type: EntityTypeRef, load: EntityBaseLoad) -> Self {
79        Self {
80            base: EntityBase::from_load(load, entity_type.dimensions),
81            entity_type,
82            entity_data: SyncMutex::new(BlockDisplayEntityData::new()),
83        }
84    }
85
86    /// Gets a reference to the entity data for reading/modifying synced state.
87    pub const fn entity_data(&self) -> &SyncMutex<BlockDisplayEntityData> {
88        &self.entity_data
89    }
90
91    /// Sets the block state ID of this entity.
92    pub fn set_block_state_id(&self, id: BlockStateId) {
93        self.entity_data.lock().block_state.set(id);
94    }
95}
96
97impl Entity for BlockDisplayEntity {
98    fn base(&self) -> &EntityBase {
99        &self.base
100    }
101
102    fn entity_type(&self) -> EntityTypeRef {
103        self.entity_type
104    }
105
106    fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
107        Some(&self.entity_data)
108    }
109
110    fn is_ignoring_block_triggers(&self) -> bool {
111        true
112    }
113
114    fn save_additional(&self, nbt: &mut NbtCompound) {
115        // Save block state ID directly - these are deterministic in Minecraft
116        let block_state_id = *self.entity_data.lock().block_state.get();
117        nbt.insert("block_state", i32::from(block_state_id.0));
118    }
119
120    fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
121        // Load block state ID
122        if let Some(state_id) = nbt.int("block_state") {
123            self.entity_data
124                .lock()
125                .block_state
126                .set(BlockStateId(state_id as u16));
127        }
128    }
129}