Skip to main content

yog_runtime/
lib.rs

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