oc_wasm_helpers/
inventory.rs

1use crate::map_decoder;
2use minicbor::decode::{Decode, Decoder, Error};
3
4/// Information about an item stack.
5///
6/// The `'buffer` lifetime is the lifetime of the buffer holding strings to which the object
7/// refers.
8#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct ItemStack<'buffer> {
10	/// The internal (Minecraft system) name of the item.
11	///
12	/// For example, this might be `minecraft:cobblestone`.
13	pub name: &'buffer str,
14
15	/// The human-readable name of the item.
16	///
17	/// For example, this might be `Cobblestone`.
18	pub label: &'buffer str,
19
20	/// The number of items in the stack.
21	pub size: u32,
22
23	/// The maximum number of items that can be held in the stack.
24	pub max_size: u32,
25
26	/// The damage value of the item, if it is a tool, or zero if not.
27	pub damage: u32,
28
29	/// The damage value at which the item breaks, if it is a tool, or zero if not.
30	pub max_damage: u32,
31
32	/// Whether the item has extra NBT data attached.
33	pub has_tag: bool,
34}
35
36impl<'buffer, Context> Decode<'buffer, Context> for ItemStack<'buffer> {
37	fn decode(d: &mut Decoder<'buffer>, context: &mut Context) -> Result<Self, Error> {
38		match OptionItemStack::decode(d, context)?.into() {
39			Some(s) => Ok(s),
40			None => Err(Error::message("missing item stack")),
41		}
42	}
43}
44
45/// Information about an item stack which may or may not exist.
46///
47/// This type exists, rather than just using `Option<ItemStack>` directly, because `Option` has a
48/// blanket `Decode` implementation, and we need a different implementation which also maps a
49/// non-null empty map to `None`.
50///
51/// The `'buffer` lifetime is the lifetime of the buffer holding strings to which the object
52/// refers.
53#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
54#[repr(transparent)]
55pub struct OptionItemStack<'buffer>(pub Option<ItemStack<'buffer>>);
56
57impl<'buffer, Context> Decode<'buffer, Context> for OptionItemStack<'buffer> {
58	fn decode(d: &mut Decoder<'buffer>, context: &mut Context) -> Result<Self, Error> {
59		map_decoder::decode_nullable::<OptionItemStackBuilder<'buffer>, Context>(d, context)
60	}
61}
62
63impl<'buffer> From<OptionItemStack<'buffer>> for Option<ItemStack<'buffer>> {
64	fn from(x: OptionItemStack<'buffer>) -> Option<ItemStack<'buffer>> {
65		x.0
66	}
67}
68
69/// A map-decoding builder for an [`OptionItemStack`].
70#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub struct OptionItemStackBuilder<'buffer> {
72	/// The internal (Minecraft system) name of the item.
73	///
74	/// For example, this might be `minecraft:cobblestone`.
75	name: Option<&'buffer str>,
76
77	/// The human-readable name of the item.
78	///
79	/// For example, this might be `Cobblestone`.
80	label: Option<&'buffer str>,
81
82	/// The number of items in the stack.
83	size: Option<u32>,
84
85	/// The maximum number of items that can be held in the stack.
86	max_size: Option<u32>,
87
88	/// The damage value of the item, if it is a tool, or zero if not.
89	damage: Option<u32>,
90
91	/// The damage value at which the item breaks, if it is a tool, or zero if not.
92	max_damage: Option<u32>,
93
94	/// Whether the item has extra NBT data attached.
95	has_tag: Option<bool>,
96}
97
98impl<'buffer> map_decoder::Builder<'buffer> for OptionItemStackBuilder<'buffer> {
99	type Output = OptionItemStack<'buffer>;
100
101	fn entry<Context>(
102		&mut self,
103		key: &str,
104		d: &mut Decoder<'buffer>,
105		_: &mut Context,
106	) -> Result<bool, Error> {
107		match key {
108			"name" => {
109				self.name = Some(d.str()?);
110				Ok(true)
111			}
112			"label" => {
113				self.label = Some(d.str()?);
114				Ok(true)
115			}
116			"size" => {
117				self.size = Some(d.u32()?);
118				Ok(true)
119			}
120			"maxSize" => {
121				self.max_size = Some(d.u32()?);
122				Ok(true)
123			}
124			"damage" => {
125				self.damage = Some(d.u32()?);
126				Ok(true)
127			}
128			"maxDamage" => {
129				self.max_damage = Some(d.u32()?);
130				Ok(true)
131			}
132			"hasTag" => {
133				self.has_tag = Some(d.bool()?);
134				Ok(true)
135			}
136			_ => Ok(false),
137		}
138	}
139
140	fn build(self) -> Result<Self::Output, Error> {
141		// If all the required keys are present, return an item stack.
142		if let Some(name) = self.name {
143			if let Some(label) = self.label {
144				if let Some(size) = self.size {
145					if let Some(max_size) = self.max_size {
146						if let Some(damage) = self.damage {
147							if let Some(max_damage) = self.max_damage {
148								if let Some(has_tag) = self.has_tag {
149									// Some APIs map an empty slot to “zero air” instead of an
150									// empty table.
151									return Ok(OptionItemStack(
152										if name == "minecraft:air" && size == 0 {
153											None
154										} else {
155											Some(ItemStack {
156												name,
157												label,
158												size,
159												max_size,
160												damage,
161												max_damage,
162												has_tag,
163											})
164										},
165									));
166								}
167							}
168						}
169					}
170				}
171			}
172		}
173
174		// If all the required keys are absent, return None.
175		if (
176			self.name,
177			self.label,
178			self.size,
179			self.max_size,
180			self.damage,
181			self.max_damage,
182			self.has_tag,
183		) == (None, None, None, None, None, None, None)
184		{
185			return Ok(OptionItemStack(None));
186		}
187
188		// If some but not all of the required keys are present, fail.
189		Err(Error::message("missing key in item stack"))
190	}
191}
192
193impl<'buffer> map_decoder::NullableBuilder<'buffer> for OptionItemStackBuilder<'buffer> {
194	fn build_null() -> OptionItemStack<'buffer> {
195		OptionItemStack(None)
196	}
197}