oc2_hlapi/
device.rs

1// TODO: Better documentation. The documentation is very badly written at the moment because I was
2// trying to get the point across. Needs rewording and examples.
3
4use crate::bus::DeviceBus;
5use crate::call::Call;
6use crate::error::Result;
7use crate::types::{Direction, ImportFileInfo, RobotActionResult, RotationDirection};
8use erased_serde::Serialize as ErasedSerialize;
9use serde::de::DeserializeOwned;
10use uuid::Uuid;
11
12// TODO: Turn this into a procedural macro
13macro_rules! interface {
14    (
15        $(#[$outer:meta])*
16        $vis:vis trait $trait_name:ident {
17            $(const $assoc_const:ident : $assoc_ty:ty;)*
18
19            $(
20                $(#[$inner:meta])*
21                fn $fn_name:ident $(<$($generic:ident),+ $(,)?>)? (&self $(, $($param_name:ident : $param_ty:ty),* $(,)?)?) $(-> $ret_ty:ty)?;
22            )*
23        }
24    ) => {
25
26        $(#[$outer])*
27        $vis trait $trait_name: $crate::device::RpcDevice {
28            $(
29                const $assoc_const: $assoc_ty;
30            )*
31
32            $(
33                $(#[$inner])*
34                #[allow(unused_parens)]
35                fn $fn_name$(<$($generic),+>)?(&self, $($($param_name: $param_ty),*)?) -> $crate::error::Result<($($ret_ty)?)>
36                    where ($($ret_ty)?): ::serde::de::DeserializeOwned + 'static;
37            )*
38        }
39    };
40}
41
42// TODO: Turn this into a procedural macro
43#[macro_export]
44macro_rules! device {
45    (
46        #[device(identifier = $identifier:literal)]
47        $(#[$doc:meta])*
48        $vis:vis struct $device_name:ident;
49
50        $(
51            impl $trait_name:ident {
52                $(
53                    #[device(invoke = $invoke_name:literal)]
54                    fn $fn_name:ident $(<$($generic:ident),+ $(,)?>)? (&self $(, $($param_name:ident : $param_ty:ty),* $(,)?)?) $(-> $ret_ty:ty)?;
55                )+
56            }
57        )+
58
59    ) => {
60        $(#[$doc])*
61        $vis struct $device_name(::uuid::Uuid, $crate::bus::DeviceBus);
62
63        impl $crate::device::RpcDevice for $device_name {
64            const IDENTIFIER: &'static ::core::primitive::str = $identifier;
65
66            fn new(id: ::uuid::Uuid, bus: &$crate::bus::DeviceBus) -> Self {
67                Self(id, bus.clone())
68            }
69
70            fn id(&self) -> ::uuid::Uuid {
71                self.0
72            }
73
74            fn bus(&self) -> &$crate::bus::DeviceBus {
75                &self.1
76            }
77        }
78
79        $(
80            impl $trait_name for $device_name {
81                $(
82                    #[allow(non_snake_case)]
83                    #[allow(unused_parens)]
84                    fn $fn_name$(<$($generic),+>)?(&self, $($($param_name: $param_ty),*)?) -> $crate::error::Result<($($ret_ty)?)>
85                        where ($($ret_ty)?): ::serde::de::DeserializeOwned + 'static
86                    {
87                        $crate::device::invoke(self.0, &self.1, $invoke_name, &[$($(&$param_name as &dyn ::erased_serde::Serialize),*)?])
88                    }
89                )+
90            }
91        )+
92    };
93}
94
95// This function is simply here to cut down on possible code duplication, since each
96// call which shares the same return type can share the same monomorphized version of this
97// function.
98#[doc(hidden)]
99#[inline(never)]
100pub fn invoke<R: DeserializeOwned + 'static>(
101    id: Uuid,
102    bus: &DeviceBus,
103    method_name: &str,
104    params: &[&dyn ErasedSerialize],
105) -> Result<R> {
106    let call = Call::invoke(id, method_name, params);
107
108    bus.call(call).map(|r| r.0)
109}
110
111pub trait RpcDevice {
112    const IDENTIFIER: &'static str;
113
114    fn new(id: Uuid, bus: &DeviceBus) -> Self;
115    fn id(&self) -> Uuid;
116    fn bus(&self) -> &DeviceBus;
117}
118
119interface! {
120    /// An interface between an energy storage device and the HLAPI.
121    pub trait EnergyStorageInterface {
122        /// Retrieves the current amount of energy stored in FE.
123        fn get_energy_stored(&self) -> i32;
124
125        /// Retrieves the maximum possible energy that can be stored in the device in FE.
126        fn get_max_energy_stored(&self) -> i32;
127
128        /// Returns a boolean indicating whether the storage device can have energy extracted from
129        /// it.
130        fn can_extract_energy(&self) -> bool;
131
132        /// Returns a boolean indicating whether the storage device can receive energy.
133        fn can_receive_energy(&self) -> bool;
134    }
135}
136
137interface! {
138    /// An interface between item storage devices or blocks and the HLAPI.
139    pub trait ItemHandlerInterface {
140        /// Returns a signed 32-bit integer that represents the slots in the storage
141        /// block.
142        fn get_item_slot_count(&self) -> i32;
143
144        /// Returns a signed 32-bit integer that represents how many items can be stored in a single
145        /// slot in the storage block.
146        fn get_item_slot_limit(&self, slot: i32) -> i32;
147
148        /// Returns a type which can be deserialized from JSON which represents the current
149        /// Minecraft IItemStack in the specified slot.
150        fn get_item_stack_in_slot<T>(&self, slot: i32) -> T;
151    }
152}
153
154interface! {
155    /// An interface between redstone signal sending and receiving devices and the HLAPI
156    pub trait RedstoneInterface {
157        /// Returns a signed 32-bit integer that represents the strength of the redstone input on
158        /// the provided side. The integer will be in the range \[0, 15\].
159        fn get_redstone_input(&self, side: Direction) -> i32;
160
161        /// Returns a signed 32-bit integer that represents the strength of the redstone output on
162        /// the provided side. The integer will be in the range \[0, 15\].
163        fn get_redstone_output(&self, side: Direction) -> i32;
164
165        /// Sets the redstone output strength on the given side to the provided value. Valid values
166        /// for redstone strength are in the range \[0, 15\]
167        fn set_redstone_output(&self, side: Direction, val: i32);
168    }
169}
170
171interface! {
172    /// An interface between sound-making devices and the HALPI
173    pub trait SoundInterface {
174        /// Returns a slice of sound effect names matching the given name. The length of the slice
175        /// is limited, so very generic names may result in a truncated list.
176        fn find_sound(&self, name: &str) -> Box<[String]>;
177
178        /// Plays the sound effect matching the given name at the given volume and pitch.
179        fn play_sound(&self, name: &str, volume: f64, pitch: f64);
180    }
181}
182
183interface! {
184    /// An interface for transferring files between a user's real computer and the HLAPI
185    pub trait FileImportExportInterface {
186        /// Requests the start of a file import, returning true if a file can be imported.
187        fn request_import_file(&self) -> bool;
188
189        /// Prompts the player to select a file to import, returning the information for the file
190        /// requested. This method should be called in a loop, as it may return None before
191        /// eventually returning the file information.
192        fn begin_import_file(&self) -> Option<ImportFileInfo>;
193
194        /// Reads a portion of the file currently being imported. Returns a slice of bytes
195        /// containing the portion of the file that has been read. Returns None when the file has
196        /// been fully imported. If the byte slice is empty, it means that the device is not ready
197        /// to import the file.
198        fn read_import_file(&self) -> Option<Vec<u8>>;
199
200        /// Prompts the user to select a path where a file with the given name should be exported.
201        fn begin_export_file(&self, name: &str);
202
203        /// Writes some data to the current file being exported.
204        fn write_export_file(&self, data: &[u8]);
205
206        /// Finishes the currently running file export operation.
207        fn finish_export_file(&self);
208
209        /// Resets the device's state, preparing it for another file import or export operation.
210        fn reset(&self);
211    }
212}
213
214interface! {
215    /// An interface between devices which carry out block operations and the HLAPI
216    pub trait BlockOperationsInterface {
217        /// Mines the adjacent block on the given side. Returns true if the block was able to be
218        /// mined.
219        fn excavate(&self, side: Direction) -> bool;
220
221        /// Places a block on the given side. Returns true if the block was able to be placed.
222        fn place(&self, side: Direction) -> bool;
223
224        /// Returns a 32-bit signed integer that represents the durability of the currently active
225        /// tool
226        fn durability(&self) -> i32;
227
228        /// Attempts to repair the currently active tool, returning true if the tool was repaired.
229        /// If the tool is at full durability, this will always return false.
230        fn repair(&self) -> bool;
231    }
232}
233
234interface! {
235    /// An interface between devices which carry out robot inventory operations and the HLAPI
236    pub trait InventoryOperationsInterface {
237        /// Attempts to move the given number of items from one robot inventory slot into another
238        /// slot.
239        fn move_item(&self, from: i32, into: i32, count: i32);
240
241        /// Attempts to drop the given number of items in the currently active slot into either the
242        /// world or an adjacent inventory on the given side. Returns the amount of items dropped
243        fn drop_item(&self, count: i32, side: Direction) -> i32;
244
245        /// Attempts to drop the given number of items in the currently active slot into the given
246        /// slot in the adjacent inventory in the given direction. Returns the amount of items
247        /// dropped.
248        fn drop_item_into(&self, into: i32, count: i32, side: Direction) -> i32;
249
250        /// Attempts to take the given number of items from either the world or an adjacent inventory
251        /// on the given side. Returns the amount of items taken.
252        fn take_item(&self, count: i32, side: Direction) -> i32;
253
254        /// Attempts to take the given number of items from the given slot in the adjacent inventory
255        /// in the given direction. Returns the amount of items taken.
256        fn take_item_from(&self, from: i32, count: i32, side: Direction) -> i32;
257    }
258}
259
260interface! {
261    /// An interface between robots and the HLAPI.
262    pub trait RobotInterface {
263        /// Returns the amount of FE stored in a robot.
264        fn get_energy_stored(&self) -> i32;
265
266        /// Returns the maximum possible energy that a robot can store.
267        fn get_energy_capacity(&self) -> i32;
268
269        /// Returns the index of the currently active slot.
270        fn get_selected_slot(&self) -> i32;
271
272        /// Sets the currently active slot to the given index.
273        fn set_selected_slot(&self, slot: i32);
274
275        /// Returns information about the item in the given slot.
276        fn get_stack_in_slot<T>(&self, slot: i32) -> T;
277
278        /// Attempts to queue an action which moves the robot in the given direction. Returns true
279        /// if the action was successfully queued.
280        fn queue_move(&self, direction: Direction) -> bool;
281
282        /// Attempts to queue an action which turns the robot in the given direction. Returns true
283        /// if the action was successfully queued.
284        fn queue_turn(&self, direction: RotationDirection) -> bool;
285
286        /// Returns the ID of the previously performed action.
287        fn get_last_action_id(&self) -> i32;
288
289        /// Returns the currently queued number of actions.
290        fn get_queued_action_count(&self) -> i32;
291
292        /// Returns the state of a robot's action with a given ID.
293        fn get_action_result(&self, id: i32) -> RobotActionResult;
294    }
295}
296
297device! {
298    #[device(identifier = "redstone")]
299    /// A device that can interact with redstone in the world.
300    pub struct RedstoneDevice;
301
302    impl RedstoneInterface {
303        #[device(invoke = "getRedstoneInput")]
304        fn get_redstone_input(&self, side: Direction) -> i32;
305
306        #[device(invoke = "getRedstoneOutput")]
307        fn get_redstone_output(&self, side: Direction) -> i32;
308
309        #[device(invoke = "setRedstoneOutput")]
310        fn set_redstone_output(&self, side: Direction, val: i32);
311    }
312}
313
314device! {
315    #[device(identifier = "sound")]
316    /// A device that allows a computer or robot to play sounds.
317    pub struct SoundCard;
318
319    impl SoundInterface {
320        #[device(invoke = "findSound")]
321        fn find_sound(&self, name: &str) -> Box<[String]>;
322
323        #[device(invoke = "playSound")]
324        fn play_sound(&self, name: &str, volume: f64, pitch: f64);
325    }
326}
327
328device! {
329    #[device(identifier = "file_import_export")]
330    /// A device that allows importing and exporting of files from the player's computer.
331    pub struct FileImportExportCard;
332
333    impl FileImportExportInterface {
334        #[device(invoke = "requestImportFile")]
335        fn request_import_file(&self) -> bool;
336
337        #[device(invoke = "beginImportFile")]
338        fn begin_import_file(&self) -> Option<ImportFileInfo>;
339
340        #[device(invoke = "readImportFile")]
341        fn read_import_file(&self) -> Option<Vec<u8>>;
342
343        #[device(invoke = "beginExportFile")]
344        fn begin_export_file(&self, name: &str);
345
346        #[device(invoke = "writeExportFile")]
347        fn write_export_file(&self, data: &[u8]);
348
349        #[device(invoke = "finishExportFile")]
350        fn finish_export_file(&self);
351
352        #[device(invoke = "reset")]
353        fn reset(&self);
354    }
355}
356
357device! {
358    #[device(identifier = "inventory_operations")]
359    /// A module that allows interaction with inventories in the the world.
360    pub struct InventoryOperationsModule;
361
362    impl InventoryOperationsInterface {
363        #[device(invoke = "move")]
364        fn move_item(&self, from: i32, into: i32, count: i32);
365
366        #[device(invoke = "drop")]
367        fn drop_item(&self, count: i32, side: Direction) -> i32;
368
369        #[device(invoke = "dropInto")]
370        fn drop_item_into(&self, into: i32, count: i32, side: Direction) -> i32;
371
372        #[device(invoke = "take")]
373        fn take_item(&self, count: i32, side: Direction) -> i32;
374
375        #[device(invoke = "takeFrom")]
376        fn take_item_from(&self, from: i32, count: i32, side: Direction) -> i32;
377    }
378}
379
380device! {
381    #[device(identifier = "block_operations")]
382    /// A module that allows interaction with blocks in the world.
383    pub struct BlockOperationsModule;
384
385    impl BlockOperationsInterface {
386        #[device(invoke = "excavate")]
387        fn excavate(&self, side: Direction) -> bool;
388
389        #[device(invoke = "place")]
390        fn place(&self, side: Direction) -> bool;
391
392        #[device(invoke = "durability")]
393        fn durability(&self) -> i32;
394
395        #[device(invoke = "repair")]
396        fn repair(&self) -> bool;
397    }
398}
399
400device! {
401    #[device(identifier = "robot")]
402    pub struct RobotDevice;
403
404    impl RobotInterface {
405        #[device(invoke = "getEnergyStored")]
406        fn get_energy_stored(&self) -> i32;
407
408        #[device(invoke = "getEnergyCapacity")]
409        fn get_energy_capacity(&self) -> i32;
410
411        #[device(invoke = "getSelectedSlot")]
412        fn get_selected_slot(&self) -> i32;
413
414        #[device(invoke = "setSelectedSlot")]
415        fn set_selected_slot(&self, slot: i32);
416
417        #[device(invoke = "getStackInSlot")]
418        fn get_stack_in_slot<T>(&self, slot: i32) -> T;
419
420        #[device(invoke = "move")]
421        fn queue_move(&self, direction: Direction) -> bool;
422
423        #[device(invoke = "turn")]
424        fn queue_turn(&self, direction: RotationDirection) -> bool;
425
426        #[device(invoke = "getLastActionId")]
427        fn get_last_action_id(&self) -> i32;
428
429        #[device(invoke = "getQueuedActionCount")]
430        fn get_queued_action_count(&self) -> i32;
431
432        #[device(invoke = "getActionResult")]
433        fn get_action_result(&self, id: i32) -> RobotActionResult;
434    }
435}