steel_core/inventory/slots/
anvil_slots.rs1use 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#[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 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}