oc_wasm_opencomputers/
robot.rs

1//! Provides high-level access to the robot component APIs.
2
3use crate::common::Rgb;
4use crate::error::Error;
5use core::convert::TryFrom;
6use core::fmt::{Display, Formatter};
7use core::num::NonZeroU32;
8use core::str::FromStr;
9use minicbor::Decode;
10use oc_wasm_futures::invoke::{component_method, Buffer};
11use oc_wasm_helpers::{
12	error::NullAndStringOr,
13	sides::{Relative as RelativeSide, Side},
14	Lockable,
15};
16use oc_wasm_safe::{
17	component::{Invoker, MethodCallError},
18	Address,
19};
20
21/// The type name for robot components.
22pub const TYPE: &str = "robot";
23
24/// An error returned when converting a [`RelativeSide`] into an [`ActionSide`] if the value does
25/// not map.
26#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub struct TryFromRelativeSideError(());
28
29impl Display for TryFromRelativeSideError {
30	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
31		"invalid value".fmt(f)
32	}
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for TryFromRelativeSideError {}
37
38/// The directions a robot can move.
39///
40/// A robot cannot strafe to the left or right. In order to move in such directions, it must first
41/// turn to face towards or away from that direction instead.
42#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
43pub enum MoveDirection {
44	Down,
45	Up,
46	Back,
47	Front,
48}
49
50impl From<MoveDirection> for u8 {
51	fn from(x: MoveDirection) -> Self {
52		match x {
53			MoveDirection::Down => 0,
54			MoveDirection::Up => 1,
55			MoveDirection::Back => 2,
56			MoveDirection::Front => 3,
57		}
58	}
59}
60
61impl From<MoveDirection> for usize {
62	fn from(x: MoveDirection) -> Self {
63		u8::from(x) as usize
64	}
65}
66
67impl From<MoveDirection> for RelativeSide {
68	fn from(x: MoveDirection) -> Self {
69		match x {
70			MoveDirection::Down => Self::Bottom,
71			MoveDirection::Up => Self::Top,
72			MoveDirection::Back => Self::Back,
73			MoveDirection::Front => Self::Front,
74		}
75	}
76}
77
78impl TryFrom<u8> for MoveDirection {
79	type Error = oc_wasm_helpers::error::TryFromInt;
80
81	fn try_from(x: u8) -> Result<Self, Self::Error> {
82		match x {
83			0 => Ok(Self::Down),
84			1 => Ok(Self::Up),
85			2 => Ok(Self::Back),
86			3 => Ok(Self::Front),
87			_ => Err(oc_wasm_helpers::error::TryFromInt),
88		}
89	}
90}
91
92impl TryFrom<RelativeSide> for MoveDirection {
93	type Error = TryFromRelativeSideError;
94
95	fn try_from(x: RelativeSide) -> Result<Self, Self::Error> {
96		match x {
97			RelativeSide::Bottom => Ok(Self::Down),
98			RelativeSide::Top => Ok(Self::Up),
99			RelativeSide::Back => Ok(Self::Back),
100			RelativeSide::Front => Ok(Self::Front),
101			_ => Err(TryFromRelativeSideError(())),
102		}
103	}
104}
105
106impl From<ActionSide> for MoveDirection {
107	fn from(x: ActionSide) -> Self {
108		match x {
109			ActionSide::Bottom => Self::Down,
110			ActionSide::Top => Self::Up,
111			ActionSide::Front => Self::Front,
112		}
113	}
114}
115
116impl Side for MoveDirection {}
117
118/// The sides on which a robot can manipulate blocks.
119///
120/// A robot cannot manipulate blocks to its left or right sides or behind it. In order to
121/// manipulate such blocks, it must first turn to face that direction instead.
122#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
123pub enum ActionSide {
124	Bottom,
125	Top,
126	Front,
127}
128
129impl From<ActionSide> for u8 {
130	fn from(x: ActionSide) -> Self {
131		match x {
132			ActionSide::Bottom => 0,
133			ActionSide::Top => 1,
134			ActionSide::Front => 3,
135		}
136	}
137}
138
139impl From<ActionSide> for usize {
140	fn from(x: ActionSide) -> Self {
141		u8::from(x) as usize
142	}
143}
144
145impl From<ActionSide> for RelativeSide {
146	fn from(x: ActionSide) -> Self {
147		match x {
148			ActionSide::Bottom => Self::Bottom,
149			ActionSide::Top => Self::Top,
150			ActionSide::Front => Self::Front,
151		}
152	}
153}
154
155impl TryFrom<u8> for ActionSide {
156	type Error = oc_wasm_helpers::error::TryFromInt;
157
158	fn try_from(x: u8) -> Result<Self, Self::Error> {
159		match x {
160			0 => Ok(Self::Bottom),
161			1 => Ok(Self::Top),
162			3 => Ok(Self::Front),
163			_ => Err(oc_wasm_helpers::error::TryFromInt),
164		}
165	}
166}
167
168impl TryFrom<RelativeSide> for ActionSide {
169	type Error = TryFromRelativeSideError;
170
171	fn try_from(x: RelativeSide) -> Result<Self, Self::Error> {
172		match x {
173			RelativeSide::Bottom => Ok(Self::Bottom),
174			RelativeSide::Top => Ok(Self::Top),
175			RelativeSide::Front => Ok(Self::Front),
176			_ => Err(TryFromRelativeSideError(())),
177		}
178	}
179}
180
181impl Side for ActionSide {}
182
183/// The directions in which a robot can rotate.
184#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
185pub enum Rotation {
186	Clockwise,
187	Counterclockwise,
188}
189
190/// The things that can be hit when swinging a tool.
191#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
192pub enum ToolHit {
193	/// The tool hit an entity, which was attacked.
194	Entity,
195
196	/// The tool hit a block, which was broken.
197	Block,
198
199	/// The tool hit a fire, which was extinguished.
200	Fire,
201
202	/// The tool hit air, which did nothing.
203	Air,
204}
205
206/// The possible results of successfully right-clicking on a location.
207#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
208pub enum ActivateResult {
209	/// A usable block, such as a lever or button, was activated.
210	BlockActivated,
211
212	/// The equipped inventory item, such as a block of dirt, a torch, a spawn egg, or a fire
213	/// charge, was placed into the world.
214	ItemPlaced,
215
216	/// The equipped inventory item, such as a water bucket or an empty bucket when facing a fluid,
217	/// was used in a way other than placing a block into the world.
218	ItemUsed,
219
220	/// The equipped inventory item, such as shears, was used on an adjacent entity.
221	ItemInteracted,
222}
223
224/// The things that can exist in the space of a block.
225#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
226pub enum BlockContent {
227	/// There is a creature (player, friendly, neutral, or hostile) or minecart in the space.
228	///
229	/// Despite the name, note that certain other entities, such as drones, do not return this
230	/// value. The name is based on the name used within OpenComputers itself.
231	Entity,
232
233	/// There is nothing in the space.
234	Air,
235
236	/// There is a liquid in the space.
237	Liquid,
238
239	/// There is a block in the space that the robot can move into, which would be destroyed if it
240	/// did so.
241	///
242	/// An example of this kind of content is tall grass.
243	Replaceable,
244
245	/// There is a block in the space that a player could walk through, but the robot cannot.
246	///
247	/// An example of this kind of content is a flower.
248	Passable,
249
250	/// There is a normal block in the space.
251	Solid,
252}
253
254impl BlockContent {
255	/// Returns a string describing the content.
256	#[must_use = "This function is only useful for its return value"]
257	pub fn as_str(&self) -> &'static str {
258		match self {
259			Self::Entity => "entity",
260			Self::Air => "air",
261			Self::Liquid => "liquid",
262			Self::Replaceable => "replaceable",
263			Self::Passable => "passable",
264			Self::Solid => "solid",
265		}
266	}
267}
268
269impl Display for BlockContent {
270	fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
271		self.as_str().fmt(f)
272	}
273}
274
275impl FromStr for BlockContent {
276	type Err = Error;
277
278	fn from_str(s: &str) -> Result<Self, Self::Err> {
279		match s {
280			"entity" => Ok(Self::Entity),
281			"air" => Ok(Self::Air),
282			"liquid" => Ok(Self::Liquid),
283			"replaceable" => Ok(Self::Replaceable),
284			"passable" => Ok(Self::Passable),
285			"solid" => Ok(Self::Solid),
286			_ => Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown)),
287		}
288	}
289}
290
291impl TryFrom<&str> for BlockContent {
292	type Error = <Self as FromStr>::Err;
293
294	fn try_from(s: &str) -> Result<Self, Self::Error> {
295		Self::from_str(s)
296	}
297}
298
299/// A robot component.
300#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
301pub struct Robot(Address);
302
303impl Robot {
304	/// Creates a wrapper around a robot.
305	///
306	/// The `address` parameter is the address of the robot. It is not checked for correctness at
307	/// this time because network topology could change after this function returns; as such, each
308	/// usage of the value may fail instead.
309	#[must_use = "This function is only useful for its return value"]
310	pub fn new(address: Address) -> Self {
311		Self(address)
312	}
313
314	/// Returns the address of the robot.
315	#[must_use = "This function is only useful for its return value"]
316	pub fn address(&self) -> &Address {
317		&self.0
318	}
319}
320
321impl<'invoker, 'buffer, B: 'buffer + Buffer> Lockable<'invoker, 'buffer, B> for Robot {
322	type Locked = Locked<'invoker, 'buffer, B>;
323
324	fn lock(&self, invoker: &'invoker mut Invoker, buffer: &'buffer mut B) -> Self::Locked {
325		Locked {
326			address: self.0,
327			invoker,
328			buffer,
329		}
330	}
331}
332
333/// A robot component on which methods can be invoked.
334///
335/// This type combines a robot address, an [`Invoker`] that can be used to make method calls, and a
336/// scratch buffer used to perform CBOR encoding and decoding. A value of this type can be created
337/// by calling [`Robot::lock`], and it can be dropped to return the borrow of the invoker and
338/// buffer to the caller so they can be reused for other purposes.
339///
340/// The `'invoker` lifetime is the lifetime of the invoker. The `'buffer` lifetime is the lifetime
341/// of the buffer. The `B` type is the type of scratch buffer to use.
342pub struct Locked<'invoker, 'buffer, B: Buffer> {
343	/// The component address.
344	address: Address,
345
346	/// The invoker.
347	invoker: &'invoker mut Invoker,
348
349	/// The buffer.
350	buffer: &'buffer mut B,
351}
352
353impl<'invoker, 'buffer, B: Buffer> Locked<'invoker, 'buffer, B> {
354	/// Returns the colour of the robot’s side body light.
355	///
356	/// # Errors
357	/// * [`BadComponent`](Error::BadComponent)
358	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
359	pub async fn get_light_colour(&mut self) -> Result<Rgb, Error> {
360		let ret: (u32,) = component_method::<(), _, _>(
361			self.invoker,
362			self.buffer,
363			&self.address,
364			"getLightColor",
365			None,
366		)
367		.await?;
368		Ok(Rgb(ret.0))
369	}
370
371	/// Sets the colour of the robot’s side body light.
372	///
373	/// # Errors
374	/// * [`BadComponent`](Error::BadComponent)
375	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
376	pub async fn set_light_colour(&mut self, colour: Rgb) -> Result<(), Error> {
377		component_method::<_, (u32,), _>(
378			self.invoker,
379			self.buffer,
380			&self.address,
381			"setLightColor",
382			Some(&(colour.0,)),
383		)
384		.await?;
385		Ok(())
386	}
387
388	/// Returns the durability of the equipped tool, or `None` if no tool is equipped or the
389	/// equipped tool does not have a concept of durability.
390	///
391	/// The durability value, if available, is a number between 0 and 1.
392	///
393	/// # Errors
394	/// * [`BadComponent`](Error::BadComponent)
395	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
396	pub async fn durability(&mut self) -> Result<Option<f64>, Error> {
397		let ret: NullAndStringOr<'_, (f64,)> = component_method::<(), _, _>(
398			self.invoker,
399			self.buffer,
400			&self.address,
401			"durability",
402			None,
403		)
404		.await?;
405		Ok(match ret {
406			NullAndStringOr::Ok(v) => Some(v.0),
407			NullAndStringOr::Err(_) => None,
408		})
409	}
410
411	/// Moves the robot.
412	///
413	/// The `direction` parameter indicates in which direction to try to move.
414	///
415	/// # Errors
416	/// * [`BadComponent`](Error::BadComponent)
417	/// * [`Blocked`](Error::Blocked)
418	/// * [`ImpossibleMove`](Error::ImpossibleMove)
419	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
420	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
421	pub async fn move_robot(&mut self, direction: MoveDirection) -> Result<(), Error> {
422		let ret: NullAndStringOr<'_, (bool,)> = component_method(
423			self.invoker,
424			self.buffer,
425			&self.address,
426			"move",
427			Some(&(u8::from(direction),)),
428		)
429		.await?;
430		match ret {
431			NullAndStringOr::Ok(_) => Ok(()),
432			NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
433			NullAndStringOr::Err("impossible move") => Err(Error::ImpossibleMove),
434			NullAndStringOr::Err(s) => Err(Error::Blocked(s.parse()?)),
435		}
436	}
437
438	/// Turns the robot.
439	///
440	/// The `direction` parameter indicates in which direction to turn.
441	///
442	/// # Errors
443	/// * [`BadComponent`](Error::BadComponent)
444	/// * [`NotEnoughEnergy`](Error::NotEnoughEnergy)
445	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
446	pub async fn turn(&mut self, direction: Rotation) -> Result<(), Error> {
447		let ret: NullAndStringOr<'_, (bool,)> = component_method(
448			self.invoker,
449			self.buffer,
450			&self.address,
451			"turn",
452			Some(&(direction == Rotation::Clockwise,)),
453		)
454		.await?;
455		match ret {
456			NullAndStringOr::Ok(_) => Ok(()),
457			NullAndStringOr::Err("not enough energy") => Err(Error::NotEnoughEnergy),
458			NullAndStringOr::Err(_) => {
459				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
460			}
461		}
462	}
463
464	/// Returns the robot’s name.
465	///
466	/// # Errors
467	/// * [`BadComponent`](Error::BadComponent)
468	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
469	pub async fn name(self) -> Result<&'buffer str, Error> {
470		Ok(component_method::<(), (&'buffer str,), _>(
471			self.invoker,
472			self.buffer,
473			&self.address,
474			"name",
475			None,
476		)
477		.await?
478		.0)
479	}
480
481	/// Left-clicks the currently equipped tool on an adjacent block or space.
482	///
483	/// The `side` parameter indicates where to swing the tool, relative the robot’s current facing
484	/// direction. The `face` parameter indicates which face of the target location to aim at. The
485	/// `sneak` parameter indicates whether or not to sneak while operating the tool.
486	///
487	/// # Errors
488	/// * [`BadComponent`](Error::BadComponent)
489	/// * [`BadItem`](Error::BadItem) is returned if the block in the target location is too hard
490	///   for the equipped tool to break, or the tool is of the wrong kind (for example, a shovel
491	///   swinging at cobblestone).
492	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
493	pub async fn swing(
494		&mut self,
495		side: ActionSide,
496		face: Option<RelativeSide>,
497		sneak: bool,
498	) -> Result<ToolHit, Error> {
499		let ret: (bool, &str) = component_method(
500			self.invoker,
501			self.buffer,
502			&self.address,
503			"swing",
504			Some(&(u8::from(side), face.map(u8::from), sneak)),
505		)
506		.await?;
507		if ret.1 == "entity" {
508			Ok(ToolHit::Entity)
509		} else if ret.1 == "block" {
510			if ret.0 {
511				Ok(ToolHit::Block)
512			} else {
513				Err(Error::BadItem)
514			}
515		} else if ret.1 == "fire" {
516			Ok(ToolHit::Fire)
517		} else if ret.1 == "air" {
518			Ok(ToolHit::Air)
519		} else {
520			// The OpenComputers robot component’s swing method does not return any other values.
521			// Therefore, if we see another value, we must be addressing a different component that
522			// also has a method named swing.
523			Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
524		}
525	}
526
527	/// Right-clicks the currently equipped item on an adjacent block or space.
528	///
529	/// The `side` parameter indicates where to use the item, relative the robot’s current facing
530	/// direction. The `face` parameter indicates which face of the target location to aim at. The
531	/// `sneak` parameter indicates whether or not to sneak while operating the tool. The
532	/// `duration` parameter indicates how long to hold down the right mouse button.
533	///
534	/// # Errors
535	/// * [`BadComponent`](Error::BadComponent)
536	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
537	/// * [`Failed`](Error::Failed) is returned if the item is not usable (for example, if it is a
538	///   piece of coal), is usable on certain entities but such entities are not present (for
539	///   example, if it is a shears and there is nothing in front of the robot or the entity in
540	///   front of the robot is a zombie), or is a placeable block but there is no adjacent block
541	///   on which to mount it and the robot is not equipped with an angel upgrade.
542	pub async fn use_item(
543		&mut self,
544		side: ActionSide,
545		face: Option<RelativeSide>,
546		sneak: bool,
547		duration: f64,
548	) -> Result<ActivateResult, Error> {
549		let ret: (bool, Option<&str>) = component_method(
550			self.invoker,
551			self.buffer,
552			&self.address,
553			"use",
554			Some(&(u8::from(side), face.map(u8::from), sneak, duration)),
555		)
556		.await?;
557		if ret.1 == Some("block_activated") {
558			Ok(ActivateResult::BlockActivated)
559		} else if ret.1 == Some("item_placed") {
560			Ok(ActivateResult::ItemPlaced)
561		} else if ret.1 == Some("item_used") {
562			Ok(ActivateResult::ItemUsed)
563		} else if ret.1 == Some("item_interacted") {
564			Ok(ActivateResult::ItemInteracted)
565		} else if ret.0 {
566			// The OpenComputers robot component’s swing method does not return any other success
567			// reasons. Therefore, if we see another value, we must be addressing a different
568			// component that also has a method named use.
569			Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
570		} else {
571			Err(Error::Failed)
572		}
573	}
574
575	/// Places one of the currently selected item into the world as a block.
576	///
577	/// The `side` parameter indicates in which location, relative to the robot’s current facing
578	/// direction, the item should be placed. The `face` parameter indicates on which face of the
579	/// target location the item should be placed (for example, for a lever or torch, on which
580	/// adjacent surface the item should be mounted), again relative to the robot’s current facing
581	/// direction; or is set to `None` to try all possible faces. The `sneak` parameter indicates
582	/// whether or not to sneak while placing the item.
583	///
584	/// # Errors
585	/// * [`BadComponent`](Error::BadComponent)
586	/// * [`BadItem`](Error::BadItem) is returned if there is no item selected.
587	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
588	/// * [`Failed`](Error::Failed) is returned if the selected item does not have a placeable
589	///   block form (e.g. it is a tool instead); the target location is obstructed by an existing
590	///   block; there is no existing block adjacent to the target placement location and the robot
591	///   is not equipped with an angel upgrade; or `face` is provided, there is no existing block
592	///   adjacent to the target placement location on the specified side, and the robot is not
593	///   equipped with an angel upgrade; `face` is provided and points back towards the robot; or
594	///   there is not enough energy.
595	pub async fn place(
596		&mut self,
597		side: ActionSide,
598		face: Option<RelativeSide>,
599		sneak: bool,
600	) -> Result<(), Error> {
601		let ret: NullAndStringOr<'_, (bool,)> = component_method(
602			self.invoker,
603			self.buffer,
604			&self.address,
605			"place",
606			Some(&(u8::from(side), face.map(u8::from), sneak)),
607		)
608		.await?;
609		match ret {
610			NullAndStringOr::Ok((true,)) => Ok(()),
611			NullAndStringOr::Ok((false,)) => Err(Error::Failed),
612			NullAndStringOr::Err("nothing selected") => Err(Error::BadItem),
613			NullAndStringOr::Err(_) => {
614				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
615			}
616		}
617	}
618
619	/// Checks what’s present in a specified direction.
620	///
621	/// The `side` parameter indicates which space, relative to the robot’s current facing
622	/// direction, to scan.
623	///
624	/// # Errors
625	/// * [`BadComponent`](Error::BadComponent)
626	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
627	pub async fn detect(&mut self, side: ActionSide) -> Result<BlockContent, Error> {
628		let ret: (bool, &str) = component_method(
629			self.invoker,
630			self.buffer,
631			&self.address,
632			"detect",
633			Some(&(u8::from(side),)),
634		)
635		.await?;
636		ret.1.parse()
637	}
638
639	/// Returns the size of the robot’s inventory, in slots.
640	///
641	/// # Errors
642	/// * [`BadComponent`](Error::BadComponent)
643	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
644	pub async fn inventory_size(&mut self) -> Result<u32, Error> {
645		Ok(component_method::<(), (u32,), _>(
646			self.invoker,
647			self.buffer,
648			&self.address,
649			"inventorySize",
650			None,
651		)
652		.await?
653		.0)
654	}
655
656	/// Returns the currently selected inventory slot number.
657	///
658	/// # Errors
659	/// * [`BadComponent`](Error::BadComponent)
660	/// * [`NoInventory`](Error::NoInventory)
661	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
662	pub async fn selected(&mut self) -> Result<NonZeroU32, Error> {
663		let ret: (u32,) =
664			component_method::<(), _, _>(self.invoker, self.buffer, &self.address, "select", None)
665				.await?;
666		match NonZeroU32::new(ret.0) {
667			Some(n) => Ok(n),
668			None => Err(Error::NoInventory),
669		}
670	}
671
672	/// Selects an inventory slot.
673	///
674	/// # Errors
675	/// * [`BadComponent`](Error::BadComponent)
676	/// * [`BadInventorySlot`](Error::BadInventorySlot)
677	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
678	pub async fn select(&mut self, slot: NonZeroU32) -> Result<(), Error> {
679		let ret = component_method::<_, (u32,), _>(
680			self.invoker,
681			self.buffer,
682			&self.address,
683			"select",
684			Some(&(slot,)),
685		)
686		.await;
687		match ret {
688			Ok(_) => Ok(()),
689			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
690			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
691			Err(e) => Err(Error::BadComponent(e.into())),
692		}
693	}
694
695	/// Returns the number of items in an inventory slot.
696	///
697	/// # Errors
698	/// * [`BadComponent`](Error::BadComponent)
699	/// * [`BadInventorySlot`](Error::BadInventorySlot)
700	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
701	pub async fn count(&mut self, slot: NonZeroU32) -> Result<u32, Error> {
702		let ret = component_method::<_, (u32,), _>(
703			self.invoker,
704			self.buffer,
705			&self.address,
706			"count",
707			Some(&(slot,)),
708		)
709		.await;
710		match ret {
711			Ok((n,)) => Ok(n),
712			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
713			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
714			Err(e) => Err(Error::BadComponent(e.into())),
715		}
716	}
717
718	/// Returns the number of items in the currently selected inventory slot.
719	///
720	/// If the robot does not have an inventory, this function returns 0.
721	///
722	/// # Errors
723	/// * [`BadComponent`](Error::BadComponent)
724	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
725	pub async fn count_selected(&mut self) -> Result<u32, Error> {
726		Ok(component_method::<(), (u32,), _>(
727			self.invoker,
728			self.buffer,
729			&self.address,
730			"count",
731			None,
732		)
733		.await?
734		.0)
735	}
736
737	/// Returns the number of items that can be added to an inventory slot.
738	///
739	/// # Errors
740	/// * [`BadComponent`](Error::BadComponent)
741	/// * [`BadInventorySlot`](Error::BadInventorySlot)
742	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
743	pub async fn space(&mut self, slot: NonZeroU32) -> Result<u32, Error> {
744		let ret = component_method::<_, (u32,), _>(
745			self.invoker,
746			self.buffer,
747			&self.address,
748			"space",
749			Some(&(slot,)),
750		)
751		.await;
752		match ret {
753			Ok((n,)) => Ok(n),
754			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
755			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
756			Err(e) => Err(Error::BadComponent(e.into())),
757		}
758	}
759
760	/// Returns the number of items that can be added to the currently selected inventory slot.
761	///
762	/// If the robot does not have an inventory, this function returns 64.
763	///
764	/// # Errors
765	/// * [`BadComponent`](Error::BadComponent)
766	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
767	pub async fn space_selected(&mut self) -> Result<u32, Error> {
768		Ok(component_method::<(), (u32,), _>(
769			self.invoker,
770			self.buffer,
771			&self.address,
772			"space",
773			None,
774		)
775		.await?
776		.0)
777	}
778
779	/// Returns whether or not the currently selected inventory slot contains the same item as the
780	/// specified inventory slot.
781	///
782	/// The `nbt` parameter indicates whether to consider NBT data during the comparison. Two empty
783	/// slots are considered equal to each other but not to any nonempty slot. Between two nonempty
784	/// slots, the sizes of the stacks do not matter; a slot with one torch and a slot with two
785	/// torches are considered equal.
786	///
787	/// # Errors
788	/// * [`BadComponent`](Error::BadComponent)
789	/// * [`BadInventorySlot`](Error::BadInventorySlot)
790	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
791	pub async fn compare_to(&mut self, other_slot: NonZeroU32, nbt: bool) -> Result<bool, Error> {
792		let ret = component_method::<_, (bool,), _>(
793			self.invoker,
794			self.buffer,
795			&self.address,
796			"compareTo",
797			Some(&(other_slot, nbt)),
798		)
799		.await;
800		match ret {
801			Ok((f,)) => Ok(f),
802			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
803			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
804			Err(e) => Err(Error::BadComponent(e.into())),
805		}
806	}
807
808	/// Moves items from the currently selected inventory slot to the specified inventory slot.
809	///
810	/// The `amount` parameter indicates the maximum number of items to transfer. If the target
811	/// slot is empty, then the lesser of `amount` and the number of items available is moved. If
812	/// the selected and target slots contain the same type of item, then the lesser of `amount`,
813	/// the number of items available, and the remaining space in the target is moved. If the
814	/// target slot contains a different type of item and `amount` is greater than or equal to the
815	/// number of items in the selected slot, then the two stacks are swapped.
816	///
817	/// # Errors
818	/// * [`BadComponent`](Error::BadComponent)
819	/// * [`BadInventorySlot`](Error::BadInventorySlot)
820	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
821	/// * [`Failed`](Error::Failed) is returned if `amount` was nonzero but no items could be moved
822	///   (because the source stack is empty, the target stack is of the same type and full, or the
823	///   target stack is of a different type and `amount` is less than the size of the source
824	///   stack and therefore the stacks cannot be swapped).
825	pub async fn transfer_to(&mut self, target_slot: NonZeroU32, amount: u32) -> Result<(), Error> {
826		let ret = component_method::<_, (bool,), _>(
827			self.invoker,
828			self.buffer,
829			&self.address,
830			"transferTo",
831			Some(&(target_slot, amount)),
832		)
833		.await;
834		match ret {
835			Ok((true,)) => Ok(()),
836			Ok((false,)) => Err(Error::Failed),
837			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
838			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
839			Err(e) => Err(Error::BadComponent(e.into())),
840		}
841	}
842
843	/// Compares a block in the world with the selected inventory slot.
844	///
845	/// The `fuzzy` parameter, when `true`, indicates that the comparison should ignore item
846	/// subtypes (for example, dirt and coarse dirt are considered equal under fuzzy comparison
847	/// rules). An empty itemstack is considered unequal to any block, and an air block is
848	/// considered unequal to any itemstack.
849	///
850	/// # Errors
851	/// * [`BadComponent`](Error::BadComponent)
852	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
853	pub async fn compare(&mut self, side: ActionSide, fuzzy: bool) -> Result<bool, Error> {
854		Ok(component_method::<_, (bool,), _>(
855			self.invoker,
856			self.buffer,
857			&self.address,
858			"compare",
859			Some(&(u8::from(side), fuzzy)),
860		)
861		.await?
862		.0)
863	}
864
865	/// Drops items from the robot’s selected slot into the world as an itemstack entity or into an
866	/// adjacent inventory.
867	///
868	/// If a block with an inventory is present on the specified side, up to `count` items from the
869	/// currently selected slot are inserted into the inventory following the usual item insertion
870	/// rules. If there are fewer than `count` items available or the inventory does not have space
871	/// to hold `count` items, then fewer items are moved.
872	///
873	/// If no inventory is present on the specified side, up to `count` items are dropped into the
874	/// world as an itemstack entity. If there are fewer than `count` items available, then a
875	/// smaller stack is dropped.
876	///
877	/// # Errors
878	/// * [`BadComponent`](Error::BadComponent)
879	/// * [`InventoryFull`](Error::InventoryFull)
880	/// * [`NoItem`](Error::NoItem)
881	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
882	pub async fn drop(&mut self, side: ActionSide, count: u32) -> Result<(), Error> {
883		let ret = component_method::<_, (bool, Option<&str>), _>(
884			self.invoker,
885			self.buffer,
886			&self.address,
887			"drop",
888			Some(&(u8::from(side), count)),
889		)
890		.await;
891		match ret {
892			Ok((true, _)) => Ok(()),
893			Ok((false, None)) => Err(Error::NoItem),
894			Ok((false, Some("inventory full"))) => Err(Error::InventoryFull),
895			Ok((false, Some(_))) => Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown)),
896			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
897			Err(e) => Err(Error::BadComponent(e.into())),
898		}
899	}
900
901	/// Sucks up items from an itemstack entity or an adjacent inventory block.
902	///
903	/// If a block with an inventory is present on the specified side, up to `count` items from a
904	/// single stack in that inventory are inserted into the robot’s inventory.
905	///
906	/// If no inventory is present on the specified side, one itemstack entity (of any size,
907	/// ignoring `count`) is picked up from the world.
908	///
909	/// The sucked items are placed into the robot’s inventory, initially into the currently
910	/// selected slot, then into slots after it, then wrapping around to slots before it, as
911	/// necessary to hold all the sucked items. If there is not enough space to hold the items,
912	/// then the items that cannot be held are left behind in their original location.
913	///
914	/// If all the robot’s inventory slots contain items, preventing a new type of item from being
915	/// added, external inventory slots or itemstack entities are selected to match an item type
916	/// already present in the robot. Otherwise, the first populated inventory slot, or an
917	/// arbitrary itemstack entity, is selected.
918	///
919	/// On success, the number of items actually moved is returned, which may be less than `count`
920	/// if the source stack does not have that many items or if that many items do not fit into the
921	/// robot’s inventory, or may be more than `count` if the source is an itemstack entity because
922	/// `count` is ignored.
923	///
924	/// # Errors
925	/// * [`BadComponent`](Error::BadComponent)
926	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
927	/// * [`Failed`](Error::Failed) is returned if there are no items in the specified direction,
928	///   there is no space to move the items into (due to either number or type), or the adjacent
929	///   inventory does not allow items to be removed.
930	pub async fn suck(&mut self, side: ActionSide, count: u32) -> Result<u32, Error> {
931		enum BoolOrU32 {
932			Bool(bool),
933			U32(u32),
934		}
935		impl<Context> Decode<'_, Context> for BoolOrU32 {
936			fn decode(
937				d: &mut minicbor::Decoder<'_>,
938				_: &mut Context,
939			) -> Result<Self, minicbor::decode::Error> {
940				if d.datatype()? == minicbor::data::Type::Bool {
941					Ok(Self::Bool(d.bool()?))
942				} else {
943					Ok(Self::U32(d.u32()?))
944				}
945			}
946		}
947		let ret: (BoolOrU32,) = component_method(
948			self.invoker,
949			self.buffer,
950			&self.address,
951			"suck",
952			Some(&(u8::from(side), count)),
953		)
954		.await?;
955		match ret.0 {
956			BoolOrU32::Bool(false) => Err(Error::Failed),
957			BoolOrU32::Bool(true) => {
958				// The OpenComputers robot component’s suck method never returns true. Therefore,
959				// if we see true, we must be addressing a different component that also has a
960				// method named suck.
961				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
962			}
963			BoolOrU32::U32(count) => Ok(count),
964		}
965	}
966
967	/// Returns the number of internal fluid tanks.
968	///
969	/// This is typically equal to the number of tank upgrades (not tank controller upgrades)
970	/// installed in the robot.
971	///
972	/// # Errors
973	/// * [`BadComponent`](Error::BadComponent)
974	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
975	pub async fn tank_count(&mut self) -> Result<u32, Error> {
976		Ok(component_method::<(), (u32,), _>(
977			self.invoker,
978			self.buffer,
979			&self.address,
980			"tankCount",
981			None,
982		)
983		.await?
984		.0)
985	}
986
987	/// Returns the currently selected internal fluid tank number.
988	///
989	/// If the robot has no tanks at all, this function returns 1.
990	///
991	/// # Errors
992	/// * [`BadComponent`](Error::BadComponent)
993	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
994	pub async fn selected_tank(&mut self) -> Result<NonZeroU32, Error> {
995		Ok(component_method::<(), (NonZeroU32,), _>(
996			self.invoker,
997			self.buffer,
998			&self.address,
999			"selectTank",
1000			None,
1001		)
1002		.await?
1003		.0)
1004	}
1005
1006	/// Selects an internal fluid tank.
1007	///
1008	/// # Errors
1009	/// * [`BadComponent`](Error::BadComponent)
1010	/// * [`BadInventorySlot`](Error::BadInventorySlot)
1011	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1012	pub async fn select_tank(&mut self, tank: NonZeroU32) -> Result<(), Error> {
1013		let ret = component_method::<_, (u32,), _>(
1014			self.invoker,
1015			self.buffer,
1016			&self.address,
1017			"selectTank",
1018			Some(&(tank,)),
1019		)
1020		.await;
1021		match ret {
1022			Ok(_) => Ok(()),
1023			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
1024			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
1025			Err(e) => Err(Error::BadComponent(e.into())),
1026		}
1027	}
1028
1029	/// Returns the number of millibuckets of fluid in an internal fluid tank.
1030	///
1031	/// # Errors
1032	/// * [`BadComponent`](Error::BadComponent)
1033	/// * [`BadInventorySlot`](Error::BadInventorySlot)
1034	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1035	pub async fn tank_level(&mut self, tank: NonZeroU32) -> Result<u32, Error> {
1036		let ret = component_method::<_, (u32,), _>(
1037			self.invoker,
1038			self.buffer,
1039			&self.address,
1040			"tankLevel",
1041			Some(&(tank,)),
1042		)
1043		.await;
1044		match ret {
1045			Ok((n,)) => Ok(n),
1046			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
1047			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
1048			Err(e) => Err(Error::BadComponent(e.into())),
1049		}
1050	}
1051
1052	/// Returns the number of millibuckets of fluid in the currently selected internal fluid tank.
1053	///
1054	/// If the robot does not have any tanks, this function returns 0.
1055	///
1056	/// # Errors
1057	/// * [`BadComponent`](Error::BadComponent)
1058	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1059	pub async fn tank_level_selected(&mut self) -> Result<u32, Error> {
1060		Ok(component_method::<(), (u32,), _>(
1061			self.invoker,
1062			self.buffer,
1063			&self.address,
1064			"tankLevel",
1065			None,
1066		)
1067		.await?
1068		.0)
1069	}
1070
1071	/// Returns the number of millibuckets of fluid that can be added to an internal fluid tank.
1072	///
1073	/// # Errors
1074	/// * [`BadComponent`](Error::BadComponent)
1075	/// * [`BadInventorySlot`](Error::BadInventorySlot)
1076	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1077	pub async fn tank_space(&mut self, tank: NonZeroU32) -> Result<u32, Error> {
1078		let ret = component_method::<_, (u32,), _>(
1079			self.invoker,
1080			self.buffer,
1081			&self.address,
1082			"tankSpace",
1083			Some(&(tank,)),
1084		)
1085		.await;
1086		match ret {
1087			Ok((n,)) => Ok(n),
1088			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
1089			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
1090			Err(e) => Err(Error::BadComponent(e.into())),
1091		}
1092	}
1093
1094	/// Returns the number of millibuckets of fluid that can be added to the currently selected
1095	/// internal fluid tank.
1096	///
1097	/// If the robot does not have any tanks, this function returns 0.
1098	///
1099	/// # Errors
1100	/// * [`BadComponent`](Error::BadComponent)
1101	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1102	pub async fn tank_space_selected(&mut self) -> Result<u32, Error> {
1103		Ok(component_method::<(), (u32,), _>(
1104			self.invoker,
1105			self.buffer,
1106			&self.address,
1107			"tankSpace",
1108			None,
1109		)
1110		.await?
1111		.0)
1112	}
1113
1114	/// Returns whether or not the currently selected internal fluid tank contains the same type of
1115	/// fluid as the specified internal fluid tank.
1116	///
1117	/// Two empty tanks are considered equal. An empty tank is not equal to any nonempty tank.
1118	///
1119	/// # Errors
1120	/// * [`BadComponent`](Error::BadComponent)
1121	/// * [`BadInventorySlot`](Error::BadInventorySlot)
1122	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1123	pub async fn compare_fluid_to(&mut self, other_tank: NonZeroU32) -> Result<bool, Error> {
1124		let ret = component_method::<_, (bool,), _>(
1125			self.invoker,
1126			self.buffer,
1127			&self.address,
1128			"compareFluidTo",
1129			Some(&(other_tank,)),
1130		)
1131		.await;
1132		match ret {
1133			Ok((f,)) => Ok(f),
1134			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
1135			Err(MethodCallError::TooManyDescriptors) => Err(Error::TooManyDescriptors),
1136			Err(e) => Err(Error::BadComponent(e.into())),
1137		}
1138	}
1139
1140	/// Moves fluid from the currently selected internal fluid tank to the specified internal fluid
1141	/// tank.
1142	///
1143	/// The `amount` parameter indicates the maximum number of millibuckets to transfer. If the
1144	/// target tank is able to hold any fluid of the type held in the source tank, then the minimum
1145	/// of `amount`, the amount of fluid in the source tank, and the amount of space in the target
1146	/// tank is moved. If the target tank is not able to hold any of the fluid type held in the
1147	/// source tank (either because it is full or because the fluids are of different types), and
1148	/// if `amount` is greater than or equal to the amount of fluid in the source tank, then the
1149	/// two tanks’ contents are swapped. If the source tank is empty, the entire destination tank
1150	/// is moved to the source tank (i.e. the tanks’s contents are swapped).
1151	///
1152	/// # Errors
1153	/// * [`BadComponent`](Error::BadComponent)
1154	/// * [`BadInventorySlot`](Error::BadInventorySlot)
1155	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1156	/// * [`Failed`](Error::Failed) is returned if the requested tank number is greater than the
1157	///   tank count, or `amount` was nonzero but no fluid could be moved (because the source tank
1158	///   is empty, the target tank is of the same type and full, or the target tank is of a
1159	///   different type and `amount` is less than the size of the source tank and therefore the
1160	///   fluids cannot be swapped).
1161	pub async fn transfer_fluid_to(
1162		&mut self,
1163		target_tank: NonZeroU32,
1164		amount: u32,
1165	) -> Result<(), Error> {
1166		let ret = component_method::<_, NullAndStringOr<'_, bool>, _>(
1167			self.invoker,
1168			self.buffer,
1169			&self.address,
1170			"transferFluidTo",
1171			Some(&(target_tank, amount)),
1172		)
1173		.await;
1174		match ret {
1175			Ok(NullAndStringOr::Ok(true)) => Ok(()),
1176			Ok(NullAndStringOr::Err("incompatible or no fluid")) => Err(Error::Failed),
1177			Ok(NullAndStringOr::Err("invalid index")) | Err(MethodCallError::BadParameters(_)) => {
1178				Err(Error::BadInventorySlot)
1179			}
1180			Ok(NullAndStringOr::Ok(false) | NullAndStringOr::Err(_)) => {
1181				// The OpenComputers robot component’s transferFluidTo method never returns false.
1182				// Therefore, if we see true, we must be addressing a different component that also
1183				// has a method named transferFluidTo. Also do that for any other null-and-string
1184				// returns we don’t recognize.
1185				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
1186			}
1187			Err(e) => Err(e.into()),
1188		}
1189	}
1190
1191	/// Returns whether or not the currently selected internal fluid tank contains the same type of
1192	/// fluid as a tank in an external block.
1193	///
1194	/// The `tank` parameter selects the tank within the external block. An empty tank (or
1195	/// nonexistent tank, in the case of a robot without any internal tanks) is considered unequal
1196	/// to everything, including another empty tank.
1197	///
1198	/// # Errors
1199	/// * [`BadComponent`](Error::BadComponent)
1200	/// * [`BadInventorySlot`](Error::BadInventorySlot) is returned if there is no fluid-containing
1201	///   block on the specified side or if the `tank` parameter is greater than the number of
1202	///   tanks in the block and the selected internal tank is nonempty, but only if the selected
1203	///   tank is non-empty.
1204	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1205	pub async fn compare_fluid(
1206		&mut self,
1207		side: ActionSide,
1208		tank: NonZeroU32,
1209	) -> Result<bool, Error> {
1210		let ret = component_method::<_, (bool,), _>(
1211			self.invoker,
1212			self.buffer,
1213			&self.address,
1214			"compareFluid",
1215			Some(&(u8::from(side), tank)),
1216		)
1217		.await;
1218		match ret {
1219			Ok((b,)) => Ok(b),
1220			Err(MethodCallError::BadParameters(_)) => Err(Error::BadInventorySlot),
1221			Err(e) => Err(e.into()),
1222		}
1223	}
1224
1225	/// Moves fluid from an external block’s fluid tank to the currently selected internal fluid
1226	/// tank.
1227	///
1228	/// On success, the amount of fluid moved is the minimum of `amount`, the amount of fluid in
1229	/// the source tank, and the amount of space in the target tank, and this number is returned.
1230	///
1231	/// # Errors
1232	/// * [`BadComponent`](Error::BadComponent)
1233	/// * [`BadItem`](Error::BadItem) is returned if the destination already contains an
1234	///   incompatible fluid.
1235	/// * [`InventoryFull`](Error::InventoryFull)
1236	/// * [`NoInventory`](Error::NoInventory) is returned if the robot does not contain any tanks.
1237	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1238	pub async fn drain(&mut self, side: ActionSide, amount: u32) -> Result<u32, Error> {
1239		self.drain_or_fill(side, amount, "drain").await
1240	}
1241
1242	/// Moves fluid from the currently selected internal fluid tank to an external block.
1243	///
1244	/// On success, the amount of fluid moved is the minimum of `amount`, the amount of fluid in
1245	/// the source tank, and the amount of space in the target tank, and this number is returned.
1246	///
1247	/// # Errors
1248	/// * [`BadComponent`](Error::BadComponent)
1249	/// * [`BadItem`](Error::BadItem) is returned if the destination already contains an
1250	///   incompatible fluid.
1251	/// * [`InventoryFull`](Error::InventoryFull)
1252	/// * [`NoInventory`](Error::NoInventory) is returned if the robot does not contain any tanks.
1253	/// * [`NoItem`](Error::NoItem) is returned if the source tank is empty and `amount` is
1254	///   nonzero.
1255	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1256	pub async fn fill(&mut self, side: ActionSide, amount: u32) -> Result<u32, Error> {
1257		self.drain_or_fill(side, amount, "fill").await
1258	}
1259
1260	/// Implements the [`drain`] or [`fill`] function.
1261	///
1262	/// # Errors
1263	/// * [`BadComponent`](Error::BadComponent)
1264	/// * [`BadItem`](Error::BadItem) is returned if the destination already contains an
1265	///   incompatible fluid.
1266	/// * [`InventoryFull`](Error::InventoryFull)
1267	/// * [`NoInventory`](Error::NoInventory) is returned if the robot does not contain any tanks.
1268	/// * [`NoItem`](Error::NoItem) is returned if the source tank is empty and `amount` is
1269	///   nonzero.
1270	/// * [`TooManyDescriptors`](Error::TooManyDescriptors)
1271	async fn drain_or_fill(
1272		&mut self,
1273		side: ActionSide,
1274		amount: u32,
1275		method: &str,
1276	) -> Result<u32, Error> {
1277		let ret: NullAndStringOr<'_, (bool, u32)> = component_method(
1278			self.invoker,
1279			self.buffer,
1280			&self.address,
1281			method,
1282			Some(&(u8::from(side), amount)),
1283		)
1284		.await?;
1285		match ret {
1286			NullAndStringOr::Ok((_, n)) => Ok(n),
1287			NullAndStringOr::Err("incompatible or no fluid") => Err(Error::BadItem),
1288			NullAndStringOr::Err("no space" | "tank is full") => Err(Error::InventoryFull),
1289			NullAndStringOr::Err("no tank selected") => Err(Error::NoInventory),
1290			NullAndStringOr::Err("tank is empty") => Err(Error::NoItem),
1291			NullAndStringOr::Err(_) => {
1292				Err(Error::BadComponent(oc_wasm_safe::error::Error::Unknown))
1293			}
1294		}
1295	}
1296}