Skip to main content

steel_core/inventory/slots/
grindstone_slots.rs

1//! Grindstone result slot.
2use std::sync::Arc;
3
4use steel_registry::vanilla_enchantment_tags::EnchantmentTag;
5use steel_registry::{REGISTRY, RegistryExt, TaggedRegistryExt};
6use steel_registry::{item_stack::ItemStack, level_events};
7use steel_utils::{BlockPos, locks::Shared};
8
9use crate::{
10    entity::entities::ExperienceOrbEntity,
11    inventory::{
12        container::{ResultContainer, SimpleContainer},
13        prelude::*,
14    },
15    world::World,
16};
17
18/// Result slot handler for a grindstone.
19#[derive(Clone)]
20pub struct GrindstoneResultHandler {
21    input_container: Shared<SimpleContainer>,
22    result_container: Shared<ResultContainer>,
23    block_pos: BlockPos,
24    world: Arc<World>,
25}
26
27impl GrindstoneResultHandler {
28    /// Creates a new handler.
29    pub const fn new(
30        input_container: Shared<SimpleContainer>,
31        result_container: Shared<ResultContainer>,
32        block_pos: BlockPos,
33        world: Arc<World>,
34    ) -> Self {
35        Self {
36            input_container,
37            result_container,
38            block_pos,
39            world,
40        }
41    }
42
43    /// Experience dropped when the result is taken: roughly half the enchanting
44    /// cost of both inputs, randomized upward. Must be called before the inputs
45    /// are cleared.
46    fn get_experience_amount(&self, guard: &ContainerLockGuard) -> i32 {
47        let Some(input) = guard.get(ContainerId::from_arc(&self.input_container)) else {
48            log::warn!("input container not locked while awarding grindstone experience");
49            return 0;
50        };
51
52        let amount = Self::get_experience_from_item(input.get_item(0))
53            + Self::get_experience_from_item(input.get_item(1));
54
55        if amount > 0 {
56            // Ceiling division; `amount` is positive here so truncation cannot bite.
57            let half_amount = (amount + 1) / 2;
58            return half_amount + rand::random_range(0..half_amount);
59        }
60        0
61    }
62
63    fn get_experience_from_item(item: &ItemStack) -> i32 {
64        let mut amount = 0;
65        let Some(enchantments) = item.get_enchantments_for_crafting() else {
66            return 0;
67        };
68
69        enchantments.iter().for_each(|(id, level)| {
70            let Some(enchantment) = REGISTRY.enchantments.by_key(id) else {
71                return;
72            };
73
74            if REGISTRY
75                .enchantments
76                .is_in_tag(enchantment, &EnchantmentTag::CURSE)
77            {
78                return;
79            }
80
81            amount += enchantment.min_cost.base
82                + enchantment.min_cost.per_level_above_first * (*level as i32 - 1);
83        });
84
85        amount
86    }
87}
88
89impl ResultHandler for GrindstoneResultHandler {
90    fn result_container(&self) -> ContainerRef {
91        ContainerRef::from(self.result_container.clone())
92    }
93
94    fn dependencies(&self) -> Vec<ContainerRef> {
95        vec![ContainerRef::from(self.input_container.clone())]
96    }
97
98    fn update_result(&self, _guard: &mut ContainerLockGuard) {}
99
100    fn on_result_taken(
101        &self,
102        guard: &mut ContainerLockGuard,
103        _player: &Player,
104    ) -> Option<ItemStack> {
105        // Read before the inputs are cleared below.
106        let experience = self.get_experience_amount(guard);
107        ExperienceOrbEntity::award(&self.world, self.block_pos.get_center().into(), experience);
108
109        self.world
110            .level_event(level_events::SOUND_GRINDSTONE_USED, self.block_pos, 0, None);
111
112        let id = ContainerId::from_arc(&self.input_container);
113        let Some(input) = guard.get_mut(id) else {
114            log::warn!("Couldn't get lock for grindstone.");
115            return None;
116        };
117
118        input.set_item(0, ItemStack::empty());
119        input.set_item(1, ItemStack::empty());
120
121        None
122    }
123
124    fn is_result_valid(&self, _guard: &ContainerLockGuard, _player: &Player) -> bool {
125        true
126    }
127}