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}