Skip to main content

steel_core/inventory/slots/
anvil_slots.rs

1use std::sync::{
2    Arc,
3    atomic::{AtomicBool, AtomicI32, Ordering},
4};
5
6use steel_registry::{
7    REGISTRY, TaggedRegistryExt, blocks::block_state_ext::BlockStateExt, item_stack::ItemStack,
8    level_events, vanilla_block_tags::BlockTag,
9};
10use steel_utils::{BlockPos, BlockStateId, locks::Shared, types::UpdateFlags};
11
12use crate::{
13    behavior::blocks::AnvilBlock,
14    inventory::{
15        container::{ResultContainer, SimpleContainer},
16        lock::{ContainerId, ContainerLockGuard, ContainerRef},
17        slots::ResultHandler,
18    },
19    player::Player,
20    world::World,
21};
22
23/// Result slot handler for an anvil.
24#[derive(Clone)]
25pub struct AnvilResultHandler {
26    input_container: Shared<SimpleContainer>,
27    result_container: Shared<ResultContainer>,
28    repair_item_count: Arc<AtomicI32>,
29    level_cost: Arc<AtomicI32>,
30    only_renaming: Arc<AtomicBool>,
31    block_pos: BlockPos,
32    world: Arc<World>,
33}
34
35impl AnvilResultHandler {
36    /// Creates a new handler.
37    pub const fn new(
38        input_container: Shared<SimpleContainer>,
39        result_container: Shared<ResultContainer>,
40        repair_item_count: Arc<AtomicI32>,
41        level_cost: Arc<AtomicI32>,
42        only_renaming: Arc<AtomicBool>,
43        block_pos: BlockPos,
44        world: Arc<World>,
45    ) -> Self {
46        Self {
47            input_container,
48            result_container,
49            repair_item_count,
50            level_cost,
51            only_renaming,
52            block_pos,
53            world,
54        }
55    }
56
57    fn damage_anvil(&self, state: BlockStateId) {
58        if let Some(new_state) = AnvilBlock::damage(state) {
59            self.world
60                .set_block(self.block_pos, new_state, UpdateFlags::UPDATE_CLIENTS);
61            self.world
62                .level_event(level_events::SOUND_ANVIL_USED, self.block_pos, 0, None);
63        } else {
64            self.world.remove_block(self.block_pos, false);
65            self.world
66                .level_event(level_events::SOUND_ANVIL_BROKEN, self.block_pos, 0, None);
67        }
68    }
69}
70
71impl ResultHandler for AnvilResultHandler {
72    fn result_container(&self) -> ContainerRef {
73        ContainerRef::from(self.result_container.clone())
74    }
75
76    fn dependencies(&self) -> Vec<ContainerRef> {
77        vec![ContainerRef::from(self.input_container.clone())]
78    }
79
80    fn update_result(&self, _guard: &mut ContainerLockGuard) {}
81
82    fn on_result_taken(
83        &self,
84        guard: &mut ContainerLockGuard,
85        player: &Player,
86    ) -> Option<ItemStack> {
87        if !player.has_infinite_materials() {
88            let mut experience = player.experience.lock();
89            let cost = -self.level_cost.load(Ordering::Relaxed);
90            experience.add_levels(cost);
91        }
92
93        let input_id = ContainerId::from_arc(&self.input_container);
94        let input = guard.get_mut(input_id).expect("input container not locked");
95
96        input.set_item(0, ItemStack::empty());
97
98        let repair_cost = self.repair_item_count.load(Ordering::Relaxed);
99        if repair_cost > 0 {
100            let second = input.get_item_mut(1);
101            if !second.is_empty() && second.count() > repair_cost {
102                second.shrink(repair_cost);
103            } else {
104                input.set_item(1, ItemStack::empty());
105            }
106        } else if !self.only_renaming.load(Ordering::Relaxed) {
107            input.set_item(1, ItemStack::empty());
108        }
109
110        self.level_cost.store(0, Ordering::Relaxed);
111
112        let state = self.world.get_block_state(self.block_pos);
113        if !player.has_infinite_materials()
114            && REGISTRY
115                .blocks
116                .is_in_tag(state.get_block(), &BlockTag::ANVIL)
117            && rand::random_bool(0.12)
118        {
119            self.damage_anvil(state);
120        } else {
121            self.world
122                .level_event(level_events::SOUND_ANVIL_USED, self.block_pos, 0, None);
123        }
124
125        input.set_changed();
126        guard
127            .get_mut(ContainerId::from_arc(&self.result_container))
128            .expect("container not locked")
129            .set_changed();
130        None
131    }
132
133    fn is_result_valid(&self, _guard: &ContainerLockGuard, player: &Player) -> bool {
134        let level_cost = self.level_cost.load(Ordering::Relaxed);
135        (player.has_infinite_materials() || player.experience.lock().level() >= level_cost)
136            && level_cost > 0
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use std::sync::{
143        Arc,
144        atomic::{AtomicBool, AtomicI32},
145    };
146
147    use steel_registry::{
148        blocks::{block_state_ext::BlockStateExt as _, properties::BlockStateProperties},
149        init_vanilla_registry, vanilla_blocks,
150    };
151    use steel_utils::{BlockPos, ChunkPos, locks::IntoShared as _, types::UpdateFlags};
152
153    use super::AnvilResultHandler;
154    use crate::{
155        behavior::init_behaviors,
156        inventory::container::{ResultContainer, SimpleContainer},
157        test_support::{fresh_test_world, insert_ready_full_chunk},
158        world::SignalGetter as _,
159    };
160
161    #[test]
162    fn anvil_damage_does_not_notify_neighbors() {
163        init_vanilla_registry();
164        init_behaviors();
165        let world = fresh_test_world("anvil_damage_update_flags");
166        let anvil_pos = BlockPos::new(8, 64, 8);
167        let lamp_pos = anvil_pos.east();
168        let power_pos = lamp_pos.east();
169        insert_ready_full_chunk(&world, ChunkPos::from_block_pos(anvil_pos));
170
171        assert!(world.set_block(
172            power_pos,
173            vanilla_blocks::REDSTONE_BLOCK.default_state(),
174            UpdateFlags::UPDATE_CLIENTS,
175        ));
176        assert!(world.set_block(
177            lamp_pos,
178            vanilla_blocks::REDSTONE_LAMP.default_state(),
179            UpdateFlags::UPDATE_CLIENTS,
180        ));
181        assert!(world.set_block(
182            anvil_pos,
183            vanilla_blocks::ANVIL.default_state(),
184            UpdateFlags::UPDATE_CLIENTS,
185        ));
186        assert!(world.has_neighbor_signal(lamp_pos));
187        assert!(
188            !world
189                .get_block_state(lamp_pos)
190                .get_value(&BlockStateProperties::LIT)
191        );
192
193        let handler = AnvilResultHandler::new(
194            SimpleContainer::new(2).into_shared(),
195            ResultContainer::new().into_shared(),
196            Arc::new(AtomicI32::new(0)),
197            Arc::new(AtomicI32::new(0)),
198            Arc::new(AtomicBool::new(false)),
199            anvil_pos,
200            Arc::clone(&world),
201        );
202        handler.damage_anvil(world.get_block_state(anvil_pos));
203
204        assert_eq!(
205            world.get_block_state(anvil_pos).get_block(),
206            &vanilla_blocks::CHIPPED_ANVIL
207        );
208        assert!(
209            !world
210                .get_block_state(lamp_pos)
211                .get_value(&BlockStateProperties::LIT)
212        );
213    }
214}