steel_core/inventory/slots/
restricted_slot.rs1use std::sync::Arc;
2
3use steel_registry::item_stack::ItemStack;
4use steel_utils::{DowncastType, DowncastTypeKey};
5
6use crate::{
7 inventory::{
8 lock::{ContainerLockGuard, ContainerRef},
9 slots::{NormalSlot, Slot, SlotStorage},
10 },
11 player::Player,
12};
13
14type MayPlace = Box<dyn Fn(usize, &ItemStack) -> bool + Send + Sync>;
15type MayPickup = Box<dyn Fn(usize, &ItemStack, &ContainerLockGuard, &Player) -> bool + Send + Sync>;
16
17pub struct RestrictedRules {
20 may_place: MayPlace,
21 may_pickup: Option<MayPickup>,
22}
23
24impl RestrictedRules {
25 pub(crate) fn place_only(
26 may_place: impl Fn(usize, &ItemStack) -> bool + Send + Sync + 'static,
27 ) -> Arc<Self> {
28 Arc::new(Self {
29 may_place: Box::new(may_place),
30 may_pickup: None,
31 })
32 }
33
34 pub(crate) fn guarded(
35 may_place: impl Fn(usize, &ItemStack) -> bool + Send + Sync + 'static,
36 may_pickup: impl Fn(usize, &ItemStack, &ContainerLockGuard, &Player) -> bool
37 + Send
38 + Sync
39 + 'static,
40 ) -> Arc<Self> {
41 Arc::new(Self {
42 may_place: Box::new(may_place),
43 may_pickup: Some(Box::new(may_pickup)),
44 })
45 }
46}
47
48pub struct RestrictedSlot {
50 base: NormalSlot,
51 rules: Arc<RestrictedRules>,
52}
53
54unsafe impl DowncastType for RestrictedSlot {
56 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:slot/restricted");
57}
58
59impl RestrictedSlot {
60 pub fn new(
63 container: impl Into<ContainerRef>,
64 index: usize,
65 may_place: impl Fn(usize, &ItemStack) -> bool + Send + Sync + 'static,
66 ) -> Self {
67 Self::with_rules(container, index, RestrictedRules::place_only(may_place))
68 }
69
70 pub fn guarded(
72 container: impl Into<ContainerRef>,
73 index: usize,
74 may_place: impl Fn(usize, &ItemStack) -> bool + Send + Sync + 'static,
75 may_pickup: impl Fn(usize, &ItemStack, &ContainerLockGuard, &Player) -> bool
76 + Send
77 + Sync
78 + 'static,
79 ) -> Self {
80 Self::with_rules(
81 container,
82 index,
83 RestrictedRules::guarded(may_place, may_pickup),
84 )
85 }
86
87 pub(crate) fn with_rules(
89 container: impl Into<ContainerRef>,
90 index: usize,
91 rules: Arc<RestrictedRules>,
92 ) -> Self {
93 Self {
94 base: NormalSlot::new(container, index),
95 rules,
96 }
97 }
98}
99
100impl Slot for RestrictedSlot {
101 fn storage(&self) -> &SlotStorage {
102 self.base.storage()
103 }
104
105 fn get_item<'a>(&self, guard: &'a ContainerLockGuard) -> &'a ItemStack {
106 self.base.get_item(guard)
107 }
108
109 fn get_item_mut<'a>(&self, guard: &'a mut ContainerLockGuard) -> &'a mut ItemStack {
110 self.base.get_item_mut(guard)
111 }
112
113 fn set_item(&self, guard: &mut ContainerLockGuard, stack: ItemStack) {
114 self.base.set_item(guard, stack);
115 }
116
117 fn may_place(&self, stack: &ItemStack) -> bool {
118 (self.rules.may_place)(self.base.get_container_slot(), stack)
119 }
120
121 fn may_pickup(&self, guard: &ContainerLockGuard, player: &Player) -> bool {
122 self.rules.may_pickup.as_ref().is_none_or(|it| {
123 it(
124 self.base.get_container_slot(),
125 self.base.get_item(guard),
126 guard,
127 player,
128 )
129 })
130 }
131
132 fn get_max_stack_size(&self, guard: &ContainerLockGuard) -> i32 {
133 self.base.get_max_stack_size(guard)
134 }
135
136 fn set_changed(&self, guard: &mut ContainerLockGuard) {
137 self.base.set_changed(guard);
138 }
139
140 fn get_container_slot(&self) -> usize {
141 self.base.get_container_slot()
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use std::slice;
148 use std::sync::Arc;
149
150 use super::RestrictedSlot;
151 use crate::inventory::container::{Container, SimpleContainer};
152 use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
153 use crate::inventory::slots::Slot as _;
154 use steel_registry::data_components::vanilla_components::MAX_STACK_SIZE;
155 use steel_registry::{init_vanilla_registry, item_stack::ItemStack, vanilla_items};
156 use steel_utils::locks::{IntoShared as _, SyncMutex};
157 use steel_utils::{DowncastType, DowncastTypeKey};
158
159 struct SingleItemContainer {
160 item: ItemStack,
161 max_stack_size: i32,
162 }
163
164 unsafe impl DowncastType for SingleItemContainer {
166 const TYPE_KEY: DowncastTypeKey =
167 DowncastTypeKey::new("steel:test/container/restricted_slot_single_item");
168 }
169
170 impl Container for SingleItemContainer {
171 fn items(&self) -> &[ItemStack] {
172 slice::from_ref(&self.item)
173 }
174
175 fn items_mut(&mut self) -> &mut [ItemStack] {
176 slice::from_mut(&mut self.item)
177 }
178
179 fn get_max_stack_size(&self) -> i32 {
180 self.max_stack_size
181 }
182
183 fn set_changed(&mut self) {}
184 }
185
186 #[test]
187 fn max_stack_size_delegates_to_the_container_and_item() {
188 init_vanilla_registry();
189 let capped = Arc::new(SyncMutex::new(SingleItemContainer {
190 item: ItemStack::empty(),
191 max_stack_size: 1,
192 }));
193 let capped_ref = ContainerRef::from(capped);
194 let capped_slot = RestrictedSlot::new(capped_ref.clone(), 0, |_, _| true);
195 let mut capped_guard = ContainerLockGuard::lock_all(&[capped_ref]);
196 let capped_remainder = capped_slot.safe_insert(
197 &mut capped_guard,
198 ItemStack::with_count(&vanilla_items::STONE, 64),
199 64,
200 );
201 assert_eq!(capped_slot.get_item(&capped_guard).count(), 1);
202 assert_eq!(capped_remainder.count(), 63);
203
204 let default = SimpleContainer::new(1).into_shared();
205 let default_ref = ContainerRef::from(default);
206 let default_slot = RestrictedSlot::new(default_ref.clone(), 0, |_, _| true);
207 let mut default_guard = ContainerLockGuard::lock_all(&[default_ref]);
208 let mut stack = ItemStack::new(&vanilla_items::STONE);
209 stack.set(MAX_STACK_SIZE, 99);
210 stack.set_count(99);
211 let default_remainder = default_slot.safe_insert(&mut default_guard, stack, 99);
212
213 assert!(default_remainder.is_empty());
214 assert_eq!(default_slot.get_item(&default_guard).count(), 99);
215 }
216}