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    control_socket::start_if_requested();
3707
3708    yog_logging::info!("runtime initialised — the gate is open.");
3709}
3710
3711#[no_mangle]
3712pub extern "system" fn Java_dev_yog_NativeBridge_nativeHotReload<'l>(
3713    mut env: JNIEnv<'l>,
3714    _class: JClass<'l>,
3715    mod_id: JString<'l>,
3716    new_yog_path: JString<'l>,
3717) -> jni::sys::jboolean {
3718    let Ok(mod_id) = env.get_string(&mod_id).map(String::from) else { return 0 };
3719    let Ok(yog_path) = env.get_string(&new_yog_path).map(String::from) else { return 0 };
3720    let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| trigger_hot_reload(&mod_id, Path::new(&yog_path)))).unwrap_or(false);
3721    ok as jni::sys::jboolean
3722}
3723
3724/// Rebuilds and re-registers `mod_id` from a freshly built `.yog` at
3725/// `new_yog_path`: extracts the current platform's native, loads it as a
3726/// new generation via the shared `HotReloader` (which retires the previous
3727/// generation per its hybrid soft/hard-unload policy — hard-unloaded right
3728/// away if nothing's mid-call, deferred otherwise), then calls its
3729/// `yog_mod_register` again so its handlers get tagged under the new
3730/// generation. How this actually gets triggered — a keybind, a file-watch,
3731/// an IDE-driven socket — is a separate, later concern; this only needs to
3732/// work correctly once called.
3733fn trigger_hot_reload(mod_id: &str, new_yog_path: &Path) -> bool {
3734    let Some(native_path) = extract_yog(new_yog_path) else {
3735        yog_logging::error!("hot-reload {}: no native for {} in {}", mod_id, platform_tag(), new_yog_path.display());
3736        return false;
3737    };
3738
3739    let api = API_TABLE.get().expect("yog: nativeInit not called yet");
3740
3741    let module = match unsafe { hot_reloader().reload(mod_id, &native_path) } {
3742        Ok(m) => m,
3743        Err(e) => {
3744            yog_logging::error!("hot-reload {}: failed to load {}: {}", mod_id, native_path.display(), e);
3745            return false;
3746        }
3747    };
3748
3749    let abi = module.with_library(|lib| unsafe {
3750        let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
3751        Some(abi())
3752    });
3753    match abi.flatten() {
3754        Some(v) if v == ABI_VERSION => {}
3755        other => {
3756            yog_logging::error!(
3757                "hot-reload {}: ABI {:?} incompatible with runtime ABI {} — mod has no active handlers until a compatible reload succeeds",
3758                mod_id,
3759                other,
3760                ABI_VERSION
3761            );
3762            return false;
3763        }
3764    }
3765
3766    if let Err(e) = invoke_mod_register(&module, api, mod_id) {
3767        yog_logging::error!("hot-reload {}: {}", mod_id, e);
3768        return false;
3769    }
3770
3771    yog_logging::info!("hot-reloaded mod {}", mod_id);
3772    true
3773}
3774
3775#[no_mangle]
3776pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
3777    mut env: JNIEnv<'l>,
3778    _class: JClass<'l>,
3779    player: JString<'l>,
3780    block: JString<'l>,
3781    x: jint,
3782    y: jint,
3783    z: jint,
3784    dimension: JString<'l>,
3785) {
3786    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3787    let ev = yog_abi::YogBlockBreakEvent {
3788        player: YogStr::from_str(&p),
3789        block: YogStr::from_str(&b),
3790        pos: YogBlockPos { x, y, z },
3791        dimension: YogStr::from_str(&d),
3792    };
3793    let srv = srv_ptr();
3794    guard("on_block_break", || {
3795        for (module, ud, f) in &handlers().block_break {
3796            if module.is_retiring() { continue; }
3797            let Some(_call) = module.enter() else { continue };
3798            unsafe { f(*ud, srv, &ev, 1) };
3799        }
3800    });
3801}
3802
3803#[no_mangle]
3804pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
3805    mut env: JNIEnv<'l>,
3806    _class: JClass<'l>,
3807    player: JString<'l>,
3808    message: JString<'l>,
3809    dimension: JString<'l>,
3810) {
3811    let (p, m, d) = (
3812        jstr!(env, player),
3813        jstr!(env, message),
3814        jstr!(env, dimension),
3815    );
3816    let ev = yog_abi::YogChatEvent {
3817        player: YogStr::from_str(&p),
3818        message: YogStr::from_str(&m),
3819        dimension: YogStr::from_str(&d),
3820    };
3821    let srv = srv_ptr();
3822    guard("on_chat", || {
3823        for (module, ud, f) in &handlers().chat {
3824            if module.is_retiring() { continue; }
3825            let Some(_call) = module.enter() else { continue };
3826            unsafe { f(*ud, srv, &ev, 1) };
3827        }
3828    });
3829}
3830
3831#[no_mangle]
3832pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
3833    mut env: JNIEnv<'l>,
3834    _class: JClass<'l>,
3835    player: JString<'l>,
3836    uuid: JString<'l>,
3837    dimension: JString<'l>,
3838) {
3839    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3840    let ev = yog_abi::YogPlayerEvent {
3841        player: YogStr::from_str(&p),
3842        uuid: YogStr::from_str(&u),
3843        dimension: YogStr::from_str(&d),
3844    };
3845    let srv = srv_ptr();
3846    guard("on_player_join", || {
3847        for (module, ud, f) in &handlers().player_join {
3848            if module.is_retiring() { continue; }
3849            let Some(_call) = module.enter() else { continue };
3850            unsafe { f(*ud, srv, &ev, 1) };
3851        }
3852    });
3853    // Queue startup grants: giving items directly inside the join callback
3854    // fails for the host of a freshly created world (player not fully in the
3855    // world yet), so grants are processed a few server ticks later.
3856    let h = handlers();
3857    if !h.startup_grants.is_empty() {
3858        h.pending_grants
3859            .lock()
3860            .expect("pending_grants poisoned")
3861            .push((p, u, 0));
3862    }
3863}
3864
3865/// Try to fulfil pending startup grants. Called each server tick; a grant is
3866/// only marked as given (and persisted) after `give_item` actually succeeds.
3867fn process_pending_grants() {
3868    let h = handlers();
3869    let mut pending = match h.pending_grants.try_lock() {
3870        Ok(g) => g,
3871        Err(_) => return,
3872    };
3873    if pending.is_empty() {
3874        return;
3875    }
3876    // Wait a couple of ticks after join before the first attempt.
3877    const FIRST_TRY_TICK: u32 = 2;
3878    // Give up after 30 seconds — the player probably left.
3879    const MAX_TICKS: u32 = 600;
3880
3881    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
3882    let mut still_pending = Vec::new();
3883    let mut any_new = false;
3884
3885    for (p, u, ticks) in pending.drain(..) {
3886        if ticks < FIRST_TRY_TICK {
3887            still_pending.push((p, u, ticks + 1));
3888            continue;
3889        }
3890        let mut all_ok = true;
3891        for sg in &h.startup_grants {
3892            let key = format!("{}::{}", u, sg.id);
3893            if granted.contains_key(&key) {
3894                continue;
3895            }
3896            let mut ok = true;
3897            for item_id in &sg.items {
3898                ok &= unsafe {
3899                    srv_give_item(
3900                        std::ptr::null_mut(),
3901                        YogStr::from_str(&p),
3902                        YogStr::from_str(item_id),
3903                        1,
3904                    )
3905                };
3906            }
3907            if let Some(_book) = &sg.book {
3908                ok &= unsafe {
3909                    srv_give_item(
3910                        std::ptr::null_mut(),
3911                        YogStr::from_str(&p),
3912                        YogStr::from_str("minecraft:written_book"),
3913                        1,
3914                    )
3915                };
3916            }
3917            if ok {
3918                yog_logging::info!("startup grant {} given to {}", sg.id, p);
3919                granted.insert(key, true);
3920                any_new = true;
3921            } else {
3922                all_ok = false;
3923            }
3924        }
3925        if !all_ok && ticks < MAX_TICKS {
3926            still_pending.push((p, u, ticks + 1));
3927        }
3928    }
3929    *pending = still_pending;
3930    if any_new {
3931        save_startup_granted(&granted);
3932    }
3933}
3934
3935#[no_mangle]
3936pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
3937    mut env: JNIEnv<'l>,
3938    _class: JClass<'l>,
3939    player: JString<'l>,
3940    uuid: JString<'l>,
3941    dimension: JString<'l>,
3942) {
3943    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
3944    let ev = yog_abi::YogPlayerEvent {
3945        player: YogStr::from_str(&p),
3946        uuid: YogStr::from_str(&u),
3947        dimension: YogStr::from_str(&d),
3948    };
3949    let srv = srv_ptr();
3950    guard("on_player_leave", || {
3951        for (module, ud, f) in &handlers().player_leave {
3952            if module.is_retiring() { continue; }
3953            let Some(_call) = module.enter() else { continue };
3954            unsafe { f(*ud, srv, &ev, 1) };
3955        }
3956    });
3957}
3958
3959#[no_mangle]
3960pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
3961    mut env: JNIEnv<'l>,
3962    _class: JClass<'l>,
3963    player: JString<'l>,
3964    item: JString<'l>,
3965    sneaking: jni::sys::jboolean,
3966    dimension: JString<'l>,
3967) {
3968    let (p, i, d) = (jstr!(env, player), jstr!(env, item), jstr!(env, dimension));
3969    let ev = yog_abi::YogUseItemEvent {
3970        player: YogStr::from_str(&p),
3971        item: YogStr::from_str(&i),
3972        sneaking: sneaking != 0,
3973        dimension: YogStr::from_str(&d),
3974    };
3975    let srv = srv_ptr();
3976    guard("on_use_item", || {
3977        for (module, ud, f) in &handlers().use_item {
3978            if module.is_retiring() { continue; }
3979            let Some(_call) = module.enter() else { continue };
3980            unsafe { f(*ud, srv, &ev, 1) };
3981        }
3982    });
3983}
3984
3985#[no_mangle]
3986pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItemPre<'l>(
3987    mut env: JNIEnv<'l>,
3988    _class: JClass<'l>,
3989    player: JString<'l>,
3990    item: JString<'l>,
3991    sneaking: jni::sys::jboolean,
3992    dimension: JString<'l>,
3993) -> jni::sys::jboolean {
3994    let h = handlers();
3995    if h.use_item.is_empty() {
3996        return 1;
3997    }
3998    let p = match env.get_string(&player) {
3999        Ok(s) => String::from(s),
4000        Err(_) => return 1,
4001    };
4002    let i = match env.get_string(&item) {
4003        Ok(s) => String::from(s),
4004        Err(_) => return 1,
4005    };
4006    let d = match env.get_string(&dimension) {
4007        Ok(s) => String::from(s),
4008        Err(_) => return 1,
4009    };
4010    let ev = yog_abi::YogUseItemEvent {
4011        player: YogStr::from_str(&p),
4012        item: YogStr::from_str(&i),
4013        sneaking: sneaking != 0,
4014        dimension: YogStr::from_str(&d),
4015    };
4016    let srv = srv_ptr();
4017    let mut allow = true;
4018    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4019        for (module, ud, f) in &h.use_item {
4020            if module.is_retiring() { continue; }
4021            let Some(_call) = module.enter() else { continue };
4022            if !unsafe { f(*ud, srv, &ev, 0) } {
4023                allow = false;
4024                break;
4025            }
4026        }
4027    }))
4028    .ok();
4029    allow as jni::sys::jboolean
4030}
4031
4032#[no_mangle]
4033pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
4034    mut env: JNIEnv<'l>,
4035    _class: JClass<'l>,
4036    player: JString<'l>,
4037    block: JString<'l>,
4038    x: jint,
4039    y: jint,
4040    z: jint,
4041    dimension: JString<'l>,
4042) {
4043    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
4044    let ev = yog_abi::YogUseBlockEvent {
4045        player: YogStr::from_str(&p),
4046        block: YogStr::from_str(&b),
4047        pos: YogBlockPos { x, y, z },
4048        dimension: YogStr::from_str(&d),
4049    };
4050    let srv = srv_ptr();
4051    guard("on_use_block", || {
4052        for (module, ud, f) in &handlers().use_block {
4053            if module.is_retiring() { continue; }
4054            let Some(_call) = module.enter() else { continue };
4055            unsafe { f(*ud, srv, &ev, 1) };
4056        }
4057    });
4058}
4059
4060#[no_mangle]
4061pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlockPre<'l>(
4062    mut env: JNIEnv<'l>,
4063    _class: JClass<'l>,
4064    player: JString<'l>,
4065    block: JString<'l>,
4066    x: jint,
4067    y: jint,
4068    z: jint,
4069    dimension: JString<'l>,
4070) -> jni::sys::jboolean {
4071    let h = handlers();
4072    if h.use_block.is_empty() {
4073        return 1;
4074    }
4075    let p = match env.get_string(&player) {
4076        Ok(s) => String::from(s),
4077        Err(_) => return 1,
4078    };
4079    let b = match env.get_string(&block) {
4080        Ok(s) => String::from(s),
4081        Err(_) => return 1,
4082    };
4083    let d = match env.get_string(&dimension) {
4084        Ok(s) => String::from(s),
4085        Err(_) => return 1,
4086    };
4087    let ev = yog_abi::YogUseBlockEvent {
4088        player: YogStr::from_str(&p),
4089        block: YogStr::from_str(&b),
4090        pos: YogBlockPos { x, y, z },
4091        dimension: YogStr::from_str(&d),
4092    };
4093    let srv = srv_ptr();
4094    let mut allow = true;
4095    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4096        for (module, ud, f) in &h.use_block {
4097            if module.is_retiring() { continue; }
4098            let Some(_call) = module.enter() else { continue };
4099            if !unsafe { f(*ud, srv, &ev, 0) } {
4100                allow = false;
4101                break;
4102            }
4103        }
4104    }))
4105    .ok();
4106    allow as jni::sys::jboolean
4107}
4108
4109#[no_mangle]
4110pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
4111    mut env: JNIEnv<'l>,
4112    _class: JClass<'l>,
4113    player: JString<'l>,
4114    target_type: JString<'l>,
4115    target_uuid: JString<'l>,
4116    dimension: JString<'l>,
4117) {
4118    let (p, tt, tu, d) = (
4119        jstr!(env, player),
4120        jstr!(env, target_type),
4121        jstr!(env, target_uuid),
4122        jstr!(env, dimension),
4123    );
4124    let ev = yog_abi::YogAttackEntityEvent {
4125        player: YogStr::from_str(&p),
4126        target_type: YogStr::from_str(&tt),
4127        target_uuid: YogStr::from_str(&tu),
4128        dimension: YogStr::from_str(&d),
4129    };
4130    let srv = srv_ptr();
4131    guard("on_attack_entity", || {
4132        for (module, ud, f) in &handlers().attack_entity {
4133            if module.is_retiring() { continue; }
4134            let Some(_call) = module.enter() else { continue };
4135            unsafe { f(*ud, srv, &ev, 1) };
4136        }
4137    });
4138}
4139
4140#[no_mangle]
4141pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
4142    mut env: JNIEnv<'l>,
4143    _class: JClass<'l>,
4144    entity_type: JString<'l>,
4145    uuid: JString<'l>,
4146    amount: jfloat,
4147    source: JString<'l>,
4148    dimension: JString<'l>,
4149) {
4150    let (et, u, s, d) = (
4151        jstr!(env, entity_type),
4152        jstr!(env, uuid),
4153        jstr!(env, source),
4154        jstr!(env, dimension),
4155    );
4156    let ev = yog_abi::YogEntityDamageEvent {
4157        entity_type: YogStr::from_str(&et),
4158        uuid: YogStr::from_str(&u),
4159        amount,
4160        source: YogStr::from_str(&s),
4161        dimension: YogStr::from_str(&d),
4162    };
4163    let srv = srv_ptr();
4164    guard("on_entity_damage", || {
4165        for (module, ud, f) in &handlers().entity_damage {
4166            if module.is_retiring() { continue; }
4167            let Some(_call) = module.enter() else { continue };
4168            unsafe { f(*ud, srv, &ev, 1) };
4169        }
4170    });
4171}
4172
4173#[no_mangle]
4174pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
4175    mut env: JNIEnv<'l>,
4176    _class: JClass<'l>,
4177    entity_type: JString<'l>,
4178    uuid: JString<'l>,
4179    source: JString<'l>,
4180    dimension: JString<'l>,
4181) {
4182    let (et, u, s, d) = (
4183        jstr!(env, entity_type),
4184        jstr!(env, uuid),
4185        jstr!(env, source),
4186        jstr!(env, dimension),
4187    );
4188    let ev = yog_abi::YogEntityDeathEvent {
4189        entity_type: YogStr::from_str(&et),
4190        uuid: YogStr::from_str(&u),
4191        source: YogStr::from_str(&s),
4192        dimension: YogStr::from_str(&d),
4193    };
4194    let srv = srv_ptr();
4195    guard("on_entity_death", || {
4196        for (module, ud, f) in &handlers().entity_death {
4197            if module.is_retiring() { continue; }
4198            let Some(_call) = module.enter() else { continue };
4199            unsafe { f(*ud, srv, &ev, 1) };
4200        }
4201    });
4202}
4203
4204#[no_mangle]
4205pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
4206    _env: JNIEnv<'l>,
4207    _class: JClass<'l>,
4208) {
4209    let h = handlers();
4210    let srv = srv_ptr();
4211    guard("on_tick", || {
4212        for (module, ud, f) in &h.server_tick {
4213            if module.is_retiring() { continue; }
4214            let Some(_call) = module.enter() else { continue };
4215            unsafe { f(*ud, srv) };
4216        }
4217    });
4218
4219    guard("startup_grants", process_pending_grants);
4220
4221    // Scheduler — once tasks
4222    {
4223        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
4224        let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
4225        let mut remaining = Vec::new();
4226        for task in sched.once_tasks.drain(..) {
4227            if task.delay_remaining == 0 {
4228                to_fire.push((task.module.clone(), task.ud, task.f));
4229            } else {
4230                remaining.push(OnceTask {
4231                    delay_remaining: task.delay_remaining - 1,
4232                    ..task
4233                });
4234            }
4235        }
4236        sched.once_tasks = remaining;
4237        drop(sched);
4238        for (module, ud, f) in to_fire {
4239            if module.is_retiring() {
4240                continue;
4241            }
4242            let Some(_call) = module.enter() else { continue };
4243            guard("schedule_once", || unsafe { f(ud, srv) });
4244        }
4245    }
4246
4247    // Scheduler — repeating tasks
4248    {
4249        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
4250        let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
4251        for task in &mut sched.repeating_tasks {
4252            if task.ticks_left == 0 {
4253                to_fire.push((task.module.clone(), task.ud, task.f));
4254                task.ticks_left = task.period;
4255            } else {
4256                task.ticks_left -= 1;
4257            }
4258        }
4259        drop(sched);
4260        for (module, ud, f) in to_fire {
4261            if module.is_retiring() {
4262                continue;
4263            }
4264            let Some(_call) = module.enter() else { continue };
4265            guard("schedule_repeating", || unsafe { f(ud, srv) });
4266        }
4267    }
4268}
4269
4270/// Absolute path of the current world save folder (set on server start).
4271static WORLD_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
4272
4273/// Per-world data path: `<world>/yog-data/startup_grants.json`.
4274/// Falls back to the working directory before any server has started.
4275fn startup_grants_path() -> std::path::PathBuf {
4276    let base = WORLD_DIR
4277        .lock()
4278        .ok()
4279        .and_then(|d| d.clone())
4280        .unwrap_or_else(|| std::path::PathBuf::from("."));
4281    base.join("yog-data").join("startup_grants.json")
4282}
4283
4284fn load_startup_granted() {
4285    let h = handlers();
4286    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
4287    // Reset first — in singleplayer the process outlives individual worlds,
4288    // and grant state is per-world.
4289    granted.clear();
4290    h.pending_grants
4291        .lock()
4292        .expect("pending_grants poisoned")
4293        .clear();
4294    let path = startup_grants_path();
4295    let Ok(data) = std::fs::read_to_string(&path) else {
4296        return;
4297    };
4298    let Ok(keys) = serde_json::from_str::<Vec<String>>(&data) else {
4299        yog_logging::warn!("startup_grants.json: invalid JSON, ignoring");
4300        return;
4301    };
4302    for k in keys {
4303        granted.insert(k, true);
4304    }
4305    yog_logging::info!("loaded {} startup grants from disk", granted.len());
4306}
4307
4308fn save_startup_granted(granted: &HashMap<String, bool>) {
4309    let path = startup_grants_path();
4310    if let Some(parent) = path.parent() {
4311        let _ = std::fs::create_dir_all(parent);
4312    }
4313    let keys: Vec<&String> = granted.keys().collect();
4314    match serde_json::to_string(&keys) {
4315        Ok(json) => {
4316            let _ = std::fs::write(&path, json);
4317        }
4318        Err(e) => yog_logging::error!("failed to save startup_grants.json: {}", e),
4319    }
4320}
4321
4322#[no_mangle]
4323pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
4324    mut env: JNIEnv<'l>,
4325    _class: JClass<'l>,
4326    world_dir: JString<'l>,
4327) {
4328    let dir = jstr!(env, world_dir);
4329    *WORLD_DIR.lock().expect("WORLD_DIR poisoned") = if dir.is_empty() {
4330        None
4331    } else {
4332        Some(std::path::PathBuf::from(dir))
4333    };
4334    load_startup_granted();
4335    let srv = srv_ptr();
4336    guard("on_server_started", || {
4337        for (module, ud, f) in &handlers().server_started {
4338            if module.is_retiring() { continue; }
4339            let Some(_call) = module.enter() else { continue };
4340            unsafe { f(*ud, srv) };
4341        }
4342    });
4343}
4344
4345#[no_mangle]
4346pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
4347    _env: JNIEnv<'l>,
4348    _class: JClass<'l>,
4349) {
4350    let srv = srv_ptr();
4351    guard("on_server_stopping", || {
4352        for (module, ud, f) in &handlers().server_stopping {
4353            if module.is_retiring() { continue; }
4354            let Some(_call) = module.enter() else { continue };
4355            unsafe { f(*ud, srv) };
4356        }
4357    });
4358}
4359
4360#[no_mangle]
4361pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
4362    env: JNIEnv<'l>,
4363    _class: JClass<'l>,
4364) -> jstring {
4365    let names = handlers()
4366        .commands
4367        .keys()
4368        .cloned()
4369        .collect::<Vec<_>>()
4370        .join("\n");
4371    env.new_string(names)
4372        .map(|s| s.into_raw())
4373        .unwrap_or(std::ptr::null_mut())
4374}
4375
4376#[no_mangle]
4377pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
4378    mut env: JNIEnv<'l>,
4379    _class: JClass<'l>,
4380    player: JString<'l>,
4381    block: JString<'l>,
4382    x: jint,
4383    y: jint,
4384    z: jint,
4385    dimension: JString<'l>,
4386) -> jni::sys::jboolean {
4387    let h = handlers();
4388    if h.block_break.is_empty() {
4389        return 1;
4390    }
4391    let p = match env.get_string(&player) {
4392        Ok(s) => String::from(s),
4393        Err(_) => return 1,
4394    };
4395    let b = match env.get_string(&block) {
4396        Ok(s) => String::from(s),
4397        Err(_) => return 1,
4398    };
4399    let d = match env.get_string(&dimension) {
4400        Ok(s) => String::from(s),
4401        Err(_) => return 1,
4402    };
4403    let ev = yog_abi::YogBlockBreakEvent {
4404        player: YogStr::from_str(&p),
4405        block: YogStr::from_str(&b),
4406        pos: YogBlockPos { x, y, z },
4407        dimension: YogStr::from_str(&d),
4408    };
4409    let srv = srv_ptr();
4410    let mut allow = true;
4411    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4412        for (module, ud, f) in &h.block_break {
4413            if module.is_retiring() { continue; }
4414            let Some(_call) = module.enter() else { continue };
4415            if !unsafe { f(*ud, srv, &ev, 0) } {
4416                allow = false;
4417                break;
4418            }
4419        }
4420    }))
4421    .ok();
4422    allow as jni::sys::jboolean
4423}
4424
4425#[no_mangle]
4426pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
4427    mut env: JNIEnv<'l>,
4428    _class: JClass<'l>,
4429    player: JString<'l>,
4430    message: JString<'l>,
4431    dimension: JString<'l>,
4432) -> jni::sys::jboolean {
4433    let h = handlers();
4434    if h.chat.is_empty() {
4435        return 1;
4436    }
4437    let p = match env.get_string(&player) {
4438        Ok(s) => String::from(s),
4439        Err(_) => return 1,
4440    };
4441    let m = match env.get_string(&message) {
4442        Ok(s) => String::from(s),
4443        Err(_) => return 1,
4444    };
4445    let d = match env.get_string(&dimension) {
4446        Ok(s) => String::from(s),
4447        Err(_) => return 1,
4448    };
4449    let ev = yog_abi::YogChatEvent {
4450        player: YogStr::from_str(&p),
4451        message: YogStr::from_str(&m),
4452        dimension: YogStr::from_str(&d),
4453    };
4454    let srv = srv_ptr();
4455    let mut allow = true;
4456    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4457        for (module, ud, f) in &h.chat {
4458            if module.is_retiring() { continue; }
4459            let Some(_call) = module.enter() else { continue };
4460            if !unsafe { f(*ud, srv, &ev, 0) } {
4461                allow = false;
4462                break;
4463            }
4464        }
4465    }))
4466    .ok();
4467    allow as jni::sys::jboolean
4468}
4469
4470#[no_mangle]
4471pub extern "system" fn Java_dev_yog_NativeBridge_nativeRecipeJsons<'l>(
4472    env: JNIEnv<'l>,
4473    _class: JClass<'l>,
4474) -> jstring {
4475    let s = handlers()
4476        .recipes
4477        .iter()
4478        .map(|(ns, name, json)| format!("{}\t{}\t{}", ns, name, json))
4479        .collect::<Vec<_>>()
4480        .join("\n");
4481    env.new_string(s)
4482        .map(|s| s.into_raw())
4483        .unwrap_or(std::ptr::null_mut())
4484}
4485
4486#[no_mangle]
4487pub extern "system" fn Java_dev_yog_NativeBridge_nativeDimensionJsons<'l>(
4488    env: JNIEnv<'l>,
4489    _class: JClass<'l>,
4490) -> jstring {
4491    // One line per declared dimension: `id \t def_json`. The host converts
4492    // `def_json` (our YogDimensionDef shape) into vanilla dimension_type /
4493    // dimension datapack JSON — see `yog-dimensions` crate docs.
4494    let s = handlers()
4495        .dimensions
4496        .iter()
4497        .map(|(id, json)| format!("{}\t{}", id, json))
4498        .collect::<Vec<_>>()
4499        .join("\n");
4500    env.new_string(s)
4501        .map(|s| s.into_raw())
4502        .unwrap_or(std::ptr::null_mut())
4503}
4504
4505/// Called by a registered chunk-generator closure (via `ChunkWriter::set_block`)
4506/// to place one block in the chunk currently being generated. Calls back
4507/// into `YogCallbackChunkGenerator.setBlockInGeneratingChunk` on the Java
4508/// side — same thread, since the whole round trip is synchronous.
4509unsafe extern "C" fn chunk_writer_set_block(
4510    _ctx: *mut c_void,
4511    x: i32,
4512    y: i32,
4513    z: i32,
4514    block_id: YogStr,
4515) -> bool {
4516    let Some(mut env) = get_env() else {
4517        return false;
4518    };
4519    let Some(jb) = ys_to_java(&mut env, block_id) else {
4520        return false;
4521    };
4522    env.call_static_method(
4523        "dev/yog/NativeBridge",
4524        "setBlockInGeneratingChunk",
4525        "(IIILjava/lang/String;)Z",
4526        &[
4527            JValue::Int(x),
4528            JValue::Int(y),
4529            JValue::Int(z),
4530            JValue::Object(&jb),
4531        ],
4532    )
4533    .and_then(|v| v.z())
4534    .unwrap_or(false)
4535}
4536
4537#[no_mangle]
4538pub extern "system" fn Java_dev_yog_NativeBridge_nativeGenerateChunk<'l>(
4539    mut env: JNIEnv<'l>,
4540    _class: JClass<'l>,
4541    generator_type_id: JString<'l>,
4542    chunk_x: jint,
4543    chunk_z: jint,
4544    min_y: jint,
4545    height: jint,
4546) {
4547    let id: String = match env.get_string(&generator_type_id) {
4548        Ok(s) => String::from(s),
4549        Err(_) => return,
4550    };
4551    let Some((module, ud, cb)) = handlers().chunk_generators.get(&id).cloned() else {
4552        return;
4553    };
4554    if module.is_retiring() {
4555        return;
4556    }
4557    let Some(_call) = module.enter() else { return };
4558    let writer = yog_abi::YogChunkWriterApi {
4559        chunk_x,
4560        chunk_z,
4561        min_y,
4562        height,
4563        ctx: std::ptr::null_mut(),
4564        set_block: chunk_writer_set_block,
4565    };
4566    guard("chunk_generator", || unsafe {
4567        cb(ud, &writer as *const _);
4568    });
4569}
4570
4571#[no_mangle]
4572pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
4573    env: JNIEnv<'l>,
4574    _class: JClass<'l>,
4575) -> jstring {
4576    // One line per DISTINCT (name, schema) pair — a name can have several
4577    // typed registrations (different arg shapes); Brigadier merges repeated
4578    // `dispatcher.register()` calls for the same literal name into one tree,
4579    // so the Java side just needs every distinct schema once each.
4580    let mut lines = Vec::new();
4581    for (name, regs) in handlers().commands.iter() {
4582        let mut seen = std::collections::HashSet::new();
4583        for (_module, schema, _, _) in regs {
4584            if let Some(schema) = schema {
4585                if seen.insert(schema.as_str()) {
4586                    lines.push(format!("{}\t{}", name, schema));
4587                }
4588            }
4589        }
4590    }
4591    env.new_string(lines.join("\n"))
4592        .map(|s| s.into_raw())
4593        .unwrap_or(std::ptr::null_mut())
4594}
4595
4596#[no_mangle]
4597pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
4598    mut env: JNIEnv<'l>,
4599    _class: JClass<'l>,
4600    name: JString<'l>,
4601    args: JString<'l>,
4602    source: JString<'l>,
4603    uuid: JString<'l>,
4604    dimension: JString<'l>,
4605) -> jstring {
4606    let (n, a, s, u, d) = (
4607        env.get_string(&name).map(String::from).unwrap_or_default(),
4608        env.get_string(&args).map(String::from).unwrap_or_default(),
4609        env.get_string(&source)
4610            .map(String::from)
4611            .unwrap_or_default(),
4612        env.get_string(&uuid).map(String::from).unwrap_or_default(),
4613        env.get_string(&dimension)
4614            .map(String::from)
4615            .unwrap_or_default(),
4616    );
4617    let ev = yog_abi::YogCommandEvent {
4618        name: YogStr::from_str(&n),
4619        args: YogStr::from_str(&a),
4620        source: YogStr::from_str(&s),
4621        uuid: YogStr::from_str(&u),
4622        dimension: YogStr::from_str(&d),
4623    };
4624    let h = handlers();
4625    let srv = srv_ptr();
4626    // Actual arg count of this invocation (0 for a bare command with no args).
4627    let actual_argc = if a.is_empty() {
4628        0
4629    } else {
4630        a.split('\t').count()
4631    };
4632    let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4633        let Some(regs) = h.commands.get(&n) else {
4634            return String::new();
4635        };
4636        for (module, schema, ud, f) in regs {
4637            let expected_argc = schema.as_ref().map_or(0, |s| s.split_whitespace().count());
4638            if expected_argc != actual_argc {
4639                continue;
4640            }
4641            if module.is_retiring() {
4642                continue;
4643            }
4644            let Some(_call) = module.enter() else { continue };
4645            let mut buf = [0u8; 4096];
4646            let mut reply_len: u32 = 0;
4647            unsafe {
4648                f(
4649                    *ud,
4650                    srv,
4651                    &ev,
4652                    buf.as_mut_ptr(),
4653                    buf.len() as u32,
4654                    &mut reply_len,
4655                )
4656            };
4657            if reply_len > 0 {
4658                return String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned();
4659            }
4660        }
4661        String::new()
4662    }))
4663    .unwrap_or_else(|_| {
4664        yog_logging::error!("a mod panicked handling command `{}`", n);
4665        String::new()
4666    });
4667
4668    env.new_string(reply)
4669        .map(|s| s.into_raw())
4670        .unwrap_or(std::ptr::null_mut())
4671}
4672
4673#[no_mangle]
4674pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemDefs<'l>(
4675    env: JNIEnv<'l>,
4676    _class: JClass<'l>,
4677) -> jstring {
4678    let s = handlers()
4679        .items
4680        .iter()
4681        .map(|d| {
4682            let mut parts = vec![d.id.clone()];
4683            parts.push(format!("max_stack={}", d.max_stack));
4684            if let Some(n) = &d.name {
4685                parts.push(format!("name={n}"));
4686            }
4687            if let Some(t) = &d.tooltip {
4688                parts.push(format!("tooltip={t}"));
4689            }
4690            if d.max_damage > 0 {
4691                parts.push(format!("max_damage={}", d.max_damage));
4692            }
4693            if d.fire_resistant {
4694                parts.push("fire_resistant=1".into());
4695            }
4696            if d.fuel_ticks > 0 {
4697                parts.push(format!("fuel_ticks={}", d.fuel_ticks));
4698            }
4699            if let Some(f) = &d.food {
4700                parts.push(format!(
4701                    "food={}:{}:{}",
4702                    f.nutrition,
4703                    f.saturation,
4704                    if f.can_always_eat { 1 } else { 0 }
4705                ));
4706            }
4707            parts.join("\t")
4708        })
4709        .collect::<Vec<_>>()
4710        .join("\n");
4711    env.new_string(s)
4712        .map(|s| s.into_raw())
4713        .unwrap_or(std::ptr::null_mut())
4714}
4715
4716#[no_mangle]
4717pub extern "system" fn Java_dev_yog_NativeBridge_nativeBlockDefs<'l>(
4718    env: JNIEnv<'l>,
4719    _class: JClass<'l>,
4720) -> jstring {
4721    let s = handlers()
4722        .blocks
4723        .iter()
4724        .map(|d| {
4725            let mut parts = vec![d.id.clone()];
4726            parts.push(format!("hardness={}", d.hardness));
4727            parts.push(format!("resistance={}", d.resistance));
4728            if let Some(n) = &d.name {
4729                parts.push(format!("name={n}"));
4730            }
4731            if let Some(sh) = d.shape {
4732                parts.push(format!(
4733                    "shape={}:{}:{}:{}:{}:{}",
4734                    sh[0], sh[1], sh[2], sh[3], sh[4], sh[5]
4735                ));
4736            }
4737            if d.light_level > 0 {
4738                parts.push(format!("light={}", d.light_level));
4739            }
4740            if let Some(snd) = &d.sound {
4741                parts.push(format!("sound={snd}"));
4742            }
4743            if d.requires_tool {
4744                parts.push("requires_tool=1".into());
4745            }
4746            if d.no_collision {
4747                parts.push("no_collision=1".into());
4748            }
4749            if d.slipperiness > 0.0 {
4750                parts.push(format!("slipperiness={}", d.slipperiness));
4751            }
4752            if d.connects {
4753                parts.push("connects=1".into());
4754            }
4755            if !d.connect_groups.is_empty() {
4756                parts.push(format!("connect_groups={}", d.connect_groups.join(",")));
4757            }
4758            if let Some(inv) = &d.inventory_id {
4759                parts.push(format!("inventory_id={inv}"));
4760            }
4761            parts.join("\t")
4762        })
4763        .collect::<Vec<_>>()
4764        .join("\n");
4765    env.new_string(s)
4766        .map(|s| s.into_raw())
4767        .unwrap_or(std::ptr::null_mut())
4768}
4769
4770/// Tab/newline-encoded inventory defs — one line per registered
4771/// `InventoryDef` (see `yog_inventory`): `id\tslot_count=N\tlayout=x:y,...
4772/// \tinclude_player_inventory=0|1\tplayer_inv=x:y\tbackground_texture=...\ttitle=...`
4773#[no_mangle]
4774pub extern "system" fn Java_dev_yog_NativeBridge_nativeInventoryDefs<'l>(
4775    env: JNIEnv<'l>,
4776    _class: JClass<'l>,
4777) -> jstring {
4778    let s = handlers()
4779        .inventories
4780        .iter()
4781        .map(|d| {
4782            let mut parts = vec![d.id.clone()];
4783            parts.push(format!("slot_count={}", d.slot_count));
4784            parts.push(format!(
4785                "layout={}",
4786                yog_inventory::encode_layout(&d.resolved_layout())
4787            ));
4788            parts.push(format!(
4789                "include_player_inventory={}",
4790                d.include_player_inventory as u8
4791            ));
4792            parts.push(format!(
4793                "player_inv={}:{}",
4794                d.player_inv_offset.0, d.player_inv_offset.1
4795            ));
4796            if let Some(tex) = &d.background_texture {
4797                parts.push(format!("background_texture={tex}"));
4798            }
4799            if !d.title.is_empty() {
4800                parts.push(format!("title={}", d.title));
4801            }
4802            if let Some((w, h)) = d.background_size {
4803                parts.push(format!("background_size={w}:{h}"));
4804            }
4805            parts.join("\t")
4806        })
4807        .collect::<Vec<_>>()
4808        .join("\n");
4809    env.new_string(s)
4810        .map(|s| s.into_raw())
4811        .unwrap_or(std::ptr::null_mut())
4812}
4813
4814#[no_mangle]
4815pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
4816    mut env: JNIEnv<'l>,
4817    _class: JClass<'l>,
4818    channel: JString<'l>,
4819    player: JString<'l>,
4820    payload: JByteArray<'l>,
4821) {
4822    let ch = env
4823        .get_string(&channel)
4824        .map(String::from)
4825        .unwrap_or_default();
4826    let pl = env
4827        .get_string(&player)
4828        .map(String::from)
4829        .unwrap_or_default();
4830    let data = env.convert_byte_array(&payload).unwrap_or_default();
4831    let ev = yog_abi::YogPacketEvent {
4832        channel: YogStr::from_str(&ch),
4833        player: YogStr::from_str(&pl),
4834        payload: data.as_ptr(),
4835        payload_len: data.len() as u32,
4836    };
4837    let h = handlers();
4838    let srv = srv_ptr();
4839    guard("on_packet", || {
4840        if let Some((module, ud, f)) = h.packets.get(&ch) {
4841            if module.is_retiring() {
4842                return;
4843            }
4844            let Some(_call) = module.enter() else { return };
4845            unsafe { f(*ud, srv, &ev) };
4846        }
4847    });
4848}
4849
4850#[no_mangle]
4851pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
4852    mut env: JNIEnv<'l>,
4853    _class: JClass<'l>,
4854    channel: JString<'l>,
4855    payload: JByteArray<'l>,
4856) {
4857    let ch = env
4858        .get_string(&channel)
4859        .map(String::from)
4860        .unwrap_or_default();
4861    let data = env.convert_byte_array(&payload).unwrap_or_default();
4862    let ev = yog_abi::YogPacketEvent {
4863        channel: YogStr::from_str(&ch),
4864        player: YogStr::EMPTY,
4865        payload: data.as_ptr(),
4866        payload_len: data.len() as u32,
4867    };
4868    let h = handlers();
4869    let srv = srv_ptr();
4870    guard("on_client_packet", || {
4871        if let Some((module, ud, f)) = h.client_packets.get(&ch) {
4872            if module.is_retiring() {
4873                return;
4874            }
4875            let Some(_call) = module.enter() else { return };
4876            unsafe { f(*ud, srv, &ev) };
4877        }
4878    });
4879}
4880
4881#[no_mangle]
4882pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
4883    env: JNIEnv<'l>,
4884    _class: JClass<'l>,
4885) -> jstring {
4886    let s = handlers()
4887        .packets
4888        .keys()
4889        .cloned()
4890        .collect::<Vec<_>>()
4891        .join("\n");
4892    env.new_string(s)
4893        .map(|s| s.into_raw())
4894        .unwrap_or(std::ptr::null_mut())
4895}
4896
4897#[no_mangle]
4898pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
4899    env: JNIEnv<'l>,
4900    _class: JClass<'l>,
4901) -> jstring {
4902    let s = handlers()
4903        .client_packets
4904        .keys()
4905        .cloned()
4906        .collect::<Vec<_>>()
4907        .join("\n");
4908    env.new_string(s)
4909        .map(|s| s.into_raw())
4910        .unwrap_or(std::ptr::null_mut())
4911}
4912
4913#[no_mangle]
4914pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
4915    mut env: JNIEnv<'l>,
4916    _class: JClass<'l>,
4917    entity_type: JString<'l>,
4918    uuid: JString<'l>,
4919    dimension: JString<'l>,
4920) {
4921    let h = handlers();
4922    if h.entity_spawn.is_empty() {
4923        return;
4924    }
4925    let (et, u, d) = (
4926        jstr!(env, entity_type),
4927        jstr!(env, uuid),
4928        jstr!(env, dimension),
4929    );
4930    let ev = yog_abi::YogEntitySpawnEvent {
4931        entity_type: YogStr::from_str(&et),
4932        uuid: YogStr::from_str(&u),
4933        dimension: YogStr::from_str(&d),
4934    };
4935    let srv = srv_ptr();
4936    guard("on_entity_spawn", || {
4937        for (module, ud, f) in &h.entity_spawn {
4938            if module.is_retiring() { continue; }
4939            let Some(_call) = module.enter() else { continue };
4940            unsafe { f(*ud, srv, &ev, 1) };
4941        }
4942    });
4943}
4944
4945#[no_mangle]
4946pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
4947    mut env: JNIEnv<'l>,
4948    _class: JClass<'l>,
4949    entity_type: JString<'l>,
4950    uuid: JString<'l>,
4951    dimension: JString<'l>,
4952) -> jni::sys::jboolean {
4953    let h = handlers();
4954    if h.entity_spawn.is_empty() {
4955        return 1;
4956    }
4957    let et = match env.get_string(&entity_type) {
4958        Ok(s) => String::from(s),
4959        Err(_) => return 1,
4960    };
4961    let u = match env.get_string(&uuid) {
4962        Ok(s) => String::from(s),
4963        Err(_) => return 1,
4964    };
4965    let d = match env.get_string(&dimension) {
4966        Ok(s) => String::from(s),
4967        Err(_) => return 1,
4968    };
4969    let ev = yog_abi::YogEntitySpawnEvent {
4970        entity_type: YogStr::from_str(&et),
4971        uuid: YogStr::from_str(&u),
4972        dimension: YogStr::from_str(&d),
4973    };
4974    let srv = srv_ptr();
4975    let mut allow = true;
4976    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4977        for (module, ud, f) in &h.entity_spawn {
4978            if module.is_retiring() { continue; }
4979            let Some(_call) = module.enter() else { continue };
4980            if !unsafe { f(*ud, srv, &ev, 0) } {
4981                allow = false;
4982                break;
4983            }
4984        }
4985    }))
4986    .ok();
4987    allow as jni::sys::jboolean
4988}
4989
4990#[no_mangle]
4991pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
4992    mut env: JNIEnv<'l>,
4993    _class: JClass<'l>,
4994    entity_type: JString<'l>,
4995    uuid: JString<'l>,
4996    amount: jfloat,
4997    source: JString<'l>,
4998    dimension: JString<'l>,
4999) -> jni::sys::jboolean {
5000    let h = handlers();
5001    if h.entity_damage.is_empty() {
5002        return 1;
5003    }
5004    let et = match env.get_string(&entity_type) {
5005        Ok(s) => String::from(s),
5006        Err(_) => return 1,
5007    };
5008    let u = match env.get_string(&uuid) {
5009        Ok(s) => String::from(s),
5010        Err(_) => return 1,
5011    };
5012    let s = match env.get_string(&source) {
5013        Ok(s) => String::from(s),
5014        Err(_) => return 1,
5015    };
5016    let d = match env.get_string(&dimension) {
5017        Ok(s) => String::from(s),
5018        Err(_) => return 1,
5019    };
5020    let ev = yog_abi::YogEntityDamageEvent {
5021        entity_type: YogStr::from_str(&et),
5022        uuid: YogStr::from_str(&u),
5023        amount,
5024        source: YogStr::from_str(&s),
5025        dimension: YogStr::from_str(&d),
5026    };
5027    let srv = srv_ptr();
5028    let mut allow = true;
5029    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5030        for (module, ud, f) in &h.entity_damage {
5031            if module.is_retiring() { continue; }
5032            let Some(_call) = module.enter() else { continue };
5033            if !unsafe { f(*ud, srv, &ev, 0) } {
5034                allow = false;
5035                break;
5036            }
5037        }
5038    }))
5039    .ok();
5040    allow as jni::sys::jboolean
5041}
5042
5043#[no_mangle]
5044pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
5045    mut env: JNIEnv<'l>,
5046    _class: JClass<'l>,
5047    player: JString<'l>,
5048    block: JString<'l>,
5049    x: jint,
5050    y: jint,
5051    z: jint,
5052    dimension: JString<'l>,
5053) -> jni::sys::jboolean {
5054    let h = handlers();
5055    if h.player_place_block.is_empty() {
5056        return 1;
5057    }
5058    let p = match env.get_string(&player) {
5059        Ok(s) => String::from(s),
5060        Err(_) => return 1,
5061    };
5062    let b = match env.get_string(&block) {
5063        Ok(s) => String::from(s),
5064        Err(_) => return 1,
5065    };
5066    let d = match env.get_string(&dimension) {
5067        Ok(s) => String::from(s),
5068        Err(_) => return 1,
5069    };
5070    let ev = YogPlaceBlockEvent {
5071        player: YogStr::from_str(&p),
5072        block: YogStr::from_str(&b),
5073        pos: YogBlockPos { x, y, z },
5074        dimension: YogStr::from_str(&d),
5075    };
5076    let srv = srv_ptr();
5077    let mut allow = true;
5078    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5079        for (module, ud, f) in &h.player_place_block {
5080            if module.is_retiring() { continue; }
5081            let Some(_call) = module.enter() else { continue };
5082            if !unsafe { f(*ud, srv, &ev, 0) } {
5083                allow = false;
5084                break;
5085            }
5086        }
5087    }))
5088    .ok();
5089    allow as jni::sys::jboolean
5090}
5091
5092#[no_mangle]
5093pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
5094    mut env: JNIEnv<'l>,
5095    _class: JClass<'l>,
5096    player: JString<'l>,
5097    block: JString<'l>,
5098    x: jint,
5099    y: jint,
5100    z: jint,
5101    dimension: JString<'l>,
5102) {
5103    let h = handlers();
5104    if h.player_place_block.is_empty() {
5105        return;
5106    }
5107    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
5108    let ev = YogPlaceBlockEvent {
5109        player: YogStr::from_str(&p),
5110        block: YogStr::from_str(&b),
5111        pos: YogBlockPos { x, y, z },
5112        dimension: YogStr::from_str(&d),
5113    };
5114    let srv = srv_ptr();
5115    guard("on_player_place_block", || {
5116        for (module, ud, f) in &h.player_place_block {
5117            if module.is_retiring() { continue; }
5118            let Some(_call) = module.enter() else { continue };
5119            unsafe { f(*ud, srv, &ev, 1) };
5120        }
5121    });
5122}
5123
5124#[no_mangle]
5125pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
5126    mut env: JNIEnv<'l>,
5127    _class: JClass<'l>,
5128    player: JString<'l>,
5129    uuid: JString<'l>,
5130    source: JString<'l>,
5131    dimension: JString<'l>,
5132) -> jni::sys::jboolean {
5133    let h = handlers();
5134    if h.player_death.is_empty() {
5135        return 1;
5136    }
5137    let p = match env.get_string(&player) {
5138        Ok(s) => String::from(s),
5139        Err(_) => return 1,
5140    };
5141    let u = match env.get_string(&uuid) {
5142        Ok(s) => String::from(s),
5143        Err(_) => return 1,
5144    };
5145    let s = match env.get_string(&source) {
5146        Ok(s) => String::from(s),
5147        Err(_) => return 1,
5148    };
5149    let d = match env.get_string(&dimension) {
5150        Ok(s) => String::from(s),
5151        Err(_) => return 1,
5152    };
5153    let ev = YogPlayerDeathEvent {
5154        player: YogStr::from_str(&p),
5155        uuid: YogStr::from_str(&u),
5156        source: YogStr::from_str(&s),
5157        dimension: YogStr::from_str(&d),
5158    };
5159    let srv = srv_ptr();
5160    let mut allow = true;
5161    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5162        for (module, ud, f) in &h.player_death {
5163            if module.is_retiring() { continue; }
5164            let Some(_call) = module.enter() else { continue };
5165            if !unsafe { f(*ud, srv, &ev, 0) } {
5166                allow = false;
5167                break;
5168            }
5169        }
5170    }))
5171    .ok();
5172    allow as jni::sys::jboolean
5173}
5174
5175#[no_mangle]
5176pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
5177    mut env: JNIEnv<'l>,
5178    _class: JClass<'l>,
5179    player: JString<'l>,
5180    uuid: JString<'l>,
5181    source: JString<'l>,
5182    dimension: JString<'l>,
5183) {
5184    let h = handlers();
5185    if h.player_death.is_empty() {
5186        return;
5187    }
5188    let (p, u, s, d) = (
5189        jstr!(env, player),
5190        jstr!(env, uuid),
5191        jstr!(env, source),
5192        jstr!(env, dimension),
5193    );
5194    let ev = YogPlayerDeathEvent {
5195        player: YogStr::from_str(&p),
5196        uuid: YogStr::from_str(&u),
5197        source: YogStr::from_str(&s),
5198        dimension: YogStr::from_str(&d),
5199    };
5200    let srv = srv_ptr();
5201    guard("on_player_death", || {
5202        for (module, ud, f) in &h.player_death {
5203            if module.is_retiring() { continue; }
5204            let Some(_call) = module.enter() else { continue };
5205            unsafe { f(*ud, srv, &ev, 1) };
5206        }
5207    });
5208}
5209
5210#[no_mangle]
5211pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
5212    mut env: JNIEnv<'l>,
5213    _class: JClass<'l>,
5214    player: JString<'l>,
5215    uuid: JString<'l>,
5216    at_anchor: jni::sys::jboolean,
5217    dimension: JString<'l>,
5218) {
5219    let h = handlers();
5220    if h.player_respawn.is_empty() {
5221        return;
5222    }
5223    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
5224    let ev = YogPlayerRespawnEvent {
5225        player: YogStr::from_str(&p),
5226        uuid: YogStr::from_str(&u),
5227        at_anchor: at_anchor != 0,
5228        dimension: YogStr::from_str(&d),
5229    };
5230    let srv = srv_ptr();
5231    guard("on_player_respawn", || {
5232        for (module, ud, f) in &h.player_respawn {
5233            if module.is_retiring() { continue; }
5234            let Some(_call) = module.enter() else { continue };
5235            unsafe { f(*ud, srv, &ev, 1) };
5236        }
5237    });
5238}
5239
5240#[no_mangle]
5241pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
5242    mut env: JNIEnv<'l>,
5243    _class: JClass<'l>,
5244    player: JString<'l>,
5245    uuid: JString<'l>,
5246    advancement: JString<'l>,
5247    dimension: JString<'l>,
5248) {
5249    let h = handlers();
5250    if h.advancement.is_empty() {
5251        return;
5252    }
5253    let (p, u, a, d) = (
5254        jstr!(env, player),
5255        jstr!(env, uuid),
5256        jstr!(env, advancement),
5257        jstr!(env, dimension),
5258    );
5259    let ev = YogAdvancementEvent {
5260        player: YogStr::from_str(&p),
5261        uuid: YogStr::from_str(&u),
5262        advancement: YogStr::from_str(&a),
5263        dimension: YogStr::from_str(&d),
5264    };
5265    let srv = srv_ptr();
5266    guard("on_advancement", || {
5267        for (module, ud, f) in &h.advancement {
5268            if module.is_retiring() { continue; }
5269            let Some(_call) = module.enter() else { continue };
5270            unsafe { f(*ud, srv, &ev, 1) };
5271        }
5272    });
5273}
5274
5275#[no_mangle]
5276pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
5277    mut env: JNIEnv<'l>,
5278    _class: JClass<'l>,
5279    player: JString<'l>,
5280    player_uuid: JString<'l>,
5281    entity_type: JString<'l>,
5282    entity_uuid: JString<'l>,
5283    hand: JString<'l>,
5284    dimension: JString<'l>,
5285) -> jni::sys::jboolean {
5286    let h = handlers();
5287    if h.entity_interact.is_empty() {
5288        return 1;
5289    }
5290    let p = match env.get_string(&player) {
5291        Ok(s) => String::from(s),
5292        Err(_) => return 1,
5293    };
5294    let pu = match env.get_string(&player_uuid) {
5295        Ok(s) => String::from(s),
5296        Err(_) => return 1,
5297    };
5298    let et = match env.get_string(&entity_type) {
5299        Ok(s) => String::from(s),
5300        Err(_) => return 1,
5301    };
5302    let eu = match env.get_string(&entity_uuid) {
5303        Ok(s) => String::from(s),
5304        Err(_) => return 1,
5305    };
5306    let ha = match env.get_string(&hand) {
5307        Ok(s) => String::from(s),
5308        Err(_) => return 1,
5309    };
5310    let d = match env.get_string(&dimension) {
5311        Ok(s) => String::from(s),
5312        Err(_) => return 1,
5313    };
5314    let ev = YogEntityInteractEvent {
5315        player: YogStr::from_str(&p),
5316        player_uuid: YogStr::from_str(&pu),
5317        entity_type: YogStr::from_str(&et),
5318        entity_uuid: YogStr::from_str(&eu),
5319        hand: YogStr::from_str(&ha),
5320        dimension: YogStr::from_str(&d),
5321    };
5322    let srv = srv_ptr();
5323    let mut allow = true;
5324    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5325        for (module, ud, f) in &h.entity_interact {
5326            if module.is_retiring() { continue; }
5327            let Some(_call) = module.enter() else { continue };
5328            if !unsafe { f(*ud, srv, &ev, 0) } {
5329                allow = false;
5330                break;
5331            }
5332        }
5333    }))
5334    .ok();
5335    allow as jni::sys::jboolean
5336}
5337
5338#[no_mangle]
5339pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
5340    mut env: JNIEnv<'l>,
5341    _class: JClass<'l>,
5342    player: JString<'l>,
5343    player_uuid: JString<'l>,
5344    entity_type: JString<'l>,
5345    entity_uuid: JString<'l>,
5346    hand: JString<'l>,
5347    dimension: JString<'l>,
5348) {
5349    let h = handlers();
5350    if h.entity_interact.is_empty() {
5351        return;
5352    }
5353    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5354    let (et, eu, ha, d) = (
5355        jstr!(env, entity_type),
5356        jstr!(env, entity_uuid),
5357        jstr!(env, hand),
5358        jstr!(env, dimension),
5359    );
5360    let ev = YogEntityInteractEvent {
5361        player: YogStr::from_str(&p),
5362        player_uuid: YogStr::from_str(&pu),
5363        entity_type: YogStr::from_str(&et),
5364        entity_uuid: YogStr::from_str(&eu),
5365        hand: YogStr::from_str(&ha),
5366        dimension: YogStr::from_str(&d),
5367    };
5368    let srv = srv_ptr();
5369    guard("on_entity_interact", || {
5370        for (module, ud, f) in &h.entity_interact {
5371            if module.is_retiring() { continue; }
5372            let Some(_call) = module.enter() else { continue };
5373            unsafe { f(*ud, srv, &ev, 1) };
5374        }
5375    });
5376}
5377
5378#[no_mangle]
5379pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
5380    mut env: JNIEnv<'l>,
5381    _class: JClass<'l>,
5382    player: JString<'l>,
5383    player_uuid: JString<'l>,
5384    result_item: JString<'l>,
5385    result_count: jint,
5386    dimension: JString<'l>,
5387) {
5388    let h = handlers();
5389    if h.item_craft.is_empty() {
5390        return;
5391    }
5392    let (p, pu, ri, d) = (
5393        jstr!(env, player),
5394        jstr!(env, player_uuid),
5395        jstr!(env, result_item),
5396        jstr!(env, dimension),
5397    );
5398    let ev = YogCraftEvent {
5399        player: YogStr::from_str(&p),
5400        player_uuid: YogStr::from_str(&pu),
5401        result_item: YogStr::from_str(&ri),
5402        result_count: result_count as u32,
5403        dimension: YogStr::from_str(&d),
5404    };
5405    let srv = srv_ptr();
5406    guard("on_item_craft", || {
5407        for (module, ud, f) in &h.item_craft {
5408            if module.is_retiring() { continue; }
5409            let Some(_call) = module.enter() else { continue };
5410            unsafe { f(*ud, srv, &ev, 1) };
5411        }
5412    });
5413}
5414
5415#[no_mangle]
5416pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
5417    mut env: JNIEnv<'l>,
5418    _class: JClass<'l>,
5419    dimension: JString<'l>,
5420    x: jdouble,
5421    y: jdouble,
5422    z: jdouble,
5423    power: jfloat,
5424    cause_uuid: JString<'l>,
5425) -> jni::sys::jboolean {
5426    let h = handlers();
5427    if h.explosion.is_empty() {
5428        return 1;
5429    }
5430    let d = match env.get_string(&dimension) {
5431        Ok(s) => String::from(s),
5432        Err(_) => return 1,
5433    };
5434    let cu = match env.get_string(&cause_uuid) {
5435        Ok(s) => String::from(s),
5436        Err(_) => return 1,
5437    };
5438    let ev = YogExplosionEvent {
5439        dimension: YogStr::from_str(&d),
5440        x,
5441        y,
5442        z,
5443        power,
5444        cause_uuid: YogStr::from_str(&cu),
5445    };
5446    let srv = srv_ptr();
5447    let mut allow = true;
5448    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5449        for (module, ud, f) in &h.explosion {
5450            if module.is_retiring() { continue; }
5451            let Some(_call) = module.enter() else { continue };
5452            if !unsafe { f(*ud, srv, &ev, 0) } {
5453                allow = false;
5454                break;
5455            }
5456        }
5457    }))
5458    .ok();
5459    allow as jni::sys::jboolean
5460}
5461
5462#[no_mangle]
5463pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
5464    mut env: JNIEnv<'l>,
5465    _class: JClass<'l>,
5466    dimension: JString<'l>,
5467    x: jdouble,
5468    y: jdouble,
5469    z: jdouble,
5470    power: jfloat,
5471    cause_uuid: JString<'l>,
5472) {
5473    let h = handlers();
5474    if h.explosion.is_empty() {
5475        return;
5476    }
5477    let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
5478    let ev = YogExplosionEvent {
5479        dimension: YogStr::from_str(&d),
5480        x,
5481        y,
5482        z,
5483        power,
5484        cause_uuid: YogStr::from_str(&cu),
5485    };
5486    let srv = srv_ptr();
5487    guard("on_explosion", || {
5488        for (module, ud, f) in &h.explosion {
5489            if module.is_retiring() { continue; }
5490            let Some(_call) = module.enter() else { continue };
5491            unsafe { f(*ud, srv, &ev, 1) };
5492        }
5493    });
5494}
5495
5496// ── ABI minor 9 JNI entry points ─────────────────────────────────────────────
5497
5498#[no_mangle]
5499pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
5500    mut env: JNIEnv<'l>,
5501    _class: JClass<'l>,
5502    player: JString<'l>,
5503    player_uuid: JString<'l>,
5504    item_id: JString<'l>,
5505    item_count: jint,
5506    entity_uuid: JString<'l>,
5507    dimension: JString<'l>,
5508) -> jni::sys::jboolean {
5509    let h = handlers();
5510    if h.item_pickup.is_empty() {
5511        return 1;
5512    }
5513    let p = match env.get_string(&player) {
5514        Ok(s) => String::from(s),
5515        Err(_) => return 1,
5516    };
5517    let pu = match env.get_string(&player_uuid) {
5518        Ok(s) => String::from(s),
5519        Err(_) => return 1,
5520    };
5521    let ii = match env.get_string(&item_id) {
5522        Ok(s) => String::from(s),
5523        Err(_) => return 1,
5524    };
5525    let eu = match env.get_string(&entity_uuid) {
5526        Ok(s) => String::from(s),
5527        Err(_) => return 1,
5528    };
5529    let d = match env.get_string(&dimension) {
5530        Ok(s) => String::from(s),
5531        Err(_) => return 1,
5532    };
5533    let ev = YogItemPickupEvent {
5534        player: YogStr::from_str(&p),
5535        player_uuid: YogStr::from_str(&pu),
5536        item_id: YogStr::from_str(&ii),
5537        item_count: item_count as u32,
5538        entity_uuid: YogStr::from_str(&eu),
5539        dimension: YogStr::from_str(&d),
5540    };
5541    let srv = srv_ptr();
5542    let mut allow = true;
5543    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5544        for (module, ud, f) in &h.item_pickup {
5545            if module.is_retiring() { continue; }
5546            let Some(_call) = module.enter() else { continue };
5547            if !unsafe { f(*ud, srv, &ev, 0) } {
5548                allow = false;
5549                break;
5550            }
5551        }
5552    }))
5553    .ok();
5554    allow as jni::sys::jboolean
5555}
5556
5557#[no_mangle]
5558pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
5559    mut env: JNIEnv<'l>,
5560    _class: JClass<'l>,
5561    player: JString<'l>,
5562    player_uuid: JString<'l>,
5563    item_id: JString<'l>,
5564    item_count: jint,
5565    entity_uuid: JString<'l>,
5566    dimension: JString<'l>,
5567) {
5568    let h = handlers();
5569    if h.item_pickup.is_empty() {
5570        return;
5571    }
5572    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5573    let (ii, eu, d) = (
5574        jstr!(env, item_id),
5575        jstr!(env, entity_uuid),
5576        jstr!(env, dimension),
5577    );
5578    let ev = YogItemPickupEvent {
5579        player: YogStr::from_str(&p),
5580        player_uuid: YogStr::from_str(&pu),
5581        item_id: YogStr::from_str(&ii),
5582        item_count: item_count as u32,
5583        entity_uuid: YogStr::from_str(&eu),
5584        dimension: YogStr::from_str(&d),
5585    };
5586    let srv = srv_ptr();
5587    guard("on_item_pickup", || {
5588        for (module, ud, f) in &h.item_pickup {
5589            if module.is_retiring() { continue; }
5590            let Some(_call) = module.enter() else { continue };
5591            unsafe { f(*ud, srv, &ev, 1) };
5592        }
5593    });
5594}
5595
5596#[no_mangle]
5597pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
5598    mut env: JNIEnv<'l>,
5599    _class: JClass<'l>,
5600    player: JString<'l>,
5601    player_uuid: JString<'l>,
5602    x: jdouble,
5603    y: jdouble,
5604    z: jdouble,
5605    yaw: jfloat,
5606    pitch: jfloat,
5607    dimension: JString<'l>,
5608) {
5609    let h = handlers();
5610    if h.player_move.is_empty() {
5611        return;
5612    }
5613    let (p, pu, d) = (
5614        jstr!(env, player),
5615        jstr!(env, player_uuid),
5616        jstr!(env, dimension),
5617    );
5618    let ev = YogPlayerMoveEvent {
5619        player: YogStr::from_str(&p),
5620        player_uuid: YogStr::from_str(&pu),
5621        x,
5622        y,
5623        z,
5624        yaw,
5625        pitch,
5626        dimension: YogStr::from_str(&d),
5627    };
5628    let srv = srv_ptr();
5629    guard("on_player_move", || {
5630        for (module, ud, f) in &h.player_move {
5631            if module.is_retiring() { continue; }
5632            let Some(_call) = module.enter() else { continue };
5633            unsafe { f(*ud, srv, &ev, 1) };
5634        }
5635    });
5636}
5637
5638#[no_mangle]
5639pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
5640    mut env: JNIEnv<'l>,
5641    _class: JClass<'l>,
5642    player: JString<'l>,
5643    player_uuid: JString<'l>,
5644    dimension: JString<'l>,
5645) -> jni::sys::jboolean {
5646    let h = handlers();
5647    if h.container_open.is_empty() {
5648        return 1;
5649    }
5650    let p = match env.get_string(&player) {
5651        Ok(s) => String::from(s),
5652        Err(_) => return 1,
5653    };
5654    let pu = match env.get_string(&player_uuid) {
5655        Ok(s) => String::from(s),
5656        Err(_) => return 1,
5657    };
5658    let d = match env.get_string(&dimension) {
5659        Ok(s) => String::from(s),
5660        Err(_) => return 1,
5661    };
5662    let ev = YogContainerOpenEvent {
5663        player: YogStr::from_str(&p),
5664        player_uuid: YogStr::from_str(&pu),
5665        container_type: YogStr::EMPTY,
5666        dimension: YogStr::from_str(&d),
5667    };
5668    let srv = srv_ptr();
5669    let mut allow = true;
5670    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5671        for (module, ud, f) in &h.container_open {
5672            if module.is_retiring() { continue; }
5673            let Some(_call) = module.enter() else { continue };
5674            if !unsafe { f(*ud, srv, &ev, 0) } {
5675                allow = false;
5676                break;
5677            }
5678        }
5679    }))
5680    .ok();
5681    allow as jni::sys::jboolean
5682}
5683
5684#[no_mangle]
5685pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
5686    mut env: JNIEnv<'l>,
5687    _class: JClass<'l>,
5688    player: JString<'l>,
5689    player_uuid: JString<'l>,
5690    container_type: JString<'l>,
5691    dimension: JString<'l>,
5692) {
5693    let h = handlers();
5694    if h.container_open.is_empty() {
5695        return;
5696    }
5697    let (p, pu, ct, d) = (
5698        jstr!(env, player),
5699        jstr!(env, player_uuid),
5700        jstr!(env, container_type),
5701        jstr!(env, dimension),
5702    );
5703    let ev = YogContainerOpenEvent {
5704        player: YogStr::from_str(&p),
5705        player_uuid: YogStr::from_str(&pu),
5706        container_type: YogStr::from_str(&ct),
5707        dimension: YogStr::from_str(&d),
5708    };
5709    let srv = srv_ptr();
5710    guard("on_container_open", || {
5711        for (module, ud, f) in &h.container_open {
5712            if module.is_retiring() { continue; }
5713            let Some(_call) = module.enter() else { continue };
5714            unsafe { f(*ud, srv, &ev, 1) };
5715        }
5716    });
5717}
5718
5719#[no_mangle]
5720pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
5721    mut env: JNIEnv<'l>,
5722    _class: JClass<'l>,
5723    player: JString<'l>,
5724    player_uuid: JString<'l>,
5725    dimension: JString<'l>,
5726) {
5727    let h = handlers();
5728    if h.container_close.is_empty() {
5729        return;
5730    }
5731    let (p, pu, d) = (
5732        jstr!(env, player),
5733        jstr!(env, player_uuid),
5734        jstr!(env, dimension),
5735    );
5736    let ev = YogContainerCloseEvent {
5737        player: YogStr::from_str(&p),
5738        player_uuid: YogStr::from_str(&pu),
5739        dimension: YogStr::from_str(&d),
5740    };
5741    let srv = srv_ptr();
5742    guard("on_container_close", || {
5743        for (module, ud, f) in &h.container_close {
5744            if module.is_retiring() { continue; }
5745            let Some(_call) = module.enter() else { continue };
5746            unsafe { f(*ud, srv, &ev, 1) };
5747        }
5748    });
5749}
5750
5751#[no_mangle]
5752pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
5753    mut env: JNIEnv<'l>,
5754    _class: JClass<'l>,
5755    projectile_type: JString<'l>,
5756    projectile_uuid: JString<'l>,
5757    shooter_uuid: JString<'l>,
5758    hit_type: JString<'l>,
5759    hit_entity_uuid: JString<'l>,
5760    x: jdouble,
5761    y: jdouble,
5762    z: jdouble,
5763    dimension: JString<'l>,
5764) -> jni::sys::jboolean {
5765    let h = handlers();
5766    if h.projectile_hit.is_empty() {
5767        return 1;
5768    }
5769    let pt = match env.get_string(&projectile_type) {
5770        Ok(s) => String::from(s),
5771        Err(_) => return 1,
5772    };
5773    let pu = match env.get_string(&projectile_uuid) {
5774        Ok(s) => String::from(s),
5775        Err(_) => return 1,
5776    };
5777    let su = match env.get_string(&shooter_uuid) {
5778        Ok(s) => String::from(s),
5779        Err(_) => return 1,
5780    };
5781    let ht = match env.get_string(&hit_type) {
5782        Ok(s) => String::from(s),
5783        Err(_) => return 1,
5784    };
5785    let heu = match env.get_string(&hit_entity_uuid) {
5786        Ok(s) => String::from(s),
5787        Err(_) => return 1,
5788    };
5789    let dim = match env.get_string(&dimension) {
5790        Ok(s) => String::from(s),
5791        Err(_) => return 1,
5792    };
5793    let ev = YogProjectileHitEvent {
5794        projectile_type: YogStr::from_str(&pt),
5795        projectile_uuid: YogStr::from_str(&pu),
5796        shooter_uuid: YogStr::from_str(&su),
5797        hit_type: YogStr::from_str(&ht),
5798        hit_entity_uuid: YogStr::from_str(&heu),
5799        x,
5800        y,
5801        z,
5802        dimension: YogStr::from_str(&dim),
5803    };
5804    let srv = srv_ptr();
5805    let mut allow = true;
5806    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5807        for (module, ud, f) in &h.projectile_hit {
5808            if module.is_retiring() { continue; }
5809            let Some(_call) = module.enter() else { continue };
5810            if !unsafe { f(*ud, srv, &ev, 0) } {
5811                allow = false;
5812                break;
5813            }
5814        }
5815    }))
5816    .ok();
5817    allow as jni::sys::jboolean
5818}
5819
5820#[no_mangle]
5821pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
5822    mut env: JNIEnv<'l>,
5823    _class: JClass<'l>,
5824    projectile_type: JString<'l>,
5825    projectile_uuid: JString<'l>,
5826    shooter_uuid: JString<'l>,
5827    hit_type: JString<'l>,
5828    hit_entity_uuid: JString<'l>,
5829    x: jdouble,
5830    y: jdouble,
5831    z: jdouble,
5832    dimension: JString<'l>,
5833) {
5834    let h = handlers();
5835    if h.projectile_hit.is_empty() {
5836        return;
5837    }
5838    let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
5839    let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
5840    let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
5841    let ev = YogProjectileHitEvent {
5842        projectile_type: YogStr::from_str(&pt),
5843        projectile_uuid: YogStr::from_str(&pu),
5844        shooter_uuid: YogStr::from_str(&su),
5845        hit_type: YogStr::from_str(&ht),
5846        hit_entity_uuid: YogStr::from_str(&heu),
5847        x,
5848        y,
5849        z,
5850        dimension: YogStr::from_str(&dim),
5851    };
5852    let srv = srv_ptr();
5853    guard("on_projectile_hit", || {
5854        for (module, ud, f) in &h.projectile_hit {
5855            if module.is_retiring() { continue; }
5856            let Some(_call) = module.enter() else { continue };
5857            unsafe { f(*ud, srv, &ev, 1) };
5858        }
5859    });
5860}
5861
5862// ── ABI minor 10 — client-side JNI entry points ───────────────────────────────
5863
5864#[no_mangle]
5865pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
5866    _env: JNIEnv<'l>,
5867    _class: JClass<'l>,
5868) {
5869    let h = handlers();
5870    if h.client_tick.is_empty() {
5871        return;
5872    }
5873    guard("on_client_tick", || {
5874        for (module, ud, f) in &h.client_tick {
5875            if module.is_retiring() { continue; }
5876            let Some(_call) = module.enter() else { continue };
5877            unsafe { f(*ud) };
5878        }
5879    });
5880}
5881
5882#[no_mangle]
5883pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
5884    _env: JNIEnv<'l>,
5885    _class: JClass<'l>,
5886) {
5887    if GL.get().is_some() {
5888        return;
5889    }
5890    let mut raw_get_binary: usize = 0;
5891    let mut raw_prog_binary: usize = 0;
5892    let gl = unsafe {
5893        glow::Context::from_loader_function(|sym| {
5894            let Some(mut env) = get_env() else {
5895                return std::ptr::null();
5896            };
5897            let jsym = match env.new_string(sym) {
5898                Ok(s) => s,
5899                Err(_) => return std::ptr::null(),
5900            };
5901            let jsym_obj: JObject = jsym.into();
5902            let val = env.call_static_method(
5903                "dev/yog/NativeBridge",
5904                "glProcAddress",
5905                "(Ljava/lang/String;)J",
5906                &[JValue::Object(&jsym_obj)],
5907            );
5908            let ptr = match val.and_then(|v| v.j()) {
5909                Ok(p) if p != 0 => p as usize as *const _,
5910                _ => std::ptr::null(),
5911            };
5912            // Capture extension pointers while the loader runs.
5913            match sym {
5914                "glGetProgramBinary" => raw_get_binary = ptr as usize,
5915                "glProgramBinary" => raw_prog_binary = ptr as usize,
5916                _ => {}
5917            }
5918            ptr
5919        })
5920    };
5921    let _ = GL.set(GlCtx(gl));
5922    let _ = GL_GET_PROGRAM_BINARY.set(if raw_get_binary != 0 {
5923        Some(raw_get_binary)
5924    } else {
5925        None
5926    });
5927    let _ = GL_PROGRAM_BINARY.set(if raw_prog_binary != 0 {
5928        Some(raw_prog_binary)
5929    } else {
5930        None
5931    });
5932    // `glGetProgramiv` is a core function; always available.  We look it up once here
5933    // to avoid depending on glow internals for the PROGRAM_BINARY_LENGTH query.
5934    if let Some(mut env) = get_env() {
5935        if let Ok(jsym) = env.new_string("glGetProgramiv") {
5936            let jsym_obj: JObject = jsym.into();
5937            if let Ok(jv) = env.call_static_method(
5938                "dev/yog/NativeBridge",
5939                "glProcAddress",
5940                "(Ljava/lang/String;)J",
5941                &[JValue::Object(&jsym_obj)],
5942            ) {
5943                if let Ok(ptr) = jv.j() {
5944                    let _ = GL_GET_PROGRAM_IV.set(if ptr != 0 { Some(ptr as usize) } else { None });
5945                }
5946            }
5947        }
5948    }
5949}
5950
5951#[no_mangle]
5952pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
5953    _env: JNIEnv<'l>,
5954    _class: JClass<'l>,
5955    delta_tick: jfloat,
5956    screen_w: jint,
5957    screen_h: jint,
5958    scale_factor: jfloat,
5959    player_x: jfloat,
5960    player_y: jfloat,
5961    player_z: jfloat,
5962) {
5963    let h = handlers();
5964    let mut gfx = GFX_FN_TABLE;
5965    gfx.screen_w = screen_w;
5966    gfx.screen_h = screen_h;
5967    gfx.delta_tick = delta_tick;
5968    gfx.scale_factor = scale_factor;
5969    gfx.player_pos = [player_x, player_y, player_z];
5970
5971    // Mod hud_render callbacks.
5972    if !h.hud_render.is_empty() {
5973        guard("on_hud_render", || {
5974            for (module, ud, f) in &h.hud_render {
5975                if module.is_retiring() { continue; }
5976                let Some(_call) = module.enter() else { continue };
5977                unsafe { f(*ud, &gfx) };
5978            }
5979        });
5980    }
5981
5982    // Render active book UIs.
5983    let active_layers: Vec<UiLayer> = h.active_uis.lock().unwrap().clone();
5984    let sw = screen_w as f32;
5985    let sh = screen_h as f32;
5986    let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
5987    let mut renderers = h.book_renderers.lock().unwrap();
5988    for layer in &active_layers {
5989        if !layer.visible {
5990            continue;
5991        }
5992        if let Some(renderer) = renderers.get_mut(&layer.id) {
5993            renderer.render(&ctx, sw, sh);
5994        }
5995    }
5996}
5997
5998#[no_mangle]
5999pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
6000    env: JNIEnv<'l>,
6001    _class: JClass<'l>,
6002    delta_tick: jfloat,
6003    screen_w: jint,
6004    screen_h: jint,
6005    scale_factor: jfloat,
6006    view_proj_arr: JFloatArray<'l>,
6007    cam_x: jfloat,
6008    cam_y: jfloat,
6009    cam_z: jfloat,
6010    player_x: jfloat,
6011    player_y: jfloat,
6012    player_z: jfloat,
6013) {
6014    let h = handlers();
6015    if h.world_render.is_empty() {
6016        return;
6017    }
6018    let mut view_proj = [0f32; 16];
6019    if env
6020        .get_float_array_region(&view_proj_arr, 0, &mut view_proj)
6021        .is_err()
6022    {
6023        return;
6024    }
6025    let mut gfx = GFX_FN_TABLE;
6026    gfx.screen_w = screen_w;
6027    gfx.screen_h = screen_h;
6028    gfx.delta_tick = delta_tick;
6029    gfx.scale_factor = scale_factor;
6030    gfx.view_proj = view_proj;
6031    gfx.camera_pos = [cam_x, cam_y, cam_z];
6032    gfx.player_pos = [player_x, player_y, player_z];
6033    guard("on_world_render", || {
6034        for (module, ud, f) in &h.world_render {
6035            if module.is_retiring() { continue; }
6036            let Some(_call) = module.enter() else { continue };
6037            unsafe { f(*ud, &gfx) };
6038        }
6039    });
6040}
6041
6042#[no_mangle]
6043pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
6044    _env: JNIEnv<'l>,
6045    _class: JClass<'l>,
6046    key_code: jint,
6047    scan_code: jint,
6048    action: jint,
6049    modifiers: jint,
6050) -> jni::sys::jboolean {
6051    let h = handlers();
6052    if h.key_press.is_empty() {
6053        return 1;
6054    }
6055    let ev = YogKeyPressEvent {
6056        key_code,
6057        scan_code,
6058        action,
6059        modifiers,
6060    };
6061    let mut allow = true;
6062    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
6063        for (module, ud, f) in &h.key_press {
6064            if module.is_retiring() { continue; }
6065            let Some(_call) = module.enter() else { continue };
6066            if !unsafe { f(*ud, &ev) } {
6067                allow = false;
6068                break;
6069            }
6070        }
6071    }))
6072    .ok();
6073    allow as jni::sys::jboolean
6074}
6075
6076#[no_mangle]
6077pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
6078    mut env: JNIEnv<'l>,
6079    _class: JClass<'l>,
6080    screen_class: JString<'l>,
6081) {
6082    let h = handlers();
6083    if h.screen_open.is_empty() {
6084        return;
6085    }
6086    let sc = match env.get_string(&screen_class) {
6087        Ok(s) => String::from(s),
6088        Err(_) => return,
6089    };
6090    guard("on_screen_open", || {
6091        for (module, ud, f) in &h.screen_open {
6092            if module.is_retiring() { continue; }
6093            let Some(_call) = module.enter() else { continue };
6094            unsafe { f(*ud, YogStr::from_str(&sc)) };
6095        }
6096    });
6097}
6098
6099#[no_mangle]
6100pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
6101    mut env: JNIEnv<'l>,
6102    _class: JClass<'l>,
6103    screen_class: JString<'l>,
6104) {
6105    let h = handlers();
6106    if h.screen_close.is_empty() {
6107        return;
6108    }
6109    let sc = match env.get_string(&screen_class) {
6110        Ok(s) => String::from(s),
6111        Err(_) => return,
6112    };
6113    guard("on_screen_close", || {
6114        for (module, ud, f) in &h.screen_close {
6115            if module.is_retiring() { continue; }
6116            let Some(_call) = module.enter() else { continue };
6117            unsafe { f(*ud, YogStr::from_str(&sc)) };
6118        }
6119    });
6120}
6121
6122mod control_socket;
6123mod ui_jni;