Skip to main content

yog_runtime/
lib.rs

1//! Yog runtime — the native library loaded by the Fabric host.
2//!
3//! Exposes JNI entry points (`Java_dev_yog_NativeBridge_*`) that the host calls,
4//! and a stable C ABI (`YogApi` / `YogServer`) that mods program against.
5//!
6//! Architecture:
7//!   - `YogServer`  — a `#[repr(C)]` table of standalone JNI-calling functions
8//!                    that mods call to mutate the world.
9//!   - `YogApi`     — a `#[repr(C)]` table of registration functions; mods call
10//!                    them inside `yog_mod_register` to subscribe to events.
11//!   - `RuntimeHandlers` — the runtime's internal event/handler storage.
12//!     Filled during `nativeInit` (write), read-only after. The scheduler sub-
13//!     state uses an inner `Mutex` for safe addition during event dispatch.
14
15use std::collections::HashMap;
16use std::num::NonZeroU32;
17use std::os::raw::c_void;
18use std::path::{Path, PathBuf};
19use std::sync::{Mutex, OnceLock};
20
21use glow::HasContext;
22use jni::objects::{JByteArray, JClass, JFloatArray, JObject, JString, JValue};
23use jni::sys::{jdouble, jfloat, jint, jstring};
24use jni::{JNIEnv, JavaVM};
25use libloading::{Library, Symbol};
26
27use yog_abi::{
28    YogAdvancementEvent, YogAdvancementFn, YogApi, YogAttackEntityFn, YogBlockBreakFn, YogBlockDef,
29    YogBlockPos, YogChatFn, YogClientFn, YogCommandFn, YogContainerCloseEvent, YogContainerCloseFn,
30    YogContainerOpenEvent, YogContainerOpenFn, YogCraftEvent, YogCraftFn, YogEntityDamageFn,
31    YogEntityDeathFn, YogEntityInteractEvent, YogEntityInteractFn, YogEntitySpawnFn,
32    YogExplosionEvent, YogExplosionFn, YogGfxApi, YogHudRenderFn, YogItemDef, YogItemPickupEvent,
33    YogItemPickupFn, YogKeyPressEvent, YogKeyPressFn, YogOwnedStr, YogPacketFn, YogPlaceBlockEvent,
34    YogPlaceBlockFn, YogPlayerDeathEvent, YogPlayerDeathFn, YogPlayerFn, YogPlayerMoveEvent,
35    YogPlayerMoveFn, YogPlayerRespawnEvent, YogPlayerRespawnFn, YogProjectileHitEvent,
36    YogProjectileHitFn, YogScheduledFn, YogScreenFn, YogServer, YogServerFn, YogStartupGrantDef,
37    YogStr, YogUseBlockFn, YogUseItemFn, YogVec3, YogWorldRenderFn, ABI_VERSION,
38};
39use yog_registry::{BlockDef, FoodDef, ItemDef};
40
41// ── Static globals ────────────────────────────────────────────────────────────
42
43/// Cached JVM handle for any-thread callbacks.
44static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
45/// Loaded mod libraries — kept alive so the code pages stay mapped.
46static LOADED_MODS: Mutex<Vec<Library>> = Mutex::new(Vec::new());
47/// Metadata of loaded .yog mods: [id, name, version, authors, description].
48static MOD_INFOS: Mutex<Vec<[String; 5]>> = Mutex::new(Vec::new());
49/// Inter-mod symbol registry: mod_id → (symbol_name → function pointer).
50/// Filled during `yog_mod_register` (export calls), read during import calls.
51static INTEROP_SYMBOLS: OnceLock<Mutex<HashMap<String, HashMap<String, usize>>>> = OnceLock::new();
52/// Stable server table (populated once in nativeInit, then read-only).
53static SERVER: OnceLock<YogServer> = OnceLock::new();
54/// Stable api table. Mods capture this pointer at registration (yog-api's
55/// `installed_mods()` calls through it later, e.g. from UI render) — so it
56/// must outlive `nativeInit`, not live on its stack. NB: `ctx` inside points
57/// at the pre-move handlers Box and is only valid during registration; late
58/// api calls must not dereference it (api_mods_list doesn't).
59static API_TABLE: OnceLock<YogApi> = OnceLock::new();
60/// All registered handlers + content (populated during mod loading, then read-only).
61static HANDLERS: OnceLock<RuntimeHandlers> = OnceLock::new();
62
63// ── OpenGL context (client-side, render thread only) ─────────────────────────
64
65struct GlCtx(glow::Context);
66unsafe impl Send for GlCtx {}
67unsafe impl Sync for GlCtx {}
68
69/// Initialized by `nativeGlInit` on the render thread.  `None` on dedicated server.
70static GL: OnceLock<GlCtx> = OnceLock::new();
71
72// Raw GL function pointers for GL_ARB_get_program_binary (not exposed by glow 0.13).
73// Captured during the glow loader callback in `nativeGlInit`.
74// `None` when the extension is unavailable (very old drivers).
75static GL_GET_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
76static GL_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
77static GL_GET_PROGRAM_IV: OnceLock<Option<usize>> = OnceLock::new();
78
79// ── Handler storage ───────────────────────────────────────────────────────────
80
81#[derive(Debug, Clone)]
82pub struct UiLayer {
83    pub id: String,
84    pub parent: Option<String>,
85    pub modal: bool,
86    pub pause_game: bool,
87    pub visible: bool,
88    pub enabled: bool,
89    pub z_index: i32,
90}
91
92struct RuntimeHandlers {
93    block_break: Vec<(*mut c_void, YogBlockBreakFn)>,
94    chat: Vec<(*mut c_void, YogChatFn)>,
95    player_join: Vec<(*mut c_void, YogPlayerFn)>,
96    player_leave: Vec<(*mut c_void, YogPlayerFn)>,
97    use_item: Vec<(*mut c_void, YogUseItemFn)>,
98    use_block: Vec<(*mut c_void, YogUseBlockFn)>,
99    attack_entity: Vec<(*mut c_void, YogAttackEntityFn)>,
100    entity_damage: Vec<(*mut c_void, YogEntityDamageFn)>,
101    entity_death: Vec<(*mut c_void, YogEntityDeathFn)>,
102    entity_spawn: Vec<(*mut c_void, YogEntitySpawnFn)>,
103    player_place_block: Vec<(*mut c_void, YogPlaceBlockFn)>,
104    player_death: Vec<(*mut c_void, YogPlayerDeathFn)>,
105    player_respawn: Vec<(*mut c_void, YogPlayerRespawnFn)>,
106    advancement: Vec<(*mut c_void, YogAdvancementFn)>,
107    entity_interact: Vec<(*mut c_void, YogEntityInteractFn)>,
108    item_craft: Vec<(*mut c_void, YogCraftFn)>,
109    explosion: Vec<(*mut c_void, YogExplosionFn)>,
110    item_pickup: Vec<(*mut c_void, YogItemPickupFn)>,
111    player_move: Vec<(*mut c_void, YogPlayerMoveFn)>,
112    container_open: Vec<(*mut c_void, YogContainerOpenFn)>,
113    container_close: Vec<(*mut c_void, YogContainerCloseFn)>,
114    projectile_hit: Vec<(*mut c_void, YogProjectileHitFn)>,
115    client_tick: Vec<(*mut c_void, YogClientFn)>,
116    hud_render: Vec<(*mut c_void, YogHudRenderFn)>,
117    world_render: Vec<(*mut c_void, YogWorldRenderFn)>,
118    key_press: Vec<(*mut c_void, YogKeyPressFn)>,
119    screen_open: Vec<(*mut c_void, YogScreenFn)>,
120    screen_close: Vec<(*mut c_void, YogScreenFn)>,
121    server_tick: Vec<(*mut c_void, YogServerFn)>,
122    server_started: Vec<(*mut c_void, YogServerFn)>,
123    server_stopping: Vec<(*mut c_void, YogServerFn)>,
124    /// All handlers registered under one command name, in registration order.
125    /// `schema` is `None` for a plain `on_command` (bare, zero-argument)
126    /// registration and `Some(schema)` for `on_typed_command`. Kept as a
127    /// `Vec` (not a single slot) so a mod can register several subcommands —
128    /// e.g. differing arg counts, or several bare/typed registrations sharing
129    /// one top-level name — without later registrations silently clobbering
130    /// earlier ones (see `nativeOnCommand`, which tries each entry whose
131    /// schema's arg count matches the actual invocation, in order, until one
132    /// produces a reply).
133    commands: HashMap<String, Vec<(Option<String>, *mut c_void, YogCommandFn)>>,
134    recipes: Vec<(String, String, String)>,
135    packets: HashMap<String, (*mut c_void, YogPacketFn)>,
136    client_packets: HashMap<String, (*mut c_void, YogPacketFn)>,
137    items: Vec<ItemDef>,
138    blocks: Vec<BlockDef>,
139    inventories: Vec<yog_inventory::InventoryDef>,
140    books: HashMap<String, String>,
141    book_renderers: Mutex<HashMap<String, yog_book::BookRenderer>>,
142    ui_handlers: HashMap<String, (*mut c_void, yog_abi::YogUIEventFn)>,
143    ui_render_handlers: HashMap<String, Vec<(*mut c_void, YogHudRenderFn)>>,
144    menu_entries: Vec<(String, String)>, // (label, ui_id) for vanilla screen buttons
145    pub active_uis: Mutex<Vec<UiLayer>>,
146    startup_grants: Vec<yog_registry::StartupGrant>,
147    startup_granted: Mutex<HashMap<String, bool>>,
148    /// Players whose startup grants are pending: (name, uuid, ticks_waited).
149    /// Granting happens on a server tick after join — giving items directly in
150    /// the join callback fails for the host of a freshly created world.
151    pending_grants: Mutex<Vec<(String, String, u32)>>,
152    scheduler: Mutex<SchedulerState>,
153}
154
155// All fn ptrs are C-ABI; ud pointers are from Box::into_raw of Send+Sync closures.
156unsafe impl Send for RuntimeHandlers {}
157unsafe impl Sync for RuntimeHandlers {}
158
159impl RuntimeHandlers {
160    fn new() -> Self {
161        Self {
162            block_break: Vec::new(),
163            chat: Vec::new(),
164            player_join: Vec::new(),
165            player_leave: Vec::new(),
166            use_item: Vec::new(),
167            use_block: Vec::new(),
168            attack_entity: Vec::new(),
169            entity_damage: Vec::new(),
170            entity_death: Vec::new(),
171            entity_spawn: Vec::new(),
172            player_place_block: Vec::new(),
173            player_death: Vec::new(),
174            player_respawn: Vec::new(),
175            advancement: Vec::new(),
176            entity_interact: Vec::new(),
177            item_craft: Vec::new(),
178            explosion: Vec::new(),
179            item_pickup: Vec::new(),
180            player_move: Vec::new(),
181            container_open: Vec::new(),
182            container_close: Vec::new(),
183            projectile_hit: Vec::new(),
184            client_tick: Vec::new(),
185            hud_render: Vec::new(),
186            world_render: Vec::new(),
187            key_press: Vec::new(),
188            screen_open: Vec::new(),
189            screen_close: Vec::new(),
190            server_tick: Vec::new(),
191            server_started: Vec::new(),
192            server_stopping: Vec::new(),
193            commands: HashMap::new(),
194            recipes: Vec::new(),
195            packets: HashMap::new(),
196            client_packets: HashMap::new(),
197            items: Vec::new(),
198            ui_handlers: HashMap::new(),
199            ui_render_handlers: HashMap::new(),
200            menu_entries: Vec::new(),
201            active_uis: Mutex::new(Vec::new()),
202            startup_grants: Vec::new(),
203            blocks: Vec::new(),
204            inventories: Vec::new(),
205            books: HashMap::new(),
206            book_renderers: Mutex::new(HashMap::new()),
207            startup_granted: Mutex::new(HashMap::new()),
208            pending_grants: Mutex::new(Vec::new()),
209            scheduler: Mutex::new(SchedulerState::new()),
210        }
211    }
212}
213
214struct SchedulerState {
215    once_tasks: Vec<OnceTask>,
216    repeating_tasks: Vec<RepeatingTask>,
217}
218
219struct OnceTask {
220    delay_remaining: u64,
221    ud: *mut c_void,
222    f: YogScheduledFn,
223}
224struct RepeatingTask {
225    period: u64,
226    ticks_left: u64,
227    ud: *mut c_void,
228    f: YogScheduledFn,
229}
230
231unsafe impl Send for SchedulerState {}
232unsafe impl Sync for SchedulerState {}
233unsafe impl Send for OnceTask {}
234unsafe impl Send for RepeatingTask {}
235
236impl SchedulerState {
237    fn new() -> Self {
238        Self {
239            once_tasks: Vec::new(),
240            repeating_tasks: Vec::new(),
241        }
242    }
243}
244
245fn handlers() -> &'static RuntimeHandlers {
246    HANDLERS.get().expect("yog: nativeInit not called yet")
247}
248
249// ── JNI helpers ──────────────────────────────────────────────────────────────
250
251fn guard(label: &str, f: impl FnOnce()) {
252    if std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).is_err() {
253        yog_logging::error!("a mod panicked handling `{}` (ignored)", label);
254    }
255}
256
257macro_rules! jstr {
258    ($env:expr, $s:expr) => {
259        match $env.get_string(&$s) {
260            Ok(s) => String::from(s),
261            Err(_) => return,
262        }
263    };
264}
265
266/// Convert a `YogStr` into a Java String. Caller must ensure `s` is valid UTF-8.
267unsafe fn ys_to_java<'l>(env: &mut JNIEnv<'l>, s: YogStr) -> Option<jni::objects::JString<'l>> {
268    env.new_string(s.as_str()).ok()
269}
270
271fn get_env() -> Option<jni::AttachGuard<'static>> {
272    JAVA_VM.get()?.attach_current_thread().ok()
273}
274
275// ── Free-str allocator used by YogOwnedStr ────────────────────────────────────
276
277unsafe extern "C" fn yog_free_str(ptr: *mut u8, len: u32) {
278    if !ptr.is_null() {
279        drop(Box::from_raw(std::slice::from_raw_parts_mut(
280            ptr,
281            len as usize,
282        )));
283    }
284}
285
286fn jstring_to_owned(env: &mut JNIEnv, obj: jni::objects::JObject) -> YogOwnedStr {
287    if obj.as_raw().is_null() {
288        return YogOwnedStr::NONE;
289    }
290    match env.get_string(&JString::from(obj)) {
291        Ok(s) => YogOwnedStr::from_string(String::from(s)),
292        Err(_) => YogOwnedStr::NONE,
293    }
294}
295
296// ── YogServer standalone functions (one per action) ───────────────────────────
297//
298// ctx is unused here — all state is in the JAVA_VM static.
299
300unsafe extern "C" fn srv_broadcast(_ctx: *mut c_void, msg: YogStr) {
301    let Some(mut env) = get_env() else { return };
302    if let Some(jmsg) = ys_to_java(&mut env, msg) {
303        let _ = env.call_static_method(
304            "dev/yog/NativeBridge",
305            "broadcast",
306            "(Ljava/lang/String;)V",
307            &[JValue::Object(&jmsg)],
308        );
309    }
310}
311
312unsafe extern "C" fn srv_get_block(
313    _ctx: *mut c_void,
314    dim: YogStr,
315    pos: YogBlockPos,
316) -> YogOwnedStr {
317    let Some(mut env) = get_env() else {
318        return YogOwnedStr::NONE;
319    };
320    let (Some(jd),) = (ys_to_java(&mut env, dim),) else {
321        return YogOwnedStr::NONE;
322    };
323    let ret = env.call_static_method(
324        "dev/yog/NativeBridge",
325        "getBlock",
326        "(Ljava/lang/String;III)Ljava/lang/String;",
327        &[
328            JValue::Object(&jd),
329            JValue::Int(pos.x),
330            JValue::Int(pos.y),
331            JValue::Int(pos.z),
332        ],
333    );
334    match ret.and_then(|v| v.l()) {
335        Ok(obj) => jstring_to_owned(&mut env, obj),
336        _ => YogOwnedStr::NONE,
337    }
338}
339
340unsafe extern "C" fn srv_set_block(
341    _ctx: *mut c_void,
342    dim: YogStr,
343    pos: YogBlockPos,
344    block: YogStr,
345) -> bool {
346    let Some(mut env) = get_env() else {
347        return false;
348    };
349    let (Some(jd), Some(jb)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, block)) else {
350        return false;
351    };
352    env.call_static_method(
353        "dev/yog/NativeBridge",
354        "setBlock",
355        "(Ljava/lang/String;IIILjava/lang/String;)Z",
356        &[
357            JValue::Object(&jd),
358            JValue::Int(pos.x),
359            JValue::Int(pos.y),
360            JValue::Int(pos.z),
361            JValue::Object(&jb),
362        ],
363    )
364    .and_then(|v| v.z())
365    .unwrap_or(false)
366}
367
368unsafe extern "C" fn srv_world_time(_ctx: *mut c_void, dim: YogStr, out: *mut i64) -> bool {
369    let Some(mut env) = get_env() else {
370        return false;
371    };
372    let Some(jd) = ys_to_java(&mut env, dim) else {
373        return false;
374    };
375    match env
376        .call_static_method(
377            "dev/yog/NativeBridge",
378            "worldTime",
379            "(Ljava/lang/String;)J",
380            &[JValue::Object(&jd)],
381        )
382        .and_then(|v| v.j())
383    {
384        Ok(v) if v != i64::MIN => {
385            *out = v;
386            true
387        }
388        _ => false,
389    }
390}
391
392unsafe extern "C" fn srv_set_time(_ctx: *mut c_void, dim: YogStr, time: i64) -> bool {
393    let Some(mut env) = get_env() else {
394        return false;
395    };
396    let Some(jd) = ys_to_java(&mut env, dim) else {
397        return false;
398    };
399    env.call_static_method(
400        "dev/yog/NativeBridge",
401        "worldSetTime",
402        "(Ljava/lang/String;J)Z",
403        &[JValue::Object(&jd), JValue::Long(time)],
404    )
405    .and_then(|v| v.z())
406    .unwrap_or(false)
407}
408
409unsafe extern "C" fn srv_is_raining(_ctx: *mut c_void, dim: YogStr) -> bool {
410    let Some(mut env) = get_env() else {
411        return false;
412    };
413    let Some(jd) = ys_to_java(&mut env, dim) else {
414        return false;
415    };
416    env.call_static_method(
417        "dev/yog/NativeBridge",
418        "worldIsRaining",
419        "(Ljava/lang/String;)Z",
420        &[JValue::Object(&jd)],
421    )
422    .and_then(|v| v.z())
423    .unwrap_or(false)
424}
425
426unsafe extern "C" fn srv_set_weather(
427    _ctx: *mut c_void,
428    dim: YogStr,
429    raining: bool,
430    dur: i32,
431) -> bool {
432    let Some(mut env) = get_env() else {
433        return false;
434    };
435    let Some(jd) = ys_to_java(&mut env, dim) else {
436        return false;
437    };
438    env.call_static_method(
439        "dev/yog/NativeBridge",
440        "worldSetWeather",
441        "(Ljava/lang/String;ZI)Z",
442        &[
443            JValue::Object(&jd),
444            JValue::Bool(raining as u8),
445            JValue::Int(dur),
446        ],
447    )
448    .and_then(|v| v.z())
449    .unwrap_or(false)
450}
451
452unsafe extern "C" fn srv_give_item(
453    _ctx: *mut c_void,
454    player: YogStr,
455    item: YogStr,
456    count: u32,
457) -> bool {
458    let Some(mut env) = get_env() else {
459        return false;
460    };
461    let (Some(jp), Some(ji)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, item)) else {
462        return false;
463    };
464    env.call_static_method(
465        "dev/yog/NativeBridge",
466        "giveItem",
467        "(Ljava/lang/String;Ljava/lang/String;I)Z",
468        &[
469            JValue::Object(&jp),
470            JValue::Object(&ji),
471            JValue::Int(count as i32),
472        ],
473    )
474    .and_then(|v| v.z())
475    .unwrap_or(false)
476}
477
478unsafe extern "C" fn srv_player_teleport(_ctx: *mut c_void, player: YogStr, pos: YogVec3) -> bool {
479    let Some(mut env) = get_env() else {
480        return false;
481    };
482    let Some(jp) = ys_to_java(&mut env, player) else {
483        return false;
484    };
485    env.call_static_method(
486        "dev/yog/NativeBridge",
487        "teleport",
488        "(Ljava/lang/String;DDD)Z",
489        &[
490            JValue::Object(&jp),
491            JValue::Double(pos.x),
492            JValue::Double(pos.y),
493            JValue::Double(pos.z),
494        ],
495    )
496    .and_then(|v| v.z())
497    .unwrap_or(false)
498}
499
500unsafe extern "C" fn srv_send_to_player(
501    _ctx: *mut c_void,
502    player: YogStr,
503    channel: YogStr,
504    data: *const u8,
505    len: u32,
506) -> bool {
507    let Some(mut env) = get_env() else {
508        return false;
509    };
510    let (Some(jp), Some(jc)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, channel)) else {
511        return false;
512    };
513    let payload = std::slice::from_raw_parts(data, len as usize);
514    let Ok(jdata) = env.byte_array_from_slice(payload) else {
515        return false;
516    };
517    env.call_static_method(
518        "dev/yog/NativeBridge",
519        "sendToPlayer",
520        "(Ljava/lang/String;Ljava/lang/String;[B)Z",
521        &[
522            JValue::Object(&jp),
523            JValue::Object(&jc),
524            JValue::Object(&jdata),
525        ],
526    )
527    .and_then(|v| v.z())
528    .unwrap_or(false)
529}
530
531unsafe extern "C" fn srv_send_to_server(
532    _ctx: *mut c_void,
533    channel: YogStr,
534    data: *const u8,
535    len: u32,
536) -> bool {
537    let Some(mut env) = get_env() else {
538        return false;
539    };
540    let Some(jc) = ys_to_java(&mut env, channel) else {
541        return false;
542    };
543    let payload = std::slice::from_raw_parts(data, len as usize);
544    let Ok(jdata) = env.byte_array_from_slice(payload) else {
545        return false;
546    };
547    let result = env.call_static_method(
548        "dev/yog/YogClient",
549        "sendToServer",
550        "(Ljava/lang/String;[B)Z",
551        &[JValue::Object(&jc), JValue::Object(&jdata)],
552    );
553    let _ = env.exception_clear();
554    result.and_then(|v| v.z()).unwrap_or(false)
555}
556
557unsafe extern "C" fn srv_kick_player(_ctx: *mut c_void, player: YogStr, reason: YogStr) -> bool {
558    let Some(mut env) = get_env() else {
559        return false;
560    };
561    let (Some(jp), Some(jr)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, reason)) else {
562        return false;
563    };
564    env.call_static_method(
565        "dev/yog/NativeBridge",
566        "kickPlayer",
567        "(Ljava/lang/String;Ljava/lang/String;)Z",
568        &[JValue::Object(&jp), JValue::Object(&jr)],
569    )
570    .and_then(|v| v.z())
571    .unwrap_or(false)
572}
573
574unsafe extern "C" fn srv_set_gamemode(_ctx: *mut c_void, player: YogStr, mode: YogStr) -> bool {
575    let Some(mut env) = get_env() else {
576        return false;
577    };
578    let (Some(jp), Some(jg)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, mode)) else {
579        return false;
580    };
581    env.call_static_method(
582        "dev/yog/NativeBridge",
583        "setGamemode",
584        "(Ljava/lang/String;Ljava/lang/String;)Z",
585        &[JValue::Object(&jp), JValue::Object(&jg)],
586    )
587    .and_then(|v| v.z())
588    .unwrap_or(false)
589}
590
591unsafe extern "C" fn srv_send_title(
592    _ctx: *mut c_void,
593    player: YogStr,
594    title: YogStr,
595    sub: YogStr,
596    fi: i32,
597    stay: i32,
598    fo: i32,
599) -> bool {
600    let Some(mut env) = get_env() else {
601        return false;
602    };
603    let (Some(jp), Some(jt), Some(js)) = (
604        ys_to_java(&mut env, player),
605        ys_to_java(&mut env, title),
606        ys_to_java(&mut env, sub),
607    ) else {
608        return false;
609    };
610    env.call_static_method(
611        "dev/yog/NativeBridge",
612        "sendTitle",
613        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III)Z",
614        &[
615            JValue::Object(&jp),
616            JValue::Object(&jt),
617            JValue::Object(&js),
618            JValue::Int(fi),
619            JValue::Int(stay),
620            JValue::Int(fo),
621        ],
622    )
623    .and_then(|v| v.z())
624    .unwrap_or(false)
625}
626
627unsafe extern "C" fn srv_send_actionbar(_ctx: *mut c_void, player: YogStr, msg: YogStr) -> bool {
628    let Some(mut env) = get_env() else {
629        return false;
630    };
631    let (Some(jp), Some(jm)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, msg)) else {
632        return false;
633    };
634    env.call_static_method(
635        "dev/yog/NativeBridge",
636        "sendActionbar",
637        "(Ljava/lang/String;Ljava/lang/String;)Z",
638        &[JValue::Object(&jp), JValue::Object(&jm)],
639    )
640    .and_then(|v| v.z())
641    .unwrap_or(false)
642}
643
644unsafe extern "C" fn srv_play_sound(
645    _ctx: *mut c_void,
646    dim: YogStr,
647    pos: YogVec3,
648    sound: YogStr,
649    vol: f32,
650    pitch: f32,
651) -> bool {
652    let Some(mut env) = get_env() else {
653        return false;
654    };
655    let (Some(jd), Some(js)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, sound)) else {
656        return false;
657    };
658    env.call_static_method(
659        "dev/yog/NativeBridge",
660        "playSound",
661        "(Ljava/lang/String;DDDLjava/lang/String;FF)Z",
662        &[
663            JValue::Object(&jd),
664            JValue::Double(pos.x),
665            JValue::Double(pos.y),
666            JValue::Double(pos.z),
667            JValue::Object(&js),
668            JValue::Float(vol),
669            JValue::Float(pitch),
670        ],
671    )
672    .and_then(|v| v.z())
673    .unwrap_or(false)
674}
675
676unsafe extern "C" fn srv_play_sound_player(
677    _ctx: *mut c_void,
678    player: YogStr,
679    sound: YogStr,
680    vol: f32,
681    pitch: f32,
682) -> bool {
683    let Some(mut env) = get_env() else {
684        return false;
685    };
686    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, sound)) else {
687        return false;
688    };
689    env.call_static_method(
690        "dev/yog/NativeBridge",
691        "playSoundToPlayer",
692        "(Ljava/lang/String;Ljava/lang/String;FF)Z",
693        &[
694            JValue::Object(&jp),
695            JValue::Object(&js),
696            JValue::Float(vol),
697            JValue::Float(pitch),
698        ],
699    )
700    .and_then(|v| v.z())
701    .unwrap_or(false)
702}
703
704unsafe extern "C" fn srv_entity_teleport(_ctx: *mut c_void, uuid: YogStr, pos: YogVec3) -> bool {
705    let Some(mut env) = get_env() else {
706        return false;
707    };
708    let Some(ju) = ys_to_java(&mut env, uuid) else {
709        return false;
710    };
711    env.call_static_method(
712        "dev/yog/NativeBridge",
713        "entityTeleport",
714        "(Ljava/lang/String;DDD)Z",
715        &[
716            JValue::Object(&ju),
717            JValue::Double(pos.x),
718            JValue::Double(pos.y),
719            JValue::Double(pos.z),
720        ],
721    )
722    .and_then(|v| v.z())
723    .unwrap_or(false)
724}
725
726unsafe extern "C" fn srv_entity_rotation(
727    _ctx: *mut c_void,
728    uuid: YogStr,
729    out: *mut YogVec3,
730) -> bool {
731    let Some(mut env) = get_env() else {
732        return false;
733    };
734    let Some(ju) = ys_to_java(&mut env, uuid) else {
735        return false;
736    };
737    let ret = env.call_static_method(
738        "dev/yog/NativeBridge",
739        "entityRotation",
740        "(Ljava/lang/String;)Ljava/lang/String;",
741        &[JValue::Object(&ju)],
742    );
743    let obj = match ret.and_then(|v| v.l()) {
744        Ok(o) => o,
745        Err(_) => {
746            env.exception_clear().ok();
747            return false;
748        }
749    };
750    if obj.as_raw().is_null() {
751        return false;
752    }
753    let s: String = match env.get_string(&JString::from(obj)) {
754        Ok(s) => String::from(s),
755        Err(_) => return false,
756    };
757    let mut it = s.split('\t');
758    let (yaw, pitch) = (it.next(), it.next());
759    if let (Some(yaw), Some(pitch)) = (
760        yaw.and_then(|v| v.parse().ok()),
761        pitch.and_then(|v| v.parse().ok()),
762    ) {
763        *out = YogVec3 {
764            x: yaw,
765            y: pitch,
766            z: 0.0,
767        };
768        true
769    } else {
770        false
771    }
772}
773
774unsafe extern "C" fn srv_entity_position(
775    _ctx: *mut c_void,
776    uuid: YogStr,
777    out: *mut YogVec3,
778) -> bool {
779    let Some(mut env) = get_env() else {
780        return false;
781    };
782    let Some(ju) = ys_to_java(&mut env, uuid) else {
783        return false;
784    };
785    let ret = env.call_static_method(
786        "dev/yog/NativeBridge",
787        "entityPosition",
788        "(Ljava/lang/String;)Ljava/lang/String;",
789        &[JValue::Object(&ju)],
790    );
791    let obj = match ret.and_then(|v| v.l()) {
792        Ok(o) => o,
793        Err(_) => return false,
794    };
795    if obj.as_raw().is_null() {
796        return false;
797    }
798    let s: String = match env.get_string(&JString::from(obj)) {
799        Ok(s) => String::from(s),
800        Err(_) => return false,
801    };
802    let mut it = s.split('\t');
803    let (x, y, z) = (it.next(), it.next(), it.next());
804    if let (Some(x), Some(y), Some(z)) = (
805        x.and_then(|v| v.parse().ok()),
806        y.and_then(|v| v.parse().ok()),
807        z.and_then(|v| v.parse().ok()),
808    ) {
809        *out = YogVec3 { x, y, z };
810        true
811    } else {
812        false
813    }
814}
815
816unsafe extern "C" fn srv_entity_health(_ctx: *mut c_void, uuid: YogStr, out: *mut f32) -> bool {
817    let Some(mut env) = get_env() else {
818        return false;
819    };
820    let Some(ju) = ys_to_java(&mut env, uuid) else {
821        return false;
822    };
823    match env
824        .call_static_method(
825            "dev/yog/NativeBridge",
826            "entityHealth",
827            "(Ljava/lang/String;)D",
828            &[JValue::Object(&ju)],
829        )
830        .and_then(|v| v.d())
831    {
832        Ok(v) if !v.is_nan() => {
833            *out = v as f32;
834            true
835        }
836        _ => false,
837    }
838}
839
840unsafe extern "C" fn srv_entity_set_health(_ctx: *mut c_void, uuid: YogStr, hp: f32) -> bool {
841    let Some(mut env) = get_env() else {
842        return false;
843    };
844    let Some(ju) = ys_to_java(&mut env, uuid) else {
845        return false;
846    };
847    env.call_static_method(
848        "dev/yog/NativeBridge",
849        "entitySetHealth",
850        "(Ljava/lang/String;D)Z",
851        &[JValue::Object(&ju), JValue::Double(hp as f64)],
852    )
853    .and_then(|v| v.z())
854    .unwrap_or(false)
855}
856
857unsafe extern "C" fn srv_entity_kill(_ctx: *mut c_void, uuid: YogStr) -> bool {
858    let Some(mut env) = get_env() else {
859        return false;
860    };
861    let Some(ju) = ys_to_java(&mut env, uuid) else {
862        return false;
863    };
864    env.call_static_method(
865        "dev/yog/NativeBridge",
866        "entityKill",
867        "(Ljava/lang/String;)Z",
868        &[JValue::Object(&ju)],
869    )
870    .and_then(|v| v.z())
871    .unwrap_or(false)
872}
873
874unsafe extern "C" fn srv_spawn_entity(
875    _ctx: *mut c_void,
876    type_id: YogStr,
877    dim: YogStr,
878    pos: YogVec3,
879) -> YogOwnedStr {
880    let Some(mut env) = get_env() else {
881        return YogOwnedStr::NONE;
882    };
883    let (Some(jt), Some(jd)) = (ys_to_java(&mut env, type_id), ys_to_java(&mut env, dim)) else {
884        return YogOwnedStr::NONE;
885    };
886    let ret = env.call_static_method(
887        "dev/yog/NativeBridge",
888        "spawnEntity",
889        "(Ljava/lang/String;Ljava/lang/String;DDD)Ljava/lang/String;",
890        &[
891            JValue::Object(&jt),
892            JValue::Object(&jd),
893            JValue::Double(pos.x),
894            JValue::Double(pos.y),
895            JValue::Double(pos.z),
896        ],
897    );
898    match ret.and_then(|v| v.l()) {
899        Ok(obj) => jstring_to_owned(&mut env, obj),
900        _ => YogOwnedStr::NONE,
901    }
902}
903
904unsafe extern "C" fn srv_entity_add_effect(
905    _ctx: *mut c_void,
906    uuid: YogStr,
907    fx: YogStr,
908    dur: i32,
909    amp: u8,
910    particles: bool,
911) -> bool {
912    let Some(mut env) = get_env() else {
913        return false;
914    };
915    let (Some(ju), Some(je)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, fx)) else {
916        return false;
917    };
918    env.call_static_method(
919        "dev/yog/NativeBridge",
920        "entityAddEffect",
921        "(Ljava/lang/String;Ljava/lang/String;IIZ)Z",
922        &[
923            JValue::Object(&ju),
924            JValue::Object(&je),
925            JValue::Int(dur),
926            JValue::Int(amp as i32),
927            JValue::Bool(particles as u8),
928        ],
929    )
930    .and_then(|v| v.z())
931    .unwrap_or(false)
932}
933
934unsafe extern "C" fn srv_entity_remove_effect(_ctx: *mut c_void, uuid: YogStr, fx: YogStr) -> bool {
935    let Some(mut env) = get_env() else {
936        return false;
937    };
938    let (Some(ju), Some(je)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, fx)) else {
939        return false;
940    };
941    env.call_static_method(
942        "dev/yog/NativeBridge",
943        "entityRemoveEffect",
944        "(Ljava/lang/String;Ljava/lang/String;)Z",
945        &[JValue::Object(&ju), JValue::Object(&je)],
946    )
947    .and_then(|v| v.z())
948    .unwrap_or(false)
949}
950
951unsafe extern "C" fn srv_entity_clear_effects(_ctx: *mut c_void, uuid: YogStr) -> bool {
952    let Some(mut env) = get_env() else {
953        return false;
954    };
955    let Some(ju) = ys_to_java(&mut env, uuid) else {
956        return false;
957    };
958    env.call_static_method(
959        "dev/yog/NativeBridge",
960        "entityClearEffects",
961        "(Ljava/lang/String;)Z",
962        &[JValue::Object(&ju)],
963    )
964    .and_then(|v| v.z())
965    .unwrap_or(false)
966}
967
968unsafe extern "C" fn srv_entity_velocity(
969    _ctx: *mut c_void,
970    uuid: YogStr,
971    out: *mut YogVec3,
972) -> bool {
973    let Some(mut env) = get_env() else {
974        return false;
975    };
976    let Some(ju) = ys_to_java(&mut env, uuid) else {
977        return false;
978    };
979    let ret = env.call_static_method(
980        "dev/yog/NativeBridge",
981        "entityVelocity",
982        "(Ljava/lang/String;)Ljava/lang/String;",
983        &[JValue::Object(&ju)],
984    );
985    let obj = match ret.and_then(|v| v.l()) {
986        Ok(o) => o,
987        Err(_) => return false,
988    };
989    if obj.as_raw().is_null() {
990        return false;
991    }
992    let s: String = match env.get_string(&JString::from(obj)) {
993        Ok(s) => String::from(s),
994        Err(_) => return false,
995    };
996    let mut it = s.split('\t');
997    let (x, y, z) = (it.next(), it.next(), it.next());
998    if let (Some(x), Some(y), Some(z)) = (
999        x.and_then(|v| v.parse().ok()),
1000        y.and_then(|v| v.parse().ok()),
1001        z.and_then(|v| v.parse().ok()),
1002    ) {
1003        *out = YogVec3 { x, y, z };
1004        true
1005    } else {
1006        false
1007    }
1008}
1009
1010unsafe extern "C" fn srv_entity_set_velocity(
1011    _ctx: *mut c_void,
1012    uuid: YogStr,
1013    vel: YogVec3,
1014) -> bool {
1015    let Some(mut env) = get_env() else {
1016        return false;
1017    };
1018    let Some(ju) = ys_to_java(&mut env, uuid) else {
1019        return false;
1020    };
1021    env.call_static_method(
1022        "dev/yog/NativeBridge",
1023        "entitySetVelocity",
1024        "(Ljava/lang/String;DDD)Z",
1025        &[
1026            JValue::Object(&ju),
1027            JValue::Double(vel.x),
1028            JValue::Double(vel.y),
1029            JValue::Double(vel.z),
1030        ],
1031    )
1032    .and_then(|v| v.z())
1033    .unwrap_or(false)
1034}
1035
1036unsafe extern "C" fn srv_entity_add_velocity(
1037    _ctx: *mut c_void,
1038    uuid: YogStr,
1039    vel: YogVec3,
1040) -> bool {
1041    let Some(mut env) = get_env() else {
1042        return false;
1043    };
1044    let Some(ju) = ys_to_java(&mut env, uuid) else {
1045        return false;
1046    };
1047    env.call_static_method(
1048        "dev/yog/NativeBridge",
1049        "entityAddVelocity",
1050        "(Ljava/lang/String;DDD)Z",
1051        &[
1052            JValue::Object(&ju),
1053            JValue::Double(vel.x),
1054            JValue::Double(vel.y),
1055            JValue::Double(vel.z),
1056        ],
1057    )
1058    .and_then(|v| v.z())
1059    .unwrap_or(false)
1060}
1061
1062unsafe extern "C" fn srv_has_item_tag(_ctx: *mut c_void, item: YogStr, tag: YogStr) -> bool {
1063    let Some(mut env) = get_env() else {
1064        return false;
1065    };
1066    let (Some(ji), Some(jt)) = (ys_to_java(&mut env, item), ys_to_java(&mut env, tag)) else {
1067        return false;
1068    };
1069    env.call_static_method(
1070        "dev/yog/NativeBridge",
1071        "hasItemTag",
1072        "(Ljava/lang/String;Ljava/lang/String;)Z",
1073        &[JValue::Object(&ji), JValue::Object(&jt)],
1074    )
1075    .and_then(|v| v.z())
1076    .unwrap_or(false)
1077}
1078
1079unsafe extern "C" fn srv_has_block_tag(_ctx: *mut c_void, block: YogStr, tag: YogStr) -> bool {
1080    let Some(mut env) = get_env() else {
1081        return false;
1082    };
1083    let (Some(jb), Some(jt)) = (ys_to_java(&mut env, block), ys_to_java(&mut env, tag)) else {
1084        return false;
1085    };
1086    env.call_static_method(
1087        "dev/yog/NativeBridge",
1088        "hasBlockTag",
1089        "(Ljava/lang/String;Ljava/lang/String;)Z",
1090        &[JValue::Object(&jb), JValue::Object(&jt)],
1091    )
1092    .and_then(|v| v.z())
1093    .unwrap_or(false)
1094}
1095
1096unsafe extern "C" fn srv_drop_loot(
1097    _ctx: *mut c_void,
1098    table: YogStr,
1099    dim: YogStr,
1100    pos: YogVec3,
1101) -> bool {
1102    let Some(mut env) = get_env() else {
1103        return false;
1104    };
1105    let (Some(jt), Some(jd)) = (ys_to_java(&mut env, table), ys_to_java(&mut env, dim)) else {
1106        return false;
1107    };
1108    env.call_static_method(
1109        "dev/yog/NativeBridge",
1110        "dropLoot",
1111        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
1112        &[
1113            JValue::Object(&jt),
1114            JValue::Object(&jd),
1115            JValue::Double(pos.x),
1116            JValue::Double(pos.y),
1117            JValue::Double(pos.z),
1118        ],
1119    )
1120    .and_then(|v| v.z())
1121    .unwrap_or(false)
1122}
1123
1124unsafe extern "C" fn srv_scoreboard_get(
1125    _ctx: *mut c_void,
1126    obj: YogStr,
1127    player: YogStr,
1128    out: *mut i32,
1129) -> bool {
1130    let Some(mut env) = get_env() else {
1131        return false;
1132    };
1133    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else {
1134        return false;
1135    };
1136    match env
1137        .call_static_method(
1138            "dev/yog/NativeBridge",
1139            "scoreboardGet",
1140            "(Ljava/lang/String;Ljava/lang/String;)I",
1141            &[JValue::Object(&jo), JValue::Object(&jp)],
1142        )
1143        .and_then(|v| v.i())
1144    {
1145        Ok(v) if v != i32::MIN => {
1146            *out = v;
1147            true
1148        }
1149        _ => false,
1150    }
1151}
1152
1153unsafe extern "C" fn srv_scoreboard_set(
1154    _ctx: *mut c_void,
1155    obj: YogStr,
1156    player: YogStr,
1157    score: i32,
1158) -> bool {
1159    let Some(mut env) = get_env() else {
1160        return false;
1161    };
1162    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else {
1163        return false;
1164    };
1165    env.call_static_method(
1166        "dev/yog/NativeBridge",
1167        "scoreboardSet",
1168        "(Ljava/lang/String;Ljava/lang/String;I)Z",
1169        &[JValue::Object(&jo), JValue::Object(&jp), JValue::Int(score)],
1170    )
1171    .and_then(|v| v.z())
1172    .unwrap_or(false)
1173}
1174
1175unsafe extern "C" fn srv_scoreboard_add(
1176    _ctx: *mut c_void,
1177    obj: YogStr,
1178    player: YogStr,
1179    delta: i32,
1180    out: *mut i32,
1181) -> bool {
1182    let Some(mut env) = get_env() else {
1183        return false;
1184    };
1185    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else {
1186        return false;
1187    };
1188    match env
1189        .call_static_method(
1190            "dev/yog/NativeBridge",
1191            "scoreboardAdd",
1192            "(Ljava/lang/String;Ljava/lang/String;I)I",
1193            &[JValue::Object(&jo), JValue::Object(&jp), JValue::Int(delta)],
1194        )
1195        .and_then(|v| v.i())
1196    {
1197        Ok(v) if v != i32::MIN => {
1198            *out = v;
1199            true
1200        }
1201        _ => false,
1202    }
1203}
1204
1205unsafe extern "C" fn srv_bossbar_create(
1206    _ctx: *mut c_void,
1207    id: YogStr,
1208    title: YogStr,
1209    color: YogStr,
1210    style: YogStr,
1211) -> bool {
1212    let Some(mut env) = get_env() else {
1213        return false;
1214    };
1215    let (Some(ji), Some(jt), Some(jc), Some(js)) = (
1216        ys_to_java(&mut env, id),
1217        ys_to_java(&mut env, title),
1218        ys_to_java(&mut env, color),
1219        ys_to_java(&mut env, style),
1220    ) else {
1221        return false;
1222    };
1223    env.call_static_method(
1224        "dev/yog/NativeBridge",
1225        "bossbarCreate",
1226        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
1227        &[
1228            JValue::Object(&ji),
1229            JValue::Object(&jt),
1230            JValue::Object(&jc),
1231            JValue::Object(&js),
1232        ],
1233    )
1234    .and_then(|v| v.z())
1235    .unwrap_or(false)
1236}
1237
1238unsafe extern "C" fn srv_bossbar_remove(_ctx: *mut c_void, id: YogStr) -> bool {
1239    let Some(mut env) = get_env() else {
1240        return false;
1241    };
1242    let Some(ji) = ys_to_java(&mut env, id) else {
1243        return false;
1244    };
1245    env.call_static_method(
1246        "dev/yog/NativeBridge",
1247        "bossbarRemove",
1248        "(Ljava/lang/String;)Z",
1249        &[JValue::Object(&ji)],
1250    )
1251    .and_then(|v| v.z())
1252    .unwrap_or(false)
1253}
1254
1255unsafe extern "C" fn srv_bossbar_set_title(_ctx: *mut c_void, id: YogStr, title: YogStr) -> bool {
1256    let Some(mut env) = get_env() else {
1257        return false;
1258    };
1259    let (Some(ji), Some(jt)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, title)) else {
1260        return false;
1261    };
1262    env.call_static_method(
1263        "dev/yog/NativeBridge",
1264        "bossbarSetTitle",
1265        "(Ljava/lang/String;Ljava/lang/String;)Z",
1266        &[JValue::Object(&ji), JValue::Object(&jt)],
1267    )
1268    .and_then(|v| v.z())
1269    .unwrap_or(false)
1270}
1271
1272unsafe extern "C" fn srv_bossbar_set_progress(
1273    _ctx: *mut c_void,
1274    id: YogStr,
1275    progress: f32,
1276) -> bool {
1277    let Some(mut env) = get_env() else {
1278        return false;
1279    };
1280    let Some(ji) = ys_to_java(&mut env, id) else {
1281        return false;
1282    };
1283    env.call_static_method(
1284        "dev/yog/NativeBridge",
1285        "bossbarSetProgress",
1286        "(Ljava/lang/String;F)Z",
1287        &[JValue::Object(&ji), JValue::Float(progress)],
1288    )
1289    .and_then(|v| v.z())
1290    .unwrap_or(false)
1291}
1292
1293unsafe extern "C" fn srv_bossbar_set_color(_ctx: *mut c_void, id: YogStr, color: YogStr) -> bool {
1294    let Some(mut env) = get_env() else {
1295        return false;
1296    };
1297    let (Some(ji), Some(jc)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, color)) else {
1298        return false;
1299    };
1300    env.call_static_method(
1301        "dev/yog/NativeBridge",
1302        "bossbarSetColor",
1303        "(Ljava/lang/String;Ljava/lang/String;)Z",
1304        &[JValue::Object(&ji), JValue::Object(&jc)],
1305    )
1306    .and_then(|v| v.z())
1307    .unwrap_or(false)
1308}
1309
1310unsafe extern "C" fn srv_bossbar_add_player(_ctx: *mut c_void, id: YogStr, player: YogStr) -> bool {
1311    let Some(mut env) = get_env() else {
1312        return false;
1313    };
1314    let (Some(ji), Some(jp)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, player)) else {
1315        return false;
1316    };
1317    env.call_static_method(
1318        "dev/yog/NativeBridge",
1319        "bossbarAddPlayer",
1320        "(Ljava/lang/String;Ljava/lang/String;)Z",
1321        &[JValue::Object(&ji), JValue::Object(&jp)],
1322    )
1323    .and_then(|v| v.z())
1324    .unwrap_or(false)
1325}
1326
1327unsafe extern "C" fn srv_bossbar_remove_player(
1328    _ctx: *mut c_void,
1329    id: YogStr,
1330    player: YogStr,
1331) -> bool {
1332    let Some(mut env) = get_env() else {
1333        return false;
1334    };
1335    let (Some(ji), Some(jp)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, player)) else {
1336        return false;
1337    };
1338    env.call_static_method(
1339        "dev/yog/NativeBridge",
1340        "bossbarRemovePlayer",
1341        "(Ljava/lang/String;Ljava/lang/String;)Z",
1342        &[JValue::Object(&ji), JValue::Object(&jp)],
1343    )
1344    .and_then(|v| v.z())
1345    .unwrap_or(false)
1346}
1347
1348unsafe extern "C" fn srv_bossbar_set_visible(_ctx: *mut c_void, id: YogStr, visible: bool) -> bool {
1349    let Some(mut env) = get_env() else {
1350        return false;
1351    };
1352    let Some(ji) = ys_to_java(&mut env, id) else {
1353        return false;
1354    };
1355    env.call_static_method(
1356        "dev/yog/NativeBridge",
1357        "bossbarSetVisible",
1358        "(Ljava/lang/String;Z)Z",
1359        &[JValue::Object(&ji), JValue::Bool(visible as u8)],
1360    )
1361    .and_then(|v| v.z())
1362    .unwrap_or(false)
1363}
1364
1365unsafe extern "C" fn srv_get_block_nbt(
1366    _ctx: *mut c_void,
1367    dim: YogStr,
1368    pos: YogBlockPos,
1369) -> YogOwnedStr {
1370    let Some(mut env) = get_env() else {
1371        return YogOwnedStr::NONE;
1372    };
1373    let Some(jd) = ys_to_java(&mut env, dim) else {
1374        return YogOwnedStr::NONE;
1375    };
1376    let ret = env.call_static_method(
1377        "dev/yog/NativeBridge",
1378        "getBlockNbt",
1379        "(Ljava/lang/String;III)Ljava/lang/String;",
1380        &[
1381            JValue::Object(&jd),
1382            JValue::Int(pos.x),
1383            JValue::Int(pos.y),
1384            JValue::Int(pos.z),
1385        ],
1386    );
1387    match ret.and_then(|v| v.l()) {
1388        Ok(obj) => jstring_to_owned(&mut env, obj),
1389        _ => YogOwnedStr::NONE,
1390    }
1391}
1392
1393unsafe extern "C" fn srv_set_block_nbt(
1394    _ctx: *mut c_void,
1395    dim: YogStr,
1396    pos: YogBlockPos,
1397    snbt: YogStr,
1398) -> bool {
1399    let Some(mut env) = get_env() else {
1400        return false;
1401    };
1402    let (Some(jd), Some(js)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, snbt)) else {
1403        return false;
1404    };
1405    env.call_static_method(
1406        "dev/yog/NativeBridge",
1407        "setBlockNbt",
1408        "(Ljava/lang/String;IIILjava/lang/String;)Z",
1409        &[
1410            JValue::Object(&jd),
1411            JValue::Int(pos.x),
1412            JValue::Int(pos.y),
1413            JValue::Int(pos.z),
1414            JValue::Object(&js),
1415        ],
1416    )
1417    .and_then(|v| v.z())
1418    .unwrap_or(false)
1419}
1420
1421unsafe extern "C" fn srv_get_inventory_slot(
1422    _ctx: *mut c_void,
1423    dim: YogStr,
1424    pos: yog_abi::YogBlockPos,
1425    slot: u32,
1426) -> YogOwnedStr {
1427    let Some(mut env) = get_env() else {
1428        return YogOwnedStr::NONE;
1429    };
1430    let Some(jd) = ys_to_java(&mut env, dim) else {
1431        return YogOwnedStr::NONE;
1432    };
1433    let ret = env.call_static_method(
1434        "dev/yog/NativeBridge",
1435        "getInventorySlot",
1436        "(Ljava/lang/String;IIII)Ljava/lang/String;",
1437        &[
1438            JValue::Object(&jd),
1439            JValue::Int(pos.x),
1440            JValue::Int(pos.y),
1441            JValue::Int(pos.z),
1442            JValue::Int(slot as i32),
1443        ],
1444    );
1445    match ret.and_then(|v| v.l()) {
1446        Ok(obj) => jstring_to_owned(&mut env, obj),
1447        _ => YogOwnedStr::NONE,
1448    }
1449}
1450
1451unsafe extern "C" fn srv_set_inventory_slot(
1452    _ctx: *mut c_void,
1453    dim: YogStr,
1454    pos: yog_abi::YogBlockPos,
1455    slot: u32,
1456    item_id: YogStr,
1457    count: u32,
1458) -> bool {
1459    let Some(mut env) = get_env() else {
1460        return false;
1461    };
1462    let (Some(jd), Some(ji)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, item_id)) else {
1463        return false;
1464    };
1465    env.call_static_method(
1466        "dev/yog/NativeBridge",
1467        "setInventorySlot",
1468        "(Ljava/lang/String;IIIILjava/lang/String;I)Z",
1469        &[
1470            JValue::Object(&jd),
1471            JValue::Int(pos.x),
1472            JValue::Int(pos.y),
1473            JValue::Int(pos.z),
1474            JValue::Int(slot as i32),
1475            JValue::Object(&ji),
1476            JValue::Int(count as i32),
1477        ],
1478    )
1479    .and_then(|v| v.z())
1480    .unwrap_or(false)
1481}
1482
1483unsafe extern "C" fn srv_player_inventory(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1484    let Some(mut env) = get_env() else {
1485        return YogOwnedStr::NONE;
1486    };
1487    let Some(jp) = ys_to_java(&mut env, player) else {
1488        return YogOwnedStr::NONE;
1489    };
1490    let ret = env.call_static_method(
1491        "dev/yog/NativeBridge",
1492        "playerInventory",
1493        "(Ljava/lang/String;)Ljava/lang/String;",
1494        &[JValue::Object(&jp)],
1495    );
1496    match ret.and_then(|v| v.l()) {
1497        Ok(obj) => jstring_to_owned(&mut env, obj),
1498        _ => YogOwnedStr::NONE,
1499    }
1500}
1501
1502unsafe extern "C" fn srv_player_set_slot(
1503    _ctx: *mut c_void,
1504    player: YogStr,
1505    slot: u32,
1506    item_id: YogStr,
1507    count: u32,
1508) -> bool {
1509    let Some(mut env) = get_env() else {
1510        return false;
1511    };
1512    let (Some(jp), Some(ji)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, item_id)) else {
1513        return false;
1514    };
1515    env.call_static_method(
1516        "dev/yog/NativeBridge",
1517        "playerSetSlot",
1518        "(Ljava/lang/String;ILjava/lang/String;I)Z",
1519        &[
1520            JValue::Object(&jp),
1521            JValue::Int(slot as i32),
1522            JValue::Object(&ji),
1523            JValue::Int(count as i32),
1524        ],
1525    )
1526    .and_then(|v| v.z())
1527    .unwrap_or(false)
1528}
1529
1530unsafe extern "C" fn srv_player_teleport_dim(
1531    _ctx: *mut c_void,
1532    player: YogStr,
1533    dim: YogStr,
1534    pos: YogVec3,
1535) -> bool {
1536    let Some(mut env) = get_env() else {
1537        return false;
1538    };
1539    let (Some(jp), Some(jd)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, dim)) else {
1540        return false;
1541    };
1542    env.call_static_method(
1543        "dev/yog/NativeBridge",
1544        "teleportToDim",
1545        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
1546        &[
1547            JValue::Object(&jp),
1548            JValue::Object(&jd),
1549            JValue::Double(pos.x),
1550            JValue::Double(pos.y),
1551            JValue::Double(pos.z),
1552        ],
1553    )
1554    .and_then(|v| v.z())
1555    .unwrap_or(false)
1556}
1557
1558unsafe extern "C" fn srv_entity_teleport_dim(
1559    _ctx: *mut c_void,
1560    uuid: YogStr,
1561    dim: YogStr,
1562    pos: YogVec3,
1563) -> bool {
1564    let Some(mut env) = get_env() else {
1565        return false;
1566    };
1567    let (Some(ju), Some(jd)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, dim)) else {
1568        return false;
1569    };
1570    env.call_static_method(
1571        "dev/yog/NativeBridge",
1572        "entityTeleportToDim",
1573        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
1574        &[
1575            JValue::Object(&ju),
1576            JValue::Object(&jd),
1577            JValue::Double(pos.x),
1578            JValue::Double(pos.y),
1579            JValue::Double(pos.z),
1580        ],
1581    )
1582    .and_then(|v| v.z())
1583    .unwrap_or(false)
1584}
1585
1586unsafe extern "C" fn srv_online_players(_ctx: *mut c_void) -> YogOwnedStr {
1587    let Some(mut env) = get_env() else {
1588        return YogOwnedStr::NONE;
1589    };
1590    let ret = env.call_static_method(
1591        "dev/yog/NativeBridge",
1592        "onlinePlayers",
1593        "()Ljava/lang/String;",
1594        &[],
1595    );
1596    match ret.and_then(|v| v.l()) {
1597        Ok(obj) => jstring_to_owned(&mut env, obj),
1598        _ => YogOwnedStr::NONE,
1599    }
1600}
1601
1602unsafe extern "C" fn srv_world_entity_count(
1603    _ctx: *mut c_void,
1604    dim: YogStr,
1605    entity_type: YogStr,
1606) -> i32 {
1607    let Some(mut env) = get_env() else { return -1 };
1608    let d = dim.as_str();
1609    let et = entity_type.as_str();
1610    let jd = match env.new_string(d) {
1611        Ok(s) => s,
1612        Err(_) => return -1,
1613    };
1614    let jet = match env.new_string(et) {
1615        Ok(s) => s,
1616        Err(_) => return -1,
1617    };
1618    let ret = env.call_static_method(
1619        "dev/yog/NativeBridge",
1620        "worldEntityCount",
1621        "(Ljava/lang/String;Ljava/lang/String;)I",
1622        &[JValue::Object(&jd), JValue::Object(&jet)],
1623    );
1624    match ret.and_then(|v| v.i()) {
1625        Ok(n) => n,
1626        _ => -1,
1627    }
1628}
1629
1630unsafe extern "C" fn srv_game_dir(_ctx: *mut c_void) -> YogOwnedStr {
1631    let Some(mut env) = get_env() else {
1632        return YogOwnedStr::NONE;
1633    };
1634    let ret = env.call_static_method(
1635        "dev/yog/NativeBridge",
1636        "gameDir",
1637        "()Ljava/lang/String;",
1638        &[],
1639    );
1640    match ret.and_then(|v| v.l()) {
1641        Ok(obj) => jstring_to_owned(&mut env, obj),
1642        _ => YogOwnedStr::NONE,
1643    }
1644}
1645
1646unsafe extern "C" fn srv_entity_get_nbt(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
1647    let Some(mut env) = get_env() else {
1648        return YogOwnedStr::NONE;
1649    };
1650    let Some(ju) = ys_to_java(&mut env, uuid) else {
1651        return YogOwnedStr::NONE;
1652    };
1653    let ret = env.call_static_method(
1654        "dev/yog/NativeBridge",
1655        "entityGetNbt",
1656        "(Ljava/lang/String;)Ljava/lang/String;",
1657        &[JValue::Object(&ju)],
1658    );
1659    match ret.and_then(|v| v.l()) {
1660        Ok(obj) => jstring_to_owned(&mut env, obj),
1661        _ => YogOwnedStr::NONE,
1662    }
1663}
1664
1665unsafe extern "C" fn srv_entity_set_nbt(_ctx: *mut c_void, uuid: YogStr, snbt: YogStr) -> bool {
1666    let Some(mut env) = get_env() else {
1667        return false;
1668    };
1669    let (Some(ju), Some(js)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, snbt)) else {
1670        return false;
1671    };
1672    env.call_static_method(
1673        "dev/yog/NativeBridge",
1674        "entitySetNbt",
1675        "(Ljava/lang/String;Ljava/lang/String;)Z",
1676        &[JValue::Object(&ju), JValue::Object(&js)],
1677    )
1678    .and_then(|v| v.z())
1679    .unwrap_or(false)
1680}
1681
1682unsafe extern "C" fn srv_spawn_particles(
1683    _ctx: *mut c_void,
1684    dim: YogStr,
1685    pos: YogVec3,
1686    particle_type: YogStr,
1687    count: i32,
1688    dx: f64,
1689    dy: f64,
1690    dz: f64,
1691    speed: f64,
1692) -> bool {
1693    let Some(mut env) = get_env() else {
1694        return false;
1695    };
1696    let (Some(jd), Some(jp)) = (
1697        ys_to_java(&mut env, dim),
1698        ys_to_java(&mut env, particle_type),
1699    ) else {
1700        return false;
1701    };
1702    env.call_static_method(
1703        "dev/yog/NativeBridge",
1704        "spawnParticles",
1705        "(Ljava/lang/String;DDDLjava/lang/String;IDDDD)Z",
1706        &[
1707            JValue::Object(&jd),
1708            JValue::Double(pos.x),
1709            JValue::Double(pos.y),
1710            JValue::Double(pos.z),
1711            JValue::Object(&jp),
1712            JValue::Int(count),
1713            JValue::Double(dx),
1714            JValue::Double(dy),
1715            JValue::Double(dz),
1716            JValue::Double(speed),
1717        ],
1718    )
1719    .and_then(|v| v.z())
1720    .unwrap_or(false)
1721}
1722
1723unsafe extern "C" fn srv_entity_attribute_get(
1724    _ctx: *mut c_void,
1725    uuid: YogStr,
1726    attr: YogStr,
1727) -> f64 {
1728    let Some(mut env) = get_env() else {
1729        return f64::NAN;
1730    };
1731    let (Some(ju), Some(ja)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, attr)) else {
1732        return f64::NAN;
1733    };
1734    env.call_static_method(
1735        "dev/yog/NativeBridge",
1736        "entityAttributeGet",
1737        "(Ljava/lang/String;Ljava/lang/String;)D",
1738        &[JValue::Object(&ju), JValue::Object(&ja)],
1739    )
1740    .and_then(|v| v.d())
1741    .unwrap_or(f64::NAN)
1742}
1743
1744unsafe extern "C" fn srv_entity_attribute_set(
1745    _ctx: *mut c_void,
1746    uuid: YogStr,
1747    attr: YogStr,
1748    value: f64,
1749) -> bool {
1750    let Some(mut env) = get_env() else {
1751        return false;
1752    };
1753    let (Some(ju), Some(ja)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, attr)) else {
1754        return false;
1755    };
1756    env.call_static_method(
1757        "dev/yog/NativeBridge",
1758        "entityAttributeSet",
1759        "(Ljava/lang/String;Ljava/lang/String;D)Z",
1760        &[
1761            JValue::Object(&ju),
1762            JValue::Object(&ja),
1763            JValue::Double(value),
1764        ],
1765    )
1766    .and_then(|v| v.z())
1767    .unwrap_or(false)
1768}
1769
1770unsafe extern "C" fn srv_get_held_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1771    let Some(mut env) = get_env() else {
1772        return YogOwnedStr::NONE;
1773    };
1774    let Some(jp) = ys_to_java(&mut env, player) else {
1775        return YogOwnedStr::NONE;
1776    };
1777    let ret = env.call_static_method(
1778        "dev/yog/NativeBridge",
1779        "getHeldItemNbt",
1780        "(Ljava/lang/String;)Ljava/lang/String;",
1781        &[JValue::Object(&jp)],
1782    );
1783    match ret.and_then(|v| v.l()) {
1784        Ok(obj) => jstring_to_owned(&mut env, obj),
1785        _ => YogOwnedStr::NONE,
1786    }
1787}
1788
1789unsafe extern "C" fn srv_set_held_item_nbt(
1790    _ctx: *mut c_void,
1791    player: YogStr,
1792    snbt: YogStr,
1793) -> bool {
1794    let Some(mut env) = get_env() else {
1795        return false;
1796    };
1797    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, snbt)) else {
1798        return false;
1799    };
1800    env.call_static_method(
1801        "dev/yog/NativeBridge",
1802        "setHeldItemNbt",
1803        "(Ljava/lang/String;Ljava/lang/String;)Z",
1804        &[JValue::Object(&jp), JValue::Object(&js)],
1805    )
1806    .and_then(|v| v.z())
1807    .unwrap_or(false)
1808}
1809
1810unsafe extern "C" fn srv_get_offhand_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
1811    let Some(mut env) = get_env() else {
1812        return YogOwnedStr::NONE;
1813    };
1814    let Some(jp) = ys_to_java(&mut env, player) else {
1815        return YogOwnedStr::NONE;
1816    };
1817    let ret = env.call_static_method(
1818        "dev/yog/NativeBridge",
1819        "getOffhandItemNbt",
1820        "(Ljava/lang/String;)Ljava/lang/String;",
1821        &[JValue::Object(&jp)],
1822    );
1823    match ret.and_then(|v| v.l()) {
1824        Ok(obj) => jstring_to_owned(&mut env, obj),
1825        _ => YogOwnedStr::NONE,
1826    }
1827}
1828
1829unsafe extern "C" fn srv_set_offhand_item_nbt(
1830    _ctx: *mut c_void,
1831    player: YogStr,
1832    snbt: YogStr,
1833) -> bool {
1834    let Some(mut env) = get_env() else {
1835        return false;
1836    };
1837    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, snbt)) else {
1838        return false;
1839    };
1840    env.call_static_method(
1841        "dev/yog/NativeBridge",
1842        "setOffhandItemNbt",
1843        "(Ljava/lang/String;Ljava/lang/String;)Z",
1844        &[JValue::Object(&jp), JValue::Object(&js)],
1845    )
1846    .and_then(|v| v.z())
1847    .unwrap_or(false)
1848}
1849
1850unsafe extern "C" fn srv_get_slot_item(
1851    _ctx: *mut c_void,
1852    player: YogStr,
1853    slot: u32,
1854) -> YogOwnedStr {
1855    let Some(mut env) = get_env() else {
1856        return YogOwnedStr::NONE;
1857    };
1858    let Some(jp) = ys_to_java(&mut env, player) else {
1859        return YogOwnedStr::NONE;
1860    };
1861    let ret = env.call_static_method(
1862        "dev/yog/NativeBridge",
1863        "getSlotItem",
1864        "(Ljava/lang/String;I)Ljava/lang/String;",
1865        &[JValue::Object(&jp), JValue::Int(slot as i32)],
1866    );
1867    match ret.and_then(|v| v.l()) {
1868        Ok(obj) => jstring_to_owned(&mut env, obj),
1869        _ => YogOwnedStr::NONE,
1870    }
1871}
1872
1873unsafe extern "C" fn srv_set_slot_item(
1874    _ctx: *mut c_void,
1875    player: YogStr,
1876    slot: u32,
1877    item_id: YogStr,
1878    count: u32,
1879    snbt: YogStr,
1880) -> bool {
1881    let Some(mut env) = get_env() else {
1882        return false;
1883    };
1884    let (Some(jp), Some(ji), Some(js)) = (
1885        ys_to_java(&mut env, player),
1886        ys_to_java(&mut env, item_id),
1887        ys_to_java(&mut env, snbt),
1888    ) else {
1889        return false;
1890    };
1891    env.call_static_method(
1892        "dev/yog/NativeBridge",
1893        "setSlotItem",
1894        "(Ljava/lang/String;ILjava/lang/String;ILjava/lang/String;)Z",
1895        &[
1896            JValue::Object(&jp),
1897            JValue::Int(slot as i32),
1898            JValue::Object(&ji),
1899            JValue::Int(count as i32),
1900            JValue::Object(&js),
1901        ],
1902    )
1903    .and_then(|v| v.z())
1904    .unwrap_or(false)
1905}
1906
1907// ── ABI minor 14 — low-level GPU pipeline ────────────────────────────────────
1908//
1909// All raw GL calls go through `glow::Context` stored in GL.
1910// `draw2d_*` functions still call JNI (NativeDraw) for MC text/texture rendering.
1911// Everything is called on the render thread — no synchronization needed.
1912
1913// ── helpers ───────────────────────────────────────────────────────────────────
1914
1915fn gl_draw_mode(mode: u8) -> u32 {
1916    match mode {
1917        1 => glow::LINES,
1918        2 => glow::LINE_STRIP,
1919        3 => glow::TRIANGLE_STRIP,
1920        4 => glow::TRIANGLE_FAN,
1921        _ => glow::TRIANGLES,
1922    }
1923}
1924
1925fn gl_attr_type(dtype: u8) -> u32 {
1926    match dtype {
1927        1 => glow::UNSIGNED_BYTE,
1928        2 => glow::INT,
1929        3 => glow::UNSIGNED_INT,
1930        _ => glow::FLOAT,
1931    }
1932}
1933
1934unsafe fn compile_shader(gl: &glow::Context, stage: u32, src: &str) -> Option<glow::NativeShader> {
1935    let sh = gl.create_shader(stage).ok()?;
1936    gl.shader_source(sh, src);
1937    gl.compile_shader(sh);
1938    if !gl.get_shader_compile_status(sh) {
1939        yog_logging::error!("yog-gfx shader compile: {}", gl.get_shader_info_log(sh));
1940        gl.delete_shader(sh);
1941        return None;
1942    }
1943    Some(sh)
1944}
1945
1946// ── GPU buffers ───────────────────────────────────────────────────────────────
1947
1948unsafe extern "C" fn gfx_buf_create() -> u32 {
1949    let Some(g) = GL.get() else { return 0 };
1950    g.0.create_buffer().map(|b| b.0.get()).unwrap_or(0)
1951}
1952
1953unsafe extern "C" fn gfx_buf_delete(handle: u32) {
1954    let Some(g) = GL.get() else { return };
1955    let Some(n) = NonZeroU32::new(handle) else {
1956        return;
1957    };
1958    g.0.delete_buffer(glow::NativeBuffer(n));
1959}
1960
1961unsafe extern "C" fn gfx_buf_data(handle: u32, bytes: *const u8, len: u32, dynamic: bool) {
1962    let Some(g) = GL.get() else { return };
1963    let Some(n) = NonZeroU32::new(handle) else {
1964        return;
1965    };
1966    let gl = &g.0;
1967    let data = std::slice::from_raw_parts(bytes, len as usize);
1968    let usage = if dynamic {
1969        glow::DYNAMIC_DRAW
1970    } else {
1971        glow::STATIC_DRAW
1972    };
1973    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
1974    gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, data, usage);
1975    gl.bind_buffer(glow::ARRAY_BUFFER, None);
1976}
1977
1978unsafe extern "C" fn gfx_buf_subdata(handle: u32, offset: u32, bytes: *const u8, len: u32) {
1979    let Some(g) = GL.get() else { return };
1980    let Some(n) = NonZeroU32::new(handle) else {
1981        return;
1982    };
1983    let gl = &g.0;
1984    let data = std::slice::from_raw_parts(bytes, len as usize);
1985    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
1986    gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, offset as i32, data);
1987    gl.bind_buffer(glow::ARRAY_BUFFER, None);
1988}
1989
1990// ── Vertex arrays ─────────────────────────────────────────────────────────────
1991
1992unsafe extern "C" fn gfx_vao_create() -> u32 {
1993    let Some(g) = GL.get() else { return 0 };
1994    g.0.create_vertex_array().map(|v| v.0.get()).unwrap_or(0)
1995}
1996
1997unsafe extern "C" fn gfx_vao_delete(handle: u32) {
1998    let Some(g) = GL.get() else { return };
1999    let Some(n) = NonZeroU32::new(handle) else {
2000        return;
2001    };
2002    g.0.delete_vertex_array(glow::NativeVertexArray(n));
2003}
2004
2005unsafe extern "C" fn gfx_vao_attrib(
2006    vao: u32,
2007    vbo: u32,
2008    index: u32,
2009    components: u8,
2010    dtype: u8,
2011    normalized: bool,
2012    stride: u32,
2013    offset: u32,
2014) {
2015    let Some(g) = GL.get() else { return };
2016    let (Some(vn), Some(bn)) = (NonZeroU32::new(vao), NonZeroU32::new(vbo)) else {
2017        return;
2018    };
2019    let gl = &g.0;
2020    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
2021    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(bn)));
2022    let gl_type = gl_attr_type(dtype);
2023    if dtype == 2 || dtype == 3 {
2024        gl.vertex_attrib_pointer_i32(
2025            index,
2026            components as i32,
2027            gl_type,
2028            stride as i32,
2029            offset as i32,
2030        );
2031    } else {
2032        gl.vertex_attrib_pointer_f32(
2033            index,
2034            components as i32,
2035            gl_type,
2036            normalized,
2037            stride as i32,
2038            offset as i32,
2039        );
2040    }
2041    gl.enable_vertex_attrib_array(index);
2042    gl.bind_vertex_array(None);
2043}
2044
2045unsafe extern "C" fn gfx_vao_set_ebo(vao: u32, ebo: u32) {
2046    let Some(g) = GL.get() else { return };
2047    let (Some(vn), Some(en)) = (NonZeroU32::new(vao), NonZeroU32::new(ebo)) else {
2048        return;
2049    };
2050    let gl = &g.0;
2051    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
2052    gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(glow::NativeBuffer(en)));
2053    gl.bind_vertex_array(None);
2054}
2055
2056// ── Shader programs ───────────────────────────────────────────────────────────
2057
2058/// Returns a path under `~/.cache/yog/shaders/<hash>.ysc` for the given GLSL source pair,
2059/// creating the directory if needed.  Returns `None` if the home directory is unknown.
2060fn shader_cache_path(vert: &str, frag: &str) -> Option<PathBuf> {
2061    use std::hash::{Hash, Hasher};
2062    let mut h = std::collections::hash_map::DefaultHasher::new();
2063    vert.hash(&mut h);
2064    frag.hash(&mut h);
2065    let hash = h.finish();
2066    let dir = std::env::var("HOME")
2067        .map(|home| {
2068            PathBuf::from(home)
2069                .join(".cache")
2070                .join("yog")
2071                .join("shaders")
2072        })
2073        .ok()?;
2074    std::fs::create_dir_all(&dir).ok()?;
2075    Some(dir.join(format!("{hash:016x}.ysc")))
2076}
2077
2078/// Try restoring a program from a binary blob (`[4 LE bytes: GL format][binary…]`).
2079/// Returns `Some(handle)` if the driver accepts the binary, `None` otherwise.
2080///
2081/// Uses `glProgramBinary` (GL 4.1 / ARB_get_program_binary) via the raw pointer
2082/// captured in `nativeGlInit`.  Returns `None` if the extension is unavailable.
2083unsafe fn load_shader_binary(gl: &glow::Context, data: &[u8]) -> Option<glow::NativeProgram> {
2084    if data.len() < 4 {
2085        return None;
2086    }
2087    type ProgramBinaryFn = unsafe extern "system" fn(u32, u32, *const c_void, i32);
2088    let fn_ptr = (*GL_PROGRAM_BINARY.get()?)?;
2089    let program_binary: ProgramBinaryFn = std::mem::transmute(fn_ptr);
2090
2091    let fmt = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
2092    let prog = gl.create_program().ok()?;
2093    program_binary(
2094        prog.0.get(),
2095        fmt,
2096        data[4..].as_ptr() as *const c_void,
2097        (data.len() - 4) as i32,
2098    );
2099    if gl.get_program_link_status(prog) {
2100        Some(prog)
2101    } else {
2102        gl.delete_program(prog);
2103        None
2104    }
2105}
2106
2107/// Read the compiled binary of a linked program.  Returns empty `Vec` if unsupported.
2108unsafe fn get_shader_binary(prog: glow::NativeProgram) -> (Vec<u8>, u32) {
2109    type GetProgramivFn = unsafe extern "system" fn(u32, u32, *mut i32);
2110    type GetProgramBinaryFn = unsafe extern "system" fn(u32, i32, *mut i32, *mut u32, *mut c_void);
2111
2112    let Some(get_iv_raw) = GL_GET_PROGRAM_IV.get().and_then(|v| *v) else {
2113        return (vec![], 0);
2114    };
2115    let Some(get_bin_raw) = GL_GET_PROGRAM_BINARY.get().and_then(|v| *v) else {
2116        return (vec![], 0);
2117    };
2118    let get_program_iv: GetProgramivFn = std::mem::transmute(get_iv_raw);
2119    let get_program_binary: GetProgramBinaryFn = std::mem::transmute(get_bin_raw);
2120
2121    // Query binary size via glGetProgramiv(GL_PROGRAM_BINARY_LENGTH).
2122    const PROGRAM_BINARY_LENGTH: u32 = 0x8741;
2123    let mut size: i32 = 0;
2124    get_program_iv(prog.0.get(), PROGRAM_BINARY_LENGTH, &mut size);
2125    if size <= 0 {
2126        return (vec![], 0);
2127    }
2128
2129    let mut buf = vec![0u8; size as usize];
2130    let mut actual_len: i32 = 0;
2131    let mut fmt: u32 = 0;
2132    get_program_binary(
2133        prog.0.get(),
2134        size,
2135        &mut actual_len,
2136        &mut fmt,
2137        buf.as_mut_ptr() as *mut c_void,
2138    );
2139    buf.truncate(actual_len.max(0) as usize);
2140    (buf, fmt)
2141}
2142
2143unsafe extern "C" fn gfx_prog_create(vert: YogStr, frag: YogStr, out: *mut u32) -> bool {
2144    let Some(g) = GL.get() else { return false };
2145    let gl = &g.0;
2146    let vert_s = vert.as_str();
2147    let frag_s = frag.as_str();
2148
2149    // Fast path: binary shader cache — avoids GLSL re-compilation on subsequent launches.
2150    let cache_path = shader_cache_path(vert_s, frag_s);
2151    if let Some(ref path) = cache_path {
2152        if let Ok(data) = std::fs::read(path) {
2153            if let Some(prog) = load_shader_binary(gl, &data) {
2154                *out = prog.0.get();
2155                return true;
2156            }
2157            // Cache stale (driver updated?); remove and fall through to GLSL compile.
2158            let _ = std::fs::remove_file(path);
2159        }
2160    }
2161
2162    // GLSL compile path.
2163    let vs = match compile_shader(gl, glow::VERTEX_SHADER, vert_s) {
2164        Some(s) => s,
2165        None => return false,
2166    };
2167    let fs = match compile_shader(gl, glow::FRAGMENT_SHADER, frag_s) {
2168        Some(s) => s,
2169        None => {
2170            gl.delete_shader(vs);
2171            return false;
2172        }
2173    };
2174    let prog = match gl.create_program() {
2175        Ok(p) => p,
2176        Err(e) => {
2177            yog_logging::error!("yog-gfx: create_program: {}", e);
2178            gl.delete_shader(vs);
2179            gl.delete_shader(fs);
2180            return false;
2181        }
2182    };
2183    gl.attach_shader(prog, vs);
2184    gl.attach_shader(prog, fs);
2185    gl.link_program(prog);
2186    gl.detach_shader(prog, vs);
2187    gl.detach_shader(prog, fs);
2188    gl.delete_shader(vs);
2189    gl.delete_shader(fs);
2190    if !gl.get_program_link_status(prog) {
2191        yog_logging::error!("yog-gfx: shader link: {}", gl.get_program_info_log(prog));
2192        gl.delete_program(prog);
2193        return false;
2194    }
2195
2196    // Persist binary so the next launch skips GLSL compilation entirely.
2197    if let Some(ref path) = cache_path {
2198        let (binary, fmt) = get_shader_binary(prog);
2199        if !binary.is_empty() {
2200            let mut blob = fmt.to_le_bytes().to_vec();
2201            blob.extend_from_slice(&binary);
2202            let _ = std::fs::write(path, &blob);
2203        }
2204    }
2205
2206    *out = prog.0.get();
2207    true
2208}
2209
2210unsafe extern "C" fn gfx_prog_delete(handle: u32) {
2211    let Some(g) = GL.get() else { return };
2212    let Some(n) = NonZeroU32::new(handle) else {
2213        return;
2214    };
2215    g.0.delete_program(glow::NativeProgram(n));
2216}
2217
2218macro_rules! with_prog {
2219    ($handle:expr, |$gl:ident, $prog:ident, $loc:ident ($name:expr)| $body:expr) => {{
2220        let Some(g) = GL.get() else { return };
2221        let Some(n) = NonZeroU32::new($handle) else {
2222            return;
2223        };
2224        let $gl = &g.0;
2225        let $prog = glow::NativeProgram(n);
2226        $gl.use_program(Some($prog));
2227        let $loc = $gl.get_uniform_location($prog, $name.as_str());
2228        $body
2229    }};
2230}
2231
2232unsafe extern "C" fn gfx_prog_uniform_1i(prog: u32, name: YogStr, v: i32) {
2233    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_i32(loc.as_ref(), v));
2234}
2235unsafe extern "C" fn gfx_prog_uniform_1f(prog: u32, name: YogStr, v: f32) {
2236    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_f32(loc.as_ref(), v));
2237}
2238unsafe extern "C" fn gfx_prog_uniform_2f(prog: u32, name: YogStr, x: f32, y: f32) {
2239    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_2_f32(
2240        loc.as_ref(),
2241        x,
2242        y
2243    ));
2244}
2245unsafe extern "C" fn gfx_prog_uniform_3f(prog: u32, name: YogStr, x: f32, y: f32, z: f32) {
2246    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_3_f32(
2247        loc.as_ref(),
2248        x,
2249        y,
2250        z
2251    ));
2252}
2253unsafe extern "C" fn gfx_prog_uniform_4f(prog: u32, name: YogStr, x: f32, y: f32, z: f32, w: f32) {
2254    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_4_f32(
2255        loc.as_ref(),
2256        x,
2257        y,
2258        z,
2259        w
2260    ));
2261}
2262unsafe extern "C" fn gfx_prog_uniform_mat4(prog: u32, name: YogStr, col_major: *const f32) {
2263    with_prog!(prog, |gl, _p, loc(name)| {
2264        let data = std::slice::from_raw_parts(col_major, 16);
2265        gl.uniform_matrix_4_f32_slice(loc.as_ref(), false, data);
2266    });
2267}
2268
2269// ── Textures ──────────────────────────────────────────────────────────────────
2270
2271unsafe extern "C" fn gfx_tex_create(w: u32, h: u32, rgba: *const u8, linear: bool) -> u32 {
2272    let Some(g) = GL.get() else { return 0 };
2273    let gl = &g.0;
2274    let tex = match gl.create_texture() {
2275        Ok(t) => t,
2276        Err(_) => return 0,
2277    };
2278    gl.bind_texture(glow::TEXTURE_2D, Some(tex));
2279    let filter = if linear { glow::LINEAR } else { glow::NEAREST };
2280    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, filter as i32);
2281    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, filter as i32);
2282    gl.tex_parameter_i32(
2283        glow::TEXTURE_2D,
2284        glow::TEXTURE_WRAP_S,
2285        glow::CLAMP_TO_EDGE as i32,
2286    );
2287    gl.tex_parameter_i32(
2288        glow::TEXTURE_2D,
2289        glow::TEXTURE_WRAP_T,
2290        glow::CLAMP_TO_EDGE as i32,
2291    );
2292    let pixels = std::slice::from_raw_parts(rgba, (w * h * 4) as usize);
2293    gl.tex_image_2d(
2294        glow::TEXTURE_2D,
2295        0,
2296        glow::RGBA8 as i32,
2297        w as i32,
2298        h as i32,
2299        0,
2300        glow::RGBA,
2301        glow::UNSIGNED_BYTE,
2302        glow::PixelUnpackData::Slice(Some(pixels)),
2303    );
2304    gl.bind_texture(glow::TEXTURE_2D, None);
2305    tex.0.get()
2306}
2307
2308unsafe extern "C" fn gfx_tex_delete(handle: u32) {
2309    let Some(g) = GL.get() else { return };
2310    let Some(n) = NonZeroU32::new(handle) else {
2311        return;
2312    };
2313    g.0.delete_texture(glow::NativeTexture(n));
2314}
2315
2316unsafe extern "C" fn gfx_tex_bind(unit: u32, handle: u32) {
2317    let Some(g) = GL.get() else { return };
2318    let gl = &g.0;
2319    gl.active_texture(glow::TEXTURE0 + unit);
2320    match NonZeroU32::new(handle) {
2321        Some(n) => gl.bind_texture(glow::TEXTURE_2D, Some(glow::NativeTexture(n))),
2322        None => gl.bind_texture(glow::TEXTURE_2D, None),
2323    }
2324}
2325
2326unsafe extern "C" fn gfx_tex_from_mc(id: YogStr) -> u32 {
2327    let Some(mut env) = get_env() else { return 0 };
2328    let Some(ji) = ys_to_java(&mut env, id) else {
2329        return 0;
2330    };
2331    env.call_static_method(
2332        "dev/yog/NativeDraw",
2333        "getMcTextureId",
2334        "(Ljava/lang/String;)I",
2335        &[JValue::Object(&ji)],
2336    )
2337    .and_then(|v| v.i())
2338    .map(|id| id as u32)
2339    .unwrap_or(0)
2340}
2341
2342// ── Draw calls ────────────────────────────────────────────────────────────────
2343
2344unsafe extern "C" fn gfx_draw_arrays(vao: u32, prog: u32, mode: u8, first: u32, count: u32) {
2345    let Some(g) = GL.get() else { return };
2346    let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
2347        return;
2348    };
2349    let gl = &g.0;
2350    gl.use_program(Some(glow::NativeProgram(pn)));
2351    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
2352    gl.draw_arrays(gl_draw_mode(mode), first as i32, count as i32);
2353    gl.bind_vertex_array(None);
2354}
2355
2356unsafe extern "C" fn gfx_draw_elements(
2357    vao: u32,
2358    ebo: u32,
2359    prog: u32,
2360    mode: u8,
2361    count: u32,
2362    u32_idx: bool,
2363) {
2364    let Some(g) = GL.get() else { return };
2365    let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else {
2366        return;
2367    };
2368    let gl = &g.0;
2369    gl.use_program(Some(glow::NativeProgram(pn)));
2370    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
2371    // EBO is stored in the VAO; ebo param is informational for safety but not re-bound here.
2372    let _ = ebo;
2373    let idx_type = if u32_idx {
2374        glow::UNSIGNED_INT
2375    } else {
2376        glow::UNSIGNED_SHORT
2377    };
2378    gl.draw_elements(gl_draw_mode(mode), count as i32, idx_type, 0);
2379    gl.bind_vertex_array(None);
2380}
2381
2382// ── Render state ──────────────────────────────────────────────────────────────
2383
2384unsafe extern "C" fn gfx_set_blend(enabled: bool, src: u32, dst: u32) {
2385    let Some(g) = GL.get() else { return };
2386    let gl = &g.0;
2387    if enabled {
2388        gl.enable(glow::BLEND);
2389        gl.blend_func(src, dst);
2390    } else {
2391        gl.disable(glow::BLEND);
2392    }
2393}
2394
2395unsafe extern "C" fn gfx_set_depth(test: bool, write: bool) {
2396    let Some(g) = GL.get() else { return };
2397    let gl = &g.0;
2398    if test {
2399        gl.enable(glow::DEPTH_TEST);
2400    } else {
2401        gl.disable(glow::DEPTH_TEST);
2402    }
2403    gl.depth_mask(write);
2404    // Disable face culling: our 2D quads use CW winding (back-face) due to y-flip.
2405    if !test {
2406        gl.disable(glow::CULL_FACE);
2407    }
2408}
2409
2410unsafe extern "C" fn gfx_set_scissor(x: i32, y: i32, w: i32, h: i32) {
2411    let Some(g) = GL.get() else { return };
2412    let gl = &g.0;
2413    gl.enable(glow::SCISSOR_TEST);
2414    gl.scissor(x, y, w, h);
2415}
2416
2417unsafe extern "C" fn gfx_clear_scissor() {
2418    let Some(g) = GL.get() else { return };
2419    g.0.disable(glow::SCISSOR_TEST);
2420}
2421
2422unsafe extern "C" fn gfx_set_viewport(x: i32, y: i32, w: i32, h: i32) {
2423    let Some(g) = GL.get() else { return };
2424    g.0.viewport(x, y, w, h);
2425}
2426
2427// ── 2D convenience (JNI — uses MC's DrawContext / text renderer) ──────────────
2428
2429unsafe extern "C" fn gfx_draw2d_rect(x1: f32, y1: f32, x2: f32, y2: f32, color: u32) {
2430    let Some(mut env) = get_env() else { return };
2431    let _ = env.call_static_method(
2432        "dev/yog/NativeDraw",
2433        "drawRect",
2434        "(FFFFI)V",
2435        &[
2436            JValue::Float(x1),
2437            JValue::Float(y1),
2438            JValue::Float(x2),
2439            JValue::Float(y2),
2440            JValue::Int(color as i32),
2441        ],
2442    );
2443}
2444
2445unsafe extern "C" fn gfx_draw2d_gradient(
2446    x1: f32,
2447    y1: f32,
2448    x2: f32,
2449    y2: f32,
2450    top: u32,
2451    bottom: u32,
2452) {
2453    let Some(mut env) = get_env() else { return };
2454    let _ = env.call_static_method(
2455        "dev/yog/NativeDraw",
2456        "drawGradientRect",
2457        "(FFFFII)V",
2458        &[
2459            JValue::Float(x1),
2460            JValue::Float(y1),
2461            JValue::Float(x2),
2462            JValue::Float(y2),
2463            JValue::Int(top as i32),
2464            JValue::Int(bottom as i32),
2465        ],
2466    );
2467}
2468
2469unsafe extern "C" fn gfx_draw2d_text(text: YogStr, x: f32, y: f32, color: u32, shadow: bool) {
2470    let Some(mut env) = get_env() else { return };
2471    if let Some(jt) = ys_to_java(&mut env, text) {
2472        let _ = env.call_static_method(
2473            "dev/yog/NativeDraw",
2474            "drawText",
2475            "(Ljava/lang/String;FFIZ)V",
2476            &[
2477                JValue::Object(&jt),
2478                JValue::Float(x),
2479                JValue::Float(y),
2480                JValue::Int(color as i32),
2481                JValue::Bool(shadow as u8),
2482            ],
2483        );
2484    }
2485}
2486
2487unsafe extern "C" fn gfx_draw2d_mc_tex(
2488    id: YogStr,
2489    x: f32,
2490    y: f32,
2491    u0: f32,
2492    v0: f32,
2493    w: f32,
2494    h: f32,
2495    tw: f32,
2496    th: f32,
2497) {
2498    let Some(mut env) = get_env() else { return };
2499    if let Some(ji) = ys_to_java(&mut env, id) {
2500        let _ = env.call_static_method(
2501            "dev/yog/NativeDraw",
2502            "drawTexture",
2503            "(Ljava/lang/String;FFFFFFFFF)V",
2504            &[
2505                JValue::Object(&ji),
2506                JValue::Float(x),
2507                JValue::Float(y),
2508                JValue::Float(u0),
2509                JValue::Float(v0),
2510                JValue::Float(w),
2511                JValue::Float(h),
2512                JValue::Float(tw),
2513                JValue::Float(th),
2514            ],
2515        );
2516    }
2517}
2518
2519unsafe extern "C" fn gfx_draw2d_item(id: YogStr, x: f32, y: f32, size: f32) {
2520    let Some(mut env) = get_env() else { return };
2521    if let Some(ji) = ys_to_java(&mut env, id) {
2522        let _ = env.call_static_method(
2523            "dev/yog/NativeDraw",
2524            "drawItem",
2525            "(Ljava/lang/String;FFF)V",
2526            &[
2527                JValue::Object(&ji),
2528                JValue::Float(x),
2529                JValue::Float(y),
2530                JValue::Float(size),
2531            ],
2532        );
2533    }
2534}
2535
2536// ── Static GFX function table (function pointers only — per-frame data filled at call time) ──
2537
2538static GFX_FN_TABLE: YogGfxApi = YogGfxApi {
2539    // Per-frame fields zeroed in the static; actual values are set on the stack per render call.
2540    screen_w: 0,
2541    screen_h: 0,
2542    delta_tick: 0.0,
2543    scale_factor: 1.0,
2544    view_proj: [0.0; 16],
2545    camera_pos: [0.0; 3],
2546    player_pos: [0.0; 3],
2547    _pad1: 0.0,
2548    buf_create: gfx_buf_create,
2549    buf_delete: gfx_buf_delete,
2550    buf_data: gfx_buf_data,
2551    buf_subdata: gfx_buf_subdata,
2552    vao_create: gfx_vao_create,
2553    vao_delete: gfx_vao_delete,
2554    vao_attrib: gfx_vao_attrib,
2555    vao_set_ebo: gfx_vao_set_ebo,
2556    prog_create: gfx_prog_create,
2557    prog_delete: gfx_prog_delete,
2558    prog_uniform_1i: gfx_prog_uniform_1i,
2559    prog_uniform_1f: gfx_prog_uniform_1f,
2560    prog_uniform_2f: gfx_prog_uniform_2f,
2561    prog_uniform_3f: gfx_prog_uniform_3f,
2562    prog_uniform_4f: gfx_prog_uniform_4f,
2563    prog_uniform_mat4: gfx_prog_uniform_mat4,
2564    tex_create: gfx_tex_create,
2565    tex_delete: gfx_tex_delete,
2566    tex_bind: gfx_tex_bind,
2567    tex_from_mc: gfx_tex_from_mc,
2568    draw_arrays: gfx_draw_arrays,
2569    draw_elements: gfx_draw_elements,
2570    set_blend: gfx_set_blend,
2571    set_depth: gfx_set_depth,
2572    set_scissor: gfx_set_scissor,
2573    clear_scissor: gfx_clear_scissor,
2574    set_viewport: gfx_set_viewport,
2575    draw2d_rect: gfx_draw2d_rect,
2576    draw2d_gradient: gfx_draw2d_gradient,
2577    draw2d_text: gfx_draw2d_text,
2578    draw2d_mc_tex: gfx_draw2d_mc_tex,
2579    draw2d_item: gfx_draw2d_item,
2580};
2581
2582// ── YogApi registration functions ─────────────────────────────────────────────
2583//
2584// ctx is *mut RuntimeHandlers (cast from *mut c_void).
2585
2586macro_rules! api_event {
2587    ($name:ident, $field:ident, $fn_ty:ty) => {
2588        unsafe extern "C" fn $name(ctx: *mut c_void, ud: *mut c_void, h: $fn_ty) {
2589            let handlers = &mut *(ctx as *mut RuntimeHandlers);
2590            handlers.$field.push((ud, h));
2591        }
2592    };
2593}
2594
2595api_event!(api_on_block_break, block_break, YogBlockBreakFn);
2596api_event!(api_on_chat, chat, YogChatFn);
2597api_event!(api_on_player_join, player_join, YogPlayerFn);
2598api_event!(api_on_player_leave, player_leave, YogPlayerFn);
2599api_event!(api_on_use_item, use_item, YogUseItemFn);
2600api_event!(api_on_use_block, use_block, YogUseBlockFn);
2601api_event!(api_on_attack_entity, attack_entity, YogAttackEntityFn);
2602api_event!(api_on_entity_damage, entity_damage, YogEntityDamageFn);
2603api_event!(api_on_entity_death, entity_death, YogEntityDeathFn);
2604api_event!(api_on_entity_spawn, entity_spawn, YogEntitySpawnFn);
2605api_event!(
2606    api_on_player_place_block,
2607    player_place_block,
2608    YogPlaceBlockFn
2609);
2610api_event!(api_on_player_death, player_death, YogPlayerDeathFn);
2611api_event!(api_on_player_respawn, player_respawn, YogPlayerRespawnFn);
2612api_event!(api_on_advancement, advancement, YogAdvancementFn);
2613api_event!(api_on_entity_interact, entity_interact, YogEntityInteractFn);
2614api_event!(api_on_item_craft, item_craft, YogCraftFn);
2615api_event!(api_on_explosion, explosion, YogExplosionFn);
2616api_event!(api_on_item_pickup, item_pickup, YogItemPickupFn);
2617api_event!(api_on_player_move, player_move, YogPlayerMoveFn);
2618api_event!(api_on_container_open, container_open, YogContainerOpenFn);
2619api_event!(api_on_container_close, container_close, YogContainerCloseFn);
2620api_event!(api_on_projectile_hit, projectile_hit, YogProjectileHitFn);
2621api_event!(api_on_client_tick, client_tick, YogClientFn);
2622api_event!(api_on_hud_render, hud_render, YogHudRenderFn);
2623api_event!(api_on_world_render, world_render, YogWorldRenderFn);
2624api_event!(api_on_key_press, key_press, YogKeyPressFn);
2625api_event!(api_on_screen_open, screen_open, YogScreenFn);
2626api_event!(api_on_screen_close, screen_close, YogScreenFn);
2627api_event!(api_on_server_tick, server_tick, YogServerFn);
2628api_event!(api_on_server_started, server_started, YogServerFn);
2629api_event!(api_on_server_stopping, server_stopping, YogServerFn);
2630
2631unsafe extern "C" fn api_on_packet(
2632    ctx: *mut c_void,
2633    channel: YogStr,
2634    ud: *mut c_void,
2635    h: YogPacketFn,
2636) {
2637    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2638    handlers
2639        .packets
2640        .insert(channel.as_str().to_owned(), (ud, h));
2641}
2642
2643unsafe extern "C" fn api_on_client_packet(
2644    ctx: *mut c_void,
2645    channel: YogStr,
2646    ud: *mut c_void,
2647    h: YogPacketFn,
2648) {
2649    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2650    handlers
2651        .client_packets
2652        .insert(channel.as_str().to_owned(), (ud, h));
2653}
2654
2655unsafe extern "C" fn api_register_command(
2656    ctx: *mut c_void,
2657    name: YogStr,
2658    ud: *mut c_void,
2659    h: YogCommandFn,
2660) {
2661    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2662    handlers
2663        .commands
2664        .entry(name.as_str().to_owned())
2665        .or_default()
2666        .push((None, ud, h));
2667}
2668
2669unsafe extern "C" fn api_register_typed_command(
2670    ctx: *mut c_void,
2671    name: YogStr,
2672    schema: YogStr,
2673    ud: *mut c_void,
2674    h: YogCommandFn,
2675) {
2676    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2677    let n = name.as_str().to_owned();
2678    handlers
2679        .commands
2680        .entry(n)
2681        .or_default()
2682        .push((Some(schema.as_str().to_owned()), ud, h));
2683}
2684
2685unsafe extern "C" fn api_register_recipe_json(
2686    ctx: *mut c_void,
2687    namespace: YogStr,
2688    name: YogStr,
2689    json: YogStr,
2690) {
2691    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2692    let ns = namespace.as_str().to_owned();
2693    let n = name.as_str().to_owned();
2694    let j = json.as_str().to_owned();
2695    // Feed the recipe to already-registered book renderers (crafting pages).
2696    let full_id = format!("{}:{}", ns, n);
2697    for r in handlers.book_renderers.lock().unwrap().values_mut() {
2698        r.add_recipe(full_id.clone(), &j);
2699    }
2700    handlers.recipes.push((ns, n, j));
2701}
2702
2703unsafe extern "C" fn api_register_item(ctx: *mut c_void, def: *const YogItemDef) {
2704    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2705    let d = &*def;
2706    let food = if d.food_nutrition > 0 {
2707        Some(FoodDef {
2708            nutrition: d.food_nutrition,
2709            saturation: d.food_saturation,
2710            can_always_eat: d.food_always_eat,
2711        })
2712    } else {
2713        None
2714    };
2715    handlers.items.push(ItemDef {
2716        id: d.id.as_str().to_owned(),
2717        max_stack: d.max_stack as u8,
2718        name: if d.name.is_empty() {
2719            None
2720        } else {
2721            Some(d.name.as_str().to_owned())
2722        },
2723        tooltip: if d.tooltip.is_empty() {
2724            None
2725        } else {
2726            Some(d.tooltip.as_str().to_owned())
2727        },
2728        max_damage: d.max_damage,
2729        fire_resistant: d.fire_resistant,
2730        fuel_ticks: d.fuel_ticks,
2731        food,
2732    });
2733}
2734
2735unsafe extern "C" fn api_register_block(ctx: *mut c_void, def: *const YogBlockDef) {
2736    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2737    let d = &*def;
2738    handlers.blocks.push(BlockDef {
2739        id: d.id.as_str().to_owned(),
2740        hardness: d.hardness,
2741        resistance: d.resistance,
2742        name: if d.name.is_empty() {
2743            None
2744        } else {
2745            Some(d.name.as_str().to_owned())
2746        },
2747        light_level: d.light_level,
2748        sound: if d.sound.is_empty() {
2749            None
2750        } else {
2751            Some(d.sound.as_str().to_owned())
2752        },
2753        requires_tool: d.requires_tool,
2754        no_collision: d.no_collision,
2755        slipperiness: d.slipperiness,
2756        shape: if d.shape == [0.0f32; 6] {
2757            None
2758        } else {
2759            Some(d.shape)
2760        },
2761        connects: d.connects,
2762        connect_groups: if d.connect_groups.is_empty() {
2763            Vec::new()
2764        } else {
2765            d.connect_groups
2766                .as_str()
2767                .split(',')
2768                .map(str::to_owned)
2769                .collect()
2770        },
2771        inventory_id: if d.inventory_id.is_empty() {
2772            None
2773        } else {
2774            Some(d.inventory_id.as_str().to_owned())
2775        },
2776    });
2777}
2778
2779unsafe extern "C" fn api_register_inventory(
2780    ctx: *mut c_void,
2781    def: *const yog_abi::YogInventoryDef,
2782) {
2783    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2784    let d = &*def;
2785    handlers.inventories.push(yog_inventory::InventoryDef {
2786        id: d.id.as_str().to_owned(),
2787        slot_count: d.slot_count as usize,
2788        layout: yog_inventory::decode_layout(d.layout.as_str()),
2789        include_player_inventory: d.include_player_inventory,
2790        player_inv_offset: (d.player_inv_x, d.player_inv_y),
2791        background_texture: if d.background_texture.is_empty() {
2792            None
2793        } else {
2794            Some(d.background_texture.as_str().to_owned())
2795        },
2796        title: d.title.as_str().to_owned(),
2797    });
2798}
2799
2800unsafe extern "C" fn api_schedule_once(
2801    ctx: *mut c_void,
2802    delay_ticks: u64,
2803    ud: *mut c_void,
2804    h: YogScheduledFn,
2805) {
2806    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2807    handlers
2808        .scheduler
2809        .lock()
2810        .expect("scheduler poisoned")
2811        .once_tasks
2812        .push(OnceTask {
2813            delay_remaining: delay_ticks,
2814            ud,
2815            f: h,
2816        });
2817}
2818
2819unsafe extern "C" fn api_schedule_repeating(
2820    ctx: *mut c_void,
2821    period_ticks: u64,
2822    ud: *mut c_void,
2823    h: YogScheduledFn,
2824) {
2825    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2826    handlers
2827        .scheduler
2828        .lock()
2829        .expect("scheduler poisoned")
2830        .repeating_tasks
2831        .push(RepeatingTask {
2832            period: period_ticks,
2833            ticks_left: period_ticks,
2834            ud,
2835            f: h,
2836        });
2837}
2838
2839unsafe extern "C" fn api_register_startup_grant(
2840    ctx: *mut c_void,
2841    grant: *const YogStartupGrantDef,
2842) {
2843    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2844    let g = &*grant;
2845    let items: Vec<String> = if g.items.is_empty() {
2846        Vec::new()
2847    } else {
2848        unsafe {
2849            g.items
2850                .as_str()
2851                .split('|')
2852                .map(|s: &str| s.to_owned())
2853                .collect()
2854        }
2855    };
2856    let book = if g.book.is_empty() {
2857        None
2858    } else {
2859        Some(unsafe { g.book.as_str().to_owned() })
2860    };
2861    let command = if g.command.is_empty() {
2862        None
2863    } else {
2864        Some(unsafe { g.command.as_str().to_owned() })
2865    };
2866    handlers.startup_grants.push(yog_registry::StartupGrant {
2867        id: unsafe { g.id.as_str().to_owned() },
2868        items,
2869        book,
2870        command,
2871    });
2872}
2873
2874unsafe extern "C" fn api_register_book(ctx: *mut c_void, book_id: YogStr, book_json: YogStr) {
2875    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2876    let id = unsafe { book_id.as_str().to_owned() };
2877    let json = unsafe { book_json.as_str().to_owned() };
2878    handlers.books.insert(id.clone(), json.clone());
2879    // Parse JSON → Book → BookRenderer so rendering works without Java round-trip.
2880    match serde_json::from_str::<yog_book::Book>(&json) {
2881        Ok(book) => {
2882            let mut renderer = yog_book::BookRenderer::new(book);
2883            // Recipes may register before the book — replay them into the renderer.
2884            for (ns, name, j) in &handlers.recipes {
2885                renderer.add_recipe(format!("{}:{}", ns, name), j);
2886            }
2887            handlers.book_renderers.lock().unwrap().insert(id, renderer);
2888        }
2889        Err(e) => {
2890            yog_logging::warn!(
2891                "book '{}': failed to parse JSON for Rust renderer: {}",
2892                id,
2893                e
2894            );
2895        }
2896    }
2897}
2898
2899unsafe extern "C" fn api_register_ui(
2900    ctx: *mut c_void,
2901    ui_id: YogStr,
2902    _layout_json: YogStr,
2903    ud: *mut c_void,
2904    h: yog_abi::YogUIEventFn,
2905) {
2906    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2907    let id = unsafe { ui_id.as_str().to_owned() };
2908    handlers.ui_handlers.insert(id.clone(), (ud, h));
2909    yog_logging::info!("registered UI handler: {}", id);
2910}
2911
2912unsafe extern "C" fn api_on_ui_render(
2913    ctx: *mut c_void,
2914    ui_id: YogStr,
2915    ud: *mut c_void,
2916    h: YogHudRenderFn,
2917) {
2918    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2919    let id = unsafe { ui_id.as_str().to_owned() };
2920    handlers
2921        .ui_render_handlers
2922        .entry(id)
2923        .or_default()
2924        .push((ud, h));
2925}
2926
2927/// Strip TSV separators so one mod can't corrupt the line format.
2928fn tsv_clean(v: &str) -> String {
2929    v.replace(['\t', '\n', '\r'], " ")
2930}
2931
2932unsafe extern "C" fn api_ui_open(_ctx: *mut c_void, ui_id: YogStr, modal: bool, pause: bool) {
2933    let id = ui_id.as_str().to_string();
2934    if id.is_empty() {
2935        return;
2936    }
2937    let Some(mut env) = get_env() else { return };
2938    let Ok(jid) = env.new_string(&id) else { return };
2939    let jid_obj: JObject = jid.into();
2940    // Java side schedules onto the render thread; no-op on dedicated servers.
2941    if env
2942        .call_static_method(
2943            "dev/yog/NativeBridge",
2944            "openUI",
2945            "(Ljava/lang/String;ZZ)V",
2946            &[
2947                JValue::Object(&jid_obj),
2948                JValue::Bool(modal as u8),
2949                JValue::Bool(pause as u8),
2950            ],
2951        )
2952        .is_err()
2953    {
2954        env.exception_clear().ok();
2955    }
2956}
2957
2958unsafe extern "C" fn api_mods_list(_ctx: *mut c_void) -> YogOwnedStr {
2959    let mut lines: Vec<String> = Vec::new();
2960    for m in MOD_INFOS.lock().expect("mod infos lock poisoned").iter() {
2961        lines.push(format!(
2962            "yog\t{}\t{}\t{}\t{}\t{}",
2963            m[0], m[1], m[2], m[3], m[4]
2964        ));
2965    }
2966    // Loader-side (Java) mods, if the host provides them.
2967    if let Some(mut env) = get_env() {
2968        if let Ok(val) = env.call_static_method(
2969            "dev/yog/NativeBridge",
2970            "listPlatformMods",
2971            "()Ljava/lang/String;",
2972            &[],
2973        ) {
2974            if let Ok(obj) = val.l() {
2975                if !obj.as_raw().is_null() {
2976                    if let Ok(js) = env.get_string(&obj.into()) {
2977                        for line in String::from(js).lines() {
2978                            if !line.trim().is_empty() {
2979                                lines.push(format!("platform\t{line}"));
2980                            }
2981                        }
2982                    }
2983                }
2984            }
2985        } else {
2986            env.exception_clear().ok();
2987        }
2988    }
2989    YogOwnedStr::from_string(lines.join("\n"))
2990}
2991
2992unsafe extern "C" fn api_register_menu_entry(ctx: *mut c_void, label: YogStr, ui_id: YogStr) {
2993    let handlers = &mut *(ctx as *mut RuntimeHandlers);
2994    let l = unsafe { label.as_str().to_owned() };
2995    let u = unsafe { ui_id.as_str().to_owned() };
2996    handlers.menu_entries.push((l, u));
2997}
2998
2999#[no_mangle]
3000pub extern "system" fn Java_dev_yog_NativeBridge_nativeBookJson<'l>(
3001    mut env: JNIEnv<'l>,
3002    _class: JClass<'l>,
3003    book_id: JString<'l>,
3004) -> jstring {
3005    let id = match env.get_string(&book_id) {
3006        Ok(s) => String::from(s),
3007        Err(_) => return std::ptr::null_mut(),
3008    };
3009    let json = handlers()
3010        .books
3011        .get(&id)
3012        .cloned()
3013        .unwrap_or_else(|| "null".to_string());
3014    env.new_string(json)
3015        .map(|s| s.into_raw())
3016        .unwrap_or(std::ptr::null_mut())
3017}
3018
3019// ── Table constructors ────────────────────────────────────────────────────────
3020
3021fn build_server_table() -> YogServer {
3022    YogServer {
3023        ctx: std::ptr::null_mut(),
3024        abi_version: ABI_VERSION,
3025        size: std::mem::size_of::<YogServer>() as u32,
3026        free_str: yog_free_str,
3027        broadcast: srv_broadcast,
3028        get_block: srv_get_block,
3029        set_block: srv_set_block,
3030        world_time: srv_world_time,
3031        set_time: srv_set_time,
3032        is_raining: srv_is_raining,
3033        set_weather: srv_set_weather,
3034        give_item: srv_give_item,
3035        player_teleport: srv_player_teleport,
3036        send_to_player: srv_send_to_player,
3037        send_to_server: srv_send_to_server,
3038        kick_player: srv_kick_player,
3039        set_gamemode: srv_set_gamemode,
3040        send_title: srv_send_title,
3041        send_actionbar: srv_send_actionbar,
3042        play_sound: srv_play_sound,
3043        play_sound_player: srv_play_sound_player,
3044        entity_teleport: srv_entity_teleport,
3045        entity_position: srv_entity_position,
3046        entity_rotation: srv_entity_rotation,
3047        entity_health: srv_entity_health,
3048        entity_set_health: srv_entity_set_health,
3049        entity_kill: srv_entity_kill,
3050        spawn_entity: srv_spawn_entity,
3051        entity_add_effect: srv_entity_add_effect,
3052        entity_remove_effect: srv_entity_remove_effect,
3053        entity_clear_effects: srv_entity_clear_effects,
3054        entity_velocity: srv_entity_velocity,
3055        entity_set_velocity: srv_entity_set_velocity,
3056        entity_add_velocity: srv_entity_add_velocity,
3057        has_item_tag: srv_has_item_tag,
3058        has_block_tag: srv_has_block_tag,
3059        drop_loot: srv_drop_loot,
3060        scoreboard_get: srv_scoreboard_get,
3061        scoreboard_set: srv_scoreboard_set,
3062        scoreboard_add: srv_scoreboard_add,
3063        bossbar_create: srv_bossbar_create,
3064        bossbar_remove: srv_bossbar_remove,
3065        bossbar_set_title: srv_bossbar_set_title,
3066        bossbar_set_progress: srv_bossbar_set_progress,
3067        bossbar_set_color: srv_bossbar_set_color,
3068        bossbar_add_player: srv_bossbar_add_player,
3069        bossbar_remove_player: srv_bossbar_remove_player,
3070        bossbar_set_visible: srv_bossbar_set_visible,
3071        game_dir: srv_game_dir,
3072        get_block_nbt: srv_get_block_nbt,
3073        set_block_nbt: srv_set_block_nbt,
3074        get_inventory_slot: srv_get_inventory_slot,
3075        set_inventory_slot: srv_set_inventory_slot,
3076        player_inventory: srv_player_inventory,
3077        player_set_slot: srv_player_set_slot,
3078        player_teleport_dim: srv_player_teleport_dim,
3079        entity_teleport_dim: srv_entity_teleport_dim,
3080        online_players: srv_online_players,
3081        world_entity_count: srv_world_entity_count,
3082        entity_get_nbt: srv_entity_get_nbt,
3083        entity_set_nbt: srv_entity_set_nbt,
3084        spawn_particles: srv_spawn_particles,
3085        entity_attribute_get: srv_entity_attribute_get,
3086        entity_attribute_set: srv_entity_attribute_set,
3087        get_held_item_nbt: srv_get_held_item_nbt,
3088        set_held_item_nbt: srv_set_held_item_nbt,
3089        get_offhand_item_nbt: srv_get_offhand_item_nbt,
3090        set_offhand_item_nbt: srv_set_offhand_item_nbt,
3091        get_slot_item: srv_get_slot_item,
3092        set_slot_item: srv_set_slot_item,
3093    }
3094}
3095
3096// ── Inter-mod communication ──────────────────────────────────────────────────
3097
3098unsafe extern "C" fn interop_export_impl(
3099    _ctx: *mut c_void,
3100    mod_id: YogStr,
3101    symbol: YogStr,
3102    ptr: *const c_void,
3103) {
3104    let mod_id = mod_id.as_str();
3105    let sym = symbol.as_str();
3106    let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3107    registry
3108        .lock()
3109        .expect("interop symbols lock poisoned")
3110        .entry(mod_id.to_string())
3111        .or_default()
3112        .insert(sym.to_string(), ptr as usize);
3113}
3114
3115unsafe extern "C" fn interop_import_impl(
3116    _ctx: *mut c_void,
3117    mod_id: YogStr,
3118    symbol: YogStr,
3119) -> *const c_void {
3120    let mod_id = mod_id.as_str();
3121    let sym = symbol.as_str();
3122    let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3123    registry
3124        .lock()
3125        .expect("interop symbols lock poisoned")
3126        .get(mod_id)
3127        .and_then(|m: &HashMap<String, usize>| m.get(sym))
3128        .map(|&ptr| ptr as *const c_void)
3129        .unwrap_or(std::ptr::null())
3130}
3131
3132fn build_api_table(ctx: *mut RuntimeHandlers, server: *const YogServer) -> YogApi {
3133    YogApi {
3134        abi_version: ABI_VERSION,
3135        size: std::mem::size_of::<YogApi>() as u32,
3136        ctx: ctx as *mut c_void,
3137        server,
3138        on_block_break: api_on_block_break,
3139        on_chat: api_on_chat,
3140        on_player_join: api_on_player_join,
3141        on_player_leave: api_on_player_leave,
3142        on_use_item: api_on_use_item,
3143        on_use_block: api_on_use_block,
3144        on_attack_entity: api_on_attack_entity,
3145        on_entity_damage: api_on_entity_damage,
3146        on_entity_death: api_on_entity_death,
3147        on_entity_spawn: api_on_entity_spawn,
3148        on_player_place_block: api_on_player_place_block,
3149        on_player_death: api_on_player_death,
3150        on_player_respawn: api_on_player_respawn,
3151        on_advancement: api_on_advancement,
3152        on_entity_interact: api_on_entity_interact,
3153        on_item_craft: api_on_item_craft,
3154        on_explosion: api_on_explosion,
3155        on_item_pickup: api_on_item_pickup,
3156        on_player_move: api_on_player_move,
3157        on_container_open: api_on_container_open,
3158        on_container_close: api_on_container_close,
3159        on_projectile_hit: api_on_projectile_hit,
3160        on_server_tick: api_on_server_tick,
3161        on_server_started: api_on_server_started,
3162        on_server_stopping: api_on_server_stopping,
3163        on_packet: api_on_packet,
3164        on_client_packet: api_on_client_packet,
3165        register_command: api_register_command,
3166        register_typed_command: api_register_typed_command,
3167        register_recipe_json: api_register_recipe_json,
3168        register_item: api_register_item,
3169        register_block: api_register_block,
3170        schedule_once: api_schedule_once,
3171        schedule_repeating: api_schedule_repeating,
3172        on_client_tick: api_on_client_tick,
3173        on_hud_render: api_on_hud_render,
3174        on_key_press: api_on_key_press,
3175        on_screen_open: api_on_screen_open,
3176        on_screen_close: api_on_screen_close,
3177        on_world_render: api_on_world_render,
3178        register_startup_grant: api_register_startup_grant,
3179        register_book: api_register_book,
3180        register_ui: api_register_ui,
3181        on_ui_render: api_on_ui_render,
3182        register_menu_entry: api_register_menu_entry,
3183        mods_list: api_mods_list,
3184        free_str: yog_free_str,
3185        ui_open: api_ui_open,
3186        register_inventory: api_register_inventory,
3187        interop_export: interop_export_impl,
3188        interop_import: interop_import_impl,
3189    }
3190}
3191
3192// ── Mod loading ───────────────────────────────────────────────────────────────
3193
3194fn platform_tag() -> String {
3195    format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
3196}
3197
3198type AbiVersionFn = unsafe extern "C" fn() -> u32;
3199/// Second arg is a null-terminated C string: the mod's `id` from its manifest.
3200type RegisterFn = unsafe extern "C" fn(*const YogApi, *const std::os::raw::c_char);
3201
3202fn load_mods(dir: &Path, api: &YogApi) {
3203    let entries = match std::fs::read_dir(dir) {
3204        Ok(e) => e,
3205        Err(_) => {
3206            yog_logging::info!("no mods directory at {} — none loaded", dir.display());
3207            return;
3208        }
3209    };
3210
3211    // First pass: collect (mod_id, dependencies, archive_path)
3212    struct ModMeta {
3213        id: String,
3214        deps: Vec<String>,
3215        yog_path: PathBuf,
3216    }
3217    let mut metas: Vec<ModMeta> = Vec::new();
3218    for entry in entries.flatten() {
3219        let path = entry.path();
3220        if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3221            continue;
3222        }
3223        let info = read_mod_info(&path);
3224        let deps = read_mod_deps(&path);
3225        metas.push(ModMeta {
3226            id: info[0].clone(),
3227            deps,
3228            yog_path: path,
3229        });
3230    }
3231
3232    // Topological sort by dependencies (Kahn's algorithm)
3233    let ids: Vec<String> = metas.iter().map(|m| m.id.clone()).collect();
3234    let deps_list: Vec<Vec<String>> = metas.iter().map(|m| m.deps.clone()).collect();
3235    let sorted = match topo_sort_mods(&ids, &deps_list) {
3236        Some(v) => v,
3237        None => {
3238            yog_logging::error!("circular dependency detected — loading in file order");
3239            (0..metas.len()).collect()
3240        }
3241    };
3242
3243    let mut count = 0u32;
3244    for idx in sorted {
3245        let meta = &metas[idx];
3246        let lib_path = match extract_yog(&meta.yog_path) {
3247            Some(p) => p,
3248            None => {
3249                yog_logging::error!(
3250                    "no native for {} in {}",
3251                    platform_tag(),
3252                    meta.yog_path.display()
3253                );
3254                continue;
3255            }
3256        };
3257        if load_mod_lib(&lib_path, api, &meta.id) {
3258            count += 1;
3259            MOD_INFOS
3260                .lock()
3261                .expect("mod infos lock poisoned")
3262                .push(read_mod_info(&meta.yog_path));
3263        }
3264    }
3265    yog_logging::info!("loaded {} mod(s) from {}", count, dir.display());
3266
3267    // Final import resolution pass — after all mods loaded, each mod's pending
3268    // imports can be resolved against the now-complete interop table.
3269    resolve_all_imports(api);
3270}
3271
3272/// After all mods loaded, call `__yog_resolve_imports_final` on each library
3273/// to resolve any imports that weren't satisfied during initial registration.
3274fn resolve_all_imports(api: &YogApi) {
3275    let libs = LOADED_MODS.lock().expect("mods lock poisoned");
3276    for lib in libs.iter() {
3277        unsafe {
3278            type ResolveFn = unsafe extern "C" fn(*const YogApi);
3279            if let Ok(resolve) = lib.get::<ResolveFn>(b"__yog_resolve_imports_final") {
3280                resolve(api as *const YogApi);
3281            }
3282        }
3283    }
3284}
3285
3286/// Kahn's algorithm — returns indices in dependency order, or None on cycle.
3287fn topo_sort_mods(ids: &[String], deps_list: &[Vec<String>]) -> Option<Vec<usize>> {
3288    let n = ids.len();
3289    let name_to_idx: HashMap<&str, usize> = ids
3290        .iter()
3291        .enumerate()
3292        .map(|(i, id)| (id.as_str(), i))
3293        .collect();
3294    let mut in_degree = vec![0u32; n];
3295    let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
3296    for (i, deps) in deps_list.iter().enumerate() {
3297        for dep in deps {
3298            if let Some(&j) = name_to_idx.get(dep.as_str()) {
3299                adj[j].push(i);
3300                in_degree[i] += 1;
3301            }
3302        }
3303    }
3304    let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
3305    let mut order = Vec::with_capacity(n);
3306    while let Some(u) = queue.pop() {
3307        order.push(u);
3308        for &v in &adj[u] {
3309            in_degree[v] -= 1;
3310            if in_degree[v] == 0 {
3311                queue.push(v);
3312            }
3313        }
3314    }
3315    if order.len() == n {
3316        Some(order)
3317    } else {
3318        None
3319    }
3320}
3321
3322fn load_mod_lib(path: &Path, api: &YogApi, mod_id: &str) -> bool {
3323    unsafe {
3324        let lib = match Library::new(path) {
3325            Ok(l) => l,
3326            Err(e) => {
3327                yog_logging::error!("failed to load {}: {}", path.display(), e);
3328                return false;
3329            }
3330        };
3331        let abi: Symbol<AbiVersionFn> = match lib.get(b"yog_abi_version") {
3332            Ok(s) => s,
3333            Err(_) => {
3334                yog_logging::error!("{} is not a Yog mod (no yog_abi_version)", path.display());
3335                return false;
3336            }
3337        };
3338        let mod_abi = abi();
3339        if mod_abi != ABI_VERSION {
3340            yog_logging::error!(
3341                "{}: ABI {} incompatible with runtime ABI {}",
3342                path.display(),
3343                mod_abi,
3344                ABI_VERSION
3345            );
3346            return false;
3347        }
3348        let register: Symbol<RegisterFn> = match lib.get(b"yog_mod_register") {
3349            Ok(s) => s,
3350            Err(_) => {
3351                yog_logging::error!("{} missing yog_mod_register", path.display());
3352                return false;
3353            }
3354        };
3355        let mod_id_c = std::ffi::CString::new(mod_id).unwrap();
3356        register(api as *const YogApi, mod_id_c.as_ptr());
3357        drop(register);
3358        drop(abi);
3359        LOADED_MODS.lock().expect("mods lock poisoned").push(lib);
3360    }
3361    true
3362}
3363
3364/// Metadata for a mod file: [id, name, version, authors, description].
3365/// For .yog archives this comes from the bundled yog.toml; bare native libs
3366/// fall back to the file stem.
3367fn read_mod_info(path: &Path) -> [String; 5] {
3368    let stem = path
3369        .file_stem()
3370        .map(|s| s.to_string_lossy().into_owned())
3371        .unwrap_or_default();
3372    let mut info = [
3373        stem.clone(),
3374        stem,
3375        String::new(),
3376        String::new(),
3377        String::new(),
3378    ];
3379    if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3380        return info;
3381    }
3382    let Some(text) = (|| {
3383        let file = std::fs::File::open(path).ok()?;
3384        let mut archive = zip::ZipArchive::new(file).ok()?;
3385        let mut entry = archive.by_name("yog.toml").ok()?;
3386        let mut text = String::new();
3387        std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3388        Some(text)
3389    })() else {
3390        return info;
3391    };
3392    // Minimal parse — enough for id/name/version/description/authors without
3393    // pulling in a TOML dependency. Source manifests keep these under [mod];
3394    // the packaged manifest (written by yog-cli) has them at top level, so
3395    // keys before any section header count too.
3396    let mut in_mod = true;
3397    for line in text.lines() {
3398        let line = line.trim();
3399        if line.starts_with('[') {
3400            in_mod = line == "[mod]";
3401            continue;
3402        }
3403        if !in_mod {
3404            continue;
3405        }
3406        let Some((key, value)) = line.split_once('=') else {
3407            continue;
3408        };
3409        let key = key.trim();
3410        let value = value.trim();
3411        let unquote = |v: &str| v.trim().trim_matches('"').to_string();
3412        match key {
3413            "id" => info[0] = tsv_clean(&unquote(value)),
3414            "name" => info[1] = tsv_clean(&unquote(value)),
3415            "version" => info[2] = tsv_clean(&unquote(value)),
3416            "description" => info[4] = tsv_clean(&unquote(value)),
3417            "authors" => {
3418                let list = value.trim_start_matches('[').trim_end_matches(']');
3419                let authors: Vec<String> = list
3420                    .split(',')
3421                    .map(|a| unquote(a))
3422                    .filter(|a| !a.is_empty())
3423                    .collect();
3424                info[3] = tsv_clean(&authors.join(", "));
3425            }
3426            _ => {}
3427        }
3428    }
3429    info
3430}
3431
3432/// Parse `[dependencies]` section from yog.toml inside a .yog archive.
3433fn read_mod_deps(path: &Path) -> Vec<String> {
3434    if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3435        return Vec::new();
3436    }
3437    let text = (|| {
3438        let file = std::fs::File::open(path).ok()?;
3439        let mut archive = zip::ZipArchive::new(file).ok()?;
3440        let mut entry = archive.by_name("yog.toml").ok()?;
3441        let mut text = String::new();
3442        std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3443        Some(text)
3444    })();
3445    let Some(text) = text else { return Vec::new() };
3446    let mut deps = Vec::new();
3447    let mut in_deps = false;
3448    for line in text.lines() {
3449        let line = line.trim();
3450        if line.starts_with('[') {
3451            in_deps = line == "[dependencies]";
3452            continue;
3453        }
3454        if !in_deps {
3455            continue;
3456        }
3457        // `mod_id = "1.0"` or `mod_id = "*"` — we only care about the key (mod_id)
3458        if let Some((key, _)) = line.split_once('=') {
3459            deps.push(key.trim().to_string());
3460        }
3461    }
3462    deps
3463}
3464
3465fn extract_yog(path: &Path) -> Option<PathBuf> {
3466    let file = std::fs::File::open(path).ok()?;
3467    let mut archive = zip::ZipArchive::new(file).ok()?;
3468    let prefix = format!("natives/{}/", platform_tag());
3469    let mut entry_name = None;
3470    for i in 0..archive.len() {
3471        let f = archive.by_index(i).ok()?;
3472        if f.name().starts_with(&prefix) && !f.name().ends_with('/') {
3473            entry_name = Some(f.name().to_string());
3474            break;
3475        }
3476    }
3477    let entry_name = entry_name?;
3478    let ext = Path::new(&entry_name)
3479        .extension()
3480        .and_then(|e| e.to_str())
3481        .unwrap_or("bin");
3482    let stem = path.file_stem()?.to_string_lossy().into_owned();
3483    let out = std::env::temp_dir().join(format!("yog-{}-{}.{}", stem, std::process::id(), ext));
3484    let mut entry = archive.by_name(&entry_name).ok()?;
3485    let mut out_file = std::fs::File::create(&out).ok()?;
3486    std::io::copy(&mut entry, &mut out_file).ok()?;
3487    Some(out)
3488}
3489
3490// ── Dispatcher helpers ────────────────────────────────────────────────────────
3491
3492fn srv_ptr() -> *const YogServer {
3493    SERVER.get().expect("yog: SERVER not initialised") as *const YogServer
3494}
3495
3496// ── JNI entry points ──────────────────────────────────────────────────────────
3497
3498#[no_mangle]
3499pub extern "system" fn Java_dev_yog_NativeBridge_nativeInit<'l>(
3500    mut env: JNIEnv<'l>,
3501    _class: JClass<'l>,
3502    mods_dir: JString<'l>,
3503) {
3504    if let Ok(vm) = env.get_java_vm() {
3505        let _ = JAVA_VM.set(vm);
3506    }
3507
3508    let dir = env
3509        .get_string(&mods_dir)
3510        .map(String::from)
3511        .unwrap_or_default();
3512
3513    // Build YogServer and store in static (gets a stable address).
3514    let _ = SERVER.set(build_server_table());
3515    let server_ptr = SERVER.get().unwrap() as *const YogServer;
3516
3517    // Build RuntimeHandlers on the heap temporarily so we have a stable pointer
3518    // to pass as ctx while mods register.
3519    let mut handlers = Box::new(RuntimeHandlers::new());
3520    let handlers_ptr = &mut *handlers as *mut RuntimeHandlers;
3521
3522    let api: &'static YogApi = API_TABLE.get_or_init(|| build_api_table(handlers_ptr, server_ptr));
3523
3524    guard("mod loading", || {
3525        load_mods(Path::new(&dir), api);
3526    });
3527
3528    // Move handlers out of Box and into the OnceLock.
3529    let _ = HANDLERS.set(*handlers);
3530
3531    yog_logging::info!("runtime initialised — the gate is open.");
3532}
3533
3534#[no_mangle]
3535pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
3536    mut env: JNIEnv<'l>,
3537    _class: JClass<'l>,
3538    player: JString<'l>,
3539    block: JString<'l>,
3540    x: jint,
3541    y: jint,
3542    z: jint,
3543    dimension: JString<'l>,
3544) {
3545    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3546    let ev = yog_abi::YogBlockBreakEvent {
3547        player: YogStr::from_str(&p),
3548        block: YogStr::from_str(&b),
3549        pos: YogBlockPos { x, y, z },
3550        dimension: YogStr::from_str(&d),
3551    };
3552    let srv = srv_ptr();
3553    guard("on_block_break", || {
3554        for (ud, f) in &handlers().block_break {
3555            unsafe { f(*ud, srv, &ev, 1) };
3556        }
3557    });
3558}
3559
3560#[no_mangle]
3561pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
3562    mut env: JNIEnv<'l>,
3563    _class: JClass<'l>,
3564    player: JString<'l>,
3565    message: JString<'l>,
3566    dimension: JString<'l>,
3567) {
3568    let (p, m, d) = (
3569        jstr!(env, player),
3570        jstr!(env, message),
3571        jstr!(env, dimension),
3572    );
3573    let ev = yog_abi::YogChatEvent {
3574        player: YogStr::from_str(&p),
3575        message: YogStr::from_str(&m),
3576        dimension: YogStr::from_str(&d),
3577    };
3578    let srv = srv_ptr();
3579    guard("on_chat", || {
3580        for (ud, f) in &handlers().chat {
3581            unsafe { f(*ud, srv, &ev, 1) };
3582        }
3583    });
3584}
3585
3586#[no_mangle]
3587pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
3588    mut env: JNIEnv<'l>,
3589    _class: JClass<'l>,
3590    player: JString<'l>,
3591    uuid: JString<'l>,
3592    dimension: JString<'l>,
3593) {
3594    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3595    let ev = yog_abi::YogPlayerEvent {
3596        player: YogStr::from_str(&p),
3597        uuid: YogStr::from_str(&u),
3598        dimension: YogStr::from_str(&d),
3599    };
3600    let srv = srv_ptr();
3601    guard("on_player_join", || {
3602        for (ud, f) in &handlers().player_join {
3603            unsafe { f(*ud, srv, &ev, 1) };
3604        }
3605    });
3606    // Queue startup grants: giving items directly inside the join callback
3607    // fails for the host of a freshly created world (player not fully in the
3608    // world yet), so grants are processed a few server ticks later.
3609    let h = handlers();
3610    if !h.startup_grants.is_empty() {
3611        h.pending_grants
3612            .lock()
3613            .expect("pending_grants poisoned")
3614            .push((p, u, 0));
3615    }
3616}
3617
3618/// Try to fulfil pending startup grants. Called each server tick; a grant is
3619/// only marked as given (and persisted) after `give_item` actually succeeds.
3620fn process_pending_grants() {
3621    let h = handlers();
3622    let mut pending = match h.pending_grants.try_lock() {
3623        Ok(g) => g,
3624        Err(_) => return,
3625    };
3626    if pending.is_empty() {
3627        return;
3628    }
3629    // Wait a couple of ticks after join before the first attempt.
3630    const FIRST_TRY_TICK: u32 = 2;
3631    // Give up after 30 seconds — the player probably left.
3632    const MAX_TICKS: u32 = 600;
3633
3634    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
3635    let mut still_pending = Vec::new();
3636    let mut any_new = false;
3637
3638    for (p, u, ticks) in pending.drain(..) {
3639        if ticks < FIRST_TRY_TICK {
3640            still_pending.push((p, u, ticks + 1));
3641            continue;
3642        }
3643        let mut all_ok = true;
3644        for sg in &h.startup_grants {
3645            let key = format!("{}::{}", u, sg.id);
3646            if granted.contains_key(&key) {
3647                continue;
3648            }
3649            let mut ok = true;
3650            for item_id in &sg.items {
3651                ok &= unsafe {
3652                    srv_give_item(
3653                        std::ptr::null_mut(),
3654                        YogStr::from_str(&p),
3655                        YogStr::from_str(item_id),
3656                        1,
3657                    )
3658                };
3659            }
3660            if let Some(_book) = &sg.book {
3661                ok &= unsafe {
3662                    srv_give_item(
3663                        std::ptr::null_mut(),
3664                        YogStr::from_str(&p),
3665                        YogStr::from_str("minecraft:written_book"),
3666                        1,
3667                    )
3668                };
3669            }
3670            if ok {
3671                yog_logging::info!("startup grant {} given to {}", sg.id, p);
3672                granted.insert(key, true);
3673                any_new = true;
3674            } else {
3675                all_ok = false;
3676            }
3677        }
3678        if !all_ok && ticks < MAX_TICKS {
3679            still_pending.push((p, u, ticks + 1));
3680        }
3681    }
3682    *pending = still_pending;
3683    if any_new {
3684        save_startup_granted(&granted);
3685    }
3686}
3687
3688#[no_mangle]
3689pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
3690    mut env: JNIEnv<'l>,
3691    _class: JClass<'l>,
3692    player: JString<'l>,
3693    uuid: JString<'l>,
3694    dimension: JString<'l>,
3695) {
3696    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3697    let ev = yog_abi::YogPlayerEvent {
3698        player: YogStr::from_str(&p),
3699        uuid: YogStr::from_str(&u),
3700        dimension: YogStr::from_str(&d),
3701    };
3702    let srv = srv_ptr();
3703    guard("on_player_leave", || {
3704        for (ud, f) in &handlers().player_leave {
3705            unsafe { f(*ud, srv, &ev, 1) };
3706        }
3707    });
3708}
3709
3710#[no_mangle]
3711pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
3712    mut env: JNIEnv<'l>,
3713    _class: JClass<'l>,
3714    player: JString<'l>,
3715    item: JString<'l>,
3716    sneaking: jni::sys::jboolean,
3717    dimension: JString<'l>,
3718) {
3719    let (p, i, d) = (jstr!(env, player), jstr!(env, item), jstr!(env, dimension));
3720    let ev = yog_abi::YogUseItemEvent {
3721        player: YogStr::from_str(&p),
3722        item: YogStr::from_str(&i),
3723        sneaking: sneaking != 0,
3724        dimension: YogStr::from_str(&d),
3725    };
3726    let srv = srv_ptr();
3727    guard("on_use_item", || {
3728        for (ud, f) in &handlers().use_item {
3729            unsafe { f(*ud, srv, &ev, 1) };
3730        }
3731    });
3732}
3733
3734#[no_mangle]
3735pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItemPre<'l>(
3736    mut env: JNIEnv<'l>,
3737    _class: JClass<'l>,
3738    player: JString<'l>,
3739    item: JString<'l>,
3740    sneaking: jni::sys::jboolean,
3741    dimension: JString<'l>,
3742) -> jni::sys::jboolean {
3743    let h = handlers();
3744    if h.use_item.is_empty() {
3745        return 1;
3746    }
3747    let p = match env.get_string(&player) {
3748        Ok(s) => String::from(s),
3749        Err(_) => return 1,
3750    };
3751    let i = match env.get_string(&item) {
3752        Ok(s) => String::from(s),
3753        Err(_) => return 1,
3754    };
3755    let d = match env.get_string(&dimension) {
3756        Ok(s) => String::from(s),
3757        Err(_) => return 1,
3758    };
3759    let ev = yog_abi::YogUseItemEvent {
3760        player: YogStr::from_str(&p),
3761        item: YogStr::from_str(&i),
3762        sneaking: sneaking != 0,
3763        dimension: YogStr::from_str(&d),
3764    };
3765    let srv = srv_ptr();
3766    let mut allow = true;
3767    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3768        for (ud, f) in &h.use_item {
3769            if !unsafe { f(*ud, srv, &ev, 0) } {
3770                allow = false;
3771                break;
3772            }
3773        }
3774    }))
3775    .ok();
3776    allow as jni::sys::jboolean
3777}
3778
3779#[no_mangle]
3780pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
3781    mut env: JNIEnv<'l>,
3782    _class: JClass<'l>,
3783    player: JString<'l>,
3784    block: JString<'l>,
3785    x: jint,
3786    y: jint,
3787    z: jint,
3788    dimension: JString<'l>,
3789) {
3790    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3791    let ev = yog_abi::YogUseBlockEvent {
3792        player: YogStr::from_str(&p),
3793        block: YogStr::from_str(&b),
3794        pos: YogBlockPos { x, y, z },
3795        dimension: YogStr::from_str(&d),
3796    };
3797    let srv = srv_ptr();
3798    guard("on_use_block", || {
3799        for (ud, f) in &handlers().use_block {
3800            unsafe { f(*ud, srv, &ev, 1) };
3801        }
3802    });
3803}
3804
3805#[no_mangle]
3806pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlockPre<'l>(
3807    mut env: JNIEnv<'l>,
3808    _class: JClass<'l>,
3809    player: JString<'l>,
3810    block: JString<'l>,
3811    x: jint,
3812    y: jint,
3813    z: jint,
3814    dimension: JString<'l>,
3815) -> jni::sys::jboolean {
3816    let h = handlers();
3817    if h.use_block.is_empty() {
3818        return 1;
3819    }
3820    let p = match env.get_string(&player) {
3821        Ok(s) => String::from(s),
3822        Err(_) => return 1,
3823    };
3824    let b = match env.get_string(&block) {
3825        Ok(s) => String::from(s),
3826        Err(_) => return 1,
3827    };
3828    let d = match env.get_string(&dimension) {
3829        Ok(s) => String::from(s),
3830        Err(_) => return 1,
3831    };
3832    let ev = yog_abi::YogUseBlockEvent {
3833        player: YogStr::from_str(&p),
3834        block: YogStr::from_str(&b),
3835        pos: YogBlockPos { x, y, z },
3836        dimension: YogStr::from_str(&d),
3837    };
3838    let srv = srv_ptr();
3839    let mut allow = true;
3840    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3841        for (ud, f) in &h.use_block {
3842            if !unsafe { f(*ud, srv, &ev, 0) } {
3843                allow = false;
3844                break;
3845            }
3846        }
3847    }))
3848    .ok();
3849    allow as jni::sys::jboolean
3850}
3851
3852#[no_mangle]
3853pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
3854    mut env: JNIEnv<'l>,
3855    _class: JClass<'l>,
3856    player: JString<'l>,
3857    target_type: JString<'l>,
3858    target_uuid: JString<'l>,
3859    dimension: JString<'l>,
3860) {
3861    let (p, tt, tu, d) = (
3862        jstr!(env, player),
3863        jstr!(env, target_type),
3864        jstr!(env, target_uuid),
3865        jstr!(env, dimension),
3866    );
3867    let ev = yog_abi::YogAttackEntityEvent {
3868        player: YogStr::from_str(&p),
3869        target_type: YogStr::from_str(&tt),
3870        target_uuid: YogStr::from_str(&tu),
3871        dimension: YogStr::from_str(&d),
3872    };
3873    let srv = srv_ptr();
3874    guard("on_attack_entity", || {
3875        for (ud, f) in &handlers().attack_entity {
3876            unsafe { f(*ud, srv, &ev, 1) };
3877        }
3878    });
3879}
3880
3881#[no_mangle]
3882pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
3883    mut env: JNIEnv<'l>,
3884    _class: JClass<'l>,
3885    entity_type: JString<'l>,
3886    uuid: JString<'l>,
3887    amount: jfloat,
3888    source: JString<'l>,
3889    dimension: JString<'l>,
3890) {
3891    let (et, u, s, d) = (
3892        jstr!(env, entity_type),
3893        jstr!(env, uuid),
3894        jstr!(env, source),
3895        jstr!(env, dimension),
3896    );
3897    let ev = yog_abi::YogEntityDamageEvent {
3898        entity_type: YogStr::from_str(&et),
3899        uuid: YogStr::from_str(&u),
3900        amount,
3901        source: YogStr::from_str(&s),
3902        dimension: YogStr::from_str(&d),
3903    };
3904    let srv = srv_ptr();
3905    guard("on_entity_damage", || {
3906        for (ud, f) in &handlers().entity_damage {
3907            unsafe { f(*ud, srv, &ev, 1) };
3908        }
3909    });
3910}
3911
3912#[no_mangle]
3913pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
3914    mut env: JNIEnv<'l>,
3915    _class: JClass<'l>,
3916    entity_type: JString<'l>,
3917    uuid: JString<'l>,
3918    source: JString<'l>,
3919    dimension: JString<'l>,
3920) {
3921    let (et, u, s, d) = (
3922        jstr!(env, entity_type),
3923        jstr!(env, uuid),
3924        jstr!(env, source),
3925        jstr!(env, dimension),
3926    );
3927    let ev = yog_abi::YogEntityDeathEvent {
3928        entity_type: YogStr::from_str(&et),
3929        uuid: YogStr::from_str(&u),
3930        source: YogStr::from_str(&s),
3931        dimension: YogStr::from_str(&d),
3932    };
3933    let srv = srv_ptr();
3934    guard("on_entity_death", || {
3935        for (ud, f) in &handlers().entity_death {
3936            unsafe { f(*ud, srv, &ev, 1) };
3937        }
3938    });
3939}
3940
3941#[no_mangle]
3942pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
3943    _env: JNIEnv<'l>,
3944    _class: JClass<'l>,
3945) {
3946    let h = handlers();
3947    let srv = srv_ptr();
3948    guard("on_tick", || {
3949        for (ud, f) in &h.server_tick {
3950            unsafe { f(*ud, srv) };
3951        }
3952    });
3953
3954    guard("startup_grants", process_pending_grants);
3955
3956    // Scheduler — once tasks
3957    {
3958        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
3959        let mut to_fire: Vec<(*mut c_void, YogScheduledFn)> = Vec::new();
3960        let mut remaining = Vec::new();
3961        for task in sched.once_tasks.drain(..) {
3962            if task.delay_remaining == 0 {
3963                to_fire.push((task.ud, task.f));
3964            } else {
3965                remaining.push(OnceTask {
3966                    delay_remaining: task.delay_remaining - 1,
3967                    ..task
3968                });
3969            }
3970        }
3971        sched.once_tasks = remaining;
3972        drop(sched);
3973        for (ud, f) in to_fire {
3974            guard("schedule_once", || unsafe { f(ud, srv) });
3975        }
3976    }
3977
3978    // Scheduler — repeating tasks
3979    {
3980        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
3981        let mut to_fire: Vec<(*mut c_void, YogScheduledFn)> = Vec::new();
3982        for task in &mut sched.repeating_tasks {
3983            if task.ticks_left == 0 {
3984                to_fire.push((task.ud, task.f));
3985                task.ticks_left = task.period;
3986            } else {
3987                task.ticks_left -= 1;
3988            }
3989        }
3990        drop(sched);
3991        for (ud, f) in to_fire {
3992            guard("schedule_repeating", || unsafe { f(ud, srv) });
3993        }
3994    }
3995}
3996
3997/// Absolute path of the current world save folder (set on server start).
3998static WORLD_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
3999
4000/// Per-world data path: `<world>/yog-data/startup_grants.json`.
4001/// Falls back to the working directory before any server has started.
4002fn startup_grants_path() -> std::path::PathBuf {
4003    let base = WORLD_DIR
4004        .lock()
4005        .ok()
4006        .and_then(|d| d.clone())
4007        .unwrap_or_else(|| std::path::PathBuf::from("."));
4008    base.join("yog-data").join("startup_grants.json")
4009}
4010
4011fn load_startup_granted() {
4012    let h = handlers();
4013    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
4014    // Reset first — in singleplayer the process outlives individual worlds,
4015    // and grant state is per-world.
4016    granted.clear();
4017    h.pending_grants
4018        .lock()
4019        .expect("pending_grants poisoned")
4020        .clear();
4021    let path = startup_grants_path();
4022    let Ok(data) = std::fs::read_to_string(&path) else {
4023        return;
4024    };
4025    let Ok(keys) = serde_json::from_str::<Vec<String>>(&data) else {
4026        yog_logging::warn!("startup_grants.json: invalid JSON, ignoring");
4027        return;
4028    };
4029    for k in keys {
4030        granted.insert(k, true);
4031    }
4032    yog_logging::info!("loaded {} startup grants from disk", granted.len());
4033}
4034
4035fn save_startup_granted(granted: &HashMap<String, bool>) {
4036    let path = startup_grants_path();
4037    if let Some(parent) = path.parent() {
4038        let _ = std::fs::create_dir_all(parent);
4039    }
4040    let keys: Vec<&String> = granted.keys().collect();
4041    match serde_json::to_string(&keys) {
4042        Ok(json) => {
4043            let _ = std::fs::write(&path, json);
4044        }
4045        Err(e) => yog_logging::error!("failed to save startup_grants.json: {}", e),
4046    }
4047}
4048
4049#[no_mangle]
4050pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
4051    mut env: JNIEnv<'l>,
4052    _class: JClass<'l>,
4053    world_dir: JString<'l>,
4054) {
4055    let dir = jstr!(env, world_dir);
4056    *WORLD_DIR.lock().expect("WORLD_DIR poisoned") = if dir.is_empty() {
4057        None
4058    } else {
4059        Some(std::path::PathBuf::from(dir))
4060    };
4061    load_startup_granted();
4062    let srv = srv_ptr();
4063    guard("on_server_started", || {
4064        for (ud, f) in &handlers().server_started {
4065            unsafe { f(*ud, srv) };
4066        }
4067    });
4068}
4069
4070#[no_mangle]
4071pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
4072    _env: JNIEnv<'l>,
4073    _class: JClass<'l>,
4074) {
4075    let srv = srv_ptr();
4076    guard("on_server_stopping", || {
4077        for (ud, f) in &handlers().server_stopping {
4078            unsafe { f(*ud, srv) };
4079        }
4080    });
4081}
4082
4083#[no_mangle]
4084pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
4085    env: JNIEnv<'l>,
4086    _class: JClass<'l>,
4087) -> jstring {
4088    let names = handlers()
4089        .commands
4090        .keys()
4091        .cloned()
4092        .collect::<Vec<_>>()
4093        .join("\n");
4094    env.new_string(names)
4095        .map(|s| s.into_raw())
4096        .unwrap_or(std::ptr::null_mut())
4097}
4098
4099#[no_mangle]
4100pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
4101    mut env: JNIEnv<'l>,
4102    _class: JClass<'l>,
4103    player: JString<'l>,
4104    block: JString<'l>,
4105    x: jint,
4106    y: jint,
4107    z: jint,
4108    dimension: JString<'l>,
4109) -> jni::sys::jboolean {
4110    let h = handlers();
4111    if h.block_break.is_empty() {
4112        return 1;
4113    }
4114    let p = match env.get_string(&player) {
4115        Ok(s) => String::from(s),
4116        Err(_) => return 1,
4117    };
4118    let b = match env.get_string(&block) {
4119        Ok(s) => String::from(s),
4120        Err(_) => return 1,
4121    };
4122    let d = match env.get_string(&dimension) {
4123        Ok(s) => String::from(s),
4124        Err(_) => return 1,
4125    };
4126    let ev = yog_abi::YogBlockBreakEvent {
4127        player: YogStr::from_str(&p),
4128        block: YogStr::from_str(&b),
4129        pos: YogBlockPos { x, y, z },
4130        dimension: YogStr::from_str(&d),
4131    };
4132    let srv = srv_ptr();
4133    let mut allow = true;
4134    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4135        for (ud, f) in &h.block_break {
4136            if !unsafe { f(*ud, srv, &ev, 0) } {
4137                allow = false;
4138                break;
4139            }
4140        }
4141    }))
4142    .ok();
4143    allow as jni::sys::jboolean
4144}
4145
4146#[no_mangle]
4147pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
4148    mut env: JNIEnv<'l>,
4149    _class: JClass<'l>,
4150    player: JString<'l>,
4151    message: JString<'l>,
4152    dimension: JString<'l>,
4153) -> jni::sys::jboolean {
4154    let h = handlers();
4155    if h.chat.is_empty() {
4156        return 1;
4157    }
4158    let p = match env.get_string(&player) {
4159        Ok(s) => String::from(s),
4160        Err(_) => return 1,
4161    };
4162    let m = match env.get_string(&message) {
4163        Ok(s) => String::from(s),
4164        Err(_) => return 1,
4165    };
4166    let d = match env.get_string(&dimension) {
4167        Ok(s) => String::from(s),
4168        Err(_) => return 1,
4169    };
4170    let ev = yog_abi::YogChatEvent {
4171        player: YogStr::from_str(&p),
4172        message: YogStr::from_str(&m),
4173        dimension: YogStr::from_str(&d),
4174    };
4175    let srv = srv_ptr();
4176    let mut allow = true;
4177    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4178        for (ud, f) in &h.chat {
4179            if !unsafe { f(*ud, srv, &ev, 0) } {
4180                allow = false;
4181                break;
4182            }
4183        }
4184    }))
4185    .ok();
4186    allow as jni::sys::jboolean
4187}
4188
4189#[no_mangle]
4190pub extern "system" fn Java_dev_yog_NativeBridge_nativeRecipeJsons<'l>(
4191    env: JNIEnv<'l>,
4192    _class: JClass<'l>,
4193) -> jstring {
4194    let s = handlers()
4195        .recipes
4196        .iter()
4197        .map(|(ns, name, json)| format!("{}\t{}\t{}", ns, name, json))
4198        .collect::<Vec<_>>()
4199        .join("\n");
4200    env.new_string(s)
4201        .map(|s| s.into_raw())
4202        .unwrap_or(std::ptr::null_mut())
4203}
4204
4205#[no_mangle]
4206pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
4207    env: JNIEnv<'l>,
4208    _class: JClass<'l>,
4209) -> jstring {
4210    // One line per DISTINCT (name, schema) pair — a name can have several
4211    // typed registrations (different arg shapes); Brigadier merges repeated
4212    // `dispatcher.register()` calls for the same literal name into one tree,
4213    // so the Java side just needs every distinct schema once each.
4214    let mut lines = Vec::new();
4215    for (name, regs) in handlers().commands.iter() {
4216        let mut seen = std::collections::HashSet::new();
4217        for (schema, _, _) in regs {
4218            if let Some(schema) = schema {
4219                if seen.insert(schema.as_str()) {
4220                    lines.push(format!("{}\t{}", name, schema));
4221                }
4222            }
4223        }
4224    }
4225    env.new_string(lines.join("\n"))
4226        .map(|s| s.into_raw())
4227        .unwrap_or(std::ptr::null_mut())
4228}
4229
4230#[no_mangle]
4231pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
4232    mut env: JNIEnv<'l>,
4233    _class: JClass<'l>,
4234    name: JString<'l>,
4235    args: JString<'l>,
4236    source: JString<'l>,
4237    uuid: JString<'l>,
4238    dimension: JString<'l>,
4239) -> jstring {
4240    let (n, a, s, u, d) = (
4241        env.get_string(&name).map(String::from).unwrap_or_default(),
4242        env.get_string(&args).map(String::from).unwrap_or_default(),
4243        env.get_string(&source)
4244            .map(String::from)
4245            .unwrap_or_default(),
4246        env.get_string(&uuid).map(String::from).unwrap_or_default(),
4247        env.get_string(&dimension)
4248            .map(String::from)
4249            .unwrap_or_default(),
4250    );
4251    let ev = yog_abi::YogCommandEvent {
4252        name: YogStr::from_str(&n),
4253        args: YogStr::from_str(&a),
4254        source: YogStr::from_str(&s),
4255        uuid: YogStr::from_str(&u),
4256        dimension: YogStr::from_str(&d),
4257    };
4258    let h = handlers();
4259    let srv = srv_ptr();
4260    // Actual arg count of this invocation (0 for a bare command with no args).
4261    let actual_argc = if a.is_empty() {
4262        0
4263    } else {
4264        a.split('\t').count()
4265    };
4266    let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4267        let Some(regs) = h.commands.get(&n) else {
4268            return String::new();
4269        };
4270        for (schema, ud, f) in regs {
4271            let expected_argc = schema.as_ref().map_or(0, |s| s.split_whitespace().count());
4272            if expected_argc != actual_argc {
4273                continue;
4274            }
4275            let mut buf = [0u8; 4096];
4276            let mut reply_len: u32 = 0;
4277            unsafe {
4278                f(
4279                    *ud,
4280                    srv,
4281                    &ev,
4282                    buf.as_mut_ptr(),
4283                    buf.len() as u32,
4284                    &mut reply_len,
4285                )
4286            };
4287            if reply_len > 0 {
4288                return String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned();
4289            }
4290        }
4291        String::new()
4292    }))
4293    .unwrap_or_else(|_| {
4294        yog_logging::error!("a mod panicked handling command `{}`", n);
4295        String::new()
4296    });
4297
4298    env.new_string(reply)
4299        .map(|s| s.into_raw())
4300        .unwrap_or(std::ptr::null_mut())
4301}
4302
4303#[no_mangle]
4304pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemDefs<'l>(
4305    env: JNIEnv<'l>,
4306    _class: JClass<'l>,
4307) -> jstring {
4308    let s = handlers()
4309        .items
4310        .iter()
4311        .map(|d| {
4312            let mut parts = vec![d.id.clone()];
4313            parts.push(format!("max_stack={}", d.max_stack));
4314            if let Some(n) = &d.name {
4315                parts.push(format!("name={n}"));
4316            }
4317            if let Some(t) = &d.tooltip {
4318                parts.push(format!("tooltip={t}"));
4319            }
4320            if d.max_damage > 0 {
4321                parts.push(format!("max_damage={}", d.max_damage));
4322            }
4323            if d.fire_resistant {
4324                parts.push("fire_resistant=1".into());
4325            }
4326            if d.fuel_ticks > 0 {
4327                parts.push(format!("fuel_ticks={}", d.fuel_ticks));
4328            }
4329            if let Some(f) = &d.food {
4330                parts.push(format!(
4331                    "food={}:{}:{}",
4332                    f.nutrition,
4333                    f.saturation,
4334                    if f.can_always_eat { 1 } else { 0 }
4335                ));
4336            }
4337            parts.join("\t")
4338        })
4339        .collect::<Vec<_>>()
4340        .join("\n");
4341    env.new_string(s)
4342        .map(|s| s.into_raw())
4343        .unwrap_or(std::ptr::null_mut())
4344}
4345
4346#[no_mangle]
4347pub extern "system" fn Java_dev_yog_NativeBridge_nativeBlockDefs<'l>(
4348    env: JNIEnv<'l>,
4349    _class: JClass<'l>,
4350) -> jstring {
4351    let s = handlers()
4352        .blocks
4353        .iter()
4354        .map(|d| {
4355            let mut parts = vec![d.id.clone()];
4356            parts.push(format!("hardness={}", d.hardness));
4357            parts.push(format!("resistance={}", d.resistance));
4358            if let Some(n) = &d.name {
4359                parts.push(format!("name={n}"));
4360            }
4361            if let Some(sh) = d.shape {
4362                parts.push(format!(
4363                    "shape={}:{}:{}:{}:{}:{}",
4364                    sh[0], sh[1], sh[2], sh[3], sh[4], sh[5]
4365                ));
4366            }
4367            if d.light_level > 0 {
4368                parts.push(format!("light={}", d.light_level));
4369            }
4370            if let Some(snd) = &d.sound {
4371                parts.push(format!("sound={snd}"));
4372            }
4373            if d.requires_tool {
4374                parts.push("requires_tool=1".into());
4375            }
4376            if d.no_collision {
4377                parts.push("no_collision=1".into());
4378            }
4379            if d.slipperiness > 0.0 {
4380                parts.push(format!("slipperiness={}", d.slipperiness));
4381            }
4382            if d.connects {
4383                parts.push("connects=1".into());
4384            }
4385            if !d.connect_groups.is_empty() {
4386                parts.push(format!("connect_groups={}", d.connect_groups.join(",")));
4387            }
4388            if let Some(inv) = &d.inventory_id {
4389                parts.push(format!("inventory_id={inv}"));
4390            }
4391            parts.join("\t")
4392        })
4393        .collect::<Vec<_>>()
4394        .join("\n");
4395    env.new_string(s)
4396        .map(|s| s.into_raw())
4397        .unwrap_or(std::ptr::null_mut())
4398}
4399
4400/// Tab/newline-encoded inventory defs — one line per registered
4401/// `InventoryDef` (see `yog_inventory`): `id\tslot_count=N\tlayout=x:y,...
4402/// \tinclude_player_inventory=0|1\tplayer_inv=x:y\tbackground_texture=...\ttitle=...`
4403#[no_mangle]
4404pub extern "system" fn Java_dev_yog_NativeBridge_nativeInventoryDefs<'l>(
4405    env: JNIEnv<'l>,
4406    _class: JClass<'l>,
4407) -> jstring {
4408    let s = handlers()
4409        .inventories
4410        .iter()
4411        .map(|d| {
4412            let mut parts = vec![d.id.clone()];
4413            parts.push(format!("slot_count={}", d.slot_count));
4414            parts.push(format!(
4415                "layout={}",
4416                yog_inventory::encode_layout(&d.resolved_layout())
4417            ));
4418            parts.push(format!(
4419                "include_player_inventory={}",
4420                d.include_player_inventory as u8
4421            ));
4422            parts.push(format!(
4423                "player_inv={}:{}",
4424                d.player_inv_offset.0, d.player_inv_offset.1
4425            ));
4426            if let Some(tex) = &d.background_texture {
4427                parts.push(format!("background_texture={tex}"));
4428            }
4429            if !d.title.is_empty() {
4430                parts.push(format!("title={}", d.title));
4431            }
4432            parts.join("\t")
4433        })
4434        .collect::<Vec<_>>()
4435        .join("\n");
4436    env.new_string(s)
4437        .map(|s| s.into_raw())
4438        .unwrap_or(std::ptr::null_mut())
4439}
4440
4441#[no_mangle]
4442pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
4443    mut env: JNIEnv<'l>,
4444    _class: JClass<'l>,
4445    channel: JString<'l>,
4446    player: JString<'l>,
4447    payload: JByteArray<'l>,
4448) {
4449    let ch = env
4450        .get_string(&channel)
4451        .map(String::from)
4452        .unwrap_or_default();
4453    let pl = env
4454        .get_string(&player)
4455        .map(String::from)
4456        .unwrap_or_default();
4457    let data = env.convert_byte_array(&payload).unwrap_or_default();
4458    let ev = yog_abi::YogPacketEvent {
4459        channel: YogStr::from_str(&ch),
4460        player: YogStr::from_str(&pl),
4461        payload: data.as_ptr(),
4462        payload_len: data.len() as u32,
4463    };
4464    let h = handlers();
4465    let srv = srv_ptr();
4466    guard("on_packet", || {
4467        if let Some((ud, f)) = h.packets.get(&ch) {
4468            unsafe { f(*ud, srv, &ev) };
4469        }
4470    });
4471}
4472
4473#[no_mangle]
4474pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
4475    mut env: JNIEnv<'l>,
4476    _class: JClass<'l>,
4477    channel: JString<'l>,
4478    payload: JByteArray<'l>,
4479) {
4480    let ch = env
4481        .get_string(&channel)
4482        .map(String::from)
4483        .unwrap_or_default();
4484    let data = env.convert_byte_array(&payload).unwrap_or_default();
4485    let ev = yog_abi::YogPacketEvent {
4486        channel: YogStr::from_str(&ch),
4487        player: YogStr::EMPTY,
4488        payload: data.as_ptr(),
4489        payload_len: data.len() as u32,
4490    };
4491    let h = handlers();
4492    let srv = srv_ptr();
4493    guard("on_client_packet", || {
4494        if let Some((ud, f)) = h.client_packets.get(&ch) {
4495            unsafe { f(*ud, srv, &ev) };
4496        }
4497    });
4498}
4499
4500#[no_mangle]
4501pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
4502    env: JNIEnv<'l>,
4503    _class: JClass<'l>,
4504) -> jstring {
4505    let s = handlers()
4506        .packets
4507        .keys()
4508        .cloned()
4509        .collect::<Vec<_>>()
4510        .join("\n");
4511    env.new_string(s)
4512        .map(|s| s.into_raw())
4513        .unwrap_or(std::ptr::null_mut())
4514}
4515
4516#[no_mangle]
4517pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
4518    env: JNIEnv<'l>,
4519    _class: JClass<'l>,
4520) -> jstring {
4521    let s = handlers()
4522        .client_packets
4523        .keys()
4524        .cloned()
4525        .collect::<Vec<_>>()
4526        .join("\n");
4527    env.new_string(s)
4528        .map(|s| s.into_raw())
4529        .unwrap_or(std::ptr::null_mut())
4530}
4531
4532#[no_mangle]
4533pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
4534    mut env: JNIEnv<'l>,
4535    _class: JClass<'l>,
4536    entity_type: JString<'l>,
4537    uuid: JString<'l>,
4538    dimension: JString<'l>,
4539) {
4540    let h = handlers();
4541    if h.entity_spawn.is_empty() {
4542        return;
4543    }
4544    let (et, u, d) = (
4545        jstr!(env, entity_type),
4546        jstr!(env, uuid),
4547        jstr!(env, dimension),
4548    );
4549    let ev = yog_abi::YogEntitySpawnEvent {
4550        entity_type: YogStr::from_str(&et),
4551        uuid: YogStr::from_str(&u),
4552        dimension: YogStr::from_str(&d),
4553    };
4554    let srv = srv_ptr();
4555    guard("on_entity_spawn", || {
4556        for (ud, f) in &h.entity_spawn {
4557            unsafe { f(*ud, srv, &ev, 1) };
4558        }
4559    });
4560}
4561
4562#[no_mangle]
4563pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
4564    mut env: JNIEnv<'l>,
4565    _class: JClass<'l>,
4566    entity_type: JString<'l>,
4567    uuid: JString<'l>,
4568    dimension: JString<'l>,
4569) -> jni::sys::jboolean {
4570    let h = handlers();
4571    if h.entity_spawn.is_empty() {
4572        return 1;
4573    }
4574    let et = match env.get_string(&entity_type) {
4575        Ok(s) => String::from(s),
4576        Err(_) => return 1,
4577    };
4578    let u = match env.get_string(&uuid) {
4579        Ok(s) => String::from(s),
4580        Err(_) => return 1,
4581    };
4582    let d = match env.get_string(&dimension) {
4583        Ok(s) => String::from(s),
4584        Err(_) => return 1,
4585    };
4586    let ev = yog_abi::YogEntitySpawnEvent {
4587        entity_type: YogStr::from_str(&et),
4588        uuid: YogStr::from_str(&u),
4589        dimension: YogStr::from_str(&d),
4590    };
4591    let srv = srv_ptr();
4592    let mut allow = true;
4593    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4594        for (ud, f) in &h.entity_spawn {
4595            if !unsafe { f(*ud, srv, &ev, 0) } {
4596                allow = false;
4597                break;
4598            }
4599        }
4600    }))
4601    .ok();
4602    allow as jni::sys::jboolean
4603}
4604
4605#[no_mangle]
4606pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
4607    mut env: JNIEnv<'l>,
4608    _class: JClass<'l>,
4609    entity_type: JString<'l>,
4610    uuid: JString<'l>,
4611    amount: jfloat,
4612    source: JString<'l>,
4613    dimension: JString<'l>,
4614) -> jni::sys::jboolean {
4615    let h = handlers();
4616    if h.entity_damage.is_empty() {
4617        return 1;
4618    }
4619    let et = match env.get_string(&entity_type) {
4620        Ok(s) => String::from(s),
4621        Err(_) => return 1,
4622    };
4623    let u = match env.get_string(&uuid) {
4624        Ok(s) => String::from(s),
4625        Err(_) => return 1,
4626    };
4627    let s = match env.get_string(&source) {
4628        Ok(s) => String::from(s),
4629        Err(_) => return 1,
4630    };
4631    let d = match env.get_string(&dimension) {
4632        Ok(s) => String::from(s),
4633        Err(_) => return 1,
4634    };
4635    let ev = yog_abi::YogEntityDamageEvent {
4636        entity_type: YogStr::from_str(&et),
4637        uuid: YogStr::from_str(&u),
4638        amount,
4639        source: YogStr::from_str(&s),
4640        dimension: YogStr::from_str(&d),
4641    };
4642    let srv = srv_ptr();
4643    let mut allow = true;
4644    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4645        for (ud, f) in &h.entity_damage {
4646            if !unsafe { f(*ud, srv, &ev, 0) } {
4647                allow = false;
4648                break;
4649            }
4650        }
4651    }))
4652    .ok();
4653    allow as jni::sys::jboolean
4654}
4655
4656#[no_mangle]
4657pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
4658    mut env: JNIEnv<'l>,
4659    _class: JClass<'l>,
4660    player: JString<'l>,
4661    block: JString<'l>,
4662    x: jint,
4663    y: jint,
4664    z: jint,
4665    dimension: JString<'l>,
4666) -> jni::sys::jboolean {
4667    let h = handlers();
4668    if h.player_place_block.is_empty() {
4669        return 1;
4670    }
4671    let p = match env.get_string(&player) {
4672        Ok(s) => String::from(s),
4673        Err(_) => return 1,
4674    };
4675    let b = match env.get_string(&block) {
4676        Ok(s) => String::from(s),
4677        Err(_) => return 1,
4678    };
4679    let d = match env.get_string(&dimension) {
4680        Ok(s) => String::from(s),
4681        Err(_) => return 1,
4682    };
4683    let ev = YogPlaceBlockEvent {
4684        player: YogStr::from_str(&p),
4685        block: YogStr::from_str(&b),
4686        pos: YogBlockPos { x, y, z },
4687        dimension: YogStr::from_str(&d),
4688    };
4689    let srv = srv_ptr();
4690    let mut allow = true;
4691    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4692        for (ud, f) in &h.player_place_block {
4693            if !unsafe { f(*ud, srv, &ev, 0) } {
4694                allow = false;
4695                break;
4696            }
4697        }
4698    }))
4699    .ok();
4700    allow as jni::sys::jboolean
4701}
4702
4703#[no_mangle]
4704pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
4705    mut env: JNIEnv<'l>,
4706    _class: JClass<'l>,
4707    player: JString<'l>,
4708    block: JString<'l>,
4709    x: jint,
4710    y: jint,
4711    z: jint,
4712    dimension: JString<'l>,
4713) {
4714    let h = handlers();
4715    if h.player_place_block.is_empty() {
4716        return;
4717    }
4718    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
4719    let ev = YogPlaceBlockEvent {
4720        player: YogStr::from_str(&p),
4721        block: YogStr::from_str(&b),
4722        pos: YogBlockPos { x, y, z },
4723        dimension: YogStr::from_str(&d),
4724    };
4725    let srv = srv_ptr();
4726    guard("on_player_place_block", || {
4727        for (ud, f) in &h.player_place_block {
4728            unsafe { f(*ud, srv, &ev, 1) };
4729        }
4730    });
4731}
4732
4733#[no_mangle]
4734pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
4735    mut env: JNIEnv<'l>,
4736    _class: JClass<'l>,
4737    player: JString<'l>,
4738    uuid: JString<'l>,
4739    source: JString<'l>,
4740    dimension: JString<'l>,
4741) -> jni::sys::jboolean {
4742    let h = handlers();
4743    if h.player_death.is_empty() {
4744        return 1;
4745    }
4746    let p = match env.get_string(&player) {
4747        Ok(s) => String::from(s),
4748        Err(_) => return 1,
4749    };
4750    let u = match env.get_string(&uuid) {
4751        Ok(s) => String::from(s),
4752        Err(_) => return 1,
4753    };
4754    let s = match env.get_string(&source) {
4755        Ok(s) => String::from(s),
4756        Err(_) => return 1,
4757    };
4758    let d = match env.get_string(&dimension) {
4759        Ok(s) => String::from(s),
4760        Err(_) => return 1,
4761    };
4762    let ev = YogPlayerDeathEvent {
4763        player: YogStr::from_str(&p),
4764        uuid: YogStr::from_str(&u),
4765        source: YogStr::from_str(&s),
4766        dimension: YogStr::from_str(&d),
4767    };
4768    let srv = srv_ptr();
4769    let mut allow = true;
4770    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4771        for (ud, f) in &h.player_death {
4772            if !unsafe { f(*ud, srv, &ev, 0) } {
4773                allow = false;
4774                break;
4775            }
4776        }
4777    }))
4778    .ok();
4779    allow as jni::sys::jboolean
4780}
4781
4782#[no_mangle]
4783pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
4784    mut env: JNIEnv<'l>,
4785    _class: JClass<'l>,
4786    player: JString<'l>,
4787    uuid: JString<'l>,
4788    source: JString<'l>,
4789    dimension: JString<'l>,
4790) {
4791    let h = handlers();
4792    if h.player_death.is_empty() {
4793        return;
4794    }
4795    let (p, u, s, d) = (
4796        jstr!(env, player),
4797        jstr!(env, uuid),
4798        jstr!(env, source),
4799        jstr!(env, dimension),
4800    );
4801    let ev = YogPlayerDeathEvent {
4802        player: YogStr::from_str(&p),
4803        uuid: YogStr::from_str(&u),
4804        source: YogStr::from_str(&s),
4805        dimension: YogStr::from_str(&d),
4806    };
4807    let srv = srv_ptr();
4808    guard("on_player_death", || {
4809        for (ud, f) in &h.player_death {
4810            unsafe { f(*ud, srv, &ev, 1) };
4811        }
4812    });
4813}
4814
4815#[no_mangle]
4816pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
4817    mut env: JNIEnv<'l>,
4818    _class: JClass<'l>,
4819    player: JString<'l>,
4820    uuid: JString<'l>,
4821    at_anchor: jni::sys::jboolean,
4822    dimension: JString<'l>,
4823) {
4824    let h = handlers();
4825    if h.player_respawn.is_empty() {
4826        return;
4827    }
4828    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
4829    let ev = YogPlayerRespawnEvent {
4830        player: YogStr::from_str(&p),
4831        uuid: YogStr::from_str(&u),
4832        at_anchor: at_anchor != 0,
4833        dimension: YogStr::from_str(&d),
4834    };
4835    let srv = srv_ptr();
4836    guard("on_player_respawn", || {
4837        for (ud, f) in &h.player_respawn {
4838            unsafe { f(*ud, srv, &ev, 1) };
4839        }
4840    });
4841}
4842
4843#[no_mangle]
4844pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
4845    mut env: JNIEnv<'l>,
4846    _class: JClass<'l>,
4847    player: JString<'l>,
4848    uuid: JString<'l>,
4849    advancement: JString<'l>,
4850    dimension: JString<'l>,
4851) {
4852    let h = handlers();
4853    if h.advancement.is_empty() {
4854        return;
4855    }
4856    let (p, u, a, d) = (
4857        jstr!(env, player),
4858        jstr!(env, uuid),
4859        jstr!(env, advancement),
4860        jstr!(env, dimension),
4861    );
4862    let ev = YogAdvancementEvent {
4863        player: YogStr::from_str(&p),
4864        uuid: YogStr::from_str(&u),
4865        advancement: YogStr::from_str(&a),
4866        dimension: YogStr::from_str(&d),
4867    };
4868    let srv = srv_ptr();
4869    guard("on_advancement", || {
4870        for (ud, f) in &h.advancement {
4871            unsafe { f(*ud, srv, &ev, 1) };
4872        }
4873    });
4874}
4875
4876#[no_mangle]
4877pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
4878    mut env: JNIEnv<'l>,
4879    _class: JClass<'l>,
4880    player: JString<'l>,
4881    player_uuid: JString<'l>,
4882    entity_type: JString<'l>,
4883    entity_uuid: JString<'l>,
4884    hand: JString<'l>,
4885    dimension: JString<'l>,
4886) -> jni::sys::jboolean {
4887    let h = handlers();
4888    if h.entity_interact.is_empty() {
4889        return 1;
4890    }
4891    let p = match env.get_string(&player) {
4892        Ok(s) => String::from(s),
4893        Err(_) => return 1,
4894    };
4895    let pu = match env.get_string(&player_uuid) {
4896        Ok(s) => String::from(s),
4897        Err(_) => return 1,
4898    };
4899    let et = match env.get_string(&entity_type) {
4900        Ok(s) => String::from(s),
4901        Err(_) => return 1,
4902    };
4903    let eu = match env.get_string(&entity_uuid) {
4904        Ok(s) => String::from(s),
4905        Err(_) => return 1,
4906    };
4907    let ha = match env.get_string(&hand) {
4908        Ok(s) => String::from(s),
4909        Err(_) => return 1,
4910    };
4911    let d = match env.get_string(&dimension) {
4912        Ok(s) => String::from(s),
4913        Err(_) => return 1,
4914    };
4915    let ev = YogEntityInteractEvent {
4916        player: YogStr::from_str(&p),
4917        player_uuid: YogStr::from_str(&pu),
4918        entity_type: YogStr::from_str(&et),
4919        entity_uuid: YogStr::from_str(&eu),
4920        hand: YogStr::from_str(&ha),
4921        dimension: YogStr::from_str(&d),
4922    };
4923    let srv = srv_ptr();
4924    let mut allow = true;
4925    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4926        for (ud, f) in &h.entity_interact {
4927            if !unsafe { f(*ud, srv, &ev, 0) } {
4928                allow = false;
4929                break;
4930            }
4931        }
4932    }))
4933    .ok();
4934    allow as jni::sys::jboolean
4935}
4936
4937#[no_mangle]
4938pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
4939    mut env: JNIEnv<'l>,
4940    _class: JClass<'l>,
4941    player: JString<'l>,
4942    player_uuid: JString<'l>,
4943    entity_type: JString<'l>,
4944    entity_uuid: JString<'l>,
4945    hand: JString<'l>,
4946    dimension: JString<'l>,
4947) {
4948    let h = handlers();
4949    if h.entity_interact.is_empty() {
4950        return;
4951    }
4952    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
4953    let (et, eu, ha, d) = (
4954        jstr!(env, entity_type),
4955        jstr!(env, entity_uuid),
4956        jstr!(env, hand),
4957        jstr!(env, dimension),
4958    );
4959    let ev = YogEntityInteractEvent {
4960        player: YogStr::from_str(&p),
4961        player_uuid: YogStr::from_str(&pu),
4962        entity_type: YogStr::from_str(&et),
4963        entity_uuid: YogStr::from_str(&eu),
4964        hand: YogStr::from_str(&ha),
4965        dimension: YogStr::from_str(&d),
4966    };
4967    let srv = srv_ptr();
4968    guard("on_entity_interact", || {
4969        for (ud, f) in &h.entity_interact {
4970            unsafe { f(*ud, srv, &ev, 1) };
4971        }
4972    });
4973}
4974
4975#[no_mangle]
4976pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
4977    mut env: JNIEnv<'l>,
4978    _class: JClass<'l>,
4979    player: JString<'l>,
4980    player_uuid: JString<'l>,
4981    result_item: JString<'l>,
4982    result_count: jint,
4983    dimension: JString<'l>,
4984) {
4985    let h = handlers();
4986    if h.item_craft.is_empty() {
4987        return;
4988    }
4989    let (p, pu, ri, d) = (
4990        jstr!(env, player),
4991        jstr!(env, player_uuid),
4992        jstr!(env, result_item),
4993        jstr!(env, dimension),
4994    );
4995    let ev = YogCraftEvent {
4996        player: YogStr::from_str(&p),
4997        player_uuid: YogStr::from_str(&pu),
4998        result_item: YogStr::from_str(&ri),
4999        result_count: result_count as u32,
5000        dimension: YogStr::from_str(&d),
5001    };
5002    let srv = srv_ptr();
5003    guard("on_item_craft", || {
5004        for (ud, f) in &h.item_craft {
5005            unsafe { f(*ud, srv, &ev, 1) };
5006        }
5007    });
5008}
5009
5010#[no_mangle]
5011pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
5012    mut env: JNIEnv<'l>,
5013    _class: JClass<'l>,
5014    dimension: JString<'l>,
5015    x: jdouble,
5016    y: jdouble,
5017    z: jdouble,
5018    power: jfloat,
5019    cause_uuid: JString<'l>,
5020) -> jni::sys::jboolean {
5021    let h = handlers();
5022    if h.explosion.is_empty() {
5023        return 1;
5024    }
5025    let d = match env.get_string(&dimension) {
5026        Ok(s) => String::from(s),
5027        Err(_) => return 1,
5028    };
5029    let cu = match env.get_string(&cause_uuid) {
5030        Ok(s) => String::from(s),
5031        Err(_) => return 1,
5032    };
5033    let ev = YogExplosionEvent {
5034        dimension: YogStr::from_str(&d),
5035        x,
5036        y,
5037        z,
5038        power,
5039        cause_uuid: YogStr::from_str(&cu),
5040    };
5041    let srv = srv_ptr();
5042    let mut allow = true;
5043    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5044        for (ud, f) in &h.explosion {
5045            if !unsafe { f(*ud, srv, &ev, 0) } {
5046                allow = false;
5047                break;
5048            }
5049        }
5050    }))
5051    .ok();
5052    allow as jni::sys::jboolean
5053}
5054
5055#[no_mangle]
5056pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
5057    mut env: JNIEnv<'l>,
5058    _class: JClass<'l>,
5059    dimension: JString<'l>,
5060    x: jdouble,
5061    y: jdouble,
5062    z: jdouble,
5063    power: jfloat,
5064    cause_uuid: JString<'l>,
5065) {
5066    let h = handlers();
5067    if h.explosion.is_empty() {
5068        return;
5069    }
5070    let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
5071    let ev = YogExplosionEvent {
5072        dimension: YogStr::from_str(&d),
5073        x,
5074        y,
5075        z,
5076        power,
5077        cause_uuid: YogStr::from_str(&cu),
5078    };
5079    let srv = srv_ptr();
5080    guard("on_explosion", || {
5081        for (ud, f) in &h.explosion {
5082            unsafe { f(*ud, srv, &ev, 1) };
5083        }
5084    });
5085}
5086
5087// ── ABI minor 9 JNI entry points ─────────────────────────────────────────────
5088
5089#[no_mangle]
5090pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
5091    mut env: JNIEnv<'l>,
5092    _class: JClass<'l>,
5093    player: JString<'l>,
5094    player_uuid: JString<'l>,
5095    item_id: JString<'l>,
5096    item_count: jint,
5097    entity_uuid: JString<'l>,
5098    dimension: JString<'l>,
5099) -> jni::sys::jboolean {
5100    let h = handlers();
5101    if h.item_pickup.is_empty() {
5102        return 1;
5103    }
5104    let p = match env.get_string(&player) {
5105        Ok(s) => String::from(s),
5106        Err(_) => return 1,
5107    };
5108    let pu = match env.get_string(&player_uuid) {
5109        Ok(s) => String::from(s),
5110        Err(_) => return 1,
5111    };
5112    let ii = match env.get_string(&item_id) {
5113        Ok(s) => String::from(s),
5114        Err(_) => return 1,
5115    };
5116    let eu = match env.get_string(&entity_uuid) {
5117        Ok(s) => String::from(s),
5118        Err(_) => return 1,
5119    };
5120    let d = match env.get_string(&dimension) {
5121        Ok(s) => String::from(s),
5122        Err(_) => return 1,
5123    };
5124    let ev = YogItemPickupEvent {
5125        player: YogStr::from_str(&p),
5126        player_uuid: YogStr::from_str(&pu),
5127        item_id: YogStr::from_str(&ii),
5128        item_count: item_count as u32,
5129        entity_uuid: YogStr::from_str(&eu),
5130        dimension: YogStr::from_str(&d),
5131    };
5132    let srv = srv_ptr();
5133    let mut allow = true;
5134    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5135        for (ud, f) in &h.item_pickup {
5136            if !unsafe { f(*ud, srv, &ev, 0) } {
5137                allow = false;
5138                break;
5139            }
5140        }
5141    }))
5142    .ok();
5143    allow as jni::sys::jboolean
5144}
5145
5146#[no_mangle]
5147pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
5148    mut env: JNIEnv<'l>,
5149    _class: JClass<'l>,
5150    player: JString<'l>,
5151    player_uuid: JString<'l>,
5152    item_id: JString<'l>,
5153    item_count: jint,
5154    entity_uuid: JString<'l>,
5155    dimension: JString<'l>,
5156) {
5157    let h = handlers();
5158    if h.item_pickup.is_empty() {
5159        return;
5160    }
5161    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5162    let (ii, eu, d) = (
5163        jstr!(env, item_id),
5164        jstr!(env, entity_uuid),
5165        jstr!(env, dimension),
5166    );
5167    let ev = YogItemPickupEvent {
5168        player: YogStr::from_str(&p),
5169        player_uuid: YogStr::from_str(&pu),
5170        item_id: YogStr::from_str(&ii),
5171        item_count: item_count as u32,
5172        entity_uuid: YogStr::from_str(&eu),
5173        dimension: YogStr::from_str(&d),
5174    };
5175    let srv = srv_ptr();
5176    guard("on_item_pickup", || {
5177        for (ud, f) in &h.item_pickup {
5178            unsafe { f(*ud, srv, &ev, 1) };
5179        }
5180    });
5181}
5182
5183#[no_mangle]
5184pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
5185    mut env: JNIEnv<'l>,
5186    _class: JClass<'l>,
5187    player: JString<'l>,
5188    player_uuid: JString<'l>,
5189    x: jdouble,
5190    y: jdouble,
5191    z: jdouble,
5192    yaw: jfloat,
5193    pitch: jfloat,
5194    dimension: JString<'l>,
5195) {
5196    let h = handlers();
5197    if h.player_move.is_empty() {
5198        return;
5199    }
5200    let (p, pu, d) = (
5201        jstr!(env, player),
5202        jstr!(env, player_uuid),
5203        jstr!(env, dimension),
5204    );
5205    let ev = YogPlayerMoveEvent {
5206        player: YogStr::from_str(&p),
5207        player_uuid: YogStr::from_str(&pu),
5208        x,
5209        y,
5210        z,
5211        yaw,
5212        pitch,
5213        dimension: YogStr::from_str(&d),
5214    };
5215    let srv = srv_ptr();
5216    guard("on_player_move", || {
5217        for (ud, f) in &h.player_move {
5218            unsafe { f(*ud, srv, &ev, 1) };
5219        }
5220    });
5221}
5222
5223#[no_mangle]
5224pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
5225    mut env: JNIEnv<'l>,
5226    _class: JClass<'l>,
5227    player: JString<'l>,
5228    player_uuid: JString<'l>,
5229    dimension: JString<'l>,
5230) -> jni::sys::jboolean {
5231    let h = handlers();
5232    if h.container_open.is_empty() {
5233        return 1;
5234    }
5235    let p = match env.get_string(&player) {
5236        Ok(s) => String::from(s),
5237        Err(_) => return 1,
5238    };
5239    let pu = match env.get_string(&player_uuid) {
5240        Ok(s) => String::from(s),
5241        Err(_) => return 1,
5242    };
5243    let d = match env.get_string(&dimension) {
5244        Ok(s) => String::from(s),
5245        Err(_) => return 1,
5246    };
5247    let ev = YogContainerOpenEvent {
5248        player: YogStr::from_str(&p),
5249        player_uuid: YogStr::from_str(&pu),
5250        container_type: YogStr::EMPTY,
5251        dimension: YogStr::from_str(&d),
5252    };
5253    let srv = srv_ptr();
5254    let mut allow = true;
5255    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5256        for (ud, f) in &h.container_open {
5257            if !unsafe { f(*ud, srv, &ev, 0) } {
5258                allow = false;
5259                break;
5260            }
5261        }
5262    }))
5263    .ok();
5264    allow as jni::sys::jboolean
5265}
5266
5267#[no_mangle]
5268pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
5269    mut env: JNIEnv<'l>,
5270    _class: JClass<'l>,
5271    player: JString<'l>,
5272    player_uuid: JString<'l>,
5273    container_type: JString<'l>,
5274    dimension: JString<'l>,
5275) {
5276    let h = handlers();
5277    if h.container_open.is_empty() {
5278        return;
5279    }
5280    let (p, pu, ct, d) = (
5281        jstr!(env, player),
5282        jstr!(env, player_uuid),
5283        jstr!(env, container_type),
5284        jstr!(env, dimension),
5285    );
5286    let ev = YogContainerOpenEvent {
5287        player: YogStr::from_str(&p),
5288        player_uuid: YogStr::from_str(&pu),
5289        container_type: YogStr::from_str(&ct),
5290        dimension: YogStr::from_str(&d),
5291    };
5292    let srv = srv_ptr();
5293    guard("on_container_open", || {
5294        for (ud, f) in &h.container_open {
5295            unsafe { f(*ud, srv, &ev, 1) };
5296        }
5297    });
5298}
5299
5300#[no_mangle]
5301pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
5302    mut env: JNIEnv<'l>,
5303    _class: JClass<'l>,
5304    player: JString<'l>,
5305    player_uuid: JString<'l>,
5306    dimension: JString<'l>,
5307) {
5308    let h = handlers();
5309    if h.container_close.is_empty() {
5310        return;
5311    }
5312    let (p, pu, d) = (
5313        jstr!(env, player),
5314        jstr!(env, player_uuid),
5315        jstr!(env, dimension),
5316    );
5317    let ev = YogContainerCloseEvent {
5318        player: YogStr::from_str(&p),
5319        player_uuid: YogStr::from_str(&pu),
5320        dimension: YogStr::from_str(&d),
5321    };
5322    let srv = srv_ptr();
5323    guard("on_container_close", || {
5324        for (ud, f) in &h.container_close {
5325            unsafe { f(*ud, srv, &ev, 1) };
5326        }
5327    });
5328}
5329
5330#[no_mangle]
5331pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
5332    mut env: JNIEnv<'l>,
5333    _class: JClass<'l>,
5334    projectile_type: JString<'l>,
5335    projectile_uuid: JString<'l>,
5336    shooter_uuid: JString<'l>,
5337    hit_type: JString<'l>,
5338    hit_entity_uuid: JString<'l>,
5339    x: jdouble,
5340    y: jdouble,
5341    z: jdouble,
5342    dimension: JString<'l>,
5343) -> jni::sys::jboolean {
5344    let h = handlers();
5345    if h.projectile_hit.is_empty() {
5346        return 1;
5347    }
5348    let pt = match env.get_string(&projectile_type) {
5349        Ok(s) => String::from(s),
5350        Err(_) => return 1,
5351    };
5352    let pu = match env.get_string(&projectile_uuid) {
5353        Ok(s) => String::from(s),
5354        Err(_) => return 1,
5355    };
5356    let su = match env.get_string(&shooter_uuid) {
5357        Ok(s) => String::from(s),
5358        Err(_) => return 1,
5359    };
5360    let ht = match env.get_string(&hit_type) {
5361        Ok(s) => String::from(s),
5362        Err(_) => return 1,
5363    };
5364    let heu = match env.get_string(&hit_entity_uuid) {
5365        Ok(s) => String::from(s),
5366        Err(_) => return 1,
5367    };
5368    let dim = match env.get_string(&dimension) {
5369        Ok(s) => String::from(s),
5370        Err(_) => return 1,
5371    };
5372    let ev = YogProjectileHitEvent {
5373        projectile_type: YogStr::from_str(&pt),
5374        projectile_uuid: YogStr::from_str(&pu),
5375        shooter_uuid: YogStr::from_str(&su),
5376        hit_type: YogStr::from_str(&ht),
5377        hit_entity_uuid: YogStr::from_str(&heu),
5378        x,
5379        y,
5380        z,
5381        dimension: YogStr::from_str(&dim),
5382    };
5383    let srv = srv_ptr();
5384    let mut allow = true;
5385    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5386        for (ud, f) in &h.projectile_hit {
5387            if !unsafe { f(*ud, srv, &ev, 0) } {
5388                allow = false;
5389                break;
5390            }
5391        }
5392    }))
5393    .ok();
5394    allow as jni::sys::jboolean
5395}
5396
5397#[no_mangle]
5398pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
5399    mut env: JNIEnv<'l>,
5400    _class: JClass<'l>,
5401    projectile_type: JString<'l>,
5402    projectile_uuid: JString<'l>,
5403    shooter_uuid: JString<'l>,
5404    hit_type: JString<'l>,
5405    hit_entity_uuid: JString<'l>,
5406    x: jdouble,
5407    y: jdouble,
5408    z: jdouble,
5409    dimension: JString<'l>,
5410) {
5411    let h = handlers();
5412    if h.projectile_hit.is_empty() {
5413        return;
5414    }
5415    let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
5416    let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
5417    let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
5418    let ev = YogProjectileHitEvent {
5419        projectile_type: YogStr::from_str(&pt),
5420        projectile_uuid: YogStr::from_str(&pu),
5421        shooter_uuid: YogStr::from_str(&su),
5422        hit_type: YogStr::from_str(&ht),
5423        hit_entity_uuid: YogStr::from_str(&heu),
5424        x,
5425        y,
5426        z,
5427        dimension: YogStr::from_str(&dim),
5428    };
5429    let srv = srv_ptr();
5430    guard("on_projectile_hit", || {
5431        for (ud, f) in &h.projectile_hit {
5432            unsafe { f(*ud, srv, &ev, 1) };
5433        }
5434    });
5435}
5436
5437// ── ABI minor 10 — client-side JNI entry points ───────────────────────────────
5438
5439#[no_mangle]
5440pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
5441    _env: JNIEnv<'l>,
5442    _class: JClass<'l>,
5443) {
5444    let h = handlers();
5445    if h.client_tick.is_empty() {
5446        return;
5447    }
5448    guard("on_client_tick", || {
5449        for (ud, f) in &h.client_tick {
5450            unsafe { f(*ud) };
5451        }
5452    });
5453}
5454
5455#[no_mangle]
5456pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
5457    _env: JNIEnv<'l>,
5458    _class: JClass<'l>,
5459) {
5460    if GL.get().is_some() {
5461        return;
5462    }
5463    let mut raw_get_binary: usize = 0;
5464    let mut raw_prog_binary: usize = 0;
5465    let gl = unsafe {
5466        glow::Context::from_loader_function(|sym| {
5467            let Some(mut env) = get_env() else {
5468                return std::ptr::null();
5469            };
5470            let jsym = match env.new_string(sym) {
5471                Ok(s) => s,
5472                Err(_) => return std::ptr::null(),
5473            };
5474            let jsym_obj: JObject = jsym.into();
5475            let val = env.call_static_method(
5476                "dev/yog/NativeBridge",
5477                "glProcAddress",
5478                "(Ljava/lang/String;)J",
5479                &[JValue::Object(&jsym_obj)],
5480            );
5481            let ptr = match val.and_then(|v| v.j()) {
5482                Ok(p) if p != 0 => p as usize as *const _,
5483                _ => std::ptr::null(),
5484            };
5485            // Capture extension pointers while the loader runs.
5486            match sym {
5487                "glGetProgramBinary" => raw_get_binary = ptr as usize,
5488                "glProgramBinary" => raw_prog_binary = ptr as usize,
5489                _ => {}
5490            }
5491            ptr
5492        })
5493    };
5494    let _ = GL.set(GlCtx(gl));
5495    let _ = GL_GET_PROGRAM_BINARY.set(if raw_get_binary != 0 {
5496        Some(raw_get_binary)
5497    } else {
5498        None
5499    });
5500    let _ = GL_PROGRAM_BINARY.set(if raw_prog_binary != 0 {
5501        Some(raw_prog_binary)
5502    } else {
5503        None
5504    });
5505    // `glGetProgramiv` is a core function; always available.  We look it up once here
5506    // to avoid depending on glow internals for the PROGRAM_BINARY_LENGTH query.
5507    if let Some(mut env) = get_env() {
5508        if let Ok(jsym) = env.new_string("glGetProgramiv") {
5509            let jsym_obj: JObject = jsym.into();
5510            if let Ok(jv) = env.call_static_method(
5511                "dev/yog/NativeBridge",
5512                "glProcAddress",
5513                "(Ljava/lang/String;)J",
5514                &[JValue::Object(&jsym_obj)],
5515            ) {
5516                if let Ok(ptr) = jv.j() {
5517                    let _ = GL_GET_PROGRAM_IV.set(if ptr != 0 { Some(ptr as usize) } else { None });
5518                }
5519            }
5520        }
5521    }
5522}
5523
5524#[no_mangle]
5525pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
5526    _env: JNIEnv<'l>,
5527    _class: JClass<'l>,
5528    delta_tick: jfloat,
5529    screen_w: jint,
5530    screen_h: jint,
5531    scale_factor: jfloat,
5532    player_x: jfloat,
5533    player_y: jfloat,
5534    player_z: jfloat,
5535) {
5536    let h = handlers();
5537    let mut gfx = GFX_FN_TABLE;
5538    gfx.screen_w = screen_w;
5539    gfx.screen_h = screen_h;
5540    gfx.delta_tick = delta_tick;
5541    gfx.scale_factor = scale_factor;
5542    gfx.player_pos = [player_x, player_y, player_z];
5543
5544    // Mod hud_render callbacks.
5545    if !h.hud_render.is_empty() {
5546        guard("on_hud_render", || {
5547            for (ud, f) in &h.hud_render {
5548                unsafe { f(*ud, &gfx) };
5549            }
5550        });
5551    }
5552
5553    // Render active book UIs.
5554    let active_layers: Vec<UiLayer> = h.active_uis.lock().unwrap().clone();
5555    let sw = screen_w as f32;
5556    let sh = screen_h as f32;
5557    let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
5558    let mut renderers = h.book_renderers.lock().unwrap();
5559    for layer in &active_layers {
5560        if !layer.visible {
5561            continue;
5562        }
5563        if let Some(renderer) = renderers.get_mut(&layer.id) {
5564            renderer.render(&ctx, sw, sh);
5565        }
5566    }
5567}
5568
5569#[no_mangle]
5570pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
5571    env: JNIEnv<'l>,
5572    _class: JClass<'l>,
5573    delta_tick: jfloat,
5574    screen_w: jint,
5575    screen_h: jint,
5576    scale_factor: jfloat,
5577    view_proj_arr: JFloatArray<'l>,
5578    cam_x: jfloat,
5579    cam_y: jfloat,
5580    cam_z: jfloat,
5581    player_x: jfloat,
5582    player_y: jfloat,
5583    player_z: jfloat,
5584) {
5585    let h = handlers();
5586    if h.world_render.is_empty() {
5587        return;
5588    }
5589    let mut view_proj = [0f32; 16];
5590    if env
5591        .get_float_array_region(&view_proj_arr, 0, &mut view_proj)
5592        .is_err()
5593    {
5594        return;
5595    }
5596    let mut gfx = GFX_FN_TABLE;
5597    gfx.screen_w = screen_w;
5598    gfx.screen_h = screen_h;
5599    gfx.delta_tick = delta_tick;
5600    gfx.scale_factor = scale_factor;
5601    gfx.view_proj = view_proj;
5602    gfx.camera_pos = [cam_x, cam_y, cam_z];
5603    gfx.player_pos = [player_x, player_y, player_z];
5604    guard("on_world_render", || {
5605        for (ud, f) in &h.world_render {
5606            unsafe { f(*ud, &gfx) };
5607        }
5608    });
5609}
5610
5611#[no_mangle]
5612pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
5613    _env: JNIEnv<'l>,
5614    _class: JClass<'l>,
5615    key_code: jint,
5616    scan_code: jint,
5617    action: jint,
5618    modifiers: jint,
5619) -> jni::sys::jboolean {
5620    let h = handlers();
5621    if h.key_press.is_empty() {
5622        return 1;
5623    }
5624    let ev = YogKeyPressEvent {
5625        key_code,
5626        scan_code,
5627        action,
5628        modifiers,
5629    };
5630    let mut allow = true;
5631    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5632        for (ud, f) in &h.key_press {
5633            if !unsafe { f(*ud, &ev) } {
5634                allow = false;
5635                break;
5636            }
5637        }
5638    }))
5639    .ok();
5640    allow as jni::sys::jboolean
5641}
5642
5643#[no_mangle]
5644pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
5645    mut env: JNIEnv<'l>,
5646    _class: JClass<'l>,
5647    screen_class: JString<'l>,
5648) {
5649    let h = handlers();
5650    if h.screen_open.is_empty() {
5651        return;
5652    }
5653    let sc = match env.get_string(&screen_class) {
5654        Ok(s) => String::from(s),
5655        Err(_) => return,
5656    };
5657    guard("on_screen_open", || {
5658        for (ud, f) in &h.screen_open {
5659            unsafe { f(*ud, YogStr::from_str(&sc)) };
5660        }
5661    });
5662}
5663
5664#[no_mangle]
5665pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
5666    mut env: JNIEnv<'l>,
5667    _class: JClass<'l>,
5668    screen_class: JString<'l>,
5669) {
5670    let h = handlers();
5671    if h.screen_close.is_empty() {
5672        return;
5673    }
5674    let sc = match env.get_string(&screen_class) {
5675        Ok(s) => String::from(s),
5676        Err(_) => return,
5677    };
5678    guard("on_screen_close", || {
5679        for (ud, f) in &h.screen_close {
5680            unsafe { f(*ud, YogStr::from_str(&sc)) };
5681        }
5682    });
5683}
5684
5685mod ui_jni;