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(Arc::clone(&self.result_container))
74    }
75
76    fn dependencies(&self) -> Vec<ContainerRef> {
77        vec![ContainerRef::from(Arc::clone(&self.input_container))]
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_fixture = fresh_test_world("anvil_damage_update_flags");
166        let world = &world_fixture.world;
167        let anvil_pos = BlockPos::new(8, 64, 8);
168        let lamp_pos = anvil_pos.east();
169        let power_pos = lamp_pos.east();
170        insert_ready_full_chunk(world, ChunkPos::from_block_pos(anvil_pos));
171
172        assert!(world.set_block(
173            power_pos,
174            vanilla_blocks::REDSTONE_BLOCK.default_state(),
175            UpdateFlags::UPDATE_CLIENTS,
176        ));
177        assert!(world.set_block(
178            lamp_pos,
179            vanilla_blocks::REDSTONE_LAMP.default_state(),
180            UpdateFlags::UPDATE_CLIENTS,
181        ));
182        assert!(world.set_block(
183            anvil_pos,
184            vanilla_blocks::ANVIL.default_state(),
185            UpdateFlags::UPDATE_CLIENTS,
186        ));
187        assert!(world.has_neighbor_signal(lamp_pos));
188        assert!(
189            !world
190                .get_block_state(lamp_pos)
191                .get_value(&BlockStateProperties::LIT)
192        );
193
194        let handler = AnvilResultHandler::new(
195            SimpleContainer::new(2).into_shared(),
196            ResultContainer::new().into_shared(),
197            Arc::new(AtomicI32::new(0)),
198            Arc::new(AtomicI32::new(0)),
199            Arc::new(AtomicBool::new(false)),
200            anvil_pos,
201            Arc::clone(world),
202        );
203        handler.damage_anvil(world.get_block_state(anvil_pos));
204
205        assert_eq!(
206            world.get_block_state(anvil_pos).get_block(),
207            &vanilla_blocks::CHIPPED_ANVIL
208        );
209        assert!(
210            !world
211                .get_block_state(lamp_pos)
212                .get_value(&BlockStateProperties::LIT)
213        );
214    }
215}