Skip to main content

yog_runtime/
lib.rs

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