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