Skip to main content

steel_core/block_entity/entities/
skull.rs

1use crate::behavior::blocks::{SkullData, SkullType};
2use crate::behavior::skull_types;
3use crate::block_entity::{BlockEntity, BlockEntityBase};
4use crate::world::World;
5use simdnbt::borrow::{BaseNbtCompound as BorrowedNbtCompound, NbtCompound as NbtCompoundView};
6use simdnbt::owned::NbtCompound;
7use simdnbt::{FromNbtTag, ToNbtTag};
8use std::sync::Weak;
9use steel_registry::blocks::block_state_ext::BlockStateExt;
10use steel_registry::data_components::DataComponentPatch;
11use steel_registry::item_stack::ItemStack;
12use steel_registry::{REGISTRY, ResolvableProfile, vanilla_block_entity_types};
13use steel_utils::locks::SyncMutex;
14use steel_utils::{BlockPos, BlockStateId, DowncastType, DowncastTypeKey, Identifier};
15use text_components::TextComponent;
16
17const PROFILE_NBT_KEY: &str = "profile";
18const NOTE_BLOCK_SOUND_NBT_KEY: &str = "note_block_sound";
19const CUSTOM_NAME_NBT_KEY: &str = "custom_name";
20
21/// Skull block entity.
22///
23/// Stores player profile, note block sound and custom name
24pub struct SkullBlockEntity {
25    base: BlockEntityBase,
26    state: SyncMutex<SkullData>,
27}
28
29// SAFETY: This key is owned by Steel and uniquely identifies `SkullBlockEntity`.
30unsafe impl DowncastType for SkullBlockEntity {
31    const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/skull");
32}
33
34impl SkullBlockEntity {
35    /// Creates a Skull block entity.
36    #[must_use]
37    pub fn new(level: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
38        Self {
39            base: BlockEntityBase::new(&vanilla_block_entity_types::SKULL, level, pos, state),
40            state: SyncMutex::new(SkullData::default()),
41        }
42    }
43
44    /// Used to get the sound identifier by noteblocks.
45    pub fn get_note_block_sound(&self) -> Option<Identifier> {
46        self.state.lock().note_block_sound.clone()
47    }
48
49    /// Returns a clone of the stored skull data
50    #[must_use]
51    pub fn data(&self) -> SkullData {
52        self.state.lock().clone()
53    }
54
55    /// Replaces the stored skull data and sends it to clients
56    pub fn set_data(&self, data: SkullData) {
57        {
58            let mut state = self.state.lock();
59            if *state == data {
60                return;
61            }
62            *state = data;
63        }
64        self.set_changed();
65        if let Some(world) = self.get_level() {
66            world.send_block_updated(self.base.pos);
67        }
68    }
69
70    /// Builds the picked item form of a placed skull with all stored components.
71    pub fn skull_as_picked_item(&self, state: BlockStateId) -> ItemStack {
72        let patch = self.state.lock().component_patch();
73        Self::skull_as_item(state, patch)
74    }
75
76    /// Builds the dropped item form of a placed skull. Only player heads keep their profile and
77    /// note block sound.
78    pub fn skull_as_dropped_item(&self, state: BlockStateId) -> ItemStack {
79        let is_player_head = SkullType::from_block(state.get_block()) == Some(&skull_types::PLAYER);
80        let data = self.state.lock();
81        let patch = if is_player_head {
82            data.component_patch()
83        } else {
84            data.custom_name_patch()
85        };
86        drop(data);
87        Self::skull_as_item(state, patch)
88    }
89
90    fn skull_as_item(state: BlockStateId, patch: DataComponentPatch) -> ItemStack {
91        let block_item = REGISTRY.items.by_block(state.get_block());
92        ItemStack::with_count_and_patch(block_item, 1, patch)
93    }
94}
95
96impl BlockEntity for SkullBlockEntity {
97    fn base(&self) -> &BlockEntityBase {
98        &self.base
99    }
100
101    fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
102        let nbt: NbtCompoundView<'_, '_> = nbt.into();
103        let mut state = self.state.lock();
104
105        state.owner =
106            ResolvableProfile::from_optional_nbt_tag(nbt.get(PROFILE_NBT_KEY)).unwrap_or_default();
107        state.note_block_sound =
108            Identifier::from_optional_nbt_tag(nbt.get(NOTE_BLOCK_SOUND_NBT_KEY))
109                .unwrap_or_default();
110        state.custom_name =
111            TextComponent::from_optional_nbt_tag(nbt.get(CUSTOM_NAME_NBT_KEY)).unwrap_or_default();
112    }
113
114    fn save_additional(&self, nbt: &mut NbtCompound) {
115        let state = self.state.lock();
116
117        if let Some(profile) = state.owner.clone().to_optional_nbt_tag() {
118            nbt.insert(PROFILE_NBT_KEY, profile);
119        }
120
121        if let Some(sound) = state.note_block_sound.clone().to_optional_nbt_tag() {
122            nbt.insert(NOTE_BLOCK_SOUND_NBT_KEY, sound);
123        }
124
125        if let Some(name) = state.custom_name.clone().to_optional_nbt_tag() {
126            nbt.insert(CUSTOM_NAME_NBT_KEY, name);
127        }
128    }
129
130    fn get_update_tag(&self) -> Option<NbtCompound> {
131        Some(self.save_custom_only())
132    }
133
134    fn apply_components_from_item_stack(&self, item: &ItemStack) {
135        let mut state = self.state.lock();
136        *state = SkullData::from_item(item);
137    }
138}