Skip to main content

yog_api/
client.rs

1//! Client-side helpers (ABI minor 1): opening a screen with flags, sounds
2//! only the local player hears, the local player's held items and the
3//! keyboard state. All are no-ops (or `None`/`false`) on a dedicated server
4//! and before the mod registered.
5//!
6//! ```ignore
7//! use yog_api::client::{self, ui_flags};
8//! client::open_ui_flags("mymod:overlay", ui_flags::MODAL | ui_flags::NO_BACKGROUND);
9//! let loop_ = client::play_sound("mymod:hum", 1.0, 1.0, true);
10//! client::stop_sound(loop_);
11//! ```
12
13pub(crate) mod op;
14pub mod player;
15pub mod world;
16
17use yog_abi::YogStr;
18pub use yog_abi::ui_flags;
19
20use crate::registry::global_api;
21
22/// Open the Yog UI `ui_id` with [`ui_flags`] bits.
23pub fn open_ui_flags(ui_id: &str, flags: u32) {
24    let api = global_api();
25    if api.is_null() {
26        return;
27    }
28    unsafe { ((*api).ui_open_flags)((*api).ctx, YogStr::from_str(ui_id), flags) }
29}
30
31/// A sound started by [`play_sound`]; `SoundHandle(0)` is "none".
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub struct SoundHandle(pub u32);
34
35/// Play sound event `sound_id` for the local player only, at their
36/// position; `looping` keeps it playing (following the player) until
37/// [`stop_sound`].
38pub fn play_sound(sound_id: &str, volume: f32, pitch: f32, looping: bool) -> SoundHandle {
39    let api = global_api();
40    if api.is_null() {
41        return SoundHandle(0);
42    }
43    SoundHandle(unsafe {
44        ((*api).client_play_sound)(
45            (*api).ctx,
46            YogStr::from_str(sound_id),
47            volume,
48            pitch,
49            looping,
50        )
51    })
52}
53
54/// Stop a sound from [`play_sound`].
55pub fn stop_sound(handle: SoundHandle) {
56    let api = global_api();
57    if api.is_null() || handle.0 == 0 {
58        return;
59    }
60    unsafe { ((*api).client_stop_sound)((*api).ctx, handle.0) }
61}
62
63/// Item id in the local player's main hand (or `off_hand`), `None` for an
64/// empty hand.
65pub fn held_item(off_hand: bool) -> Option<String> {
66    let api = global_api();
67    if api.is_null() {
68        return None;
69    }
70    let owned = unsafe { ((*api).client_held_item)((*api).ctx, off_hand) };
71    crate::registry::take_owned(unsafe { (*api).free_str }, owned)
72}
73
74/// Attribute `attribute_id`'s value on this client's player — its base
75/// changed by every modifier (effects, equipment); `None` without a player
76/// or such an attribute.
77pub fn attribute_value(attribute_id: &str) -> Option<f64> {
78    let api = global_api();
79    if api.is_null() {
80        return None;
81    }
82    let v = unsafe { ((*api).client_attribute_value)((*api).ctx, YogStr::from_str(attribute_id)) };
83    (!v.is_nan()).then_some(v)
84}
85
86/// Whether this client's player has advancement `id`.
87pub fn advancement_done(id: &str) -> bool {
88    let api = global_api();
89    !api.is_null() && unsafe { ((*api).client_advancement_done)((*api).ctx, YogStr::from_str(id)) }
90}
91
92/// Tints the block at `pos` in `dimension` on this client — the faces of
93/// its model with a `tint` index — `0xRRGGBB`; `None` clears it. The mod
94/// sends what to tint (its own packet); the tint lasts till cleared or the
95/// game closes.
96pub fn set_block_tint(dimension: &str, pos: yog_core::BlockPos, color: Option<u32>) {
97    let api = global_api();
98    if api.is_null() {
99        return;
100    }
101    let p = yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z };
102    let c = color.map_or(-1, |c| (c & 0xFF_FFFF) as i64);
103    unsafe { ((*api).client_set_block_tint)((*api).ctx, YogStr::from_str(dimension), p, c) }
104}
105
106/// GLFW key codes for [`key_down`].
107pub mod keys {
108    /// No key: a key binding left unbound (GLFW's `GLFW_KEY_UNKNOWN`).
109    pub const UNBOUND: i32 = -1;
110    /// Letters: GLFW codes them as their capital's ASCII.
111    pub const A: i32 = 65;
112    pub const B: i32 = 66;
113    pub const C: i32 = 67;
114    pub const D: i32 = 68;
115    pub const E: i32 = 69;
116    pub const F: i32 = 70;
117    pub const G: i32 = 71;
118    pub const H: i32 = 72;
119    pub const I: i32 = 73;
120    pub const J: i32 = 74;
121    pub const K: i32 = 75;
122    pub const L: i32 = 76;
123    pub const M: i32 = 77;
124    pub const N: i32 = 78;
125    pub const O: i32 = 79;
126    pub const P: i32 = 80;
127    pub const Q: i32 = 81;
128    pub const R: i32 = 82;
129    pub const S: i32 = 83;
130    pub const T: i32 = 84;
131    pub const U: i32 = 85;
132    pub const V: i32 = 86;
133    pub const W: i32 = 87;
134    pub const X: i32 = 88;
135    pub const Y: i32 = 89;
136    pub const Z: i32 = 90;
137    pub const HOME: i32 = 268;
138    pub const LEFT_SHIFT: i32 = 340;
139    pub const LEFT_CONTROL: i32 = 341;
140    pub const LEFT_ALT: i32 = 342;
141    pub const RIGHT_SHIFT: i32 = 344;
142    pub const RIGHT_CONTROL: i32 = 345;
143    pub const RIGHT_ALT: i32 = 346;
144}
145
146/// Whether GLFW key `key` is held right now.
147pub fn key_down(key: i32) -> bool {
148    let api = global_api();
149    if api.is_null() {
150        return false;
151    }
152    unsafe { ((*api).client_key_down)((*api).ctx, key) }
153}
154
155/// Either Control key is held (Minecraft's `Screen.hasControlDown()`; on
156/// macOS Minecraft uses Command — not covered here).
157pub fn control_down() -> bool {
158    key_down(keys::LEFT_CONTROL) || key_down(keys::RIGHT_CONTROL)
159}