oc_wasm_helpers/
fluid.rs

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