steel_core/behavior/blocks/container/
enchanting_table_block.rs1use std::sync::{Arc, Weak};
4
5use glam::IVec3;
6use steel_macros::block_behavior;
7use steel_registry::{
8 REGISTRY, TaggedRegistryExt,
9 blocks::{BlockRef, block_state_ext::BlockStateExt},
10 vanilla_block_entity_types,
11 vanilla_block_tags::BlockTag,
12};
13use steel_utils::{BlockPos, BlockStateId, Downcast as _};
14
15use crate::behavior::InventoryAccess;
16use crate::behavior::block::{BlockBehavior, BlockEntityCreation};
17use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult};
18use crate::block_entity::{BLOCK_ENTITIES, entities::EnchantingTableBlockEntity};
19use crate::entity::ai::path::PathComputationType;
20use crate::inventory::menu::kinds::enchantment;
21use crate::player::Player;
22use crate::world::World;
23
24#[block_behavior]
26pub struct EnchantingTableBlock {
27 block: BlockRef,
28}
29
30const fn bookshelf_offsets() -> [IVec3; 32] {
31 let mut offsets = [IVec3::ZERO; 32];
32 let mut count = 0;
33 let mut y: i32 = 0;
34 while y <= 1 {
35 let mut x: i32 = -2;
36 while x <= 2 {
37 let mut z: i32 = -2;
38 while z <= 2 {
39 if x.abs() == 2 || z.abs() == 2 {
40 offsets[count] = IVec3::new(x, y, z);
41 count += 1;
42 }
43 z += 1;
44 }
45 x += 1;
46 }
47 y += 1;
48 }
49 offsets
50}
51
52impl EnchantingTableBlock {
53 pub const BOOKSHELF_OFFSETS: [IVec3; 32] = bookshelf_offsets();
55
56 #[must_use]
58 pub const fn new(block: BlockRef) -> Self {
59 Self { block }
60 }
61
62 #[must_use]
64 pub fn is_valid_book_shelf(world: &World, pos: BlockPos, offset: IVec3) -> bool {
65 let provider = world
66 .get_block_state(pos.offset(offset.x, offset.y, offset.z))
67 .get_block();
68 if !REGISTRY
69 .blocks
70 .is_in_tag(provider, &BlockTag::ENCHANTMENT_POWER_PROVIDER)
71 {
72 return false;
73 }
74 let transmitter = world
75 .get_block_state(pos.offset(offset.x / 2, offset.y, offset.z / 2))
76 .get_block();
77 REGISTRY
78 .blocks
79 .is_in_tag(transmitter, &BlockTag::ENCHANTMENT_POWER_TRANSMITTER)
80 }
81}
82
83impl BlockBehavior for EnchantingTableBlock {
84 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
85 Some(self.block.default_state())
86 }
87
88 fn new_block_entity(
89 &self,
90 level: Weak<World>,
91 pos: BlockPos,
92 state: BlockStateId,
93 ) -> BlockEntityCreation {
94 BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
95 &vanilla_block_entity_types::ENCHANTING_TABLE,
96 level,
97 pos,
98 state,
99 ))
100 }
101
102 fn is_pathfindable(
103 &self,
104 _state: BlockStateId,
105 _computation_type: PathComputationType,
106 ) -> bool {
107 false
108 }
109
110 fn use_without_item(
111 &self,
112 _state: BlockStateId,
113 world: &Arc<World>,
114 pos: BlockPos,
115 player: &Player,
116 _hit_result: &BlockHitResult,
117 _inv: &mut InventoryAccess,
118 ) -> InteractionResult {
119 let Some(block_entity) = world.get_block_entity(pos) else {
120 return InteractionResult::Success;
121 };
122 let Some(table) = block_entity.downcast_ref::<EnchantingTableBlockEntity>() else {
123 return InteractionResult::Success;
124 };
125 let title = table.display_name();
126 let inventory = Arc::clone(&player.inventory);
127 let enchantment_seed = player.experience.lock().enchantment_seed();
128 player.open_menu(title, move |context| {
129 enchantment(
130 inventory,
131 context.container_id,
132 pos,
133 context.world,
134 enchantment_seed,
135 )
136 });
137 InteractionResult::Success
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use steel_registry::{init_vanilla_registry, vanilla_blocks};
144 use steel_utils::{ChunkPos, types::UpdateFlags};
145
146 use super::*;
147 use crate::behavior::init_behaviors;
148 use crate::block_entity::init_block_entities;
149 use crate::test_support::{TestWorld, fresh_test_world, insert_ready_full_chunk};
150
151 fn placed_table(key: &'static str) -> (TestWorld, BlockPos) {
152 init_vanilla_registry();
153 init_behaviors();
154 init_block_entities();
155 let world_fixture = fresh_test_world(key);
156 let world = &world_fixture.world;
157 let pos = BlockPos::new(8, 64, 8);
158 insert_ready_full_chunk(world, ChunkPos::from_block_pos(pos));
159 assert!(world.set_block(
160 pos,
161 vanilla_blocks::ENCHANTING_TABLE.default_state(),
162 UpdateFlags::UPDATE_ALL,
163 ));
164 (world_fixture, pos)
165 }
166
167 #[test]
168 fn bookshelves_count_only_with_a_clear_transmitter_block() {
169 let (world_fixture, pos) = placed_table("enchanting_table_bookshelf_power");
170 let world = &world_fixture.world;
171 let offset = IVec3::new(2, 0, 0);
172 assert!(!EnchantingTableBlock::is_valid_book_shelf(
173 world, pos, offset
174 ));
175
176 assert!(world.set_block(
177 pos.offset(2, 0, 0),
178 vanilla_blocks::BOOKSHELF.default_state(),
179 UpdateFlags::UPDATE_ALL,
180 ));
181 assert!(EnchantingTableBlock::is_valid_book_shelf(
182 world, pos, offset
183 ));
184
185 assert!(world.set_block(
186 pos.offset(1, 0, 0),
187 vanilla_blocks::STONE.default_state(),
188 UpdateFlags::UPDATE_ALL,
189 ));
190 assert!(!EnchantingTableBlock::is_valid_book_shelf(
191 world, pos, offset
192 ));
193 }
194}