yog_api/mods/storage/item.rs
1//! Values kept on a stack — the Rust side of a Yog Script item's `saved`
2//! and `synced` members (`docs/design/storage.md`): bytes by key in the
3//! custom data of what a player holds, so they travel with the stack and
4//! reach its clients with it.
5
6use std::marker::PhantomData;
7
8use yog_data::Schema;
9
10/// A value kept in the data of the stack a player holds, at its key (the
11/// mod's own: `<mod>:<key>`); its default where the stack has none yet.
12///
13/// ```ignore
14/// static CHARGES: ItemData<u32> = ItemData::new("charges");
15/// CHARGES.update(&use_item.player, use_item.off_hand, |n| *n = n.saturating_sub(1));
16/// ```
17pub struct ItemData<T> {
18 key: &'static str,
19 value: PhantomData<fn() -> T>,
20}
21
22impl<T: Schema + Default> ItemData<T> {
23 pub const fn new(key: &'static str) -> Self {
24 ItemData {
25 key,
26 value: PhantomData,
27 }
28 }
29
30 /// The value on the stack `player` holds (`off_hand`: in its other
31 /// hand); its default when the stack has none (or there's no stack).
32 pub fn get(&self, player: &str, off_hand: bool) -> T {
33 crate::event::player::held_item_data(player, off_hand, self.stack_key())
34 .and_then(|r| yog_data::read_bytes(&r.bytes).ok())
35 .unwrap_or_default()
36 }
37
38 /// The value on the stack this client's player holds (`off_hand`: in
39 /// its other hand), as the server last sent the stack — the game sends
40 /// stacks, so every `ItemData` is synced; its default when the stack has
41 /// none (or there's no stack).
42 pub fn held_here(&self, off_hand: bool) -> T {
43 crate::event::client::held_item_data(off_hand, self.stack_key())
44 .and_then(|r| yog_data::read_bytes(&r.bytes).ok())
45 .unwrap_or_default()
46 }
47
48 /// Puts `value` on the stack `player` holds; false when it holds nothing.
49 pub fn set(&self, player: &str, off_hand: bool, value: &T) -> bool {
50 crate::event::player::set_held_item_data(
51 player,
52 off_hand,
53 self.stack_key(),
54 yog_data::to_bytes(value),
55 )
56 }
57
58 /// The value on the stack `player` holds changed by `change`, and put
59 /// back; what `change` returns, none when it holds nothing.
60 pub fn update<R>(
61 &self,
62 player: &str,
63 off_hand: bool,
64 change: impl FnOnce(&mut T) -> R,
65 ) -> Option<R> {
66 let mut value = self.get(player, off_hand);
67 let out = change(&mut value);
68 self.set(player, off_hand, &value).then_some(out)
69 }
70
71 /// The key in a stack's data: the mod's, then its own (as Yog Script's are).
72 fn stack_key(&self) -> String {
73 format!("{}{}{}", crate::mod_id(), yog_ids::sep::NAMESPACE, self.key)
74 }
75}