steel_core/block_entity/entities/
enchanting_table.rs1use std::sync::{Arc, Weak};
4
5use simdnbt::borrow::{
6 BaseNbtCompound as BorrowedNbtCompound, NbtCompound as BorrowedNbtCompoundView,
7};
8use simdnbt::owned::NbtCompound;
9use steel_registry::data_components::{DataComponentMap, vanilla_components::CUSTOM_NAME};
10use steel_registry::vanilla_block_entity_types;
11use steel_utils::{
12 BlockPos, BlockStateId, DowncastType, DowncastTypeKey, locks::SyncMutex, translations,
13};
14use text_components::TextComponent;
15
16use crate::block_entity::{BlockEntity, BlockEntityBase, ImplicitComponentGetter};
17use crate::world::World;
18
19pub struct EnchantingTableBlockEntity {
21 base: Arc<BlockEntityBase>,
22 name: SyncMutex<Option<TextComponent>>,
23}
24
25unsafe impl DowncastType for EnchantingTableBlockEntity {
27 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:block_entity/enchanting_table");
28}
29
30impl EnchantingTableBlockEntity {
31 #[must_use]
33 pub fn new(level: Weak<World>, pos: BlockPos, state: BlockStateId) -> Self {
34 Self {
35 base: Arc::new(BlockEntityBase::new(
36 &vanilla_block_entity_types::ENCHANTING_TABLE,
37 level,
38 pos,
39 state,
40 )),
41 name: SyncMutex::new(None),
42 }
43 }
44
45 #[must_use]
47 pub fn display_name(&self) -> TextComponent {
48 self.name
49 .lock()
50 .clone()
51 .unwrap_or_else(|| TextComponent::translated(translations::CONTAINER_ENCHANT.msg()))
52 }
53
54 #[must_use]
56 pub fn custom_name(&self) -> Option<TextComponent> {
57 self.name.lock().clone()
58 }
59
60 pub fn set_custom_name(&self, name: Option<TextComponent>) {
62 *self.name.lock() = name;
63 }
64}
65
66impl BlockEntity for EnchantingTableBlockEntity {
67 fn base(&self) -> &BlockEntityBase {
68 &self.base
69 }
70
71 fn load_additional(&self, nbt: &BorrowedNbtCompound<'_>) {
72 let nbt_view: BorrowedNbtCompoundView<'_, '_> = nbt.into();
73 *self.name.lock() = nbt_view
74 .get("CustomName")
75 .and_then(|tag| TextComponent::from_nbt(&tag.to_owned()));
76 }
77
78 fn save_additional(&self, nbt: &mut NbtCompound) {
79 if let Some(name) = self.name.lock().as_ref() {
80 nbt.insert("CustomName", name.to_codec_nbt());
81 }
82 }
83
84 fn apply_implicit_components(&self, components: &mut ImplicitComponentGetter<'_>) {
85 *self.name.lock() = components.get(CUSTOM_NAME);
86 }
87
88 fn collect_implicit_components(&self, components: &mut DataComponentMap) {
89 components.set(CUSTOM_NAME, self.custom_name());
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use simdnbt::owned::NbtCompound;
96 use steel_registry::data_components::components::{BlockEntityData, CustomData};
97 use steel_registry::data_components::vanilla_components::{
98 BLOCK_ENTITY_DATA, CUSTOM_NAME, REPAIR_COST,
99 };
100 use steel_registry::item_stack::ItemStack;
101 use steel_registry::{init_vanilla_registry, vanilla_blocks, vanilla_items};
102
103 use super::*;
104 use crate::test_support::fresh_test_world;
105
106 fn table(world: &Arc<World>) -> EnchantingTableBlockEntity {
107 EnchantingTableBlockEntity::new(
108 Arc::downgrade(world),
109 BlockPos::new(8, 64, 8),
110 vanilla_blocks::ENCHANTING_TABLE.default_state(),
111 )
112 }
113
114 #[test]
115 fn applying_item_components_consumes_the_custom_name_and_stores_the_rest() {
116 init_vanilla_registry();
117 let world_fixture = fresh_test_world("enchanting_table_apply_components");
118 let world = &world_fixture.world;
119 let table = table(world);
120 let name = TextComponent::from("Arcane Desk".to_string());
121 let mut stack = ItemStack::new(&vanilla_items::ENCHANTING_TABLE);
122 stack.set(CUSTOM_NAME, name.clone());
123 stack.set(REPAIR_COST, 3);
124
125 table.apply_components_from_item_stack(&stack);
126
127 assert_eq!(table.custom_name(), Some(name.clone()));
128 let stored = table.base().stored_components();
129 assert!(
130 !stored.has(CUSTOM_NAME),
131 "an implicit component must not also be stored"
132 );
133 assert_eq!(stored.get(REPAIR_COST), Some(3));
134 assert_eq!(table.collect_components().get(CUSTOM_NAME), Some(name));
135 }
136
137 #[test]
138 fn applying_item_components_skips_placement_only_components() {
139 init_vanilla_registry();
140 let world_fixture = fresh_test_world("enchanting_table_apply_placement_components");
141 let world = &world_fixture.world;
142 let table = table(world);
143 let Some(custom_data) = CustomData::try_from_compound(NbtCompound::new()) else {
144 panic!("an empty compound should be valid custom data");
145 };
146 let mut stack = ItemStack::new(&vanilla_items::ENCHANTING_TABLE);
147 stack.set(
148 BLOCK_ENTITY_DATA,
149 BlockEntityData::new(&vanilla_block_entity_types::ENCHANTING_TABLE, custom_data),
150 );
151
152 table.apply_components_from_item_stack(&stack);
153
154 assert!(table.base().stored_components().is_empty());
155 assert_eq!(table.custom_name(), None);
156 }
157
158 #[test]
159 fn applying_an_unnamed_item_clears_a_previous_custom_name() {
160 init_vanilla_registry();
161 let world_fixture = fresh_test_world("enchanting_table_apply_clears_name");
162 let world = &world_fixture.world;
163 let table = table(world);
164 table.set_custom_name(Some(TextComponent::from("Old".to_string())));
165
166 table.apply_components_from_item_stack(&ItemStack::new(&vanilla_items::ENCHANTING_TABLE));
167
168 assert_eq!(table.custom_name(), None);
169 }
170}