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        if env
2691            .call_static_method(
2692                "dev/yog/NativeDraw",
2693                "drawTexture",
2694                // x, y, u0, v0, w, h, tw, th — eight floats. (Nine here made
2695                // every call fail jni-rs's argument-count check before reaching
2696                // Java, so no texture was ever drawn.)
2697                "(Ljava/lang/String;FFFFFFFF)V",
2698                &[
2699                    JValue::Object(&ji),
2700                    JValue::Float(x),
2701                    JValue::Float(y),
2702                    JValue::Float(u0),
2703                    JValue::Float(v0),
2704                    JValue::Float(w),
2705                    JValue::Float(h),
2706                    JValue::Float(tw),
2707                    JValue::Float(th),
2708                ],
2709            )
2710            .is_err()
2711        {
2712            env.exception_clear().ok();
2713        }
2714    }
2715}
2716
2717unsafe extern "C" fn gfx_draw2d_item(id: YogStr, x: f32, y: f32, size: f32) {
2718    let Some(mut env) = get_env() else { return };
2719    if let Some(ji) = ys_to_java(&mut env, id) {
2720        if env
2721            .call_static_method(
2722                "dev/yog/NativeDraw",
2723                "drawItem",
2724                "(Ljava/lang/String;FFF)V",
2725                &[
2726                    JValue::Object(&ji),
2727                    JValue::Float(x),
2728                    JValue::Float(y),
2729                    JValue::Float(size),
2730                ],
2731            )
2732            .is_err()
2733        {
2734            env.exception_clear().ok();
2735        }
2736    }
2737}
2738
2739// ── Static GFX function table (function pointers only — per-frame data filled at call time) ──
2740
2741static GFX_FN_TABLE: YogGfxApi = YogGfxApi {
2742    // Per-frame fields zeroed in the static; actual values are set on the stack per render call.
2743    screen_w: 0,
2744    screen_h: 0,
2745    delta_tick: 0.0,
2746    scale_factor: 1.0,
2747    view_proj: [0.0; 16],
2748    camera_pos: [0.0; 3],
2749    player_pos: [0.0; 3],
2750    _pad1: 0.0,
2751    buf_create: gfx_buf_create,
2752    buf_delete: gfx_buf_delete,
2753    buf_data: gfx_buf_data,
2754    buf_subdata: gfx_buf_subdata,
2755    vao_create: gfx_vao_create,
2756    vao_delete: gfx_vao_delete,
2757    vao_attrib: gfx_vao_attrib,
2758    vao_set_ebo: gfx_vao_set_ebo,
2759    prog_create: gfx_prog_create,
2760    prog_delete: gfx_prog_delete,
2761    prog_uniform_1i: gfx_prog_uniform_1i,
2762    prog_uniform_1f: gfx_prog_uniform_1f,
2763    prog_uniform_2f: gfx_prog_uniform_2f,
2764    prog_uniform_3f: gfx_prog_uniform_3f,
2765    prog_uniform_4f: gfx_prog_uniform_4f,
2766    prog_uniform_mat4: gfx_prog_uniform_mat4,
2767    tex_create: gfx_tex_create,
2768    tex_delete: gfx_tex_delete,
2769    tex_bind: gfx_tex_bind,
2770    tex_from_mc: gfx_tex_from_mc,
2771    draw_arrays: gfx_draw_arrays,
2772    draw_elements: gfx_draw_elements,
2773    set_blend: gfx_set_blend,
2774    set_depth: gfx_set_depth,
2775    set_scissor: gfx_set_scissor,
2776    clear_scissor: gfx_clear_scissor,
2777    set_viewport: gfx_set_viewport,
2778    draw2d_rect: gfx_draw2d_rect,
2779    draw2d_gradient: gfx_draw2d_gradient,
2780    draw2d_text: gfx_draw2d_text,
2781    draw2d_mc_tex: gfx_draw2d_mc_tex,
2782    draw2d_item: gfx_draw2d_item,
2783    draw2d_text_font: gfx_draw2d_text_font,
2784    text_width: gfx_text_width,
2785    draw2d_image: images::gfx_draw2d_image,
2786    image_size: images::gfx_image_size,
2787};
2788
2789// ── YogApi registration functions ─────────────────────────────────────────────
2790//
2791// ctx is *const RwLock<RuntimeHandlers> (cast from *mut c_void) — see
2792// `current_registration()` for how each entry gets tagged with the
2793// `LoadedModule` that's registering it.
2794
2795macro_rules! api_event {
2796    ($name:ident, $field:ident, $fn_ty:ty) => {
2797        unsafe extern "C" fn $name(ctx: *mut c_void, ud: *mut c_void, h: $fn_ty) {
2798            let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2799            let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2800            handlers.$field.push((current_registration(), ud, h));
2801        }
2802    };
2803}
2804
2805api_event!(api_on_block_break, block_break, YogBlockBreakFn);
2806api_event!(api_on_chat, chat, YogChatFn);
2807api_event!(api_on_player_join, player_join, YogPlayerFn);
2808api_event!(api_on_player_leave, player_leave, YogPlayerFn);
2809api_event!(api_on_use_item, use_item, YogUseItemFn);
2810api_event!(api_on_use_block, use_block, YogUseBlockFn);
2811api_event!(api_on_attack_entity, attack_entity, YogAttackEntityFn);
2812api_event!(api_on_entity_damage, entity_damage, YogEntityDamageFn);
2813api_event!(api_on_entity_death, entity_death, YogEntityDeathFn);
2814api_event!(api_on_entity_spawn, entity_spawn, YogEntitySpawnFn);
2815api_event!(
2816    api_on_player_place_block,
2817    player_place_block,
2818    YogPlaceBlockFn
2819);
2820api_event!(api_on_player_death, player_death, YogPlayerDeathFn);
2821api_event!(api_on_player_respawn, player_respawn, YogPlayerRespawnFn);
2822api_event!(api_on_advancement, advancement, YogAdvancementFn);
2823api_event!(api_on_entity_interact, entity_interact, YogEntityInteractFn);
2824api_event!(api_on_item_craft, item_craft, YogCraftFn);
2825api_event!(api_on_explosion, explosion, YogExplosionFn);
2826api_event!(api_on_item_pickup, item_pickup, YogItemPickupFn);
2827api_event!(api_on_player_move, player_move, YogPlayerMoveFn);
2828api_event!(api_on_container_open, container_open, YogContainerOpenFn);
2829api_event!(api_on_container_close, container_close, YogContainerCloseFn);
2830api_event!(api_on_projectile_hit, projectile_hit, YogProjectileHitFn);
2831api_event!(api_on_client_tick, client_tick, YogClientFn);
2832api_event!(api_on_hud_render, hud_render, YogHudRenderFn);
2833api_event!(api_on_world_render, world_render, YogWorldRenderFn);
2834api_event!(api_on_key_press, key_press, YogKeyPressFn);
2835api_event!(api_on_screen_open, screen_open, YogScreenFn);
2836api_event!(api_on_screen_close, screen_close, YogScreenFn);
2837api_event!(api_on_server_tick, server_tick, YogServerFn);
2838api_event!(api_on_server_started, server_started, YogServerFn);
2839api_event!(api_on_server_stopping, server_stopping, YogServerFn);
2840
2841unsafe extern "C" fn api_on_packet(
2842    ctx: *mut c_void,
2843    channel: YogStr,
2844    ud: *mut c_void,
2845    h: YogPacketFn,
2846) {
2847    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2848    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2849    handlers
2850        .packets
2851        .insert(channel.as_str().to_owned(), (current_registration(), ud, h));
2852}
2853
2854unsafe extern "C" fn api_on_client_packet(
2855    ctx: *mut c_void,
2856    channel: YogStr,
2857    ud: *mut c_void,
2858    h: YogPacketFn,
2859) {
2860    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2861    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2862    handlers
2863        .client_packets
2864        .insert(channel.as_str().to_owned(), (current_registration(), ud, h));
2865}
2866
2867unsafe extern "C" fn api_register_command(
2868    ctx: *mut c_void,
2869    name: YogStr,
2870    ud: *mut c_void,
2871    h: YogCommandFn,
2872) {
2873    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2874    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2875    handlers
2876        .commands
2877        .entry(name.as_str().to_owned())
2878        .or_default()
2879        .push((current_registration(), None, ud, h));
2880}
2881
2882unsafe extern "C" fn api_register_typed_command(
2883    ctx: *mut c_void,
2884    name: YogStr,
2885    schema: YogStr,
2886    ud: *mut c_void,
2887    h: YogCommandFn,
2888) {
2889    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2890    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2891    let n = name.as_str().to_owned();
2892    handlers.commands.entry(n).or_default().push((
2893        current_registration(),
2894        Some(schema.as_str().to_owned()),
2895        ud,
2896        h,
2897    ));
2898}
2899
2900unsafe extern "C" fn api_register_recipe_json(
2901    ctx: *mut c_void,
2902    namespace: YogStr,
2903    name: YogStr,
2904    json: YogStr,
2905) {
2906    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2907    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2908    let ns = namespace.as_str().to_owned();
2909    let n = name.as_str().to_owned();
2910    let j = json.as_str().to_owned();
2911    // Feed the recipe to already-registered book renderers (crafting pages).
2912    let full_id = format!("{}:{}", ns, n);
2913    for r in handlers.book_renderers.lock().unwrap().values_mut() {
2914        r.add_recipe(full_id.clone(), &j);
2915    }
2916    handlers.recipes.push((ns, n, j));
2917}
2918
2919unsafe extern "C" fn api_register_item(ctx: *mut c_void, def: *const YogItemDef) {
2920    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2921    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2922    let d = &*def;
2923    let food = if d.food_nutrition > 0 {
2924        Some(FoodDef {
2925            nutrition: d.food_nutrition,
2926            saturation: d.food_saturation,
2927            can_always_eat: d.food_always_eat,
2928        })
2929    } else {
2930        None
2931    };
2932    handlers.items.push(ItemDef {
2933        id: d.id.as_str().to_owned(),
2934        max_stack: d.max_stack as u8,
2935        name: if d.name.is_empty() {
2936            None
2937        } else {
2938            Some(d.name.as_str().to_owned())
2939        },
2940        tooltip: if d.tooltip.is_empty() {
2941            None
2942        } else {
2943            Some(d.tooltip.as_str().to_owned())
2944        },
2945        max_damage: d.max_damage,
2946        fire_resistant: d.fire_resistant,
2947        fuel_ticks: d.fuel_ticks,
2948        food,
2949    });
2950}
2951
2952unsafe extern "C" fn api_register_block(ctx: *mut c_void, def: *const YogBlockDef) {
2953    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
2954    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
2955    let d = &*def;
2956    handlers.blocks.push(BlockDef {
2957        id: d.id.as_str().to_owned(),
2958        hardness: d.hardness,
2959        resistance: d.resistance,
2960        name: if d.name.is_empty() {
2961            None
2962        } else {
2963            Some(d.name.as_str().to_owned())
2964        },
2965        light_level: d.light_level,
2966        sound: if d.sound.is_empty() {
2967            None
2968        } else {
2969            Some(d.sound.as_str().to_owned())
2970        },
2971        requires_tool: d.requires_tool,
2972        no_collision: d.no_collision,
2973        slipperiness: d.slipperiness,
2974        shape: if d.shape == [0.0f32; 6] {
2975            None
2976        } else {
2977            Some(d.shape)
2978        },
2979        connects: d.connects,
2980        connect_groups: if d.connect_groups.is_empty() {
2981            Vec::new()
2982        } else {
2983            d.connect_groups
2984                .as_str()
2985                .split(',')
2986                .map(str::to_owned)
2987                .collect()
2988        },
2989        inventory_id: if d.inventory_id.is_empty() {
2990            None
2991        } else {
2992            Some(d.inventory_id.as_str().to_owned())
2993        },
2994    });
2995}
2996
2997unsafe extern "C" fn api_register_inventory(
2998    ctx: *mut c_void,
2999    def: *const yog_abi::YogInventoryDef,
3000) {
3001    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3002    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3003    let d = &*def;
3004    handlers.inventories.push(yog_inventory::InventoryDef {
3005        id: d.id.as_str().to_owned(),
3006        slot_count: d.slot_count as usize,
3007        layout: yog_inventory::decode_layout(d.layout.as_str()),
3008        include_player_inventory: d.include_player_inventory,
3009        player_inv_offset: (d.player_inv_x, d.player_inv_y),
3010        background_texture: if d.background_texture.is_empty() {
3011            None
3012        } else {
3013            Some(d.background_texture.as_str().to_owned())
3014        },
3015        title: d.title.as_str().to_owned(),
3016        background_size: if d.background_w > 0.0 && d.background_h > 0.0 {
3017            Some((d.background_w, d.background_h))
3018        } else {
3019            None
3020        },
3021    });
3022}
3023
3024unsafe extern "C" fn api_schedule_once(
3025    ctx: *mut c_void,
3026    delay_ticks: u64,
3027    ud: *mut c_void,
3028    h: YogScheduledFn,
3029) {
3030    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3031    let handlers = handlers_lock.write().expect("handlers lock poisoned");
3032    handlers
3033        .scheduler
3034        .lock()
3035        .expect("scheduler poisoned")
3036        .once_tasks
3037        .push(OnceTask {
3038            module: current_registration(),
3039            delay_remaining: delay_ticks,
3040            ud,
3041            f: h,
3042        });
3043}
3044
3045unsafe extern "C" fn api_schedule_repeating(
3046    ctx: *mut c_void,
3047    period_ticks: u64,
3048    ud: *mut c_void,
3049    h: YogScheduledFn,
3050) {
3051    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3052    let handlers = handlers_lock.write().expect("handlers lock poisoned");
3053    handlers
3054        .scheduler
3055        .lock()
3056        .expect("scheduler poisoned")
3057        .repeating_tasks
3058        .push(RepeatingTask {
3059            module: current_registration(),
3060            period: period_ticks,
3061            ticks_left: period_ticks,
3062            ud,
3063            f: h,
3064        });
3065}
3066
3067unsafe extern "C" fn api_register_startup_grant(
3068    ctx: *mut c_void,
3069    grant: *const YogStartupGrantDef,
3070) {
3071    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3072    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3073    let g = &*grant;
3074    let items: Vec<String> = if g.items.is_empty() {
3075        Vec::new()
3076    } else {
3077        unsafe {
3078            g.items
3079                .as_str()
3080                .split('|')
3081                .map(|s: &str| s.to_owned())
3082                .collect()
3083        }
3084    };
3085    let book = if g.book.is_empty() {
3086        None
3087    } else {
3088        Some(unsafe { g.book.as_str().to_owned() })
3089    };
3090    let command = if g.command.is_empty() {
3091        None
3092    } else {
3093        Some(unsafe { g.command.as_str().to_owned() })
3094    };
3095    handlers.startup_grants.push(yog_registry::StartupGrant {
3096        id: unsafe { g.id.as_str().to_owned() },
3097        items,
3098        book,
3099        command,
3100    });
3101}
3102
3103unsafe extern "C" fn api_register_book(ctx: *mut c_void, book_id: YogStr, book_json: YogStr) {
3104    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3105    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3106    let id = unsafe { book_id.as_str().to_owned() };
3107    let json = unsafe { book_json.as_str().to_owned() };
3108    handlers.books.insert(id.clone(), json.clone());
3109    // Parse JSON → Book → BookRenderer so rendering works without Java round-trip.
3110    match serde_json::from_str::<yog_book::Book>(&json) {
3111        Ok(book) => {
3112            let mut renderer = yog_book::BookRenderer::new(book);
3113            // Recipes may register before the book — replay them into the renderer.
3114            for (ns, name, j) in &handlers.recipes {
3115                renderer.add_recipe(format!("{}:{}", ns, name), j);
3116            }
3117            handlers.book_renderers.lock().unwrap().insert(id, renderer);
3118        }
3119        Err(e) => {
3120            yog_logging::warn!(
3121                "book '{}': failed to parse JSON for Rust renderer: {}",
3122                id,
3123                e
3124            );
3125        }
3126    }
3127}
3128
3129unsafe extern "C" fn api_register_dimension_json(ctx: *mut c_void, id: YogStr, json: YogStr) {
3130    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3131    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3132    let id = unsafe { id.as_str().to_owned() };
3133    let json = unsafe { json.as_str().to_owned() };
3134    yog_logging::info!("registered dimension type: {}", id);
3135    handlers.dimensions.insert(id, json);
3136}
3137
3138unsafe extern "C" fn api_register_chunk_generator(
3139    ctx: *mut c_void,
3140    id: YogStr,
3141    ud: *mut c_void,
3142    h: yog_abi::YogChunkGeneratorFn,
3143) {
3144    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3145    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3146    let id = unsafe { id.as_str().to_owned() };
3147    yog_logging::info!("registered chunk generator for dimension: {}", id);
3148    handlers
3149        .chunk_generators
3150        .insert(id, (current_registration(), ud, h));
3151}
3152
3153unsafe extern "C" fn api_register_ui(
3154    ctx: *mut c_void,
3155    ui_id: YogStr,
3156    _layout_json: YogStr,
3157    ud: *mut c_void,
3158    h: yog_abi::YogUIEventFn,
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_handlers
3165        .insert(id.clone(), (current_registration(), ud, h));
3166    yog_logging::info!("registered UI handler: {}", id);
3167}
3168
3169unsafe extern "C" fn api_on_ui_render(
3170    ctx: *mut c_void,
3171    ui_id: YogStr,
3172    ud: *mut c_void,
3173    h: YogHudRenderFn,
3174) {
3175    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3176    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3177    let id = unsafe { ui_id.as_str().to_owned() };
3178    handlers
3179        .ui_render_handlers
3180        .entry(id)
3181        .or_default()
3182        .push((current_registration(), ud, h));
3183}
3184
3185/// Strip TSV separators so one mod can't corrupt the line format.
3186fn tsv_clean(v: &str) -> String {
3187    v.replace(['\t', '\n', '\r'], " ")
3188}
3189
3190unsafe extern "C" fn api_ui_open(_ctx: *mut c_void, ui_id: YogStr, modal: bool, pause: bool) {
3191    let id = ui_id.as_str().to_string();
3192    if id.is_empty() {
3193        return;
3194    }
3195    let Some(mut env) = get_env() else { return };
3196    let Ok(jid) = env.new_string(&id) else { return };
3197    let jid_obj: JObject = jid.into();
3198    // Java side schedules onto the render thread; no-op on dedicated servers.
3199    if env
3200        .call_static_method(
3201            "dev/yog/NativeBridge",
3202            "openUI",
3203            "(Ljava/lang/String;ZZ)V",
3204            &[
3205                JValue::Object(&jid_obj),
3206                JValue::Bool(modal as u8),
3207                JValue::Bool(pause as u8),
3208            ],
3209        )
3210        .is_err()
3211    {
3212        env.exception_clear().ok();
3213    }
3214}
3215
3216unsafe extern "C" fn api_ui_close(_ctx: *mut c_void, ui_id: YogStr) {
3217    let id = ui_id.as_str().to_string();
3218    let Some(mut env) = get_env() else { return };
3219    let Ok(jid) = env.new_string(&id) else { return };
3220    let jid_obj: JObject = jid.into();
3221    // Java side defers onto the render thread (after the current event) and
3222    // checks the open screen is that Yog UI; no-op on dedicated servers.
3223    if env
3224        .call_static_method(
3225            "dev/yog/NativeBridge",
3226            "closeUI",
3227            "(Ljava/lang/String;)V",
3228            &[JValue::Object(&jid_obj)],
3229        )
3230        .is_err()
3231    {
3232        env.exception_clear().ok();
3233    }
3234}
3235
3236unsafe extern "C" fn api_ui_consume_event(_ctx: *mut c_void) {
3237    ui_jni::consume_event();
3238}
3239
3240unsafe extern "C" fn api_translate(_ctx: *mut c_void, key: YogStr) -> YogOwnedStr {
3241    let key = key.as_str();
3242    if key.is_empty() {
3243        return YogOwnedStr::NONE;
3244    }
3245    let Some(mut env) = get_env() else {
3246        return YogOwnedStr::NONE;
3247    };
3248    let Ok(jkey) = env.new_string(key) else {
3249        return YogOwnedStr::NONE;
3250    };
3251    let jkey: JObject = jkey.into();
3252    let result = env.call_static_method(
3253        "dev/yog/NativeBridge",
3254        "translate",
3255        "(Ljava/lang/String;)Ljava/lang/String;",
3256        &[JValue::Object(&jkey)],
3257    );
3258    let text = match result.and_then(|v| v.l()) {
3259        Ok(obj) if !obj.as_raw().is_null() => env.get_string(&obj.into()).ok().map(String::from),
3260        Ok(_) => None,
3261        Err(_) => {
3262            env.exception_clear().ok();
3263            None
3264        }
3265    };
3266    let _ = env.delete_local_ref(jkey);
3267    text.map_or(YogOwnedStr::NONE, YogOwnedStr::from_string)
3268}
3269
3270unsafe extern "C" fn api_mods_list(_ctx: *mut c_void) -> YogOwnedStr {
3271    let mut lines: Vec<String> = Vec::new();
3272    for m in MOD_INFOS.lock().expect("mod infos lock poisoned").iter() {
3273        lines.push(format!(
3274            "yog\t{}\t{}\t{}\t{}\t{}",
3275            m[0], m[1], m[2], m[3], m[4]
3276        ));
3277    }
3278    // Loader-side (Java) mods, if the host provides them.
3279    if let Some(mut env) = get_env() {
3280        if let Ok(val) = env.call_static_method(
3281            "dev/yog/NativeBridge",
3282            "listPlatformMods",
3283            "()Ljava/lang/String;",
3284            &[],
3285        ) {
3286            if let Ok(obj) = val.l() {
3287                if !obj.as_raw().is_null() {
3288                    if let Ok(js) = env.get_string(&obj.into()) {
3289                        for line in String::from(js).lines() {
3290                            if !line.trim().is_empty() {
3291                                lines.push(format!("platform\t{line}"));
3292                            }
3293                        }
3294                    }
3295                }
3296            }
3297        } else {
3298            env.exception_clear().ok();
3299        }
3300    }
3301    YogOwnedStr::from_string(lines.join("\n"))
3302}
3303
3304unsafe extern "C" fn api_register_menu_entry(ctx: *mut c_void, label: YogStr, ui_id: YogStr) {
3305    let handlers_lock = &*(ctx as *const RwLock<RuntimeHandlers>);
3306    let mut handlers = handlers_lock.write().expect("handlers lock poisoned");
3307    let l = unsafe { label.as_str().to_owned() };
3308    let u = unsafe { ui_id.as_str().to_owned() };
3309    handlers.menu_entries.push((l, u));
3310}
3311
3312#[no_mangle]
3313pub extern "system" fn Java_dev_yog_NativeBridge_nativeBookJson<'l>(
3314    mut env: JNIEnv<'l>,
3315    _class: JClass<'l>,
3316    book_id: JString<'l>,
3317) -> jstring {
3318    let id = match env.get_string(&book_id) {
3319        Ok(s) => String::from(s),
3320        Err(_) => return std::ptr::null_mut(),
3321    };
3322    let json = handlers()
3323        .books
3324        .get(&id)
3325        .cloned()
3326        .unwrap_or_else(|| "null".to_string());
3327    env.new_string(json)
3328        .map(|s| s.into_raw())
3329        .unwrap_or(std::ptr::null_mut())
3330}
3331
3332// ── Table constructors ────────────────────────────────────────────────────────
3333
3334fn build_server_table() -> YogServer {
3335    YogServer {
3336        ctx: std::ptr::null_mut(),
3337        abi_version: ABI_VERSION,
3338        size: std::mem::size_of::<YogServer>() as u32,
3339        free_str: yog_free_str,
3340        broadcast: srv_broadcast,
3341        get_block: srv_get_block,
3342        set_block: srv_set_block,
3343        world_time: srv_world_time,
3344        set_time: srv_set_time,
3345        is_raining: srv_is_raining,
3346        set_weather: srv_set_weather,
3347        give_item: srv_give_item,
3348        player_teleport: srv_player_teleport,
3349        send_to_player: srv_send_to_player,
3350        send_to_server: srv_send_to_server,
3351        kick_player: srv_kick_player,
3352        set_gamemode: srv_set_gamemode,
3353        send_title: srv_send_title,
3354        send_actionbar: srv_send_actionbar,
3355        play_sound: srv_play_sound,
3356        play_sound_player: srv_play_sound_player,
3357        entity_teleport: srv_entity_teleport,
3358        entity_position: srv_entity_position,
3359        entity_rotation: srv_entity_rotation,
3360        entity_health: srv_entity_health,
3361        entity_set_health: srv_entity_set_health,
3362        entity_kill: srv_entity_kill,
3363        spawn_entity: srv_spawn_entity,
3364        entity_add_effect: srv_entity_add_effect,
3365        entity_remove_effect: srv_entity_remove_effect,
3366        entity_clear_effects: srv_entity_clear_effects,
3367        entity_velocity: srv_entity_velocity,
3368        entity_set_velocity: srv_entity_set_velocity,
3369        entity_add_velocity: srv_entity_add_velocity,
3370        has_item_tag: srv_has_item_tag,
3371        has_block_tag: srv_has_block_tag,
3372        drop_loot: srv_drop_loot,
3373        scoreboard_get: srv_scoreboard_get,
3374        scoreboard_set: srv_scoreboard_set,
3375        scoreboard_add: srv_scoreboard_add,
3376        bossbar_create: srv_bossbar_create,
3377        bossbar_remove: srv_bossbar_remove,
3378        bossbar_set_title: srv_bossbar_set_title,
3379        bossbar_set_progress: srv_bossbar_set_progress,
3380        bossbar_set_color: srv_bossbar_set_color,
3381        bossbar_add_player: srv_bossbar_add_player,
3382        bossbar_remove_player: srv_bossbar_remove_player,
3383        bossbar_set_visible: srv_bossbar_set_visible,
3384        game_dir: srv_game_dir,
3385        get_block_nbt: srv_get_block_nbt,
3386        set_block_nbt: srv_set_block_nbt,
3387        get_inventory_slot: srv_get_inventory_slot,
3388        set_inventory_slot: srv_set_inventory_slot,
3389        player_inventory: srv_player_inventory,
3390        player_set_slot: srv_player_set_slot,
3391        player_teleport_dim: srv_player_teleport_dim,
3392        entity_teleport_dim: srv_entity_teleport_dim,
3393        online_players: srv_online_players,
3394        world_entity_count: srv_world_entity_count,
3395        entity_get_nbt: srv_entity_get_nbt,
3396        entity_set_nbt: srv_entity_set_nbt,
3397        spawn_particles: srv_spawn_particles,
3398        entity_attribute_get: srv_entity_attribute_get,
3399        entity_attribute_set: srv_entity_attribute_set,
3400        get_held_item_nbt: srv_get_held_item_nbt,
3401        set_held_item_nbt: srv_set_held_item_nbt,
3402        get_offhand_item_nbt: srv_get_offhand_item_nbt,
3403        set_offhand_item_nbt: srv_set_offhand_item_nbt,
3404        get_slot_item: srv_get_slot_item,
3405        set_slot_item: srv_set_slot_item,
3406    }
3407}
3408
3409// ── Inter-mod communication ──────────────────────────────────────────────────
3410
3411unsafe extern "C" fn interop_export_impl(
3412    _ctx: *mut c_void,
3413    mod_id: YogStr,
3414    symbol: YogStr,
3415    ptr: *const c_void,
3416) {
3417    let mod_id = mod_id.as_str();
3418    let sym = symbol.as_str();
3419    let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3420    registry
3421        .lock()
3422        .expect("interop symbols lock poisoned")
3423        .entry(mod_id.to_string())
3424        .or_default()
3425        .insert(sym.to_string(), ptr as usize);
3426}
3427
3428unsafe extern "C" fn interop_import_impl(
3429    _ctx: *mut c_void,
3430    mod_id: YogStr,
3431    symbol: YogStr,
3432) -> *const c_void {
3433    let mod_id = mod_id.as_str();
3434    let sym = symbol.as_str();
3435    let registry = INTEROP_SYMBOLS.get_or_init(|| Mutex::new(HashMap::new()));
3436    registry
3437        .lock()
3438        .expect("interop symbols lock poisoned")
3439        .get(mod_id)
3440        .and_then(|m: &HashMap<String, usize>| m.get(sym))
3441        .map(|&ptr| ptr as *const c_void)
3442        .unwrap_or(std::ptr::null())
3443}
3444
3445fn build_api_table(ctx: *const RwLock<RuntimeHandlers>, server: *const YogServer) -> YogApi {
3446    YogApi {
3447        abi_version: ABI_VERSION,
3448        size: std::mem::size_of::<YogApi>() as u32,
3449        ctx: ctx as *mut c_void,
3450        server,
3451        on_block_break: api_on_block_break,
3452        on_chat: api_on_chat,
3453        on_player_join: api_on_player_join,
3454        on_player_leave: api_on_player_leave,
3455        on_use_item: api_on_use_item,
3456        on_use_block: api_on_use_block,
3457        on_attack_entity: api_on_attack_entity,
3458        on_entity_damage: api_on_entity_damage,
3459        on_entity_death: api_on_entity_death,
3460        on_entity_spawn: api_on_entity_spawn,
3461        on_player_place_block: api_on_player_place_block,
3462        on_player_death: api_on_player_death,
3463        on_player_respawn: api_on_player_respawn,
3464        on_advancement: api_on_advancement,
3465        on_entity_interact: api_on_entity_interact,
3466        on_item_craft: api_on_item_craft,
3467        on_explosion: api_on_explosion,
3468        on_item_pickup: api_on_item_pickup,
3469        on_player_move: api_on_player_move,
3470        on_container_open: api_on_container_open,
3471        on_container_close: api_on_container_close,
3472        on_projectile_hit: api_on_projectile_hit,
3473        on_server_tick: api_on_server_tick,
3474        on_server_started: api_on_server_started,
3475        on_server_stopping: api_on_server_stopping,
3476        on_packet: api_on_packet,
3477        on_client_packet: api_on_client_packet,
3478        register_command: api_register_command,
3479        register_typed_command: api_register_typed_command,
3480        register_recipe_json: api_register_recipe_json,
3481        register_item: api_register_item,
3482        register_block: api_register_block,
3483        schedule_once: api_schedule_once,
3484        schedule_repeating: api_schedule_repeating,
3485        on_client_tick: api_on_client_tick,
3486        on_hud_render: api_on_hud_render,
3487        on_key_press: api_on_key_press,
3488        on_screen_open: api_on_screen_open,
3489        on_screen_close: api_on_screen_close,
3490        on_world_render: api_on_world_render,
3491        register_startup_grant: api_register_startup_grant,
3492        register_book: api_register_book,
3493        register_ui: api_register_ui,
3494        on_ui_render: api_on_ui_render,
3495        register_menu_entry: api_register_menu_entry,
3496        mods_list: api_mods_list,
3497        free_str: yog_free_str,
3498        ui_open: api_ui_open,
3499        register_inventory: api_register_inventory,
3500        interop_export: interop_export_impl,
3501        interop_import: interop_import_impl,
3502        register_dimension_json: api_register_dimension_json,
3503        register_chunk_generator: api_register_chunk_generator,
3504        ui_close: api_ui_close,
3505        ui_consume_event: api_ui_consume_event,
3506        translate: api_translate,
3507    }
3508}
3509
3510// ── Mod loading ───────────────────────────────────────────────────────────────
3511
3512fn platform_tag() -> String {
3513    format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
3514}
3515
3516type AbiVersionFn = unsafe extern "C" fn() -> u32;
3517/// Second arg is a null-terminated C string: the mod's `id` from its manifest.
3518type RegisterFn = unsafe extern "C" fn(*const YogApi, *const std::os::raw::c_char);
3519
3520fn load_mods(dir: &Path, api: &YogApi) {
3521    let entries = match std::fs::read_dir(dir) {
3522        Ok(e) => e,
3523        Err(_) => {
3524            yog_logging::info!("no mods directory at {} — none loaded", dir.display());
3525            return;
3526        }
3527    };
3528
3529    // First pass: collect (mod_id, dependencies, archive_path)
3530    struct ModMeta {
3531        id: String,
3532        deps: Vec<String>,
3533        yog_path: PathBuf,
3534    }
3535    let mut metas: Vec<ModMeta> = Vec::new();
3536    for entry in entries.flatten() {
3537        let path = entry.path();
3538        if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3539            continue;
3540        }
3541        let info = read_mod_info(&path);
3542        let deps = read_mod_deps(&path);
3543        metas.push(ModMeta {
3544            id: info[0].clone(),
3545            deps,
3546            yog_path: path,
3547        });
3548    }
3549
3550    // Topological sort by dependencies (Kahn's algorithm)
3551    let ids: Vec<String> = metas.iter().map(|m| m.id.clone()).collect();
3552    let deps_list: Vec<Vec<String>> = metas.iter().map(|m| m.deps.clone()).collect();
3553    let sorted = match topo_sort_mods(&ids, &deps_list) {
3554        Some(v) => v,
3555        None => {
3556            yog_logging::error!("circular dependency detected — loading in file order");
3557            (0..metas.len()).collect()
3558        }
3559    };
3560
3561    let mut loaded: Vec<LoadedModule> = Vec::new();
3562    for idx in sorted {
3563        let meta = &metas[idx];
3564        let lib_path = match extract_yog(&meta.yog_path) {
3565            Some(p) => p,
3566            None => {
3567                yog_logging::error!(
3568                    "no native for {} in {}",
3569                    platform_tag(),
3570                    meta.yog_path.display()
3571                );
3572                continue;
3573            }
3574        };
3575        if let Some(module) = load_mod_lib(&lib_path, api, &meta.id) {
3576            loaded.push(module);
3577            MOD_INFOS
3578                .lock()
3579                .expect("mod infos lock poisoned")
3580                .push(read_mod_info(&meta.yog_path));
3581        }
3582    }
3583    yog_logging::info!("loaded {} mod(s) from {}", loaded.len(), dir.display());
3584
3585    // Final import resolution pass — after all mods loaded, each mod's pending
3586    // imports can be resolved against the now-complete interop table.
3587    resolve_all_imports(api, &loaded);
3588}
3589
3590/// After all mods loaded, call `__yog_resolve_imports_final` on each library
3591/// to resolve any imports that weren't satisfied during initial registration.
3592fn resolve_all_imports(api: &YogApi, loaded: &[LoadedModule]) {
3593    for module in loaded {
3594        module.with_library(|lib| unsafe {
3595            type ResolveFn = unsafe extern "C" fn(*const YogApi);
3596            if let Ok(resolve) = lib.get::<ResolveFn>(b"__yog_resolve_imports_final") {
3597                resolve(api as *const YogApi);
3598            }
3599        });
3600    }
3601}
3602
3603/// Kahn's algorithm — returns indices in dependency order, or None on cycle.
3604fn topo_sort_mods(ids: &[String], deps_list: &[Vec<String>]) -> Option<Vec<usize>> {
3605    let n = ids.len();
3606    let name_to_idx: HashMap<&str, usize> = ids
3607        .iter()
3608        .enumerate()
3609        .map(|(i, id)| (id.as_str(), i))
3610        .collect();
3611    let mut in_degree = vec![0u32; n];
3612    let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
3613    for (i, deps) in deps_list.iter().enumerate() {
3614        for dep in deps {
3615            if let Some(&j) = name_to_idx.get(dep.as_str()) {
3616                adj[j].push(i);
3617                in_degree[i] += 1;
3618            }
3619        }
3620    }
3621    let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
3622    let mut order = Vec::with_capacity(n);
3623    while let Some(u) = queue.pop() {
3624        order.push(u);
3625        for &v in &adj[u] {
3626            in_degree[v] -= 1;
3627            if in_degree[v] == 0 {
3628                queue.push(v);
3629            }
3630        }
3631    }
3632    if order.len() == n {
3633        Some(order)
3634    } else {
3635        None
3636    }
3637}
3638
3639/// Loads `path` as a fresh generation of `mod_id` via the shared
3640/// `HotReloader`, validates its ABI, and calls its `yog_mod_register` —
3641/// tagging every handler it registers with the returned `LoadedModule` (see
3642/// `current_registration()`). On any failure the module (if it got as far
3643/// as being tracked by the `HotReloader`) is retired immediately: nothing
3644/// can be executing inside a library that never successfully registered,
3645/// so this always hard-unloads right away rather than lingering.
3646fn load_mod_lib(path: &Path, api: &YogApi, mod_id: &str) -> Option<LoadedModule> {
3647    let module = match unsafe { hot_reloader().load_initial(mod_id, path) } {
3648        Ok(m) => m,
3649        Err(e) => {
3650            yog_logging::error!("failed to load {}: {}", path.display(), e);
3651            return None;
3652        }
3653    };
3654
3655    let abi = module.with_library(|lib| unsafe {
3656        let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
3657        Some(abi())
3658    });
3659    match abi.flatten() {
3660        Some(v) if v == ABI_VERSION => {}
3661        Some(v) => {
3662            yog_logging::error!(
3663                "{}: ABI {} incompatible with runtime ABI {}",
3664                path.display(),
3665                v,
3666                ABI_VERSION
3667            );
3668            module.retire(|| {});
3669            return None;
3670        }
3671        None => {
3672            yog_logging::error!("{} is not a Yog mod (no yog_abi_version)", path.display());
3673            module.retire(|| {});
3674            return None;
3675        }
3676    }
3677
3678    if let Err(e) = invoke_mod_register(&module, api, mod_id) {
3679        yog_logging::error!("{}: {}", path.display(), e);
3680        module.retire(|| {});
3681        return None;
3682    }
3683
3684    Some(module)
3685}
3686
3687/// Calls a loaded module's `yog_mod_register`, tagging everything it
3688/// registers with `module` via `CURRENT_REGISTRATION` for the duration of
3689/// the call — used for both a mod's initial load and a later hot-reload.
3690fn invoke_mod_register(module: &LoadedModule, api: &YogApi, mod_id: &str) -> Result<(), String> {
3691    module
3692        .with_library(|lib| unsafe {
3693            let register: Symbol<RegisterFn> = lib
3694                .get(b"yog_mod_register")
3695                .map_err(|_| "missing yog_mod_register".to_string())?;
3696            let mod_id_c = std::ffi::CString::new(mod_id).unwrap();
3697            *CURRENT_REGISTRATION.lock().unwrap() = Some(module.clone());
3698            register(api as *const YogApi, mod_id_c.as_ptr());
3699            *CURRENT_REGISTRATION.lock().unwrap() = None;
3700            Ok(())
3701        })
3702        .unwrap_or_else(|| Err("module was unloaded before registration could run".to_string()))
3703}
3704
3705/// Metadata for a mod file: [id, name, version, authors, description].
3706/// For .yog archives this comes from the bundled yog.toml; bare native libs
3707/// fall back to the file stem.
3708fn read_mod_info(path: &Path) -> [String; 5] {
3709    let stem = path
3710        .file_stem()
3711        .map(|s| s.to_string_lossy().into_owned())
3712        .unwrap_or_default();
3713    let mut info = [
3714        stem.clone(),
3715        stem,
3716        String::new(),
3717        String::new(),
3718        String::new(),
3719    ];
3720    if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3721        return info;
3722    }
3723    let Some(text) = (|| {
3724        let file = std::fs::File::open(path).ok()?;
3725        let mut archive = zip::ZipArchive::new(file).ok()?;
3726        let mut entry = archive.by_name("yog.toml").ok()?;
3727        let mut text = String::new();
3728        std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3729        Some(text)
3730    })() else {
3731        return info;
3732    };
3733    // Minimal parse — enough for id/name/version/description/authors without
3734    // pulling in a TOML dependency. Source manifests keep these under [mod];
3735    // the packaged manifest (written by yog-cli) has them at top level, so
3736    // keys before any section header count too.
3737    let mut in_mod = true;
3738    for line in text.lines() {
3739        let line = line.trim();
3740        if line.starts_with('[') {
3741            in_mod = line == "[mod]";
3742            continue;
3743        }
3744        if !in_mod {
3745            continue;
3746        }
3747        let Some((key, value)) = line.split_once('=') else {
3748            continue;
3749        };
3750        let key = key.trim();
3751        let value = value.trim();
3752        let unquote = |v: &str| v.trim().trim_matches('"').to_string();
3753        match key {
3754            "id" => info[0] = tsv_clean(&unquote(value)),
3755            "name" => info[1] = tsv_clean(&unquote(value)),
3756            "version" => info[2] = tsv_clean(&unquote(value)),
3757            "description" => info[4] = tsv_clean(&unquote(value)),
3758            "authors" => {
3759                let list = value.trim_start_matches('[').trim_end_matches(']');
3760                let authors: Vec<String> = list
3761                    .split(',')
3762                    .map(unquote)
3763                    .filter(|a| !a.is_empty())
3764                    .collect();
3765                info[3] = tsv_clean(&authors.join(", "));
3766            }
3767            _ => {}
3768        }
3769    }
3770    info
3771}
3772
3773/// Parse `[dependencies]` section from yog.toml inside a .yog archive.
3774fn read_mod_deps(path: &Path) -> Vec<String> {
3775    if path.extension().and_then(|e| e.to_str()) != Some("yog") {
3776        return Vec::new();
3777    }
3778    let text = (|| {
3779        let file = std::fs::File::open(path).ok()?;
3780        let mut archive = zip::ZipArchive::new(file).ok()?;
3781        let mut entry = archive.by_name("yog.toml").ok()?;
3782        let mut text = String::new();
3783        std::io::Read::read_to_string(&mut entry, &mut text).ok()?;
3784        Some(text)
3785    })();
3786    let Some(text) = text else { return Vec::new() };
3787    let mut deps = Vec::new();
3788    let mut in_deps = false;
3789    for line in text.lines() {
3790        let line = line.trim();
3791        if line.starts_with('[') {
3792            in_deps = line == "[dependencies]";
3793            continue;
3794        }
3795        if !in_deps {
3796            continue;
3797        }
3798        // `mod_id = "1.0"` or `mod_id = "*"` — we only care about the key (mod_id)
3799        if let Some((key, _)) = line.split_once('=') {
3800            deps.push(key.trim().to_string());
3801        }
3802    }
3803    deps
3804}
3805
3806fn extract_yog(path: &Path) -> Option<PathBuf> {
3807    let file = std::fs::File::open(path).ok()?;
3808    let mut archive = zip::ZipArchive::new(file).ok()?;
3809    let prefix = format!("natives/{}/", platform_tag());
3810    let mut entry_name = None;
3811    for i in 0..archive.len() {
3812        let f = archive.by_index(i).ok()?;
3813        if f.name().starts_with(&prefix) && !f.name().ends_with('/') {
3814            entry_name = Some(f.name().to_string());
3815            break;
3816        }
3817    }
3818    let entry_name = entry_name?;
3819    let ext = Path::new(&entry_name)
3820        .extension()
3821        .and_then(|e| e.to_str())
3822        .unwrap_or("bin");
3823    let stem = path.file_stem()?.to_string_lossy().into_owned();
3824    // A fresh name per extraction: a hot reload must never truncate the
3825    // library the game still has mapped, and `dlopen` of an already-loaded
3826    // path would just hand back the old module.
3827    static EXTRACTIONS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
3828    let n = EXTRACTIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3829    let out =
3830        std::env::temp_dir().join(format!("yog-{}-{}-{}.{}", stem, std::process::id(), n, ext));
3831    let mut entry = archive.by_name(&entry_name).ok()?;
3832    let mut out_file = std::fs::File::create(&out).ok()?;
3833    std::io::copy(&mut entry, &mut out_file).ok()?;
3834    Some(out)
3835}
3836
3837// ── Dispatcher helpers ────────────────────────────────────────────────────────
3838
3839fn srv_ptr() -> *const YogServer {
3840    SERVER.get().expect("yog: SERVER not initialised") as *const YogServer
3841}
3842
3843// ── JNI entry points ──────────────────────────────────────────────────────────
3844
3845#[no_mangle]
3846pub extern "system" fn Java_dev_yog_NativeBridge_nativeInit<'l>(
3847    mut env: JNIEnv<'l>,
3848    _class: JClass<'l>,
3849    mods_dir: JString<'l>,
3850) {
3851    if let Ok(vm) = env.get_java_vm() {
3852        let _ = JAVA_VM.set(vm);
3853    }
3854
3855    let dir = env
3856        .get_string(&mods_dir)
3857        .map(String::from)
3858        .unwrap_or_default();
3859
3860    // Build YogServer and store in static (gets a stable address).
3861    let _ = SERVER.set(build_server_table());
3862    let server_ptr = SERVER.get().unwrap() as *const YogServer;
3863
3864    // Publish HANDLERS *before* any mod registers — its address is then
3865    // `'static`-stable for the runtime's entire lifetime, unlike the old
3866    // design (a temporary heap `Box` moved into `HANDLERS` only after
3867    // `nativeInit` returned, silently invalidating the `ctx` pointer any
3868    // later re-registration — e.g. a hot-reload — would have needed).
3869    let handlers_lock = HANDLERS.get_or_init(|| RwLock::new(RuntimeHandlers::new()));
3870    let handlers_ptr = handlers_lock as *const RwLock<RuntimeHandlers>;
3871
3872    let api: &'static YogApi = API_TABLE.get_or_init(|| build_api_table(handlers_ptr, server_ptr));
3873
3874    let _ = HOT_RELOADER.get_or_init(|| HotReloader::new(RuntimeModuleRegistry));
3875
3876    guard("mod loading", || {
3877        load_mods(Path::new(&dir), api);
3878    });
3879
3880    control_socket::start_if_requested();
3881
3882    yog_logging::info!("runtime initialised — the gate is open.");
3883}
3884
3885#[no_mangle]
3886pub extern "system" fn Java_dev_yog_NativeBridge_nativeHotReload<'l>(
3887    mut env: JNIEnv<'l>,
3888    _class: JClass<'l>,
3889    mod_id: JString<'l>,
3890    new_yog_path: JString<'l>,
3891) -> jni::sys::jboolean {
3892    let Ok(mod_id) = env.get_string(&mod_id).map(String::from) else {
3893        return 0;
3894    };
3895    let Ok(yog_path) = env.get_string(&new_yog_path).map(String::from) else {
3896        return 0;
3897    };
3898    let ok = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3899        trigger_hot_reload(&mod_id, Path::new(&yog_path))
3900    }))
3901    .unwrap_or(false);
3902    ok as jni::sys::jboolean
3903}
3904
3905/// Rebuilds and re-registers `mod_id` from a freshly built `.yog` at
3906/// `new_yog_path`: extracts the current platform's native, loads it as a
3907/// new generation via the shared `HotReloader` (which retires the previous
3908/// generation per its hybrid soft/hard-unload policy — hard-unloaded right
3909/// away if nothing's mid-call, deferred otherwise), then calls its
3910/// `yog_mod_register` again so its handlers get tagged under the new
3911/// generation. How this actually gets triggered — a keybind, a file-watch,
3912/// an IDE-driven socket — is a separate, later concern; this only needs to
3913/// work correctly once called.
3914fn trigger_hot_reload(mod_id: &str, new_yog_path: &Path) -> bool {
3915    let Some(native_path) = extract_yog(new_yog_path) else {
3916        yog_logging::error!(
3917            "hot-reload {}: no native for {} in {}",
3918            mod_id,
3919            platform_tag(),
3920            new_yog_path.display()
3921        );
3922        return false;
3923    };
3924
3925    let api = API_TABLE.get().expect("yog: nativeInit not called yet");
3926
3927    let module = match unsafe { hot_reloader().reload(mod_id, &native_path) } {
3928        Ok(m) => m,
3929        Err(e) => {
3930            yog_logging::error!(
3931                "hot-reload {}: failed to load {}: {}",
3932                mod_id,
3933                native_path.display(),
3934                e
3935            );
3936            return false;
3937        }
3938    };
3939
3940    let abi = module.with_library(|lib| unsafe {
3941        let abi: Symbol<AbiVersionFn> = lib.get(b"yog_abi_version").ok()?;
3942        Some(abi())
3943    });
3944    match abi.flatten() {
3945        Some(v) if v == ABI_VERSION => {}
3946        other => {
3947            yog_logging::error!(
3948                "hot-reload {}: ABI {:?} incompatible with runtime ABI {} — mod has no active handlers until a compatible reload succeeds",
3949                mod_id,
3950                other,
3951                ABI_VERSION
3952            );
3953            return false;
3954        }
3955    }
3956
3957    if let Err(e) = invoke_mod_register(&module, api, mod_id) {
3958        yog_logging::error!("hot-reload {}: {}", mod_id, e);
3959        return false;
3960    }
3961
3962    yog_logging::info!("hot-reloaded mod {}", mod_id);
3963    true
3964}
3965
3966#[no_mangle]
3967pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
3968    mut env: JNIEnv<'l>,
3969    _class: JClass<'l>,
3970    player: JString<'l>,
3971    block: JString<'l>,
3972    x: jint,
3973    y: jint,
3974    z: jint,
3975    dimension: JString<'l>,
3976) {
3977    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
3978    let ev = yog_abi::YogBlockBreakEvent {
3979        player: YogStr::from_str(&p),
3980        block: YogStr::from_str(&b),
3981        pos: YogBlockPos { x, y, z },
3982        dimension: YogStr::from_str(&d),
3983    };
3984    let srv = srv_ptr();
3985    guard("on_block_break", || {
3986        for (module, ud, f) in &handlers().block_break {
3987            if module.is_retiring() {
3988                continue;
3989            }
3990            let Some(_call) = module.enter() else {
3991                continue;
3992            };
3993            unsafe { f(*ud, srv, &ev, 1) };
3994        }
3995    });
3996}
3997
3998#[no_mangle]
3999pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
4000    mut env: JNIEnv<'l>,
4001    _class: JClass<'l>,
4002    player: JString<'l>,
4003    message: JString<'l>,
4004    dimension: JString<'l>,
4005) {
4006    let (p, m, d) = (
4007        jstr!(env, player),
4008        jstr!(env, message),
4009        jstr!(env, dimension),
4010    );
4011    let ev = yog_abi::YogChatEvent {
4012        player: YogStr::from_str(&p),
4013        message: YogStr::from_str(&m),
4014        dimension: YogStr::from_str(&d),
4015    };
4016    let srv = srv_ptr();
4017    guard("on_chat", || {
4018        for (module, ud, f) in &handlers().chat {
4019            if module.is_retiring() {
4020                continue;
4021            }
4022            let Some(_call) = module.enter() else {
4023                continue;
4024            };
4025            unsafe { f(*ud, srv, &ev, 1) };
4026        }
4027    });
4028}
4029
4030#[no_mangle]
4031pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
4032    mut env: JNIEnv<'l>,
4033    _class: JClass<'l>,
4034    player: JString<'l>,
4035    uuid: JString<'l>,
4036    dimension: JString<'l>,
4037) {
4038    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
4039    let ev = yog_abi::YogPlayerEvent {
4040        player: YogStr::from_str(&p),
4041        uuid: YogStr::from_str(&u),
4042        dimension: YogStr::from_str(&d),
4043    };
4044    let srv = srv_ptr();
4045    guard("on_player_join", || {
4046        for (module, ud, f) in &handlers().player_join {
4047            if module.is_retiring() {
4048                continue;
4049            }
4050            let Some(_call) = module.enter() else {
4051                continue;
4052            };
4053            unsafe { f(*ud, srv, &ev, 1) };
4054        }
4055    });
4056    // Queue startup grants: giving items directly inside the join callback
4057    // fails for the host of a freshly created world (player not fully in the
4058    // world yet), so grants are processed a few server ticks later.
4059    let h = handlers();
4060    if !h.startup_grants.is_empty() {
4061        h.pending_grants
4062            .lock()
4063            .expect("pending_grants poisoned")
4064            .push((p, u, 0));
4065    }
4066}
4067
4068/// Try to fulfil pending startup grants. Called each server tick; a grant is
4069/// only marked as given (and persisted) after `give_item` actually succeeds.
4070fn process_pending_grants() {
4071    let h = handlers();
4072    let mut pending = match h.pending_grants.try_lock() {
4073        Ok(g) => g,
4074        Err(_) => return,
4075    };
4076    if pending.is_empty() {
4077        return;
4078    }
4079    // Wait a couple of ticks after join before the first attempt.
4080    const FIRST_TRY_TICK: u32 = 2;
4081    // Give up after 30 seconds — the player probably left.
4082    const MAX_TICKS: u32 = 600;
4083
4084    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
4085    let mut still_pending = Vec::new();
4086    let mut any_new = false;
4087
4088    for (p, u, ticks) in pending.drain(..) {
4089        if ticks < FIRST_TRY_TICK {
4090            still_pending.push((p, u, ticks + 1));
4091            continue;
4092        }
4093        let mut all_ok = true;
4094        for sg in &h.startup_grants {
4095            let key = format!("{}::{}", u, sg.id);
4096            if granted.contains_key(&key) {
4097                continue;
4098            }
4099            let mut ok = true;
4100            for item_id in &sg.items {
4101                ok &= unsafe {
4102                    srv_give_item(
4103                        std::ptr::null_mut(),
4104                        YogStr::from_str(&p),
4105                        YogStr::from_str(item_id),
4106                        1,
4107                    )
4108                };
4109            }
4110            if let Some(_book) = &sg.book {
4111                ok &= unsafe {
4112                    srv_give_item(
4113                        std::ptr::null_mut(),
4114                        YogStr::from_str(&p),
4115                        YogStr::from_str("minecraft:written_book"),
4116                        1,
4117                    )
4118                };
4119            }
4120            if ok {
4121                yog_logging::info!("startup grant {} given to {}", sg.id, p);
4122                granted.insert(key, true);
4123                any_new = true;
4124            } else {
4125                all_ok = false;
4126            }
4127        }
4128        if !all_ok && ticks < MAX_TICKS {
4129            still_pending.push((p, u, ticks + 1));
4130        }
4131    }
4132    *pending = still_pending;
4133    if any_new {
4134        save_startup_granted(&granted);
4135    }
4136}
4137
4138#[no_mangle]
4139pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
4140    mut env: JNIEnv<'l>,
4141    _class: JClass<'l>,
4142    player: JString<'l>,
4143    uuid: JString<'l>,
4144    dimension: JString<'l>,
4145) {
4146    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
4147    let ev = yog_abi::YogPlayerEvent {
4148        player: YogStr::from_str(&p),
4149        uuid: YogStr::from_str(&u),
4150        dimension: YogStr::from_str(&d),
4151    };
4152    let srv = srv_ptr();
4153    guard("on_player_leave", || {
4154        for (module, ud, f) in &handlers().player_leave {
4155            if module.is_retiring() {
4156                continue;
4157            }
4158            let Some(_call) = module.enter() else {
4159                continue;
4160            };
4161            unsafe { f(*ud, srv, &ev, 1) };
4162        }
4163    });
4164}
4165
4166#[no_mangle]
4167pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
4168    mut env: JNIEnv<'l>,
4169    _class: JClass<'l>,
4170    player: JString<'l>,
4171    item: JString<'l>,
4172    sneaking: jni::sys::jboolean,
4173    dimension: JString<'l>,
4174) {
4175    let (p, i, d) = (jstr!(env, player), jstr!(env, item), jstr!(env, dimension));
4176    let ev = yog_abi::YogUseItemEvent {
4177        player: YogStr::from_str(&p),
4178        item: YogStr::from_str(&i),
4179        sneaking: sneaking != 0,
4180        dimension: YogStr::from_str(&d),
4181    };
4182    let srv = srv_ptr();
4183    guard("on_use_item", || {
4184        for (module, ud, f) in &handlers().use_item {
4185            if module.is_retiring() {
4186                continue;
4187            }
4188            let Some(_call) = module.enter() else {
4189                continue;
4190            };
4191            unsafe { f(*ud, srv, &ev, 1) };
4192        }
4193    });
4194}
4195
4196#[no_mangle]
4197pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItemPre<'l>(
4198    mut env: JNIEnv<'l>,
4199    _class: JClass<'l>,
4200    player: JString<'l>,
4201    item: JString<'l>,
4202    sneaking: jni::sys::jboolean,
4203    dimension: JString<'l>,
4204) -> jni::sys::jboolean {
4205    let h = handlers();
4206    if h.use_item.is_empty() {
4207        return 1;
4208    }
4209    let p = match env.get_string(&player) {
4210        Ok(s) => String::from(s),
4211        Err(_) => return 1,
4212    };
4213    let i = match env.get_string(&item) {
4214        Ok(s) => String::from(s),
4215        Err(_) => return 1,
4216    };
4217    let d = match env.get_string(&dimension) {
4218        Ok(s) => String::from(s),
4219        Err(_) => return 1,
4220    };
4221    let ev = yog_abi::YogUseItemEvent {
4222        player: YogStr::from_str(&p),
4223        item: YogStr::from_str(&i),
4224        sneaking: sneaking != 0,
4225        dimension: YogStr::from_str(&d),
4226    };
4227    let srv = srv_ptr();
4228    let mut allow = true;
4229    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4230        for (module, ud, f) in &h.use_item {
4231            if module.is_retiring() {
4232                continue;
4233            }
4234            let Some(_call) = module.enter() else {
4235                continue;
4236            };
4237            if !unsafe { f(*ud, srv, &ev, 0) } {
4238                allow = false;
4239                break;
4240            }
4241        }
4242    }))
4243    .ok();
4244    allow as jni::sys::jboolean
4245}
4246
4247#[no_mangle]
4248pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
4249    mut env: JNIEnv<'l>,
4250    _class: JClass<'l>,
4251    player: JString<'l>,
4252    block: JString<'l>,
4253    x: jint,
4254    y: jint,
4255    z: jint,
4256    dimension: JString<'l>,
4257) {
4258    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
4259    let ev = yog_abi::YogUseBlockEvent {
4260        player: YogStr::from_str(&p),
4261        block: YogStr::from_str(&b),
4262        pos: YogBlockPos { x, y, z },
4263        dimension: YogStr::from_str(&d),
4264    };
4265    let srv = srv_ptr();
4266    guard("on_use_block", || {
4267        for (module, ud, f) in &handlers().use_block {
4268            if module.is_retiring() {
4269                continue;
4270            }
4271            let Some(_call) = module.enter() else {
4272                continue;
4273            };
4274            unsafe { f(*ud, srv, &ev, 1) };
4275        }
4276    });
4277}
4278
4279#[no_mangle]
4280pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlockPre<'l>(
4281    mut env: JNIEnv<'l>,
4282    _class: JClass<'l>,
4283    player: JString<'l>,
4284    block: JString<'l>,
4285    x: jint,
4286    y: jint,
4287    z: jint,
4288    dimension: JString<'l>,
4289) -> jni::sys::jboolean {
4290    let h = handlers();
4291    if h.use_block.is_empty() {
4292        return 1;
4293    }
4294    let p = match env.get_string(&player) {
4295        Ok(s) => String::from(s),
4296        Err(_) => return 1,
4297    };
4298    let b = match env.get_string(&block) {
4299        Ok(s) => String::from(s),
4300        Err(_) => return 1,
4301    };
4302    let d = match env.get_string(&dimension) {
4303        Ok(s) => String::from(s),
4304        Err(_) => return 1,
4305    };
4306    let ev = yog_abi::YogUseBlockEvent {
4307        player: YogStr::from_str(&p),
4308        block: YogStr::from_str(&b),
4309        pos: YogBlockPos { x, y, z },
4310        dimension: YogStr::from_str(&d),
4311    };
4312    let srv = srv_ptr();
4313    let mut allow = true;
4314    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4315        for (module, ud, f) in &h.use_block {
4316            if module.is_retiring() {
4317                continue;
4318            }
4319            let Some(_call) = module.enter() else {
4320                continue;
4321            };
4322            if !unsafe { f(*ud, srv, &ev, 0) } {
4323                allow = false;
4324                break;
4325            }
4326        }
4327    }))
4328    .ok();
4329    allow as jni::sys::jboolean
4330}
4331
4332#[no_mangle]
4333pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
4334    mut env: JNIEnv<'l>,
4335    _class: JClass<'l>,
4336    player: JString<'l>,
4337    target_type: JString<'l>,
4338    target_uuid: JString<'l>,
4339    dimension: JString<'l>,
4340) {
4341    let (p, tt, tu, d) = (
4342        jstr!(env, player),
4343        jstr!(env, target_type),
4344        jstr!(env, target_uuid),
4345        jstr!(env, dimension),
4346    );
4347    let ev = yog_abi::YogAttackEntityEvent {
4348        player: YogStr::from_str(&p),
4349        target_type: YogStr::from_str(&tt),
4350        target_uuid: YogStr::from_str(&tu),
4351        dimension: YogStr::from_str(&d),
4352    };
4353    let srv = srv_ptr();
4354    guard("on_attack_entity", || {
4355        for (module, ud, f) in &handlers().attack_entity {
4356            if module.is_retiring() {
4357                continue;
4358            }
4359            let Some(_call) = module.enter() else {
4360                continue;
4361            };
4362            unsafe { f(*ud, srv, &ev, 1) };
4363        }
4364    });
4365}
4366
4367#[no_mangle]
4368pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
4369    mut env: JNIEnv<'l>,
4370    _class: JClass<'l>,
4371    entity_type: JString<'l>,
4372    uuid: JString<'l>,
4373    amount: jfloat,
4374    source: JString<'l>,
4375    dimension: JString<'l>,
4376) {
4377    let (et, u, s, d) = (
4378        jstr!(env, entity_type),
4379        jstr!(env, uuid),
4380        jstr!(env, source),
4381        jstr!(env, dimension),
4382    );
4383    let ev = yog_abi::YogEntityDamageEvent {
4384        entity_type: YogStr::from_str(&et),
4385        uuid: YogStr::from_str(&u),
4386        amount,
4387        source: YogStr::from_str(&s),
4388        dimension: YogStr::from_str(&d),
4389    };
4390    let srv = srv_ptr();
4391    guard("on_entity_damage", || {
4392        for (module, ud, f) in &handlers().entity_damage {
4393            if module.is_retiring() {
4394                continue;
4395            }
4396            let Some(_call) = module.enter() else {
4397                continue;
4398            };
4399            unsafe { f(*ud, srv, &ev, 1) };
4400        }
4401    });
4402}
4403
4404#[no_mangle]
4405pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
4406    mut env: JNIEnv<'l>,
4407    _class: JClass<'l>,
4408    entity_type: JString<'l>,
4409    uuid: JString<'l>,
4410    source: JString<'l>,
4411    dimension: JString<'l>,
4412) {
4413    let (et, u, s, d) = (
4414        jstr!(env, entity_type),
4415        jstr!(env, uuid),
4416        jstr!(env, source),
4417        jstr!(env, dimension),
4418    );
4419    let ev = yog_abi::YogEntityDeathEvent {
4420        entity_type: YogStr::from_str(&et),
4421        uuid: YogStr::from_str(&u),
4422        source: YogStr::from_str(&s),
4423        dimension: YogStr::from_str(&d),
4424    };
4425    let srv = srv_ptr();
4426    guard("on_entity_death", || {
4427        for (module, ud, f) in &handlers().entity_death {
4428            if module.is_retiring() {
4429                continue;
4430            }
4431            let Some(_call) = module.enter() else {
4432                continue;
4433            };
4434            unsafe { f(*ud, srv, &ev, 1) };
4435        }
4436    });
4437}
4438
4439#[no_mangle]
4440pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
4441    _env: JNIEnv<'l>,
4442    _class: JClass<'l>,
4443) {
4444    let h = handlers();
4445    let srv = srv_ptr();
4446    guard("on_tick", || {
4447        for (module, ud, f) in &h.server_tick {
4448            if module.is_retiring() {
4449                continue;
4450            }
4451            let Some(_call) = module.enter() else {
4452                continue;
4453            };
4454            unsafe { f(*ud, srv) };
4455        }
4456    });
4457
4458    guard("startup_grants", process_pending_grants);
4459
4460    // Scheduler — once tasks
4461    {
4462        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
4463        let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
4464        let mut remaining = Vec::new();
4465        for task in sched.once_tasks.drain(..) {
4466            if task.delay_remaining == 0 {
4467                to_fire.push((task.module.clone(), task.ud, task.f));
4468            } else {
4469                remaining.push(OnceTask {
4470                    delay_remaining: task.delay_remaining - 1,
4471                    ..task
4472                });
4473            }
4474        }
4475        sched.once_tasks = remaining;
4476        drop(sched);
4477        for (module, ud, f) in to_fire {
4478            if module.is_retiring() {
4479                continue;
4480            }
4481            let Some(_call) = module.enter() else {
4482                continue;
4483            };
4484            guard("schedule_once", || unsafe { f(ud, srv) });
4485        }
4486    }
4487
4488    // Scheduler — repeating tasks
4489    {
4490        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
4491        let mut to_fire: Vec<(LoadedModule, *mut c_void, YogScheduledFn)> = Vec::new();
4492        for task in &mut sched.repeating_tasks {
4493            if task.ticks_left == 0 {
4494                to_fire.push((task.module.clone(), task.ud, task.f));
4495                task.ticks_left = task.period;
4496            } else {
4497                task.ticks_left -= 1;
4498            }
4499        }
4500        drop(sched);
4501        for (module, ud, f) in to_fire {
4502            if module.is_retiring() {
4503                continue;
4504            }
4505            let Some(_call) = module.enter() else {
4506                continue;
4507            };
4508            guard("schedule_repeating", || unsafe { f(ud, srv) });
4509        }
4510    }
4511}
4512
4513/// Absolute path of the current world save folder (set on server start).
4514static WORLD_DIR: Mutex<Option<std::path::PathBuf>> = Mutex::new(None);
4515
4516/// Per-world data path: `<world>/yog-data/startup_grants.json`.
4517/// Falls back to the working directory before any server has started.
4518fn startup_grants_path() -> std::path::PathBuf {
4519    let base = WORLD_DIR
4520        .lock()
4521        .ok()
4522        .and_then(|d| d.clone())
4523        .unwrap_or_else(|| std::path::PathBuf::from("."));
4524    base.join("yog-data").join("startup_grants.json")
4525}
4526
4527fn load_startup_granted() {
4528    let h = handlers();
4529    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
4530    // Reset first — in singleplayer the process outlives individual worlds,
4531    // and grant state is per-world.
4532    granted.clear();
4533    h.pending_grants
4534        .lock()
4535        .expect("pending_grants poisoned")
4536        .clear();
4537    let path = startup_grants_path();
4538    let Ok(data) = std::fs::read_to_string(&path) else {
4539        return;
4540    };
4541    let Ok(keys) = serde_json::from_str::<Vec<String>>(&data) else {
4542        yog_logging::warn!("startup_grants.json: invalid JSON, ignoring");
4543        return;
4544    };
4545    for k in keys {
4546        granted.insert(k, true);
4547    }
4548    yog_logging::info!("loaded {} startup grants from disk", granted.len());
4549}
4550
4551fn save_startup_granted(granted: &HashMap<String, bool>) {
4552    let path = startup_grants_path();
4553    if let Some(parent) = path.parent() {
4554        let _ = std::fs::create_dir_all(parent);
4555    }
4556    let keys: Vec<&String> = granted.keys().collect();
4557    match serde_json::to_string(&keys) {
4558        Ok(json) => {
4559            let _ = std::fs::write(&path, json);
4560        }
4561        Err(e) => yog_logging::error!("failed to save startup_grants.json: {}", e),
4562    }
4563}
4564
4565#[no_mangle]
4566pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
4567    mut env: JNIEnv<'l>,
4568    _class: JClass<'l>,
4569    world_dir: JString<'l>,
4570) {
4571    let dir = jstr!(env, world_dir);
4572    *WORLD_DIR.lock().expect("WORLD_DIR poisoned") = if dir.is_empty() {
4573        None
4574    } else {
4575        Some(std::path::PathBuf::from(dir))
4576    };
4577    load_startup_granted();
4578    let srv = srv_ptr();
4579    guard("on_server_started", || {
4580        for (module, ud, f) in &handlers().server_started {
4581            if module.is_retiring() {
4582                continue;
4583            }
4584            let Some(_call) = module.enter() else {
4585                continue;
4586            };
4587            unsafe { f(*ud, srv) };
4588        }
4589    });
4590}
4591
4592#[no_mangle]
4593pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
4594    _env: JNIEnv<'l>,
4595    _class: JClass<'l>,
4596) {
4597    let srv = srv_ptr();
4598    guard("on_server_stopping", || {
4599        for (module, ud, f) in &handlers().server_stopping {
4600            if module.is_retiring() {
4601                continue;
4602            }
4603            let Some(_call) = module.enter() else {
4604                continue;
4605            };
4606            unsafe { f(*ud, srv) };
4607        }
4608    });
4609}
4610
4611#[no_mangle]
4612pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
4613    env: JNIEnv<'l>,
4614    _class: JClass<'l>,
4615) -> jstring {
4616    let names = handlers()
4617        .commands
4618        .keys()
4619        .cloned()
4620        .collect::<Vec<_>>()
4621        .join("\n");
4622    env.new_string(names)
4623        .map(|s| s.into_raw())
4624        .unwrap_or(std::ptr::null_mut())
4625}
4626
4627#[no_mangle]
4628pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
4629    mut env: JNIEnv<'l>,
4630    _class: JClass<'l>,
4631    player: JString<'l>,
4632    block: JString<'l>,
4633    x: jint,
4634    y: jint,
4635    z: jint,
4636    dimension: JString<'l>,
4637) -> jni::sys::jboolean {
4638    let h = handlers();
4639    if h.block_break.is_empty() {
4640        return 1;
4641    }
4642    let p = match env.get_string(&player) {
4643        Ok(s) => String::from(s),
4644        Err(_) => return 1,
4645    };
4646    let b = match env.get_string(&block) {
4647        Ok(s) => String::from(s),
4648        Err(_) => return 1,
4649    };
4650    let d = match env.get_string(&dimension) {
4651        Ok(s) => String::from(s),
4652        Err(_) => return 1,
4653    };
4654    let ev = yog_abi::YogBlockBreakEvent {
4655        player: YogStr::from_str(&p),
4656        block: YogStr::from_str(&b),
4657        pos: YogBlockPos { x, y, z },
4658        dimension: YogStr::from_str(&d),
4659    };
4660    let srv = srv_ptr();
4661    let mut allow = true;
4662    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4663        for (module, ud, f) in &h.block_break {
4664            if module.is_retiring() {
4665                continue;
4666            }
4667            let Some(_call) = module.enter() else {
4668                continue;
4669            };
4670            if !unsafe { f(*ud, srv, &ev, 0) } {
4671                allow = false;
4672                break;
4673            }
4674        }
4675    }))
4676    .ok();
4677    allow as jni::sys::jboolean
4678}
4679
4680#[no_mangle]
4681pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
4682    mut env: JNIEnv<'l>,
4683    _class: JClass<'l>,
4684    player: JString<'l>,
4685    message: JString<'l>,
4686    dimension: JString<'l>,
4687) -> jni::sys::jboolean {
4688    let h = handlers();
4689    if h.chat.is_empty() {
4690        return 1;
4691    }
4692    let p = match env.get_string(&player) {
4693        Ok(s) => String::from(s),
4694        Err(_) => return 1,
4695    };
4696    let m = match env.get_string(&message) {
4697        Ok(s) => String::from(s),
4698        Err(_) => return 1,
4699    };
4700    let d = match env.get_string(&dimension) {
4701        Ok(s) => String::from(s),
4702        Err(_) => return 1,
4703    };
4704    let ev = yog_abi::YogChatEvent {
4705        player: YogStr::from_str(&p),
4706        message: YogStr::from_str(&m),
4707        dimension: YogStr::from_str(&d),
4708    };
4709    let srv = srv_ptr();
4710    let mut allow = true;
4711    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4712        for (module, ud, f) in &h.chat {
4713            if module.is_retiring() {
4714                continue;
4715            }
4716            let Some(_call) = module.enter() else {
4717                continue;
4718            };
4719            if !unsafe { f(*ud, srv, &ev, 0) } {
4720                allow = false;
4721                break;
4722            }
4723        }
4724    }))
4725    .ok();
4726    allow as jni::sys::jboolean
4727}
4728
4729#[no_mangle]
4730pub extern "system" fn Java_dev_yog_NativeBridge_nativeRecipeJsons<'l>(
4731    env: JNIEnv<'l>,
4732    _class: JClass<'l>,
4733) -> jstring {
4734    let s = handlers()
4735        .recipes
4736        .iter()
4737        .map(|(ns, name, json)| format!("{}\t{}\t{}", ns, name, json))
4738        .collect::<Vec<_>>()
4739        .join("\n");
4740    env.new_string(s)
4741        .map(|s| s.into_raw())
4742        .unwrap_or(std::ptr::null_mut())
4743}
4744
4745#[no_mangle]
4746pub extern "system" fn Java_dev_yog_NativeBridge_nativeDimensionJsons<'l>(
4747    env: JNIEnv<'l>,
4748    _class: JClass<'l>,
4749) -> jstring {
4750    // One line per declared dimension: `id \t def_json`. The host converts
4751    // `def_json` (our YogDimensionDef shape) into vanilla dimension_type /
4752    // dimension datapack JSON — see `yog-dimensions` crate docs.
4753    let s = handlers()
4754        .dimensions
4755        .iter()
4756        .map(|(id, json)| format!("{}\t{}", id, json))
4757        .collect::<Vec<_>>()
4758        .join("\n");
4759    env.new_string(s)
4760        .map(|s| s.into_raw())
4761        .unwrap_or(std::ptr::null_mut())
4762}
4763
4764/// Called by a registered chunk-generator closure (via `ChunkWriter::set_block`)
4765/// to place one block in the chunk currently being generated. Calls back
4766/// into `YogCallbackChunkGenerator.setBlockInGeneratingChunk` on the Java
4767/// side — same thread, since the whole round trip is synchronous.
4768unsafe extern "C" fn chunk_writer_set_block(
4769    _ctx: *mut c_void,
4770    x: i32,
4771    y: i32,
4772    z: i32,
4773    block_id: YogStr,
4774) -> bool {
4775    let Some(mut env) = get_env() else {
4776        return false;
4777    };
4778    let Some(jb) = ys_to_java(&mut env, block_id) else {
4779        return false;
4780    };
4781    env.call_static_method(
4782        "dev/yog/NativeBridge",
4783        "setBlockInGeneratingChunk",
4784        "(IIILjava/lang/String;)Z",
4785        &[
4786            JValue::Int(x),
4787            JValue::Int(y),
4788            JValue::Int(z),
4789            JValue::Object(&jb),
4790        ],
4791    )
4792    .and_then(|v| v.z())
4793    .unwrap_or(false)
4794}
4795
4796#[no_mangle]
4797pub extern "system" fn Java_dev_yog_NativeBridge_nativeGenerateChunk<'l>(
4798    mut env: JNIEnv<'l>,
4799    _class: JClass<'l>,
4800    generator_type_id: JString<'l>,
4801    chunk_x: jint,
4802    chunk_z: jint,
4803    min_y: jint,
4804    height: jint,
4805) {
4806    let id: String = match env.get_string(&generator_type_id) {
4807        Ok(s) => String::from(s),
4808        Err(_) => return,
4809    };
4810    let Some((module, ud, cb)) = handlers().chunk_generators.get(&id).cloned() else {
4811        return;
4812    };
4813    if module.is_retiring() {
4814        return;
4815    }
4816    let Some(_call) = module.enter() else { return };
4817    let writer = yog_abi::YogChunkWriterApi {
4818        chunk_x,
4819        chunk_z,
4820        min_y,
4821        height,
4822        ctx: std::ptr::null_mut(),
4823        set_block: chunk_writer_set_block,
4824    };
4825    guard("chunk_generator", || unsafe {
4826        cb(ud, &writer as *const _);
4827    });
4828}
4829
4830#[no_mangle]
4831pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
4832    env: JNIEnv<'l>,
4833    _class: JClass<'l>,
4834) -> jstring {
4835    // One line per DISTINCT (name, schema) pair — a name can have several
4836    // typed registrations (different arg shapes); Brigadier merges repeated
4837    // `dispatcher.register()` calls for the same literal name into one tree,
4838    // so the Java side just needs every distinct schema once each.
4839    let mut lines = Vec::new();
4840    for (name, regs) in handlers().commands.iter() {
4841        let mut seen = std::collections::HashSet::new();
4842        for (_module, schema, _, _) in regs {
4843            if let Some(schema) = schema {
4844                if seen.insert(schema.as_str()) {
4845                    lines.push(format!("{}\t{}", name, schema));
4846                }
4847            }
4848        }
4849    }
4850    env.new_string(lines.join("\n"))
4851        .map(|s| s.into_raw())
4852        .unwrap_or(std::ptr::null_mut())
4853}
4854
4855#[no_mangle]
4856pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
4857    mut env: JNIEnv<'l>,
4858    _class: JClass<'l>,
4859    name: JString<'l>,
4860    args: JString<'l>,
4861    source: JString<'l>,
4862    uuid: JString<'l>,
4863    dimension: JString<'l>,
4864) -> jstring {
4865    let (n, a, s, u, d) = (
4866        env.get_string(&name).map(String::from).unwrap_or_default(),
4867        env.get_string(&args).map(String::from).unwrap_or_default(),
4868        env.get_string(&source)
4869            .map(String::from)
4870            .unwrap_or_default(),
4871        env.get_string(&uuid).map(String::from).unwrap_or_default(),
4872        env.get_string(&dimension)
4873            .map(String::from)
4874            .unwrap_or_default(),
4875    );
4876    let ev = yog_abi::YogCommandEvent {
4877        name: YogStr::from_str(&n),
4878        args: YogStr::from_str(&a),
4879        source: YogStr::from_str(&s),
4880        uuid: YogStr::from_str(&u),
4881        dimension: YogStr::from_str(&d),
4882    };
4883    let h = handlers();
4884    let srv = srv_ptr();
4885    // Actual arg count of this invocation (0 for a bare command with no args).
4886    let actual_argc = if a.is_empty() {
4887        0
4888    } else {
4889        a.split('\t').count()
4890    };
4891    let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4892        let Some(regs) = h.commands.get(&n) else {
4893            return String::new();
4894        };
4895        for (module, schema, ud, f) in regs {
4896            let expected_argc = schema.as_ref().map_or(0, |s| s.split_whitespace().count());
4897            if expected_argc != actual_argc {
4898                continue;
4899            }
4900            if module.is_retiring() {
4901                continue;
4902            }
4903            let Some(_call) = module.enter() else {
4904                continue;
4905            };
4906            let mut buf = [0u8; 4096];
4907            let mut reply_len: u32 = 0;
4908            unsafe {
4909                f(
4910                    *ud,
4911                    srv,
4912                    &ev,
4913                    buf.as_mut_ptr(),
4914                    buf.len() as u32,
4915                    &mut reply_len,
4916                )
4917            };
4918            if reply_len > 0 {
4919                return String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned();
4920            }
4921        }
4922        String::new()
4923    }))
4924    .unwrap_or_else(|_| {
4925        yog_logging::error!("a mod panicked handling command `{}`", n);
4926        String::new()
4927    });
4928
4929    env.new_string(reply)
4930        .map(|s| s.into_raw())
4931        .unwrap_or(std::ptr::null_mut())
4932}
4933
4934#[no_mangle]
4935pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemDefs<'l>(
4936    env: JNIEnv<'l>,
4937    _class: JClass<'l>,
4938) -> jstring {
4939    let s = handlers()
4940        .items
4941        .iter()
4942        .map(|d| {
4943            let mut parts = vec![d.id.clone()];
4944            parts.push(format!("max_stack={}", d.max_stack));
4945            if let Some(n) = &d.name {
4946                parts.push(format!("name={n}"));
4947            }
4948            if let Some(t) = &d.tooltip {
4949                parts.push(format!("tooltip={t}"));
4950            }
4951            if d.max_damage > 0 {
4952                parts.push(format!("max_damage={}", d.max_damage));
4953            }
4954            if d.fire_resistant {
4955                parts.push("fire_resistant=1".into());
4956            }
4957            if d.fuel_ticks > 0 {
4958                parts.push(format!("fuel_ticks={}", d.fuel_ticks));
4959            }
4960            if let Some(f) = &d.food {
4961                parts.push(format!(
4962                    "food={}:{}:{}",
4963                    f.nutrition,
4964                    f.saturation,
4965                    if f.can_always_eat { 1 } else { 0 }
4966                ));
4967            }
4968            parts.join("\t")
4969        })
4970        .collect::<Vec<_>>()
4971        .join("\n");
4972    env.new_string(s)
4973        .map(|s| s.into_raw())
4974        .unwrap_or(std::ptr::null_mut())
4975}
4976
4977#[no_mangle]
4978pub extern "system" fn Java_dev_yog_NativeBridge_nativeBlockDefs<'l>(
4979    env: JNIEnv<'l>,
4980    _class: JClass<'l>,
4981) -> jstring {
4982    let s = handlers()
4983        .blocks
4984        .iter()
4985        .map(|d| {
4986            let mut parts = vec![d.id.clone()];
4987            parts.push(format!("hardness={}", d.hardness));
4988            parts.push(format!("resistance={}", d.resistance));
4989            if let Some(n) = &d.name {
4990                parts.push(format!("name={n}"));
4991            }
4992            if let Some(sh) = d.shape {
4993                parts.push(format!(
4994                    "shape={}:{}:{}:{}:{}:{}",
4995                    sh[0], sh[1], sh[2], sh[3], sh[4], sh[5]
4996                ));
4997            }
4998            if d.light_level > 0 {
4999                parts.push(format!("light={}", d.light_level));
5000            }
5001            if let Some(snd) = &d.sound {
5002                parts.push(format!("sound={snd}"));
5003            }
5004            if d.requires_tool {
5005                parts.push("requires_tool=1".into());
5006            }
5007            if d.no_collision {
5008                parts.push("no_collision=1".into());
5009            }
5010            if d.slipperiness > 0.0 {
5011                parts.push(format!("slipperiness={}", d.slipperiness));
5012            }
5013            if d.connects {
5014                parts.push("connects=1".into());
5015            }
5016            if !d.connect_groups.is_empty() {
5017                parts.push(format!("connect_groups={}", d.connect_groups.join(",")));
5018            }
5019            if let Some(inv) = &d.inventory_id {
5020                parts.push(format!("inventory_id={inv}"));
5021            }
5022            parts.join("\t")
5023        })
5024        .collect::<Vec<_>>()
5025        .join("\n");
5026    env.new_string(s)
5027        .map(|s| s.into_raw())
5028        .unwrap_or(std::ptr::null_mut())
5029}
5030
5031/// Tab/newline-encoded inventory defs — one line per registered
5032/// `InventoryDef` (see `yog_inventory`): `id\tslot_count=N\tlayout=x:y,...
5033/// \tinclude_player_inventory=0|1\tplayer_inv=x:y\tbackground_texture=...\ttitle=...`
5034#[no_mangle]
5035pub extern "system" fn Java_dev_yog_NativeBridge_nativeInventoryDefs<'l>(
5036    env: JNIEnv<'l>,
5037    _class: JClass<'l>,
5038) -> jstring {
5039    let s = handlers()
5040        .inventories
5041        .iter()
5042        .map(|d| {
5043            let mut parts = vec![d.id.clone()];
5044            parts.push(format!("slot_count={}", d.slot_count));
5045            parts.push(format!(
5046                "layout={}",
5047                yog_inventory::encode_layout(&d.resolved_layout())
5048            ));
5049            parts.push(format!(
5050                "include_player_inventory={}",
5051                d.include_player_inventory as u8
5052            ));
5053            parts.push(format!(
5054                "player_inv={}:{}",
5055                d.player_inv_offset.0, d.player_inv_offset.1
5056            ));
5057            if let Some(tex) = &d.background_texture {
5058                parts.push(format!("background_texture={tex}"));
5059            }
5060            if !d.title.is_empty() {
5061                parts.push(format!("title={}", d.title));
5062            }
5063            if let Some((w, h)) = d.background_size {
5064                parts.push(format!("background_size={w}:{h}"));
5065            }
5066            parts.join("\t")
5067        })
5068        .collect::<Vec<_>>()
5069        .join("\n");
5070    env.new_string(s)
5071        .map(|s| s.into_raw())
5072        .unwrap_or(std::ptr::null_mut())
5073}
5074
5075#[no_mangle]
5076pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
5077    mut env: JNIEnv<'l>,
5078    _class: JClass<'l>,
5079    channel: JString<'l>,
5080    player: JString<'l>,
5081    payload: JByteArray<'l>,
5082) {
5083    let ch = env
5084        .get_string(&channel)
5085        .map(String::from)
5086        .unwrap_or_default();
5087    let pl = env
5088        .get_string(&player)
5089        .map(String::from)
5090        .unwrap_or_default();
5091    let data = env.convert_byte_array(&payload).unwrap_or_default();
5092    let ev = yog_abi::YogPacketEvent {
5093        channel: YogStr::from_str(&ch),
5094        player: YogStr::from_str(&pl),
5095        payload: data.as_ptr(),
5096        payload_len: data.len() as u32,
5097    };
5098    let h = handlers();
5099    let srv = srv_ptr();
5100    guard("on_packet", || {
5101        if let Some((module, ud, f)) = h.packets.get(&ch) {
5102            if module.is_retiring() {
5103                return;
5104            }
5105            let Some(_call) = module.enter() else { return };
5106            unsafe { f(*ud, srv, &ev) };
5107        }
5108    });
5109}
5110
5111#[no_mangle]
5112pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
5113    mut env: JNIEnv<'l>,
5114    _class: JClass<'l>,
5115    channel: JString<'l>,
5116    payload: JByteArray<'l>,
5117) {
5118    let ch = env
5119        .get_string(&channel)
5120        .map(String::from)
5121        .unwrap_or_default();
5122    let data = env.convert_byte_array(&payload).unwrap_or_default();
5123    let ev = yog_abi::YogPacketEvent {
5124        channel: YogStr::from_str(&ch),
5125        player: YogStr::EMPTY,
5126        payload: data.as_ptr(),
5127        payload_len: data.len() as u32,
5128    };
5129    let h = handlers();
5130    let srv = srv_ptr();
5131    guard("on_client_packet", || {
5132        if let Some((module, ud, f)) = h.client_packets.get(&ch) {
5133            if module.is_retiring() {
5134                return;
5135            }
5136            let Some(_call) = module.enter() else { return };
5137            unsafe { f(*ud, srv, &ev) };
5138        }
5139    });
5140}
5141
5142#[no_mangle]
5143pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
5144    env: JNIEnv<'l>,
5145    _class: JClass<'l>,
5146) -> jstring {
5147    let s = handlers()
5148        .packets
5149        .keys()
5150        .cloned()
5151        .collect::<Vec<_>>()
5152        .join("\n");
5153    env.new_string(s)
5154        .map(|s| s.into_raw())
5155        .unwrap_or(std::ptr::null_mut())
5156}
5157
5158#[no_mangle]
5159pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
5160    env: JNIEnv<'l>,
5161    _class: JClass<'l>,
5162) -> jstring {
5163    let s = handlers()
5164        .client_packets
5165        .keys()
5166        .cloned()
5167        .collect::<Vec<_>>()
5168        .join("\n");
5169    env.new_string(s)
5170        .map(|s| s.into_raw())
5171        .unwrap_or(std::ptr::null_mut())
5172}
5173
5174#[no_mangle]
5175pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
5176    mut env: JNIEnv<'l>,
5177    _class: JClass<'l>,
5178    entity_type: JString<'l>,
5179    uuid: JString<'l>,
5180    dimension: JString<'l>,
5181) {
5182    let h = handlers();
5183    if h.entity_spawn.is_empty() {
5184        return;
5185    }
5186    let (et, u, d) = (
5187        jstr!(env, entity_type),
5188        jstr!(env, uuid),
5189        jstr!(env, dimension),
5190    );
5191    let ev = yog_abi::YogEntitySpawnEvent {
5192        entity_type: YogStr::from_str(&et),
5193        uuid: YogStr::from_str(&u),
5194        dimension: YogStr::from_str(&d),
5195    };
5196    let srv = srv_ptr();
5197    guard("on_entity_spawn", || {
5198        for (module, ud, f) in &h.entity_spawn {
5199            if module.is_retiring() {
5200                continue;
5201            }
5202            let Some(_call) = module.enter() else {
5203                continue;
5204            };
5205            unsafe { f(*ud, srv, &ev, 1) };
5206        }
5207    });
5208}
5209
5210#[no_mangle]
5211pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
5212    mut env: JNIEnv<'l>,
5213    _class: JClass<'l>,
5214    entity_type: JString<'l>,
5215    uuid: JString<'l>,
5216    dimension: JString<'l>,
5217) -> jni::sys::jboolean {
5218    let h = handlers();
5219    if h.entity_spawn.is_empty() {
5220        return 1;
5221    }
5222    let et = match env.get_string(&entity_type) {
5223        Ok(s) => String::from(s),
5224        Err(_) => return 1,
5225    };
5226    let u = match env.get_string(&uuid) {
5227        Ok(s) => String::from(s),
5228        Err(_) => return 1,
5229    };
5230    let d = match env.get_string(&dimension) {
5231        Ok(s) => String::from(s),
5232        Err(_) => return 1,
5233    };
5234    let ev = yog_abi::YogEntitySpawnEvent {
5235        entity_type: YogStr::from_str(&et),
5236        uuid: YogStr::from_str(&u),
5237        dimension: YogStr::from_str(&d),
5238    };
5239    let srv = srv_ptr();
5240    let mut allow = true;
5241    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5242        for (module, ud, f) in &h.entity_spawn {
5243            if module.is_retiring() {
5244                continue;
5245            }
5246            let Some(_call) = module.enter() else {
5247                continue;
5248            };
5249            if !unsafe { f(*ud, srv, &ev, 0) } {
5250                allow = false;
5251                break;
5252            }
5253        }
5254    }))
5255    .ok();
5256    allow as jni::sys::jboolean
5257}
5258
5259#[no_mangle]
5260pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
5261    mut env: JNIEnv<'l>,
5262    _class: JClass<'l>,
5263    entity_type: JString<'l>,
5264    uuid: JString<'l>,
5265    amount: jfloat,
5266    source: JString<'l>,
5267    dimension: JString<'l>,
5268) -> jni::sys::jboolean {
5269    let h = handlers();
5270    if h.entity_damage.is_empty() {
5271        return 1;
5272    }
5273    let et = match env.get_string(&entity_type) {
5274        Ok(s) => String::from(s),
5275        Err(_) => return 1,
5276    };
5277    let u = match env.get_string(&uuid) {
5278        Ok(s) => String::from(s),
5279        Err(_) => return 1,
5280    };
5281    let s = match env.get_string(&source) {
5282        Ok(s) => String::from(s),
5283        Err(_) => return 1,
5284    };
5285    let d = match env.get_string(&dimension) {
5286        Ok(s) => String::from(s),
5287        Err(_) => return 1,
5288    };
5289    let ev = yog_abi::YogEntityDamageEvent {
5290        entity_type: YogStr::from_str(&et),
5291        uuid: YogStr::from_str(&u),
5292        amount,
5293        source: YogStr::from_str(&s),
5294        dimension: YogStr::from_str(&d),
5295    };
5296    let srv = srv_ptr();
5297    let mut allow = true;
5298    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5299        for (module, ud, f) in &h.entity_damage {
5300            if module.is_retiring() {
5301                continue;
5302            }
5303            let Some(_call) = module.enter() else {
5304                continue;
5305            };
5306            if !unsafe { f(*ud, srv, &ev, 0) } {
5307                allow = false;
5308                break;
5309            }
5310        }
5311    }))
5312    .ok();
5313    allow as jni::sys::jboolean
5314}
5315
5316#[no_mangle]
5317pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
5318    mut env: JNIEnv<'l>,
5319    _class: JClass<'l>,
5320    player: JString<'l>,
5321    block: JString<'l>,
5322    x: jint,
5323    y: jint,
5324    z: jint,
5325    dimension: JString<'l>,
5326) -> jni::sys::jboolean {
5327    let h = handlers();
5328    if h.player_place_block.is_empty() {
5329        return 1;
5330    }
5331    let p = match env.get_string(&player) {
5332        Ok(s) => String::from(s),
5333        Err(_) => return 1,
5334    };
5335    let b = match env.get_string(&block) {
5336        Ok(s) => String::from(s),
5337        Err(_) => return 1,
5338    };
5339    let d = match env.get_string(&dimension) {
5340        Ok(s) => String::from(s),
5341        Err(_) => return 1,
5342    };
5343    let ev = YogPlaceBlockEvent {
5344        player: YogStr::from_str(&p),
5345        block: YogStr::from_str(&b),
5346        pos: YogBlockPos { x, y, z },
5347        dimension: YogStr::from_str(&d),
5348    };
5349    let srv = srv_ptr();
5350    let mut allow = true;
5351    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5352        for (module, ud, f) in &h.player_place_block {
5353            if module.is_retiring() {
5354                continue;
5355            }
5356            let Some(_call) = module.enter() else {
5357                continue;
5358            };
5359            if !unsafe { f(*ud, srv, &ev, 0) } {
5360                allow = false;
5361                break;
5362            }
5363        }
5364    }))
5365    .ok();
5366    allow as jni::sys::jboolean
5367}
5368
5369#[no_mangle]
5370pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
5371    mut env: JNIEnv<'l>,
5372    _class: JClass<'l>,
5373    player: JString<'l>,
5374    block: JString<'l>,
5375    x: jint,
5376    y: jint,
5377    z: jint,
5378    dimension: JString<'l>,
5379) {
5380    let h = handlers();
5381    if h.player_place_block.is_empty() {
5382        return;
5383    }
5384    let (p, b, d) = (jstr!(env, player), jstr!(env, block), jstr!(env, dimension));
5385    let ev = YogPlaceBlockEvent {
5386        player: YogStr::from_str(&p),
5387        block: YogStr::from_str(&b),
5388        pos: YogBlockPos { x, y, z },
5389        dimension: YogStr::from_str(&d),
5390    };
5391    let srv = srv_ptr();
5392    guard("on_player_place_block", || {
5393        for (module, ud, f) in &h.player_place_block {
5394            if module.is_retiring() {
5395                continue;
5396            }
5397            let Some(_call) = module.enter() else {
5398                continue;
5399            };
5400            unsafe { f(*ud, srv, &ev, 1) };
5401        }
5402    });
5403}
5404
5405#[no_mangle]
5406pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
5407    mut env: JNIEnv<'l>,
5408    _class: JClass<'l>,
5409    player: JString<'l>,
5410    uuid: JString<'l>,
5411    source: JString<'l>,
5412    dimension: JString<'l>,
5413) -> jni::sys::jboolean {
5414    let h = handlers();
5415    if h.player_death.is_empty() {
5416        return 1;
5417    }
5418    let p = match env.get_string(&player) {
5419        Ok(s) => String::from(s),
5420        Err(_) => return 1,
5421    };
5422    let u = match env.get_string(&uuid) {
5423        Ok(s) => String::from(s),
5424        Err(_) => return 1,
5425    };
5426    let s = match env.get_string(&source) {
5427        Ok(s) => String::from(s),
5428        Err(_) => return 1,
5429    };
5430    let d = match env.get_string(&dimension) {
5431        Ok(s) => String::from(s),
5432        Err(_) => return 1,
5433    };
5434    let ev = YogPlayerDeathEvent {
5435        player: YogStr::from_str(&p),
5436        uuid: YogStr::from_str(&u),
5437        source: YogStr::from_str(&s),
5438        dimension: YogStr::from_str(&d),
5439    };
5440    let srv = srv_ptr();
5441    let mut allow = true;
5442    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5443        for (module, ud, f) in &h.player_death {
5444            if module.is_retiring() {
5445                continue;
5446            }
5447            let Some(_call) = module.enter() else {
5448                continue;
5449            };
5450            if !unsafe { f(*ud, srv, &ev, 0) } {
5451                allow = false;
5452                break;
5453            }
5454        }
5455    }))
5456    .ok();
5457    allow as jni::sys::jboolean
5458}
5459
5460#[no_mangle]
5461pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
5462    mut env: JNIEnv<'l>,
5463    _class: JClass<'l>,
5464    player: JString<'l>,
5465    uuid: JString<'l>,
5466    source: JString<'l>,
5467    dimension: JString<'l>,
5468) {
5469    let h = handlers();
5470    if h.player_death.is_empty() {
5471        return;
5472    }
5473    let (p, u, s, d) = (
5474        jstr!(env, player),
5475        jstr!(env, uuid),
5476        jstr!(env, source),
5477        jstr!(env, dimension),
5478    );
5479    let ev = YogPlayerDeathEvent {
5480        player: YogStr::from_str(&p),
5481        uuid: YogStr::from_str(&u),
5482        source: YogStr::from_str(&s),
5483        dimension: YogStr::from_str(&d),
5484    };
5485    let srv = srv_ptr();
5486    guard("on_player_death", || {
5487        for (module, ud, f) in &h.player_death {
5488            if module.is_retiring() {
5489                continue;
5490            }
5491            let Some(_call) = module.enter() else {
5492                continue;
5493            };
5494            unsafe { f(*ud, srv, &ev, 1) };
5495        }
5496    });
5497}
5498
5499#[no_mangle]
5500pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
5501    mut env: JNIEnv<'l>,
5502    _class: JClass<'l>,
5503    player: JString<'l>,
5504    uuid: JString<'l>,
5505    at_anchor: jni::sys::jboolean,
5506    dimension: JString<'l>,
5507) {
5508    let h = handlers();
5509    if h.player_respawn.is_empty() {
5510        return;
5511    }
5512    let (p, u, d) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, dimension));
5513    let ev = YogPlayerRespawnEvent {
5514        player: YogStr::from_str(&p),
5515        uuid: YogStr::from_str(&u),
5516        at_anchor: at_anchor != 0,
5517        dimension: YogStr::from_str(&d),
5518    };
5519    let srv = srv_ptr();
5520    guard("on_player_respawn", || {
5521        for (module, ud, f) in &h.player_respawn {
5522            if module.is_retiring() {
5523                continue;
5524            }
5525            let Some(_call) = module.enter() else {
5526                continue;
5527            };
5528            unsafe { f(*ud, srv, &ev, 1) };
5529        }
5530    });
5531}
5532
5533#[no_mangle]
5534pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
5535    mut env: JNIEnv<'l>,
5536    _class: JClass<'l>,
5537    player: JString<'l>,
5538    uuid: JString<'l>,
5539    advancement: JString<'l>,
5540    dimension: JString<'l>,
5541) {
5542    let h = handlers();
5543    if h.advancement.is_empty() {
5544        return;
5545    }
5546    let (p, u, a, d) = (
5547        jstr!(env, player),
5548        jstr!(env, uuid),
5549        jstr!(env, advancement),
5550        jstr!(env, dimension),
5551    );
5552    let ev = YogAdvancementEvent {
5553        player: YogStr::from_str(&p),
5554        uuid: YogStr::from_str(&u),
5555        advancement: YogStr::from_str(&a),
5556        dimension: YogStr::from_str(&d),
5557    };
5558    let srv = srv_ptr();
5559    guard("on_advancement", || {
5560        for (module, ud, f) in &h.advancement {
5561            if module.is_retiring() {
5562                continue;
5563            }
5564            let Some(_call) = module.enter() else {
5565                continue;
5566            };
5567            unsafe { f(*ud, srv, &ev, 1) };
5568        }
5569    });
5570}
5571
5572#[no_mangle]
5573pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
5574    mut env: JNIEnv<'l>,
5575    _class: JClass<'l>,
5576    player: JString<'l>,
5577    player_uuid: JString<'l>,
5578    entity_type: JString<'l>,
5579    entity_uuid: JString<'l>,
5580    hand: JString<'l>,
5581    dimension: JString<'l>,
5582) -> jni::sys::jboolean {
5583    let h = handlers();
5584    if h.entity_interact.is_empty() {
5585        return 1;
5586    }
5587    let p = match env.get_string(&player) {
5588        Ok(s) => String::from(s),
5589        Err(_) => return 1,
5590    };
5591    let pu = match env.get_string(&player_uuid) {
5592        Ok(s) => String::from(s),
5593        Err(_) => return 1,
5594    };
5595    let et = match env.get_string(&entity_type) {
5596        Ok(s) => String::from(s),
5597        Err(_) => return 1,
5598    };
5599    let eu = match env.get_string(&entity_uuid) {
5600        Ok(s) => String::from(s),
5601        Err(_) => return 1,
5602    };
5603    let ha = match env.get_string(&hand) {
5604        Ok(s) => String::from(s),
5605        Err(_) => return 1,
5606    };
5607    let d = match env.get_string(&dimension) {
5608        Ok(s) => String::from(s),
5609        Err(_) => return 1,
5610    };
5611    let ev = YogEntityInteractEvent {
5612        player: YogStr::from_str(&p),
5613        player_uuid: YogStr::from_str(&pu),
5614        entity_type: YogStr::from_str(&et),
5615        entity_uuid: YogStr::from_str(&eu),
5616        hand: YogStr::from_str(&ha),
5617        dimension: YogStr::from_str(&d),
5618    };
5619    let srv = srv_ptr();
5620    let mut allow = true;
5621    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5622        for (module, ud, f) in &h.entity_interact {
5623            if module.is_retiring() {
5624                continue;
5625            }
5626            let Some(_call) = module.enter() else {
5627                continue;
5628            };
5629            if !unsafe { f(*ud, srv, &ev, 0) } {
5630                allow = false;
5631                break;
5632            }
5633        }
5634    }))
5635    .ok();
5636    allow as jni::sys::jboolean
5637}
5638
5639#[no_mangle]
5640pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
5641    mut env: JNIEnv<'l>,
5642    _class: JClass<'l>,
5643    player: JString<'l>,
5644    player_uuid: JString<'l>,
5645    entity_type: JString<'l>,
5646    entity_uuid: JString<'l>,
5647    hand: JString<'l>,
5648    dimension: JString<'l>,
5649) {
5650    let h = handlers();
5651    if h.entity_interact.is_empty() {
5652        return;
5653    }
5654    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5655    let (et, eu, ha, d) = (
5656        jstr!(env, entity_type),
5657        jstr!(env, entity_uuid),
5658        jstr!(env, hand),
5659        jstr!(env, dimension),
5660    );
5661    let ev = YogEntityInteractEvent {
5662        player: YogStr::from_str(&p),
5663        player_uuid: YogStr::from_str(&pu),
5664        entity_type: YogStr::from_str(&et),
5665        entity_uuid: YogStr::from_str(&eu),
5666        hand: YogStr::from_str(&ha),
5667        dimension: YogStr::from_str(&d),
5668    };
5669    let srv = srv_ptr();
5670    guard("on_entity_interact", || {
5671        for (module, ud, f) in &h.entity_interact {
5672            if module.is_retiring() {
5673                continue;
5674            }
5675            let Some(_call) = module.enter() else {
5676                continue;
5677            };
5678            unsafe { f(*ud, srv, &ev, 1) };
5679        }
5680    });
5681}
5682
5683#[no_mangle]
5684pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
5685    mut env: JNIEnv<'l>,
5686    _class: JClass<'l>,
5687    player: JString<'l>,
5688    player_uuid: JString<'l>,
5689    result_item: JString<'l>,
5690    result_count: jint,
5691    dimension: JString<'l>,
5692) {
5693    let h = handlers();
5694    if h.item_craft.is_empty() {
5695        return;
5696    }
5697    let (p, pu, ri, d) = (
5698        jstr!(env, player),
5699        jstr!(env, player_uuid),
5700        jstr!(env, result_item),
5701        jstr!(env, dimension),
5702    );
5703    let ev = YogCraftEvent {
5704        player: YogStr::from_str(&p),
5705        player_uuid: YogStr::from_str(&pu),
5706        result_item: YogStr::from_str(&ri),
5707        result_count: result_count as u32,
5708        dimension: YogStr::from_str(&d),
5709    };
5710    let srv = srv_ptr();
5711    guard("on_item_craft", || {
5712        for (module, ud, f) in &h.item_craft {
5713            if module.is_retiring() {
5714                continue;
5715            }
5716            let Some(_call) = module.enter() else {
5717                continue;
5718            };
5719            unsafe { f(*ud, srv, &ev, 1) };
5720        }
5721    });
5722}
5723
5724#[no_mangle]
5725pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
5726    mut env: JNIEnv<'l>,
5727    _class: JClass<'l>,
5728    dimension: JString<'l>,
5729    x: jdouble,
5730    y: jdouble,
5731    z: jdouble,
5732    power: jfloat,
5733    cause_uuid: JString<'l>,
5734) -> jni::sys::jboolean {
5735    let h = handlers();
5736    if h.explosion.is_empty() {
5737        return 1;
5738    }
5739    let d = match env.get_string(&dimension) {
5740        Ok(s) => String::from(s),
5741        Err(_) => return 1,
5742    };
5743    let cu = match env.get_string(&cause_uuid) {
5744        Ok(s) => String::from(s),
5745        Err(_) => return 1,
5746    };
5747    let ev = YogExplosionEvent {
5748        dimension: YogStr::from_str(&d),
5749        x,
5750        y,
5751        z,
5752        power,
5753        cause_uuid: YogStr::from_str(&cu),
5754    };
5755    let srv = srv_ptr();
5756    let mut allow = true;
5757    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5758        for (module, ud, f) in &h.explosion {
5759            if module.is_retiring() {
5760                continue;
5761            }
5762            let Some(_call) = module.enter() else {
5763                continue;
5764            };
5765            if !unsafe { f(*ud, srv, &ev, 0) } {
5766                allow = false;
5767                break;
5768            }
5769        }
5770    }))
5771    .ok();
5772    allow as jni::sys::jboolean
5773}
5774
5775#[no_mangle]
5776pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
5777    mut env: JNIEnv<'l>,
5778    _class: JClass<'l>,
5779    dimension: JString<'l>,
5780    x: jdouble,
5781    y: jdouble,
5782    z: jdouble,
5783    power: jfloat,
5784    cause_uuid: JString<'l>,
5785) {
5786    let h = handlers();
5787    if h.explosion.is_empty() {
5788        return;
5789    }
5790    let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
5791    let ev = YogExplosionEvent {
5792        dimension: YogStr::from_str(&d),
5793        x,
5794        y,
5795        z,
5796        power,
5797        cause_uuid: YogStr::from_str(&cu),
5798    };
5799    let srv = srv_ptr();
5800    guard("on_explosion", || {
5801        for (module, ud, f) in &h.explosion {
5802            if module.is_retiring() {
5803                continue;
5804            }
5805            let Some(_call) = module.enter() else {
5806                continue;
5807            };
5808            unsafe { f(*ud, srv, &ev, 1) };
5809        }
5810    });
5811}
5812
5813// ── ABI minor 9 JNI entry points ─────────────────────────────────────────────
5814
5815#[no_mangle]
5816pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
5817    mut env: JNIEnv<'l>,
5818    _class: JClass<'l>,
5819    player: JString<'l>,
5820    player_uuid: JString<'l>,
5821    item_id: JString<'l>,
5822    item_count: jint,
5823    entity_uuid: JString<'l>,
5824    dimension: JString<'l>,
5825) -> jni::sys::jboolean {
5826    let h = handlers();
5827    if h.item_pickup.is_empty() {
5828        return 1;
5829    }
5830    let p = match env.get_string(&player) {
5831        Ok(s) => String::from(s),
5832        Err(_) => return 1,
5833    };
5834    let pu = match env.get_string(&player_uuid) {
5835        Ok(s) => String::from(s),
5836        Err(_) => return 1,
5837    };
5838    let ii = match env.get_string(&item_id) {
5839        Ok(s) => String::from(s),
5840        Err(_) => return 1,
5841    };
5842    let eu = match env.get_string(&entity_uuid) {
5843        Ok(s) => String::from(s),
5844        Err(_) => return 1,
5845    };
5846    let d = match env.get_string(&dimension) {
5847        Ok(s) => String::from(s),
5848        Err(_) => return 1,
5849    };
5850    let ev = YogItemPickupEvent {
5851        player: YogStr::from_str(&p),
5852        player_uuid: YogStr::from_str(&pu),
5853        item_id: YogStr::from_str(&ii),
5854        item_count: item_count as u32,
5855        entity_uuid: YogStr::from_str(&eu),
5856        dimension: YogStr::from_str(&d),
5857    };
5858    let srv = srv_ptr();
5859    let mut allow = true;
5860    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
5861        for (module, ud, f) in &h.item_pickup {
5862            if module.is_retiring() {
5863                continue;
5864            }
5865            let Some(_call) = module.enter() else {
5866                continue;
5867            };
5868            if !unsafe { f(*ud, srv, &ev, 0) } {
5869                allow = false;
5870                break;
5871            }
5872        }
5873    }))
5874    .ok();
5875    allow as jni::sys::jboolean
5876}
5877
5878#[no_mangle]
5879pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
5880    mut env: JNIEnv<'l>,
5881    _class: JClass<'l>,
5882    player: JString<'l>,
5883    player_uuid: JString<'l>,
5884    item_id: JString<'l>,
5885    item_count: jint,
5886    entity_uuid: JString<'l>,
5887    dimension: JString<'l>,
5888) {
5889    let h = handlers();
5890    if h.item_pickup.is_empty() {
5891        return;
5892    }
5893    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
5894    let (ii, eu, d) = (
5895        jstr!(env, item_id),
5896        jstr!(env, entity_uuid),
5897        jstr!(env, dimension),
5898    );
5899    let ev = YogItemPickupEvent {
5900        player: YogStr::from_str(&p),
5901        player_uuid: YogStr::from_str(&pu),
5902        item_id: YogStr::from_str(&ii),
5903        item_count: item_count as u32,
5904        entity_uuid: YogStr::from_str(&eu),
5905        dimension: YogStr::from_str(&d),
5906    };
5907    let srv = srv_ptr();
5908    guard("on_item_pickup", || {
5909        for (module, ud, f) in &h.item_pickup {
5910            if module.is_retiring() {
5911                continue;
5912            }
5913            let Some(_call) = module.enter() else {
5914                continue;
5915            };
5916            unsafe { f(*ud, srv, &ev, 1) };
5917        }
5918    });
5919}
5920
5921#[no_mangle]
5922pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
5923    mut env: JNIEnv<'l>,
5924    _class: JClass<'l>,
5925    player: JString<'l>,
5926    player_uuid: JString<'l>,
5927    x: jdouble,
5928    y: jdouble,
5929    z: jdouble,
5930    yaw: jfloat,
5931    pitch: jfloat,
5932    dimension: JString<'l>,
5933) {
5934    let h = handlers();
5935    if h.player_move.is_empty() {
5936        return;
5937    }
5938    let (p, pu, d) = (
5939        jstr!(env, player),
5940        jstr!(env, player_uuid),
5941        jstr!(env, dimension),
5942    );
5943    let ev = YogPlayerMoveEvent {
5944        player: YogStr::from_str(&p),
5945        player_uuid: YogStr::from_str(&pu),
5946        x,
5947        y,
5948        z,
5949        yaw,
5950        pitch,
5951        dimension: YogStr::from_str(&d),
5952    };
5953    let srv = srv_ptr();
5954    guard("on_player_move", || {
5955        for (module, ud, f) in &h.player_move {
5956            if module.is_retiring() {
5957                continue;
5958            }
5959            let Some(_call) = module.enter() else {
5960                continue;
5961            };
5962            unsafe { f(*ud, srv, &ev, 1) };
5963        }
5964    });
5965}
5966
5967#[no_mangle]
5968pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
5969    mut env: JNIEnv<'l>,
5970    _class: JClass<'l>,
5971    player: JString<'l>,
5972    player_uuid: JString<'l>,
5973    dimension: JString<'l>,
5974) -> jni::sys::jboolean {
5975    let h = handlers();
5976    if h.container_open.is_empty() {
5977        return 1;
5978    }
5979    let p = match env.get_string(&player) {
5980        Ok(s) => String::from(s),
5981        Err(_) => return 1,
5982    };
5983    let pu = match env.get_string(&player_uuid) {
5984        Ok(s) => String::from(s),
5985        Err(_) => return 1,
5986    };
5987    let d = match env.get_string(&dimension) {
5988        Ok(s) => String::from(s),
5989        Err(_) => return 1,
5990    };
5991    let ev = YogContainerOpenEvent {
5992        player: YogStr::from_str(&p),
5993        player_uuid: YogStr::from_str(&pu),
5994        container_type: YogStr::EMPTY,
5995        dimension: YogStr::from_str(&d),
5996    };
5997    let srv = srv_ptr();
5998    let mut allow = true;
5999    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
6000        for (module, ud, f) in &h.container_open {
6001            if module.is_retiring() {
6002                continue;
6003            }
6004            let Some(_call) = module.enter() else {
6005                continue;
6006            };
6007            if !unsafe { f(*ud, srv, &ev, 0) } {
6008                allow = false;
6009                break;
6010            }
6011        }
6012    }))
6013    .ok();
6014    allow as jni::sys::jboolean
6015}
6016
6017#[no_mangle]
6018pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
6019    mut env: JNIEnv<'l>,
6020    _class: JClass<'l>,
6021    player: JString<'l>,
6022    player_uuid: JString<'l>,
6023    container_type: JString<'l>,
6024    dimension: JString<'l>,
6025) {
6026    let h = handlers();
6027    if h.container_open.is_empty() {
6028        return;
6029    }
6030    let (p, pu, ct, d) = (
6031        jstr!(env, player),
6032        jstr!(env, player_uuid),
6033        jstr!(env, container_type),
6034        jstr!(env, dimension),
6035    );
6036    let ev = YogContainerOpenEvent {
6037        player: YogStr::from_str(&p),
6038        player_uuid: YogStr::from_str(&pu),
6039        container_type: YogStr::from_str(&ct),
6040        dimension: YogStr::from_str(&d),
6041    };
6042    let srv = srv_ptr();
6043    guard("on_container_open", || {
6044        for (module, ud, f) in &h.container_open {
6045            if module.is_retiring() {
6046                continue;
6047            }
6048            let Some(_call) = module.enter() else {
6049                continue;
6050            };
6051            unsafe { f(*ud, srv, &ev, 1) };
6052        }
6053    });
6054}
6055
6056#[no_mangle]
6057pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
6058    mut env: JNIEnv<'l>,
6059    _class: JClass<'l>,
6060    player: JString<'l>,
6061    player_uuid: JString<'l>,
6062    dimension: JString<'l>,
6063) {
6064    let h = handlers();
6065    if h.container_close.is_empty() {
6066        return;
6067    }
6068    let (p, pu, d) = (
6069        jstr!(env, player),
6070        jstr!(env, player_uuid),
6071        jstr!(env, dimension),
6072    );
6073    let ev = YogContainerCloseEvent {
6074        player: YogStr::from_str(&p),
6075        player_uuid: YogStr::from_str(&pu),
6076        dimension: YogStr::from_str(&d),
6077    };
6078    let srv = srv_ptr();
6079    guard("on_container_close", || {
6080        for (module, ud, f) in &h.container_close {
6081            if module.is_retiring() {
6082                continue;
6083            }
6084            let Some(_call) = module.enter() else {
6085                continue;
6086            };
6087            unsafe { f(*ud, srv, &ev, 1) };
6088        }
6089    });
6090}
6091
6092#[no_mangle]
6093pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
6094    mut env: JNIEnv<'l>,
6095    _class: JClass<'l>,
6096    projectile_type: JString<'l>,
6097    projectile_uuid: JString<'l>,
6098    shooter_uuid: JString<'l>,
6099    hit_type: JString<'l>,
6100    hit_entity_uuid: JString<'l>,
6101    x: jdouble,
6102    y: jdouble,
6103    z: jdouble,
6104    dimension: JString<'l>,
6105) -> jni::sys::jboolean {
6106    let h = handlers();
6107    if h.projectile_hit.is_empty() {
6108        return 1;
6109    }
6110    let pt = match env.get_string(&projectile_type) {
6111        Ok(s) => String::from(s),
6112        Err(_) => return 1,
6113    };
6114    let pu = match env.get_string(&projectile_uuid) {
6115        Ok(s) => String::from(s),
6116        Err(_) => return 1,
6117    };
6118    let su = match env.get_string(&shooter_uuid) {
6119        Ok(s) => String::from(s),
6120        Err(_) => return 1,
6121    };
6122    let ht = match env.get_string(&hit_type) {
6123        Ok(s) => String::from(s),
6124        Err(_) => return 1,
6125    };
6126    let heu = match env.get_string(&hit_entity_uuid) {
6127        Ok(s) => String::from(s),
6128        Err(_) => return 1,
6129    };
6130    let dim = match env.get_string(&dimension) {
6131        Ok(s) => String::from(s),
6132        Err(_) => return 1,
6133    };
6134    let ev = YogProjectileHitEvent {
6135        projectile_type: YogStr::from_str(&pt),
6136        projectile_uuid: YogStr::from_str(&pu),
6137        shooter_uuid: YogStr::from_str(&su),
6138        hit_type: YogStr::from_str(&ht),
6139        hit_entity_uuid: YogStr::from_str(&heu),
6140        x,
6141        y,
6142        z,
6143        dimension: YogStr::from_str(&dim),
6144    };
6145    let srv = srv_ptr();
6146    let mut allow = true;
6147    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
6148        for (module, ud, f) in &h.projectile_hit {
6149            if module.is_retiring() {
6150                continue;
6151            }
6152            let Some(_call) = module.enter() else {
6153                continue;
6154            };
6155            if !unsafe { f(*ud, srv, &ev, 0) } {
6156                allow = false;
6157                break;
6158            }
6159        }
6160    }))
6161    .ok();
6162    allow as jni::sys::jboolean
6163}
6164
6165#[no_mangle]
6166pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
6167    mut env: JNIEnv<'l>,
6168    _class: JClass<'l>,
6169    projectile_type: JString<'l>,
6170    projectile_uuid: JString<'l>,
6171    shooter_uuid: JString<'l>,
6172    hit_type: JString<'l>,
6173    hit_entity_uuid: JString<'l>,
6174    x: jdouble,
6175    y: jdouble,
6176    z: jdouble,
6177    dimension: JString<'l>,
6178) {
6179    let h = handlers();
6180    if h.projectile_hit.is_empty() {
6181        return;
6182    }
6183    let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
6184    let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
6185    let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
6186    let ev = YogProjectileHitEvent {
6187        projectile_type: YogStr::from_str(&pt),
6188        projectile_uuid: YogStr::from_str(&pu),
6189        shooter_uuid: YogStr::from_str(&su),
6190        hit_type: YogStr::from_str(&ht),
6191        hit_entity_uuid: YogStr::from_str(&heu),
6192        x,
6193        y,
6194        z,
6195        dimension: YogStr::from_str(&dim),
6196    };
6197    let srv = srv_ptr();
6198    guard("on_projectile_hit", || {
6199        for (module, ud, f) in &h.projectile_hit {
6200            if module.is_retiring() {
6201                continue;
6202            }
6203            let Some(_call) = module.enter() else {
6204                continue;
6205            };
6206            unsafe { f(*ud, srv, &ev, 1) };
6207        }
6208    });
6209}
6210
6211// ── ABI minor 10 — client-side JNI entry points ───────────────────────────────
6212
6213#[no_mangle]
6214pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
6215    _env: JNIEnv<'l>,
6216    _class: JClass<'l>,
6217) {
6218    let h = handlers();
6219    if h.client_tick.is_empty() {
6220        return;
6221    }
6222    guard("on_client_tick", || {
6223        for (module, ud, f) in &h.client_tick {
6224            if module.is_retiring() {
6225                continue;
6226            }
6227            let Some(_call) = module.enter() else {
6228                continue;
6229            };
6230            unsafe { f(*ud) };
6231        }
6232    });
6233}
6234
6235#[no_mangle]
6236pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
6237    _env: JNIEnv<'l>,
6238    _class: JClass<'l>,
6239) {
6240    if GL.get().is_some() {
6241        return;
6242    }
6243    let mut raw_get_binary: usize = 0;
6244    let mut raw_prog_binary: usize = 0;
6245    let gl = unsafe {
6246        glow::Context::from_loader_function(|sym| {
6247            let Some(mut env) = get_env() else {
6248                return std::ptr::null();
6249            };
6250            let jsym = match env.new_string(sym) {
6251                Ok(s) => s,
6252                Err(_) => return std::ptr::null(),
6253            };
6254            let jsym_obj: JObject = jsym.into();
6255            let val = env.call_static_method(
6256                "dev/yog/NativeBridge",
6257                "glProcAddress",
6258                "(Ljava/lang/String;)J",
6259                &[JValue::Object(&jsym_obj)],
6260            );
6261            let ptr = match val.and_then(|v| v.j()) {
6262                Ok(p) if p != 0 => p as usize as *const _,
6263                _ => std::ptr::null(),
6264            };
6265            // Capture extension pointers while the loader runs.
6266            match sym {
6267                "glGetProgramBinary" => raw_get_binary = ptr as usize,
6268                "glProgramBinary" => raw_prog_binary = ptr as usize,
6269                _ => {}
6270            }
6271            ptr
6272        })
6273    };
6274    let _ = GL.set(GlCtx(gl));
6275    let _ = GL_GET_PROGRAM_BINARY.set(if raw_get_binary != 0 {
6276        Some(raw_get_binary)
6277    } else {
6278        None
6279    });
6280    let _ = GL_PROGRAM_BINARY.set(if raw_prog_binary != 0 {
6281        Some(raw_prog_binary)
6282    } else {
6283        None
6284    });
6285    // `glGetProgramiv` is a core function; always available.  We look it up once here
6286    // to avoid depending on glow internals for the PROGRAM_BINARY_LENGTH query.
6287    if let Some(mut env) = get_env() {
6288        if let Ok(jsym) = env.new_string("glGetProgramiv") {
6289            let jsym_obj: JObject = jsym.into();
6290            if let Ok(jv) = env.call_static_method(
6291                "dev/yog/NativeBridge",
6292                "glProcAddress",
6293                "(Ljava/lang/String;)J",
6294                &[JValue::Object(&jsym_obj)],
6295            ) {
6296                if let Ok(ptr) = jv.j() {
6297                    let _ = GL_GET_PROGRAM_IV.set(if ptr != 0 { Some(ptr as usize) } else { None });
6298                }
6299            }
6300        }
6301    }
6302}
6303
6304#[no_mangle]
6305pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
6306    _env: JNIEnv<'l>,
6307    _class: JClass<'l>,
6308    delta_tick: jfloat,
6309    screen_w: jint,
6310    screen_h: jint,
6311    scale_factor: jfloat,
6312    player_x: jfloat,
6313    player_y: jfloat,
6314    player_z: jfloat,
6315) {
6316    let h = handlers();
6317    let mut gfx = GFX_FN_TABLE;
6318    gfx.screen_w = screen_w;
6319    gfx.screen_h = screen_h;
6320    gfx.delta_tick = delta_tick;
6321    gfx.scale_factor = scale_factor;
6322    gfx.player_pos = [player_x, player_y, player_z];
6323
6324    // Mod hud_render callbacks.
6325    if !h.hud_render.is_empty() {
6326        guard("on_hud_render", || {
6327            for (module, ud, f) in &h.hud_render {
6328                if module.is_retiring() {
6329                    continue;
6330                }
6331                let Some(_call) = module.enter() else {
6332                    continue;
6333                };
6334                unsafe { f(*ud, &gfx) };
6335            }
6336        });
6337    }
6338
6339    // Render active book UIs.
6340    let active_layers: Vec<UiLayer> = h.active_uis.lock().unwrap().clone();
6341    let sw = screen_w as f32;
6342    let sh = screen_h as f32;
6343    let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
6344    let mut renderers = h.book_renderers.lock().unwrap();
6345    for layer in &active_layers {
6346        if !layer.visible {
6347            continue;
6348        }
6349        if let Some(renderer) = renderers.get_mut(&layer.id) {
6350            renderer.render(&ctx, sw, sh);
6351        }
6352    }
6353}
6354
6355#[no_mangle]
6356pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
6357    env: JNIEnv<'l>,
6358    _class: JClass<'l>,
6359    delta_tick: jfloat,
6360    screen_w: jint,
6361    screen_h: jint,
6362    scale_factor: jfloat,
6363    view_proj_arr: JFloatArray<'l>,
6364    cam_x: jfloat,
6365    cam_y: jfloat,
6366    cam_z: jfloat,
6367    player_x: jfloat,
6368    player_y: jfloat,
6369    player_z: jfloat,
6370) {
6371    let h = handlers();
6372    if h.world_render.is_empty() {
6373        return;
6374    }
6375    let mut view_proj = [0f32; 16];
6376    if env
6377        .get_float_array_region(&view_proj_arr, 0, &mut view_proj)
6378        .is_err()
6379    {
6380        return;
6381    }
6382    let mut gfx = GFX_FN_TABLE;
6383    gfx.screen_w = screen_w;
6384    gfx.screen_h = screen_h;
6385    gfx.delta_tick = delta_tick;
6386    gfx.scale_factor = scale_factor;
6387    gfx.view_proj = view_proj;
6388    gfx.camera_pos = [cam_x, cam_y, cam_z];
6389    gfx.player_pos = [player_x, player_y, player_z];
6390    guard("on_world_render", || {
6391        for (module, ud, f) in &h.world_render {
6392            if module.is_retiring() {
6393                continue;
6394            }
6395            let Some(_call) = module.enter() else {
6396                continue;
6397            };
6398            unsafe { f(*ud, &gfx) };
6399        }
6400    });
6401}
6402
6403#[no_mangle]
6404pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
6405    _env: JNIEnv<'l>,
6406    _class: JClass<'l>,
6407    key_code: jint,
6408    scan_code: jint,
6409    action: jint,
6410    modifiers: jint,
6411) -> jni::sys::jboolean {
6412    let h = handlers();
6413    if h.key_press.is_empty() {
6414        return 1;
6415    }
6416    let ev = YogKeyPressEvent {
6417        key_code,
6418        scan_code,
6419        action,
6420        modifiers,
6421    };
6422    let mut allow = true;
6423    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
6424        for (module, ud, f) in &h.key_press {
6425            if module.is_retiring() {
6426                continue;
6427            }
6428            let Some(_call) = module.enter() else {
6429                continue;
6430            };
6431            if !unsafe { f(*ud, &ev) } {
6432                allow = false;
6433                break;
6434            }
6435        }
6436    }))
6437    .ok();
6438    allow as jni::sys::jboolean
6439}
6440
6441#[no_mangle]
6442pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
6443    mut env: JNIEnv<'l>,
6444    _class: JClass<'l>,
6445    screen_class: JString<'l>,
6446) {
6447    let h = handlers();
6448    if h.screen_open.is_empty() {
6449        return;
6450    }
6451    let sc = match env.get_string(&screen_class) {
6452        Ok(s) => String::from(s),
6453        Err(_) => return,
6454    };
6455    guard("on_screen_open", || {
6456        for (module, ud, f) in &h.screen_open {
6457            if module.is_retiring() {
6458                continue;
6459            }
6460            let Some(_call) = module.enter() else {
6461                continue;
6462            };
6463            unsafe { f(*ud, YogStr::from_str(&sc)) };
6464        }
6465    });
6466}
6467
6468#[no_mangle]
6469pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
6470    mut env: JNIEnv<'l>,
6471    _class: JClass<'l>,
6472    screen_class: JString<'l>,
6473) {
6474    let h = handlers();
6475    if h.screen_close.is_empty() {
6476        return;
6477    }
6478    let sc = match env.get_string(&screen_class) {
6479        Ok(s) => String::from(s),
6480        Err(_) => return,
6481    };
6482    guard("on_screen_close", || {
6483        for (module, ud, f) in &h.screen_close {
6484            if module.is_retiring() {
6485                continue;
6486            }
6487            let Some(_call) = module.enter() else {
6488                continue;
6489            };
6490            unsafe { f(*ud, YogStr::from_str(&sc)) };
6491        }
6492    });
6493}
6494
6495mod control_socket;
6496mod images;
6497mod ui_jni;