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