Skip to main content

steel_core/behavior/blocks/container/
enchanting_table_block.rs

1//! Enchanting table block behavior.
2
3use 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/// Behavior for the enchanting table.
25#[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    /// Bookshelf offsets around an enchanting table
54    pub const BOOKSHELF_OFFSETS: [IVec3; 32] = bookshelf_offsets();
55
56    /// Creates a new enchanting table block behavior.
57    #[must_use]
58    pub const fn new(block: BlockRef) -> Self {
59        Self { block }
60    }
61
62    /// Checks whether the block at `offset` can provide enchanting power
63    #[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}