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