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    ABI_VERSION, YogAdvancementEvent, YogAdvancementFn, YogApi, YogAttackEntityFn,
29    YogBlockBreakFn, YogBlockDef, YogBlockPos, YogChatFn, YogClientFn, YogCommandFn,
30    YogContainerCloseEvent, YogContainerCloseFn, YogContainerOpenEvent, YogContainerOpenFn,
31    YogCraftEvent, YogCraftFn, YogEntityDamageFn, YogEntityDeathFn, YogEntityInteractEvent,
32    YogEntityInteractFn, YogEntitySpawnFn, YogExplosionEvent, YogExplosionFn,
33    YogGfxApi, YogHudRenderFn, YogItemDef, YogItemPickupEvent, YogItemPickupFn,
34    YogKeyPressFn, YogKeyPressEvent, YogOwnedStr, YogPacketFn, YogPlaceBlockEvent,
35    YogPlaceBlockFn, YogPlayerDeathEvent, YogPlayerDeathFn, YogPlayerFn, YogPlayerMoveEvent,
36    YogPlayerMoveFn, YogPlayerRespawnEvent, YogPlayerRespawnFn, YogProjectileHitEvent,
37    YogProjectileHitFn, YogScheduledFn, YogScreenFn, YogServer, YogServerFn, YogStr, YogStartupGrantDef,
38    YogUseBlockFn, YogUseItemFn, YogVec3, YogWorldRenderFn,
39};
40use yog_registry::{BlockDef, FoodDef, ItemDef};
41
42// ── Static globals ────────────────────────────────────────────────────────────
43
44/// Cached JVM handle for any-thread callbacks.
45static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
46/// Loaded mod libraries — kept alive so the code pages stay mapped.
47static LOADED_MODS: Mutex<Vec<Library>> = Mutex::new(Vec::new());
48/// Stable server table (populated once in nativeInit, then read-only).
49static SERVER: OnceLock<YogServer> = OnceLock::new();
50/// All registered handlers + content (populated during mod loading, then read-only).
51static HANDLERS: OnceLock<RuntimeHandlers> = OnceLock::new();
52
53// ── OpenGL context (client-side, render thread only) ─────────────────────────
54
55struct GlCtx(glow::Context);
56unsafe impl Send for GlCtx {}
57unsafe impl Sync for GlCtx {}
58
59/// Initialized by `nativeGlInit` on the render thread.  `None` on dedicated server.
60static GL: OnceLock<GlCtx> = OnceLock::new();
61
62// Raw GL function pointers for GL_ARB_get_program_binary (not exposed by glow 0.13).
63// Captured during the glow loader callback in `nativeGlInit`.
64// `None` when the extension is unavailable (very old drivers).
65static GL_GET_PROGRAM_BINARY: OnceLock<Option<usize>> = OnceLock::new();
66static GL_PROGRAM_BINARY:     OnceLock<Option<usize>> = OnceLock::new();
67static GL_GET_PROGRAM_IV:     OnceLock<Option<usize>> = OnceLock::new();
68
69// ── Handler storage ───────────────────────────────────────────────────────────
70
71#[derive(Debug, Clone)]
72pub struct UiLayer {
73    pub id:         String,
74    pub parent:     Option<String>,
75    pub modal:      bool,
76    pub pause_game: bool,
77    pub visible:    bool,
78    pub enabled:    bool,
79    pub z_index:    i32,
80}
81
82struct RuntimeHandlers {
83    block_break:        Vec<(*mut c_void, YogBlockBreakFn)>,
84    chat:               Vec<(*mut c_void, YogChatFn)>,
85    player_join:        Vec<(*mut c_void, YogPlayerFn)>,
86    player_leave:       Vec<(*mut c_void, YogPlayerFn)>,
87    use_item:           Vec<(*mut c_void, YogUseItemFn)>,
88    use_block:          Vec<(*mut c_void, YogUseBlockFn)>,
89    attack_entity:      Vec<(*mut c_void, YogAttackEntityFn)>,
90    entity_damage:      Vec<(*mut c_void, YogEntityDamageFn)>,
91    entity_death:       Vec<(*mut c_void, YogEntityDeathFn)>,
92    entity_spawn:       Vec<(*mut c_void, YogEntitySpawnFn)>,
93    player_place_block: Vec<(*mut c_void, YogPlaceBlockFn)>,
94    player_death:       Vec<(*mut c_void, YogPlayerDeathFn)>,
95    player_respawn:     Vec<(*mut c_void, YogPlayerRespawnFn)>,
96    advancement:        Vec<(*mut c_void, YogAdvancementFn)>,
97    entity_interact:    Vec<(*mut c_void, YogEntityInteractFn)>,
98    item_craft:         Vec<(*mut c_void, YogCraftFn)>,
99    explosion:          Vec<(*mut c_void, YogExplosionFn)>,
100    item_pickup:        Vec<(*mut c_void, YogItemPickupFn)>,
101    player_move:        Vec<(*mut c_void, YogPlayerMoveFn)>,
102    container_open:     Vec<(*mut c_void, YogContainerOpenFn)>,
103    container_close:    Vec<(*mut c_void, YogContainerCloseFn)>,
104    projectile_hit:     Vec<(*mut c_void, YogProjectileHitFn)>,
105    client_tick:        Vec<(*mut c_void, YogClientFn)>,
106    hud_render:         Vec<(*mut c_void, YogHudRenderFn)>,
107    world_render:       Vec<(*mut c_void, YogWorldRenderFn)>,
108    key_press:          Vec<(*mut c_void, YogKeyPressFn)>,
109    screen_open:        Vec<(*mut c_void, YogScreenFn)>,
110    screen_close:       Vec<(*mut c_void, YogScreenFn)>,
111    server_tick:        Vec<(*mut c_void, YogServerFn)>,
112    server_started:     Vec<(*mut c_void, YogServerFn)>,
113    server_stopping:    Vec<(*mut c_void, YogServerFn)>,
114    commands:           HashMap<String, (*mut c_void, YogCommandFn)>,
115    typed_schemas:      HashMap<String, String>,
116    recipes:            Vec<(String, String, String)>,
117    packets:            HashMap<String, (*mut c_void, YogPacketFn)>,
118    client_packets:     HashMap<String, (*mut c_void, YogPacketFn)>,
119    items:              Vec<ItemDef>,
120    blocks:             Vec<BlockDef>,
121    books:              HashMap<String, String>,
122    pub(crate) uis:     HashMap<String, yog_ui::LayoutNode>,
123    ui_handlers:        HashMap<String, (*mut c_void, yog_abi::YogUIEventFn)>,
124    pub active_uis:     Mutex<Vec<UiLayer>>,
125    startup_grants:     Vec<yog_registry::StartupGrant>,
126    startup_granted:    Mutex<HashMap<String, bool>>,
127    scheduler:          Mutex<SchedulerState>,
128}
129
130// All fn ptrs are C-ABI; ud pointers are from Box::into_raw of Send+Sync closures.
131unsafe impl Send for RuntimeHandlers {}
132unsafe impl Sync for RuntimeHandlers {}
133
134impl RuntimeHandlers {
135    fn new() -> Self {
136        Self {
137            block_break: Vec::new(), chat: Vec::new(),
138            player_join: Vec::new(), player_leave: Vec::new(),
139            use_item: Vec::new(), use_block: Vec::new(),
140            attack_entity: Vec::new(), entity_damage: Vec::new(),
141            entity_death: Vec::new(), entity_spawn: Vec::new(),
142            player_place_block: Vec::new(),
143            player_death: Vec::new(), player_respawn: Vec::new(), advancement: Vec::new(),
144            entity_interact: Vec::new(), item_craft: Vec::new(), explosion: Vec::new(),
145            item_pickup: Vec::new(), player_move: Vec::new(),
146            container_open: Vec::new(), container_close: Vec::new(),
147            projectile_hit: Vec::new(),
148            client_tick: Vec::new(), hud_render: Vec::new(), world_render: Vec::new(),
149            key_press: Vec::new(),
150            screen_open: Vec::new(), screen_close: Vec::new(),
151            server_tick: Vec::new(), server_started: Vec::new(), server_stopping: Vec::new(),
152            commands: HashMap::new(), typed_schemas: HashMap::new(),
153            recipes: Vec::new(), packets: HashMap::new(),
154            client_packets: HashMap::new(), items: Vec::new(),
155            ui_handlers: HashMap::new(), active_uis: Mutex::new(Vec::new()), startup_grants: Vec::new(),
156            blocks: Vec::new(), books: HashMap::new(), uis: HashMap::new(),
157            startup_granted: Mutex::new(HashMap::new()),
158            scheduler: Mutex::new(SchedulerState::new()),
159        }
160    }
161}
162
163struct SchedulerState {
164    once_tasks:       Vec<OnceTask>,
165    repeating_tasks:  Vec<RepeatingTask>,
166}
167
168struct OnceTask      { delay_remaining: u64, ud: *mut c_void, f: YogScheduledFn }
169struct RepeatingTask { period: u64, ticks_left: u64, ud: *mut c_void, f: YogScheduledFn }
170
171unsafe impl Send for SchedulerState {}
172unsafe impl Sync for SchedulerState {}
173unsafe impl Send for OnceTask {}
174unsafe impl Send for RepeatingTask {}
175
176impl SchedulerState {
177    fn new() -> Self { Self { once_tasks: Vec::new(), repeating_tasks: Vec::new() } }
178}
179
180fn handlers() -> &'static RuntimeHandlers {
181    HANDLERS.get().expect("yog: nativeInit not called yet")
182}
183
184// ── JNI helpers ──────────────────────────────────────────────────────────────
185
186fn guard(label: &str, f: impl FnOnce()) {
187    if std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).is_err() {
188        yog_logging::error!("a mod panicked handling `{}` (ignored)", label);
189    }
190}
191
192macro_rules! jstr {
193    ($env:expr, $s:expr) => {
194        match $env.get_string(&$s) { Ok(s) => String::from(s), Err(_) => return }
195    };
196}
197
198/// Convert a `YogStr` into a Java String. Caller must ensure `s` is valid UTF-8.
199unsafe fn ys_to_java<'l>(env: &mut JNIEnv<'l>, s: YogStr)
200    -> Option<jni::objects::JString<'l>>
201{
202    env.new_string(s.as_str()).ok()
203}
204
205fn get_env() -> Option<jni::AttachGuard<'static>> {
206    JAVA_VM.get()?.attach_current_thread().ok()
207}
208
209// ── Free-str allocator used by YogOwnedStr ────────────────────────────────────
210
211unsafe extern "C" fn yog_free_str(ptr: *mut u8, len: u32) {
212    if !ptr.is_null() {
213        drop(Box::from_raw(std::slice::from_raw_parts_mut(ptr, len as usize)));
214    }
215}
216
217fn jstring_to_owned(env: &mut JNIEnv, obj: jni::objects::JObject) -> YogOwnedStr {
218    if obj.as_raw().is_null() { return YogOwnedStr::NONE; }
219    match env.get_string(&JString::from(obj)) {
220        Ok(s) => YogOwnedStr::from_string(String::from(s)),
221        Err(_) => YogOwnedStr::NONE,
222    }
223}
224
225// ── YogServer standalone functions (one per action) ───────────────────────────
226//
227// ctx is unused here — all state is in the JAVA_VM static.
228
229unsafe extern "C" fn srv_broadcast(_ctx: *mut c_void, msg: YogStr) {
230    let Some(mut env) = get_env() else { return };
231    if let Some(jmsg) = ys_to_java(&mut env, msg) {
232        let _ = env.call_static_method("dev/yog/NativeBridge", "broadcast",
233            "(Ljava/lang/String;)V", &[JValue::Object(&jmsg)]);
234    }
235}
236
237unsafe extern "C" fn srv_get_block(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> YogOwnedStr {
238    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
239    let (Some(jd), ) = (ys_to_java(&mut env, dim),) else { return YogOwnedStr::NONE };
240    let ret = env.call_static_method("dev/yog/NativeBridge", "getBlock",
241        "(Ljava/lang/String;III)Ljava/lang/String;",
242        &[JValue::Object(&jd), JValue::Int(pos.x), JValue::Int(pos.y), JValue::Int(pos.z)]);
243    match ret.and_then(|v| v.l()) {
244        Ok(obj) => jstring_to_owned(&mut env, obj),
245        _ => YogOwnedStr::NONE,
246    }
247}
248
249unsafe extern "C" fn srv_set_block(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos, block: YogStr) -> bool {
250    let Some(mut env) = get_env() else { return false };
251    let (Some(jd), Some(jb)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, block)) else { return false };
252    env.call_static_method("dev/yog/NativeBridge", "setBlock",
253        "(Ljava/lang/String;IIILjava/lang/String;)Z",
254        &[JValue::Object(&jd), JValue::Int(pos.x), JValue::Int(pos.y), JValue::Int(pos.z), JValue::Object(&jb)])
255    .and_then(|v| v.z()).unwrap_or(false)
256}
257
258unsafe extern "C" fn srv_world_time(_ctx: *mut c_void, dim: YogStr, out: *mut i64) -> bool {
259    let Some(mut env) = get_env() else { return false };
260    let Some(jd) = ys_to_java(&mut env, dim) else { return false };
261    match env.call_static_method("dev/yog/NativeBridge", "worldTime",
262        "(Ljava/lang/String;)J", &[JValue::Object(&jd)]).and_then(|v| v.j()) {
263        Ok(v) if v != i64::MIN => { *out = v; true }
264        _ => false,
265    }
266}
267
268unsafe extern "C" fn srv_set_time(_ctx: *mut c_void, dim: YogStr, time: i64) -> bool {
269    let Some(mut env) = get_env() else { return false };
270    let Some(jd) = ys_to_java(&mut env, dim) else { return false };
271    env.call_static_method("dev/yog/NativeBridge", "worldSetTime",
272        "(Ljava/lang/String;J)Z", &[JValue::Object(&jd), JValue::Long(time)])
273    .and_then(|v| v.z()).unwrap_or(false)
274}
275
276unsafe extern "C" fn srv_is_raining(_ctx: *mut c_void, dim: YogStr) -> bool {
277    let Some(mut env) = get_env() else { return false };
278    let Some(jd) = ys_to_java(&mut env, dim) else { return false };
279    env.call_static_method("dev/yog/NativeBridge", "worldIsRaining",
280        "(Ljava/lang/String;)Z", &[JValue::Object(&jd)])
281    .and_then(|v| v.z()).unwrap_or(false)
282}
283
284unsafe extern "C" fn srv_set_weather(_ctx: *mut c_void, dim: YogStr, raining: bool, dur: i32) -> bool {
285    let Some(mut env) = get_env() else { return false };
286    let Some(jd) = ys_to_java(&mut env, dim) else { return false };
287    env.call_static_method("dev/yog/NativeBridge", "worldSetWeather",
288        "(Ljava/lang/String;ZI)Z",
289        &[JValue::Object(&jd), JValue::Bool(raining as u8), JValue::Int(dur)])
290    .and_then(|v| v.z()).unwrap_or(false)
291}
292
293unsafe extern "C" fn srv_give_item(_ctx: *mut c_void, player: YogStr, item: YogStr, count: u32) -> bool {
294    let Some(mut env) = get_env() else { return false };
295    let (Some(jp), Some(ji)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, item)) else { return false };
296    env.call_static_method("dev/yog/NativeBridge", "giveItem",
297        "(Ljava/lang/String;Ljava/lang/String;I)Z",
298        &[JValue::Object(&jp), JValue::Object(&ji), JValue::Int(count as i32)])
299    .and_then(|v| v.z()).unwrap_or(false)
300}
301
302unsafe extern "C" fn srv_player_teleport(_ctx: *mut c_void, player: YogStr, pos: YogVec3) -> bool {
303    let Some(mut env) = get_env() else { return false };
304    let Some(jp) = ys_to_java(&mut env, player) else { return false };
305    env.call_static_method("dev/yog/NativeBridge", "teleport",
306        "(Ljava/lang/String;DDD)Z",
307        &[JValue::Object(&jp), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)])
308    .and_then(|v| v.z()).unwrap_or(false)
309}
310
311unsafe extern "C" fn srv_send_to_player(_ctx: *mut c_void, player: YogStr, channel: YogStr, data: *const u8, len: u32) -> bool {
312    let Some(mut env) = get_env() else { return false };
313    let (Some(jp), Some(jc)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, channel)) else { return false };
314    let payload = std::slice::from_raw_parts(data, len as usize);
315    let Ok(jdata) = env.byte_array_from_slice(payload) else { return false };
316    env.call_static_method("dev/yog/NativeBridge", "sendToPlayer",
317        "(Ljava/lang/String;Ljava/lang/String;[B)Z",
318        &[JValue::Object(&jp), JValue::Object(&jc), JValue::Object(&jdata)])
319    .and_then(|v| v.z()).unwrap_or(false)
320}
321
322unsafe extern "C" fn srv_send_to_server(_ctx: *mut c_void, channel: YogStr, data: *const u8, len: u32) -> bool {
323    let Some(mut env) = get_env() else { return false };
324    let Some(jc) = ys_to_java(&mut env, channel) else { return false };
325    let payload = std::slice::from_raw_parts(data, len as usize);
326    let Ok(jdata) = env.byte_array_from_slice(payload) else { return false };
327    let result = env.call_static_method("dev/yog/YogClient", "sendToServer",
328        "(Ljava/lang/String;[B)Z", &[JValue::Object(&jc), JValue::Object(&jdata)]);
329    let _ = env.exception_clear();
330    result.and_then(|v| v.z()).unwrap_or(false)
331}
332
333unsafe extern "C" fn srv_kick_player(_ctx: *mut c_void, player: YogStr, reason: YogStr) -> bool {
334    let Some(mut env) = get_env() else { return false };
335    let (Some(jp), Some(jr)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, reason)) else { return false };
336    env.call_static_method("dev/yog/NativeBridge", "kickPlayer",
337        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jp), JValue::Object(&jr)])
338    .and_then(|v| v.z()).unwrap_or(false)
339}
340
341unsafe extern "C" fn srv_set_gamemode(_ctx: *mut c_void, player: YogStr, mode: YogStr) -> bool {
342    let Some(mut env) = get_env() else { return false };
343    let (Some(jp), Some(jg)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, mode)) else { return false };
344    env.call_static_method("dev/yog/NativeBridge", "setGamemode",
345        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jp), JValue::Object(&jg)])
346    .and_then(|v| v.z()).unwrap_or(false)
347}
348
349unsafe extern "C" fn srv_send_title(_ctx: *mut c_void, player: YogStr, title: YogStr, sub: YogStr, fi: i32, stay: i32, fo: i32) -> bool {
350    let Some(mut env) = get_env() else { return false };
351    let (Some(jp), Some(jt), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, title), ys_to_java(&mut env, sub)) else { return false };
352    env.call_static_method("dev/yog/NativeBridge", "sendTitle",
353        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;III)Z",
354        &[JValue::Object(&jp), JValue::Object(&jt), JValue::Object(&js), JValue::Int(fi), JValue::Int(stay), JValue::Int(fo)])
355    .and_then(|v| v.z()).unwrap_or(false)
356}
357
358unsafe extern "C" fn srv_send_actionbar(_ctx: *mut c_void, player: YogStr, msg: YogStr) -> bool {
359    let Some(mut env) = get_env() else { return false };
360    let (Some(jp), Some(jm)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, msg)) else { return false };
361    env.call_static_method("dev/yog/NativeBridge", "sendActionbar",
362        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jp), JValue::Object(&jm)])
363    .and_then(|v| v.z()).unwrap_or(false)
364}
365
366unsafe extern "C" fn srv_play_sound(_ctx: *mut c_void, dim: YogStr, pos: YogVec3, sound: YogStr, vol: f32, pitch: f32) -> bool {
367    let Some(mut env) = get_env() else { return false };
368    let (Some(jd), Some(js)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, sound)) else { return false };
369    env.call_static_method("dev/yog/NativeBridge", "playSound",
370        "(Ljava/lang/String;DDDLjava/lang/String;FF)Z",
371        &[JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z), JValue::Object(&js), JValue::Float(vol), JValue::Float(pitch)])
372    .and_then(|v| v.z()).unwrap_or(false)
373}
374
375unsafe extern "C" fn srv_play_sound_player(_ctx: *mut c_void, player: YogStr, sound: YogStr, vol: f32, pitch: f32) -> bool {
376    let Some(mut env) = get_env() else { return false };
377    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, sound)) else { return false };
378    env.call_static_method("dev/yog/NativeBridge", "playSoundToPlayer",
379        "(Ljava/lang/String;Ljava/lang/String;FF)Z",
380        &[JValue::Object(&jp), JValue::Object(&js), JValue::Float(vol), JValue::Float(pitch)])
381    .and_then(|v| v.z()).unwrap_or(false)
382}
383
384unsafe extern "C" fn srv_entity_teleport(_ctx: *mut c_void, uuid: YogStr, pos: YogVec3) -> bool {
385    let Some(mut env) = get_env() else { return false };
386    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
387    env.call_static_method("dev/yog/NativeBridge", "entityTeleport",
388        "(Ljava/lang/String;DDD)Z",
389        &[JValue::Object(&ju), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)])
390    .and_then(|v| v.z()).unwrap_or(false)
391}
392
393unsafe extern "C" fn srv_entity_position(_ctx: *mut c_void, uuid: YogStr, out: *mut YogVec3) -> bool {
394    let Some(mut env) = get_env() else { return false };
395    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
396    let ret = env.call_static_method("dev/yog/NativeBridge", "entityPosition",
397        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&ju)]);
398    let obj = match ret.and_then(|v| v.l()) { Ok(o) => o, Err(_) => return false };
399    if obj.as_raw().is_null() { return false; }
400    let s: String = match env.get_string(&JString::from(obj)) { Ok(s) => String::from(s), Err(_) => return false };
401    let mut it = s.split('\t');
402    let (x, y, z) = (it.next(), it.next(), it.next());
403    if let (Some(x), Some(y), Some(z)) = (x.and_then(|v| v.parse().ok()), y.and_then(|v| v.parse().ok()), z.and_then(|v| v.parse().ok())) {
404        *out = YogVec3 { x, y, z }; true
405    } else { false }
406}
407
408unsafe extern "C" fn srv_entity_health(_ctx: *mut c_void, uuid: YogStr, out: *mut f32) -> bool {
409    let Some(mut env) = get_env() else { return false };
410    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
411    match env.call_static_method("dev/yog/NativeBridge", "entityHealth",
412        "(Ljava/lang/String;)D", &[JValue::Object(&ju)]).and_then(|v| v.d()) {
413        Ok(v) if !v.is_nan() => { *out = v as f32; true }
414        _ => false,
415    }
416}
417
418unsafe extern "C" fn srv_entity_set_health(_ctx: *mut c_void, uuid: YogStr, hp: f32) -> bool {
419    let Some(mut env) = get_env() else { return false };
420    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
421    env.call_static_method("dev/yog/NativeBridge", "entitySetHealth",
422        "(Ljava/lang/String;D)Z", &[JValue::Object(&ju), JValue::Double(hp as f64)])
423    .and_then(|v| v.z()).unwrap_or(false)
424}
425
426unsafe extern "C" fn srv_entity_kill(_ctx: *mut c_void, uuid: YogStr) -> bool {
427    let Some(mut env) = get_env() else { return false };
428    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
429    env.call_static_method("dev/yog/NativeBridge", "entityKill",
430        "(Ljava/lang/String;)Z", &[JValue::Object(&ju)])
431    .and_then(|v| v.z()).unwrap_or(false)
432}
433
434unsafe extern "C" fn srv_spawn_entity(_ctx: *mut c_void, type_id: YogStr, dim: YogStr, pos: YogVec3) -> YogOwnedStr {
435    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
436    let (Some(jt), Some(jd)) = (ys_to_java(&mut env, type_id), ys_to_java(&mut env, dim)) else { return YogOwnedStr::NONE };
437    let ret = env.call_static_method("dev/yog/NativeBridge", "spawnEntity",
438        "(Ljava/lang/String;Ljava/lang/String;DDD)Ljava/lang/String;",
439        &[JValue::Object(&jt), JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)]);
440    match ret.and_then(|v| v.l()) {
441        Ok(obj) => jstring_to_owned(&mut env, obj),
442        _ => YogOwnedStr::NONE,
443    }
444}
445
446unsafe extern "C" fn srv_entity_add_effect(_ctx: *mut c_void, uuid: YogStr, fx: YogStr, dur: i32, amp: u8, particles: bool) -> bool {
447    let Some(mut env) = get_env() else { return false };
448    let (Some(ju), Some(je)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, fx)) else { return false };
449    env.call_static_method("dev/yog/NativeBridge", "entityAddEffect",
450        "(Ljava/lang/String;Ljava/lang/String;IIZ)Z",
451        &[JValue::Object(&ju), JValue::Object(&je), JValue::Int(dur), JValue::Int(amp as i32), JValue::Bool(particles as u8)])
452    .and_then(|v| v.z()).unwrap_or(false)
453}
454
455unsafe extern "C" fn srv_entity_remove_effect(_ctx: *mut c_void, uuid: YogStr, fx: YogStr) -> bool {
456    let Some(mut env) = get_env() else { return false };
457    let (Some(ju), Some(je)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, fx)) else { return false };
458    env.call_static_method("dev/yog/NativeBridge", "entityRemoveEffect",
459        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ju), JValue::Object(&je)])
460    .and_then(|v| v.z()).unwrap_or(false)
461}
462
463unsafe extern "C" fn srv_entity_clear_effects(_ctx: *mut c_void, uuid: YogStr) -> bool {
464    let Some(mut env) = get_env() else { return false };
465    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
466    env.call_static_method("dev/yog/NativeBridge", "entityClearEffects",
467        "(Ljava/lang/String;)Z", &[JValue::Object(&ju)])
468    .and_then(|v| v.z()).unwrap_or(false)
469}
470
471unsafe extern "C" fn srv_entity_velocity(_ctx: *mut c_void, uuid: YogStr, out: *mut YogVec3) -> bool {
472    let Some(mut env) = get_env() else { return false };
473    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
474    let ret = env.call_static_method("dev/yog/NativeBridge", "entityVelocity",
475        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&ju)]);
476    let obj = match ret.and_then(|v| v.l()) { Ok(o) => o, Err(_) => return false };
477    if obj.as_raw().is_null() { return false; }
478    let s: String = match env.get_string(&JString::from(obj)) { Ok(s) => String::from(s), Err(_) => return false };
479    let mut it = s.split('\t');
480    let (x, y, z) = (it.next(), it.next(), it.next());
481    if let (Some(x), Some(y), Some(z)) = (x.and_then(|v| v.parse().ok()), y.and_then(|v| v.parse().ok()), z.and_then(|v| v.parse().ok())) {
482        *out = YogVec3 { x, y, z }; true
483    } else { false }
484}
485
486unsafe extern "C" fn srv_entity_set_velocity(_ctx: *mut c_void, uuid: YogStr, vel: YogVec3) -> bool {
487    let Some(mut env) = get_env() else { return false };
488    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
489    env.call_static_method("dev/yog/NativeBridge", "entitySetVelocity",
490        "(Ljava/lang/String;DDD)Z",
491        &[JValue::Object(&ju), JValue::Double(vel.x), JValue::Double(vel.y), JValue::Double(vel.z)])
492    .and_then(|v| v.z()).unwrap_or(false)
493}
494
495unsafe extern "C" fn srv_entity_add_velocity(_ctx: *mut c_void, uuid: YogStr, vel: YogVec3) -> bool {
496    let Some(mut env) = get_env() else { return false };
497    let Some(ju) = ys_to_java(&mut env, uuid) else { return false };
498    env.call_static_method("dev/yog/NativeBridge", "entityAddVelocity",
499        "(Ljava/lang/String;DDD)Z",
500        &[JValue::Object(&ju), JValue::Double(vel.x), JValue::Double(vel.y), JValue::Double(vel.z)])
501    .and_then(|v| v.z()).unwrap_or(false)
502}
503
504unsafe extern "C" fn srv_has_item_tag(_ctx: *mut c_void, item: YogStr, tag: YogStr) -> bool {
505    let Some(mut env) = get_env() else { return false };
506    let (Some(ji), Some(jt)) = (ys_to_java(&mut env, item), ys_to_java(&mut env, tag)) else { return false };
507    env.call_static_method("dev/yog/NativeBridge", "hasItemTag",
508        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ji), JValue::Object(&jt)])
509    .and_then(|v| v.z()).unwrap_or(false)
510}
511
512unsafe extern "C" fn srv_has_block_tag(_ctx: *mut c_void, block: YogStr, tag: YogStr) -> bool {
513    let Some(mut env) = get_env() else { return false };
514    let (Some(jb), Some(jt)) = (ys_to_java(&mut env, block), ys_to_java(&mut env, tag)) else { return false };
515    env.call_static_method("dev/yog/NativeBridge", "hasBlockTag",
516        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jb), JValue::Object(&jt)])
517    .and_then(|v| v.z()).unwrap_or(false)
518}
519
520unsafe extern "C" fn srv_drop_loot(_ctx: *mut c_void, table: YogStr, dim: YogStr, pos: YogVec3) -> bool {
521    let Some(mut env) = get_env() else { return false };
522    let (Some(jt), Some(jd)) = (ys_to_java(&mut env, table), ys_to_java(&mut env, dim)) else { return false };
523    env.call_static_method("dev/yog/NativeBridge", "dropLoot",
524        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
525        &[JValue::Object(&jt), JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)])
526    .and_then(|v| v.z()).unwrap_or(false)
527}
528
529unsafe extern "C" fn srv_scoreboard_get(_ctx: *mut c_void, obj: YogStr, player: YogStr, out: *mut i32) -> bool {
530    let Some(mut env) = get_env() else { return false };
531    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else { return false };
532    match env.call_static_method("dev/yog/NativeBridge", "scoreboardGet",
533        "(Ljava/lang/String;Ljava/lang/String;)I", &[JValue::Object(&jo), JValue::Object(&jp)]).and_then(|v| v.i()) {
534        Ok(v) if v != i32::MIN => { *out = v; true }
535        _ => false,
536    }
537}
538
539unsafe extern "C" fn srv_scoreboard_set(_ctx: *mut c_void, obj: YogStr, player: YogStr, score: i32) -> bool {
540    let Some(mut env) = get_env() else { return false };
541    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else { return false };
542    env.call_static_method("dev/yog/NativeBridge", "scoreboardSet",
543        "(Ljava/lang/String;Ljava/lang/String;I)Z",
544        &[JValue::Object(&jo), JValue::Object(&jp), JValue::Int(score)])
545    .and_then(|v| v.z()).unwrap_or(false)
546}
547
548unsafe extern "C" fn srv_scoreboard_add(_ctx: *mut c_void, obj: YogStr, player: YogStr, delta: i32, out: *mut i32) -> bool {
549    let Some(mut env) = get_env() else { return false };
550    let (Some(jo), Some(jp)) = (ys_to_java(&mut env, obj), ys_to_java(&mut env, player)) else { return false };
551    match env.call_static_method("dev/yog/NativeBridge", "scoreboardAdd",
552        "(Ljava/lang/String;Ljava/lang/String;I)I",
553        &[JValue::Object(&jo), JValue::Object(&jp), JValue::Int(delta)]).and_then(|v| v.i()) {
554        Ok(v) if v != i32::MIN => { *out = v; true }
555        _ => false,
556    }
557}
558
559unsafe extern "C" fn srv_bossbar_create(_ctx: *mut c_void, id: YogStr, title: YogStr, color: YogStr, style: YogStr) -> bool {
560    let Some(mut env) = get_env() else { return false };
561    let (Some(ji), Some(jt), Some(jc), Some(js)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, title), ys_to_java(&mut env, color), ys_to_java(&mut env, style)) else { return false };
562    env.call_static_method("dev/yog/NativeBridge", "bossbarCreate",
563        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
564        &[JValue::Object(&ji), JValue::Object(&jt), JValue::Object(&jc), JValue::Object(&js)])
565    .and_then(|v| v.z()).unwrap_or(false)
566}
567
568unsafe extern "C" fn srv_bossbar_remove(_ctx: *mut c_void, id: YogStr) -> bool {
569    let Some(mut env) = get_env() else { return false };
570    let Some(ji) = ys_to_java(&mut env, id) else { return false };
571    env.call_static_method("dev/yog/NativeBridge", "bossbarRemove",
572        "(Ljava/lang/String;)Z", &[JValue::Object(&ji)])
573    .and_then(|v| v.z()).unwrap_or(false)
574}
575
576unsafe extern "C" fn srv_bossbar_set_title(_ctx: *mut c_void, id: YogStr, title: YogStr) -> bool {
577    let Some(mut env) = get_env() else { return false };
578    let (Some(ji), Some(jt)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, title)) else { return false };
579    env.call_static_method("dev/yog/NativeBridge", "bossbarSetTitle",
580        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ji), JValue::Object(&jt)])
581    .and_then(|v| v.z()).unwrap_or(false)
582}
583
584unsafe extern "C" fn srv_bossbar_set_progress(_ctx: *mut c_void, id: YogStr, progress: f32) -> bool {
585    let Some(mut env) = get_env() else { return false };
586    let Some(ji) = ys_to_java(&mut env, id) else { return false };
587    env.call_static_method("dev/yog/NativeBridge", "bossbarSetProgress",
588        "(Ljava/lang/String;F)Z", &[JValue::Object(&ji), JValue::Float(progress)])
589    .and_then(|v| v.z()).unwrap_or(false)
590}
591
592unsafe extern "C" fn srv_bossbar_set_color(_ctx: *mut c_void, id: YogStr, color: YogStr) -> bool {
593    let Some(mut env) = get_env() else { return false };
594    let (Some(ji), Some(jc)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, color)) else { return false };
595    env.call_static_method("dev/yog/NativeBridge", "bossbarSetColor",
596        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ji), JValue::Object(&jc)])
597    .and_then(|v| v.z()).unwrap_or(false)
598}
599
600unsafe extern "C" fn srv_bossbar_add_player(_ctx: *mut c_void, id: YogStr, player: YogStr) -> bool {
601    let Some(mut env) = get_env() else { return false };
602    let (Some(ji), Some(jp)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, player)) else { return false };
603    env.call_static_method("dev/yog/NativeBridge", "bossbarAddPlayer",
604        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ji), JValue::Object(&jp)])
605    .and_then(|v| v.z()).unwrap_or(false)
606}
607
608unsafe extern "C" fn srv_bossbar_remove_player(_ctx: *mut c_void, id: YogStr, player: YogStr) -> bool {
609    let Some(mut env) = get_env() else { return false };
610    let (Some(ji), Some(jp)) = (ys_to_java(&mut env, id), ys_to_java(&mut env, player)) else { return false };
611    env.call_static_method("dev/yog/NativeBridge", "bossbarRemovePlayer",
612        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ji), JValue::Object(&jp)])
613    .and_then(|v| v.z()).unwrap_or(false)
614}
615
616unsafe extern "C" fn srv_bossbar_set_visible(_ctx: *mut c_void, id: YogStr, visible: bool) -> bool {
617    let Some(mut env) = get_env() else { return false };
618    let Some(ji) = ys_to_java(&mut env, id) else { return false };
619    env.call_static_method("dev/yog/NativeBridge", "bossbarSetVisible",
620        "(Ljava/lang/String;Z)Z", &[JValue::Object(&ji), JValue::Bool(visible as u8)])
621    .and_then(|v| v.z()).unwrap_or(false)
622}
623
624unsafe extern "C" fn srv_get_block_nbt(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos) -> YogOwnedStr {
625    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
626    let Some(jd) = ys_to_java(&mut env, dim) else { return YogOwnedStr::NONE };
627    let ret = env.call_static_method("dev/yog/NativeBridge", "getBlockNbt",
628        "(Ljava/lang/String;III)Ljava/lang/String;",
629        &[JValue::Object(&jd), JValue::Int(pos.x), JValue::Int(pos.y), JValue::Int(pos.z)]);
630    match ret.and_then(|v| v.l()) {
631        Ok(obj) => jstring_to_owned(&mut env, obj),
632        _ => YogOwnedStr::NONE,
633    }
634}
635
636unsafe extern "C" fn srv_set_block_nbt(_ctx: *mut c_void, dim: YogStr, pos: YogBlockPos, snbt: YogStr) -> bool {
637    let Some(mut env) = get_env() else { return false };
638    let (Some(jd), Some(js)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, snbt)) else { return false };
639    env.call_static_method("dev/yog/NativeBridge", "setBlockNbt",
640        "(Ljava/lang/String;IIILjava/lang/String;)Z",
641        &[JValue::Object(&jd), JValue::Int(pos.x), JValue::Int(pos.y), JValue::Int(pos.z), JValue::Object(&js)])
642    .and_then(|v| v.z()).unwrap_or(false)
643}
644
645unsafe extern "C" fn srv_player_inventory(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
646    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
647    let Some(jp) = ys_to_java(&mut env, player) else { return YogOwnedStr::NONE };
648    let ret = env.call_static_method("dev/yog/NativeBridge", "playerInventory",
649        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&jp)]);
650    match ret.and_then(|v| v.l()) {
651        Ok(obj) => jstring_to_owned(&mut env, obj),
652        _ => YogOwnedStr::NONE,
653    }
654}
655
656unsafe extern "C" fn srv_player_set_slot(_ctx: *mut c_void, player: YogStr, slot: u32, item_id: YogStr, count: u32) -> bool {
657    let Some(mut env) = get_env() else { return false };
658    let (Some(jp), Some(ji)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, item_id)) else { return false };
659    env.call_static_method("dev/yog/NativeBridge", "playerSetSlot",
660        "(Ljava/lang/String;ILjava/lang/String;I)Z",
661        &[JValue::Object(&jp), JValue::Int(slot as i32), JValue::Object(&ji), JValue::Int(count as i32)])
662    .and_then(|v| v.z()).unwrap_or(false)
663}
664
665unsafe extern "C" fn srv_player_teleport_dim(_ctx: *mut c_void, player: YogStr, dim: YogStr, pos: YogVec3) -> bool {
666    let Some(mut env) = get_env() else { return false };
667    let (Some(jp), Some(jd)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, dim)) else { return false };
668    env.call_static_method("dev/yog/NativeBridge", "teleportToDim",
669        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
670        &[JValue::Object(&jp), JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)])
671    .and_then(|v| v.z()).unwrap_or(false)
672}
673
674unsafe extern "C" fn srv_entity_teleport_dim(_ctx: *mut c_void, uuid: YogStr, dim: YogStr, pos: YogVec3) -> bool {
675    let Some(mut env) = get_env() else { return false };
676    let (Some(ju), Some(jd)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, dim)) else { return false };
677    env.call_static_method("dev/yog/NativeBridge", "entityTeleportToDim",
678        "(Ljava/lang/String;Ljava/lang/String;DDD)Z",
679        &[JValue::Object(&ju), JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z)])
680    .and_then(|v| v.z()).unwrap_or(false)
681}
682
683unsafe extern "C" fn srv_online_players(_ctx: *mut c_void) -> YogOwnedStr {
684    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
685    let ret = env.call_static_method("dev/yog/NativeBridge", "onlinePlayers",
686        "()Ljava/lang/String;", &[]);
687    match ret.and_then(|v| v.l()) {
688        Ok(obj) => jstring_to_owned(&mut env, obj),
689        _ => YogOwnedStr::NONE,
690    }
691}
692
693unsafe extern "C" fn srv_world_entity_count(_ctx: *mut c_void, dim: YogStr, entity_type: YogStr) -> i32 {
694    let Some(mut env) = get_env() else { return -1 };
695    let d = dim.as_str();
696    let et = entity_type.as_str();
697    let jd  = match env.new_string(d)  { Ok(s) => s, Err(_) => return -1 };
698    let jet = match env.new_string(et) { Ok(s) => s, Err(_) => return -1 };
699    let ret = env.call_static_method("dev/yog/NativeBridge", "worldEntityCount",
700        "(Ljava/lang/String;Ljava/lang/String;)I",
701        &[JValue::Object(&jd), JValue::Object(&jet)]);
702    match ret.and_then(|v| v.i()) {
703        Ok(n) => n,
704        _ => -1,
705    }
706}
707
708unsafe extern "C" fn srv_game_dir(_ctx: *mut c_void) -> YogOwnedStr {
709    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
710    let ret = env.call_static_method("dev/yog/NativeBridge", "gameDir",
711        "()Ljava/lang/String;", &[]);
712    match ret.and_then(|v| v.l()) {
713        Ok(obj) => jstring_to_owned(&mut env, obj),
714        _ => YogOwnedStr::NONE,
715    }
716}
717
718unsafe extern "C" fn srv_entity_get_nbt(_ctx: *mut c_void, uuid: YogStr) -> YogOwnedStr {
719    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
720    let Some(ju) = ys_to_java(&mut env, uuid) else { return YogOwnedStr::NONE };
721    let ret = env.call_static_method("dev/yog/NativeBridge", "entityGetNbt",
722        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&ju)]);
723    match ret.and_then(|v| v.l()) {
724        Ok(obj) => jstring_to_owned(&mut env, obj),
725        _ => YogOwnedStr::NONE,
726    }
727}
728
729unsafe extern "C" fn srv_entity_set_nbt(_ctx: *mut c_void, uuid: YogStr, snbt: YogStr) -> bool {
730    let Some(mut env) = get_env() else { return false };
731    let (Some(ju), Some(js)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, snbt)) else { return false };
732    env.call_static_method("dev/yog/NativeBridge", "entitySetNbt",
733        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&ju), JValue::Object(&js)])
734    .and_then(|v| v.z()).unwrap_or(false)
735}
736
737unsafe extern "C" fn srv_spawn_particles(
738    _ctx: *mut c_void, dim: YogStr, pos: YogVec3, particle_type: YogStr,
739    count: i32, dx: f64, dy: f64, dz: f64, speed: f64,
740) -> bool {
741    let Some(mut env) = get_env() else { return false };
742    let (Some(jd), Some(jp)) = (ys_to_java(&mut env, dim), ys_to_java(&mut env, particle_type)) else { return false };
743    env.call_static_method("dev/yog/NativeBridge", "spawnParticles",
744        "(Ljava/lang/String;DDDLjava/lang/String;IDDDD)Z",
745        &[JValue::Object(&jd), JValue::Double(pos.x), JValue::Double(pos.y), JValue::Double(pos.z),
746          JValue::Object(&jp), JValue::Int(count),
747          JValue::Double(dx), JValue::Double(dy), JValue::Double(dz), JValue::Double(speed)])
748    .and_then(|v| v.z()).unwrap_or(false)
749}
750
751unsafe extern "C" fn srv_entity_attribute_get(_ctx: *mut c_void, uuid: YogStr, attr: YogStr) -> f64 {
752    let Some(mut env) = get_env() else { return f64::NAN };
753    let (Some(ju), Some(ja)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, attr)) else { return f64::NAN };
754    env.call_static_method("dev/yog/NativeBridge", "entityAttributeGet",
755        "(Ljava/lang/String;Ljava/lang/String;)D", &[JValue::Object(&ju), JValue::Object(&ja)])
756    .and_then(|v| v.d()).unwrap_or(f64::NAN)
757}
758
759unsafe extern "C" fn srv_entity_attribute_set(_ctx: *mut c_void, uuid: YogStr, attr: YogStr, value: f64) -> bool {
760    let Some(mut env) = get_env() else { return false };
761    let (Some(ju), Some(ja)) = (ys_to_java(&mut env, uuid), ys_to_java(&mut env, attr)) else { return false };
762    env.call_static_method("dev/yog/NativeBridge", "entityAttributeSet",
763        "(Ljava/lang/String;Ljava/lang/String;D)Z",
764        &[JValue::Object(&ju), JValue::Object(&ja), JValue::Double(value)])
765    .and_then(|v| v.z()).unwrap_or(false)
766}
767
768unsafe extern "C" fn srv_get_held_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
769    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
770    let Some(jp) = ys_to_java(&mut env, player) else { return YogOwnedStr::NONE };
771    let ret = env.call_static_method("dev/yog/NativeBridge", "getHeldItemNbt",
772        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&jp)]);
773    match ret.and_then(|v| v.l()) {
774        Ok(obj) => jstring_to_owned(&mut env, obj),
775        _ => YogOwnedStr::NONE,
776    }
777}
778
779unsafe extern "C" fn srv_set_held_item_nbt(_ctx: *mut c_void, player: YogStr, snbt: YogStr) -> bool {
780    let Some(mut env) = get_env() else { return false };
781    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, snbt)) else { return false };
782    env.call_static_method("dev/yog/NativeBridge", "setHeldItemNbt",
783        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jp), JValue::Object(&js)])
784    .and_then(|v| v.z()).unwrap_or(false)
785}
786
787unsafe extern "C" fn srv_get_offhand_item_nbt(_ctx: *mut c_void, player: YogStr) -> YogOwnedStr {
788    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
789    let Some(jp) = ys_to_java(&mut env, player) else { return YogOwnedStr::NONE };
790    let ret = env.call_static_method("dev/yog/NativeBridge", "getOffhandItemNbt",
791        "(Ljava/lang/String;)Ljava/lang/String;", &[JValue::Object(&jp)]);
792    match ret.and_then(|v| v.l()) {
793        Ok(obj) => jstring_to_owned(&mut env, obj),
794        _ => YogOwnedStr::NONE,
795    }
796}
797
798unsafe extern "C" fn srv_set_offhand_item_nbt(_ctx: *mut c_void, player: YogStr, snbt: YogStr) -> bool {
799    let Some(mut env) = get_env() else { return false };
800    let (Some(jp), Some(js)) = (ys_to_java(&mut env, player), ys_to_java(&mut env, snbt)) else { return false };
801    env.call_static_method("dev/yog/NativeBridge", "setOffhandItemNbt",
802        "(Ljava/lang/String;Ljava/lang/String;)Z", &[JValue::Object(&jp), JValue::Object(&js)])
803    .and_then(|v| v.z()).unwrap_or(false)
804}
805
806unsafe extern "C" fn srv_get_slot_item(_ctx: *mut c_void, player: YogStr, slot: u32) -> YogOwnedStr {
807    let Some(mut env) = get_env() else { return YogOwnedStr::NONE };
808    let Some(jp) = ys_to_java(&mut env, player) else { return YogOwnedStr::NONE };
809    let ret = env.call_static_method("dev/yog/NativeBridge", "getSlotItem",
810        "(Ljava/lang/String;I)Ljava/lang/String;",
811        &[JValue::Object(&jp), JValue::Int(slot as i32)]);
812    match ret.and_then(|v| v.l()) {
813        Ok(obj) => jstring_to_owned(&mut env, obj),
814        _ => YogOwnedStr::NONE,
815    }
816}
817
818unsafe extern "C" fn srv_set_slot_item(
819    _ctx: *mut c_void, player: YogStr, slot: u32,
820    item_id: YogStr, count: u32, snbt: YogStr,
821) -> bool {
822    let Some(mut env) = get_env() else { return false };
823    let (Some(jp), Some(ji), Some(js)) = (
824        ys_to_java(&mut env, player), ys_to_java(&mut env, item_id), ys_to_java(&mut env, snbt)
825    ) else { return false };
826    env.call_static_method("dev/yog/NativeBridge", "setSlotItem",
827        "(Ljava/lang/String;ILjava/lang/String;ILjava/lang/String;)Z",
828        &[JValue::Object(&jp), JValue::Int(slot as i32), JValue::Object(&ji), JValue::Int(count as i32), JValue::Object(&js)])
829    .and_then(|v| v.z()).unwrap_or(false)
830}
831
832// ── ABI minor 14 — low-level GPU pipeline ────────────────────────────────────
833//
834// All raw GL calls go through `glow::Context` stored in GL.
835// `draw2d_*` functions still call JNI (NativeDraw) for MC text/texture rendering.
836// Everything is called on the render thread — no synchronization needed.
837
838// ── helpers ───────────────────────────────────────────────────────────────────
839
840fn gl_draw_mode(mode: u8) -> u32 {
841    match mode {
842        1 => glow::LINES,
843        2 => glow::LINE_STRIP,
844        3 => glow::TRIANGLE_STRIP,
845        4 => glow::TRIANGLE_FAN,
846        _ => glow::TRIANGLES,
847    }
848}
849
850fn gl_attr_type(dtype: u8) -> u32 {
851    match dtype {
852        1 => glow::UNSIGNED_BYTE,
853        2 => glow::INT,
854        3 => glow::UNSIGNED_INT,
855        _ => glow::FLOAT,
856    }
857}
858
859unsafe fn compile_shader(gl: &glow::Context, stage: u32, src: &str) -> Option<glow::NativeShader> {
860    let sh = gl.create_shader(stage).ok()?;
861    gl.shader_source(sh, src);
862    gl.compile_shader(sh);
863    if !gl.get_shader_compile_status(sh) {
864        yog_logging::error!("yog-gfx shader compile: {}", gl.get_shader_info_log(sh));
865        gl.delete_shader(sh);
866        return None;
867    }
868    Some(sh)
869}
870
871// ── GPU buffers ───────────────────────────────────────────────────────────────
872
873unsafe extern "C" fn gfx_buf_create() -> u32 {
874    let Some(g) = GL.get() else { return 0 };
875    g.0.create_buffer().map(|b| b.0.get()).unwrap_or(0)
876}
877
878unsafe extern "C" fn gfx_buf_delete(handle: u32) {
879    let Some(g) = GL.get() else { return };
880    let Some(n) = NonZeroU32::new(handle) else { return };
881    g.0.delete_buffer(glow::NativeBuffer(n));
882}
883
884unsafe extern "C" fn gfx_buf_data(handle: u32, bytes: *const u8, len: u32, dynamic: bool) {
885    let Some(g) = GL.get() else { return };
886    let Some(n) = NonZeroU32::new(handle) else { return };
887    let gl = &g.0;
888    let data = std::slice::from_raw_parts(bytes, len as usize);
889    let usage = if dynamic { glow::DYNAMIC_DRAW } else { glow::STATIC_DRAW };
890    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
891    gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, data, usage);
892    gl.bind_buffer(glow::ARRAY_BUFFER, None);
893}
894
895unsafe extern "C" fn gfx_buf_subdata(handle: u32, offset: u32, bytes: *const u8, len: u32) {
896    let Some(g) = GL.get() else { return };
897    let Some(n) = NonZeroU32::new(handle) else { return };
898    let gl = &g.0;
899    let data = std::slice::from_raw_parts(bytes, len as usize);
900    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(n)));
901    gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, offset as i32, data);
902    gl.bind_buffer(glow::ARRAY_BUFFER, None);
903}
904
905// ── Vertex arrays ─────────────────────────────────────────────────────────────
906
907unsafe extern "C" fn gfx_vao_create() -> u32 {
908    let Some(g) = GL.get() else { return 0 };
909    g.0.create_vertex_array().map(|v| v.0.get()).unwrap_or(0)
910}
911
912unsafe extern "C" fn gfx_vao_delete(handle: u32) {
913    let Some(g) = GL.get() else { return };
914    let Some(n) = NonZeroU32::new(handle) else { return };
915    g.0.delete_vertex_array(glow::NativeVertexArray(n));
916}
917
918unsafe extern "C" fn gfx_vao_attrib(
919    vao: u32, vbo: u32, index: u32, components: u8,
920    dtype: u8, normalized: bool, stride: u32, offset: u32,
921) {
922    let Some(g) = GL.get() else { return };
923    let (Some(vn), Some(bn)) = (NonZeroU32::new(vao), NonZeroU32::new(vbo)) else { return };
924    let gl = &g.0;
925    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
926    gl.bind_buffer(glow::ARRAY_BUFFER, Some(glow::NativeBuffer(bn)));
927    let gl_type = gl_attr_type(dtype);
928    if dtype == 2 || dtype == 3 {
929        gl.vertex_attrib_pointer_i32(index, components as i32, gl_type, stride as i32, offset as i32);
930    } else {
931        gl.vertex_attrib_pointer_f32(index, components as i32, gl_type, normalized, stride as i32, offset as i32);
932    }
933    gl.enable_vertex_attrib_array(index);
934    gl.bind_vertex_array(None);
935}
936
937unsafe extern "C" fn gfx_vao_set_ebo(vao: u32, ebo: u32) {
938    let Some(g) = GL.get() else { return };
939    let (Some(vn), Some(en)) = (NonZeroU32::new(vao), NonZeroU32::new(ebo)) else { return };
940    let gl = &g.0;
941    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
942    gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(glow::NativeBuffer(en)));
943    gl.bind_vertex_array(None);
944}
945
946// ── Shader programs ───────────────────────────────────────────────────────────
947
948/// Returns a path under `~/.cache/yog/shaders/<hash>.ysc` for the given GLSL source pair,
949/// creating the directory if needed.  Returns `None` if the home directory is unknown.
950fn shader_cache_path(vert: &str, frag: &str) -> Option<PathBuf> {
951    use std::hash::{Hash, Hasher};
952    let mut h = std::collections::hash_map::DefaultHasher::new();
953    vert.hash(&mut h);
954    frag.hash(&mut h);
955    let hash = h.finish();
956    let dir = std::env::var("HOME")
957        .map(|home| PathBuf::from(home).join(".cache").join("yog").join("shaders"))
958        .ok()?;
959    std::fs::create_dir_all(&dir).ok()?;
960    Some(dir.join(format!("{hash:016x}.ysc")))
961}
962
963/// Try restoring a program from a binary blob (`[4 LE bytes: GL format][binary…]`).
964/// Returns `Some(handle)` if the driver accepts the binary, `None` otherwise.
965///
966/// Uses `glProgramBinary` (GL 4.1 / ARB_get_program_binary) via the raw pointer
967/// captured in `nativeGlInit`.  Returns `None` if the extension is unavailable.
968unsafe fn load_shader_binary(gl: &glow::Context, data: &[u8]) -> Option<glow::NativeProgram> {
969    if data.len() < 4 { return None; }
970    type ProgramBinaryFn = unsafe extern "system" fn(u32, u32, *const c_void, i32);
971    let fn_ptr = (*GL_PROGRAM_BINARY.get()?)?;
972    let program_binary: ProgramBinaryFn = std::mem::transmute(fn_ptr);
973
974    let fmt = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
975    let prog = gl.create_program().ok()?;
976    program_binary(prog.0.get(), fmt, data[4..].as_ptr() as *const c_void, (data.len() - 4) as i32);
977    if gl.get_program_link_status(prog) { Some(prog) } else { gl.delete_program(prog); None }
978}
979
980/// Read the compiled binary of a linked program.  Returns empty `Vec` if unsupported.
981unsafe fn get_shader_binary(prog: glow::NativeProgram) -> (Vec<u8>, u32) {
982    type GetProgramivFn       = unsafe extern "system" fn(u32, u32, *mut i32);
983    type GetProgramBinaryFn   = unsafe extern "system" fn(u32, i32, *mut i32, *mut u32, *mut c_void);
984
985    let Some(get_iv_raw)  = GL_GET_PROGRAM_IV.get().and_then(|v| *v) else { return (vec![], 0) };
986    let Some(get_bin_raw) = GL_GET_PROGRAM_BINARY.get().and_then(|v| *v) else { return (vec![], 0) };
987    let get_program_iv:     GetProgramivFn       = std::mem::transmute(get_iv_raw);
988    let get_program_binary: GetProgramBinaryFn   = std::mem::transmute(get_bin_raw);
989
990    // Query binary size via glGetProgramiv(GL_PROGRAM_BINARY_LENGTH).
991    const PROGRAM_BINARY_LENGTH: u32 = 0x8741;
992    let mut size: i32 = 0;
993    get_program_iv(prog.0.get(), PROGRAM_BINARY_LENGTH, &mut size);
994    if size <= 0 { return (vec![], 0); }
995
996    let mut buf = vec![0u8; size as usize];
997    let mut actual_len: i32 = 0;
998    let mut fmt: u32 = 0;
999    get_program_binary(prog.0.get(), size, &mut actual_len, &mut fmt, buf.as_mut_ptr() as *mut c_void);
1000    buf.truncate(actual_len.max(0) as usize);
1001    (buf, fmt)
1002}
1003
1004unsafe extern "C" fn gfx_prog_create(vert: YogStr, frag: YogStr, out: *mut u32) -> bool {
1005    let Some(g) = GL.get() else { return false };
1006    let gl = &g.0;
1007    let vert_s = vert.as_str();
1008    let frag_s = frag.as_str();
1009
1010    // Fast path: binary shader cache — avoids GLSL re-compilation on subsequent launches.
1011    let cache_path = shader_cache_path(vert_s, frag_s);
1012    if let Some(ref path) = cache_path {
1013        if let Ok(data) = std::fs::read(path) {
1014            if let Some(prog) = load_shader_binary(gl, &data) {
1015                *out = prog.0.get();
1016                return true;
1017            }
1018            // Cache stale (driver updated?); remove and fall through to GLSL compile.
1019            let _ = std::fs::remove_file(path);
1020        }
1021    }
1022
1023    // GLSL compile path.
1024    let vs = match compile_shader(gl, glow::VERTEX_SHADER, vert_s) {
1025        Some(s) => s,
1026        None => return false,
1027    };
1028    let fs = match compile_shader(gl, glow::FRAGMENT_SHADER, frag_s) {
1029        Some(s) => s,
1030        None => { gl.delete_shader(vs); return false; }
1031    };
1032    let prog = match gl.create_program() {
1033        Ok(p) => p,
1034        Err(e) => {
1035            yog_logging::error!("yog-gfx: create_program: {}", e);
1036            gl.delete_shader(vs); gl.delete_shader(fs);
1037            return false;
1038        }
1039    };
1040    gl.attach_shader(prog, vs);
1041    gl.attach_shader(prog, fs);
1042    gl.link_program(prog);
1043    gl.detach_shader(prog, vs);
1044    gl.detach_shader(prog, fs);
1045    gl.delete_shader(vs);
1046    gl.delete_shader(fs);
1047    if !gl.get_program_link_status(prog) {
1048        yog_logging::error!("yog-gfx: shader link: {}", gl.get_program_info_log(prog));
1049        gl.delete_program(prog);
1050        return false;
1051    }
1052
1053    // Persist binary so the next launch skips GLSL compilation entirely.
1054    if let Some(ref path) = cache_path {
1055        let (binary, fmt) = get_shader_binary(prog);
1056        if !binary.is_empty() {
1057            let mut blob = fmt.to_le_bytes().to_vec();
1058            blob.extend_from_slice(&binary);
1059            let _ = std::fs::write(path, &blob);
1060        }
1061    }
1062
1063    *out = prog.0.get();
1064    true
1065}
1066
1067unsafe extern "C" fn gfx_prog_delete(handle: u32) {
1068    let Some(g) = GL.get() else { return };
1069    let Some(n) = NonZeroU32::new(handle) else { return };
1070    g.0.delete_program(glow::NativeProgram(n));
1071}
1072
1073macro_rules! with_prog {
1074    ($handle:expr, |$gl:ident, $prog:ident, $loc:ident ($name:expr)| $body:expr) => {{
1075        let Some(g) = GL.get() else { return };
1076        let Some(n) = NonZeroU32::new($handle) else { return };
1077        let $gl = &g.0;
1078        let $prog = glow::NativeProgram(n);
1079        $gl.use_program(Some($prog));
1080        let $loc = $gl.get_uniform_location($prog, $name.as_str());
1081        $body
1082    }};
1083}
1084
1085unsafe extern "C" fn gfx_prog_uniform_1i(prog: u32, name: YogStr, v: i32) {
1086    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_i32(loc.as_ref(), v));
1087}
1088unsafe extern "C" fn gfx_prog_uniform_1f(prog: u32, name: YogStr, v: f32) {
1089    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_1_f32(loc.as_ref(), v));
1090}
1091unsafe extern "C" fn gfx_prog_uniform_2f(prog: u32, name: YogStr, x: f32, y: f32) {
1092    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_2_f32(loc.as_ref(), x, y));
1093}
1094unsafe extern "C" fn gfx_prog_uniform_3f(prog: u32, name: YogStr, x: f32, y: f32, z: f32) {
1095    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_3_f32(loc.as_ref(), x, y, z));
1096}
1097unsafe extern "C" fn gfx_prog_uniform_4f(prog: u32, name: YogStr, x: f32, y: f32, z: f32, w: f32) {
1098    with_prog!(prog, |gl, _p, loc(name)| gl.uniform_4_f32(loc.as_ref(), x, y, z, w));
1099}
1100unsafe extern "C" fn gfx_prog_uniform_mat4(prog: u32, name: YogStr, col_major: *const f32) {
1101    with_prog!(prog, |gl, _p, loc(name)| {
1102        let data = std::slice::from_raw_parts(col_major, 16);
1103        gl.uniform_matrix_4_f32_slice(loc.as_ref(), false, data);
1104    });
1105}
1106
1107// ── Textures ──────────────────────────────────────────────────────────────────
1108
1109unsafe extern "C" fn gfx_tex_create(w: u32, h: u32, rgba: *const u8, linear: bool) -> u32 {
1110    let Some(g) = GL.get() else { return 0 };
1111    let gl = &g.0;
1112    let tex = match gl.create_texture() { Ok(t) => t, Err(_) => return 0 };
1113    gl.bind_texture(glow::TEXTURE_2D, Some(tex));
1114    let filter = if linear { glow::LINEAR } else { glow::NEAREST };
1115    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, filter as i32);
1116    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, filter as i32);
1117    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
1118    gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
1119    let pixels = std::slice::from_raw_parts(rgba, (w * h * 4) as usize);
1120    gl.tex_image_2d(
1121        glow::TEXTURE_2D, 0, glow::RGBA8 as i32,
1122        w as i32, h as i32, 0,
1123        glow::RGBA, glow::UNSIGNED_BYTE,
1124        Some(pixels),
1125    );
1126    gl.bind_texture(glow::TEXTURE_2D, None);
1127    tex.0.get()
1128}
1129
1130unsafe extern "C" fn gfx_tex_delete(handle: u32) {
1131    let Some(g) = GL.get() else { return };
1132    let Some(n) = NonZeroU32::new(handle) else { return };
1133    g.0.delete_texture(glow::NativeTexture(n));
1134}
1135
1136unsafe extern "C" fn gfx_tex_bind(unit: u32, handle: u32) {
1137    let Some(g) = GL.get() else { return };
1138    let gl = &g.0;
1139    gl.active_texture(glow::TEXTURE0 + unit);
1140    match NonZeroU32::new(handle) {
1141        Some(n) => gl.bind_texture(glow::TEXTURE_2D, Some(glow::NativeTexture(n))),
1142        None    => gl.bind_texture(glow::TEXTURE_2D, None),
1143    }
1144}
1145
1146unsafe extern "C" fn gfx_tex_from_mc(id: YogStr) -> u32 {
1147    let Some(mut env) = get_env() else { return 0 };
1148    let Some(ji) = ys_to_java(&mut env, id) else { return 0 };
1149    env.call_static_method("dev/yog/NativeDraw", "getMcTextureId",
1150        "(Ljava/lang/String;)I", &[JValue::Object(&ji)])
1151        .and_then(|v| v.i())
1152        .map(|id| id as u32)
1153        .unwrap_or(0)
1154}
1155
1156// ── Draw calls ────────────────────────────────────────────────────────────────
1157
1158unsafe extern "C" fn gfx_draw_arrays(vao: u32, prog: u32, mode: u8, first: u32, count: u32) {
1159    let Some(g) = GL.get() else { return };
1160    let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else { return };
1161    let gl = &g.0;
1162    gl.use_program(Some(glow::NativeProgram(pn)));
1163    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1164    gl.draw_arrays(gl_draw_mode(mode), first as i32, count as i32);
1165    gl.bind_vertex_array(None);
1166}
1167
1168unsafe extern "C" fn gfx_draw_elements(vao: u32, ebo: u32, prog: u32, mode: u8, count: u32, u32_idx: bool) {
1169    let Some(g) = GL.get() else { return };
1170    let (Some(vn), Some(pn)) = (NonZeroU32::new(vao), NonZeroU32::new(prog)) else { return };
1171    let gl = &g.0;
1172    gl.use_program(Some(glow::NativeProgram(pn)));
1173    gl.bind_vertex_array(Some(glow::NativeVertexArray(vn)));
1174    // EBO is stored in the VAO; ebo param is informational for safety but not re-bound here.
1175    let _ = ebo;
1176    let idx_type = if u32_idx { glow::UNSIGNED_INT } else { glow::UNSIGNED_SHORT };
1177    gl.draw_elements(gl_draw_mode(mode), count as i32, idx_type, 0);
1178    gl.bind_vertex_array(None);
1179}
1180
1181// ── Render state ──────────────────────────────────────────────────────────────
1182
1183unsafe extern "C" fn gfx_set_blend(enabled: bool, src: u32, dst: u32) {
1184    let Some(g) = GL.get() else { return };
1185    let gl = &g.0;
1186    if enabled {
1187        gl.enable(glow::BLEND);
1188        gl.blend_func(src, dst);
1189    } else {
1190        gl.disable(glow::BLEND);
1191    }
1192}
1193
1194unsafe extern "C" fn gfx_set_depth(test: bool, write: bool) {
1195    let Some(g) = GL.get() else { return };
1196    let gl = &g.0;
1197    if test { gl.enable(glow::DEPTH_TEST); } else { gl.disable(glow::DEPTH_TEST); }
1198    gl.depth_mask(write);
1199}
1200
1201unsafe extern "C" fn gfx_set_scissor(x: i32, y: i32, w: i32, h: i32) {
1202    let Some(g) = GL.get() else { return };
1203    let gl = &g.0;
1204    gl.enable(glow::SCISSOR_TEST);
1205    gl.scissor(x, y, w, h);
1206}
1207
1208unsafe extern "C" fn gfx_clear_scissor() {
1209    let Some(g) = GL.get() else { return };
1210    g.0.disable(glow::SCISSOR_TEST);
1211}
1212
1213unsafe extern "C" fn gfx_set_viewport(x: i32, y: i32, w: i32, h: i32) {
1214    let Some(g) = GL.get() else { return };
1215    g.0.viewport(x, y, w, h);
1216}
1217
1218// ── 2D convenience (JNI — uses MC's DrawContext / text renderer) ──────────────
1219
1220unsafe extern "C" fn gfx_draw2d_rect(x1: f32, y1: f32, x2: f32, y2: f32, color: u32) {
1221    let Some(mut env) = get_env() else { return };
1222    let _ = env.call_static_method("dev/yog/NativeDraw", "drawRect",
1223        "(FFFFI)V",
1224        &[JValue::Float(x1), JValue::Float(y1), JValue::Float(x2), JValue::Float(y2),
1225          JValue::Int(color as i32)]);
1226}
1227
1228unsafe extern "C" fn gfx_draw2d_gradient(x1: f32, y1: f32, x2: f32, y2: f32, top: u32, bottom: u32) {
1229    let Some(mut env) = get_env() else { return };
1230    let _ = env.call_static_method("dev/yog/NativeDraw", "drawGradientRect",
1231        "(FFFFII)V",
1232        &[JValue::Float(x1), JValue::Float(y1), JValue::Float(x2), JValue::Float(y2),
1233          JValue::Int(top as i32), JValue::Int(bottom as i32)]);
1234}
1235
1236unsafe extern "C" fn gfx_draw2d_text(text: YogStr, x: f32, y: f32, color: u32, shadow: bool) {
1237    let Some(mut env) = get_env() else { return };
1238    if let Some(jt) = ys_to_java(&mut env, text) {
1239        let _ = env.call_static_method("dev/yog/NativeDraw", "drawText",
1240            "(Ljava/lang/String;FFIZ)V",
1241            &[JValue::Object(&jt), JValue::Float(x), JValue::Float(y),
1242              JValue::Int(color as i32), JValue::Bool(shadow as u8)]);
1243    }
1244}
1245
1246unsafe extern "C" fn gfx_draw2d_mc_tex(
1247    id: YogStr, x: f32, y: f32, u0: f32, v0: f32, w: f32, h: f32, tw: f32, th: f32,
1248) {
1249    let Some(mut env) = get_env() else { return };
1250    if let Some(ji) = ys_to_java(&mut env, id) {
1251        let _ = env.call_static_method("dev/yog/NativeDraw", "drawTexture",
1252            "(Ljava/lang/String;FFFFFFFFF)V",
1253            &[JValue::Object(&ji),
1254              JValue::Float(x), JValue::Float(y), JValue::Float(u0), JValue::Float(v0),
1255              JValue::Float(w), JValue::Float(h), JValue::Float(tw), JValue::Float(th)]);
1256    }
1257}
1258
1259// ── Static GFX function table (function pointers only — per-frame data filled at call time) ──
1260
1261static GFX_FN_TABLE: YogGfxApi = YogGfxApi {
1262    // Per-frame fields zeroed in the static; actual values are set on the stack per render call.
1263    screen_w: 0, screen_h: 0, delta_tick: 0.0, scale_factor: 1.0,
1264    view_proj: [0.0; 16], camera_pos: [0.0; 3], player_pos: [0.0; 3], _pad1: 0.0,
1265    buf_create:        gfx_buf_create,
1266    buf_delete:        gfx_buf_delete,
1267    buf_data:          gfx_buf_data,
1268    buf_subdata:       gfx_buf_subdata,
1269    vao_create:        gfx_vao_create,
1270    vao_delete:        gfx_vao_delete,
1271    vao_attrib:        gfx_vao_attrib,
1272    vao_set_ebo:       gfx_vao_set_ebo,
1273    prog_create:       gfx_prog_create,
1274    prog_delete:       gfx_prog_delete,
1275    prog_uniform_1i:   gfx_prog_uniform_1i,
1276    prog_uniform_1f:   gfx_prog_uniform_1f,
1277    prog_uniform_2f:   gfx_prog_uniform_2f,
1278    prog_uniform_3f:   gfx_prog_uniform_3f,
1279    prog_uniform_4f:   gfx_prog_uniform_4f,
1280    prog_uniform_mat4: gfx_prog_uniform_mat4,
1281    tex_create:        gfx_tex_create,
1282    tex_delete:        gfx_tex_delete,
1283    tex_bind:          gfx_tex_bind,
1284    tex_from_mc:       gfx_tex_from_mc,
1285    draw_arrays:       gfx_draw_arrays,
1286    draw_elements:     gfx_draw_elements,
1287    set_blend:         gfx_set_blend,
1288    set_depth:         gfx_set_depth,
1289    set_scissor:       gfx_set_scissor,
1290    clear_scissor:     gfx_clear_scissor,
1291    set_viewport:      gfx_set_viewport,
1292    draw2d_rect:       gfx_draw2d_rect,
1293    draw2d_gradient:   gfx_draw2d_gradient,
1294    draw2d_text:       gfx_draw2d_text,
1295    draw2d_mc_tex:     gfx_draw2d_mc_tex,
1296};
1297
1298// ── YogApi registration functions ─────────────────────────────────────────────
1299//
1300// ctx is *mut RuntimeHandlers (cast from *mut c_void).
1301
1302macro_rules! api_event {
1303    ($name:ident, $field:ident, $fn_ty:ty) => {
1304        unsafe extern "C" fn $name(ctx: *mut c_void, ud: *mut c_void, h: $fn_ty) {
1305            let handlers = &mut *(ctx as *mut RuntimeHandlers);
1306            handlers.$field.push((ud, h));
1307        }
1308    };
1309}
1310
1311api_event!(api_on_block_break,      block_break,        YogBlockBreakFn);
1312api_event!(api_on_chat,             chat,               YogChatFn);
1313api_event!(api_on_player_join,      player_join,        YogPlayerFn);
1314api_event!(api_on_player_leave,     player_leave,       YogPlayerFn);
1315api_event!(api_on_use_item,         use_item,           YogUseItemFn);
1316api_event!(api_on_use_block,        use_block,          YogUseBlockFn);
1317api_event!(api_on_attack_entity,    attack_entity,      YogAttackEntityFn);
1318api_event!(api_on_entity_damage,    entity_damage,      YogEntityDamageFn);
1319api_event!(api_on_entity_death,     entity_death,       YogEntityDeathFn);
1320api_event!(api_on_entity_spawn,     entity_spawn,       YogEntitySpawnFn);
1321api_event!(api_on_player_place_block, player_place_block, YogPlaceBlockFn);
1322api_event!(api_on_player_death,     player_death,       YogPlayerDeathFn);
1323api_event!(api_on_player_respawn,   player_respawn,     YogPlayerRespawnFn);
1324api_event!(api_on_advancement,      advancement,        YogAdvancementFn);
1325api_event!(api_on_entity_interact,  entity_interact,    YogEntityInteractFn);
1326api_event!(api_on_item_craft,       item_craft,         YogCraftFn);
1327api_event!(api_on_explosion,        explosion,          YogExplosionFn);
1328api_event!(api_on_item_pickup,      item_pickup,        YogItemPickupFn);
1329api_event!(api_on_player_move,      player_move,        YogPlayerMoveFn);
1330api_event!(api_on_container_open,   container_open,     YogContainerOpenFn);
1331api_event!(api_on_container_close,  container_close,    YogContainerCloseFn);
1332api_event!(api_on_projectile_hit,   projectile_hit,     YogProjectileHitFn);
1333api_event!(api_on_client_tick,      client_tick,        YogClientFn);
1334api_event!(api_on_hud_render,       hud_render,         YogHudRenderFn);
1335api_event!(api_on_world_render,     world_render,       YogWorldRenderFn);
1336api_event!(api_on_key_press,        key_press,          YogKeyPressFn);
1337api_event!(api_on_screen_open,      screen_open,        YogScreenFn);
1338api_event!(api_on_screen_close,     screen_close,       YogScreenFn);
1339api_event!(api_on_server_tick,      server_tick,        YogServerFn);
1340api_event!(api_on_server_started,   server_started,     YogServerFn);
1341api_event!(api_on_server_stopping,  server_stopping,    YogServerFn);
1342
1343unsafe extern "C" fn api_on_packet(ctx: *mut c_void, channel: YogStr, ud: *mut c_void, h: YogPacketFn) {
1344    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1345    handlers.packets.insert(channel.as_str().to_owned(), (ud, h));
1346}
1347
1348unsafe extern "C" fn api_on_client_packet(ctx: *mut c_void, channel: YogStr, ud: *mut c_void, h: YogPacketFn) {
1349    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1350    handlers.client_packets.insert(channel.as_str().to_owned(), (ud, h));
1351}
1352
1353unsafe extern "C" fn api_register_command(ctx: *mut c_void, name: YogStr, ud: *mut c_void, h: YogCommandFn) {
1354    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1355    handlers.commands.insert(name.as_str().to_owned(), (ud, h));
1356}
1357
1358unsafe extern "C" fn api_register_typed_command(ctx: *mut c_void, name: YogStr, schema: YogStr, ud: *mut c_void, h: YogCommandFn) {
1359    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1360    let n = name.as_str().to_owned();
1361    handlers.typed_schemas.insert(n.clone(), schema.as_str().to_owned());
1362    handlers.commands.insert(n, (ud, h));
1363}
1364
1365
1366unsafe extern "C" fn api_register_recipe_json(ctx: *mut c_void, namespace: YogStr, name: YogStr, json: YogStr) {
1367    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1368    handlers.recipes.push((
1369        namespace.as_str().to_owned(),
1370        name.as_str().to_owned(),
1371        json.as_str().to_owned(),
1372    ));
1373}
1374
1375unsafe extern "C" fn api_register_item(ctx: *mut c_void, def: *const YogItemDef) {
1376    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1377    let d = &*def;
1378    let food = if d.food_nutrition > 0 {
1379        Some(FoodDef { nutrition: d.food_nutrition, saturation: d.food_saturation, can_always_eat: d.food_always_eat })
1380    } else { None };
1381    handlers.items.push(ItemDef {
1382        id:            d.id.as_str().to_owned(),
1383        max_stack:     d.max_stack as u8,
1384        name:          if d.name.is_empty() { None } else { Some(d.name.as_str().to_owned()) },
1385        tooltip:       if d.tooltip.is_empty() { None } else { Some(d.tooltip.as_str().to_owned()) },
1386        max_damage:    d.max_damage,
1387        fire_resistant: d.fire_resistant,
1388        fuel_ticks:    d.fuel_ticks,
1389        food,
1390    });
1391}
1392
1393unsafe extern "C" fn api_register_block(ctx: *mut c_void, def: *const YogBlockDef) {
1394    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1395    let d = &*def;
1396    handlers.blocks.push(BlockDef {
1397        id:            d.id.as_str().to_owned(),
1398        hardness:      d.hardness,
1399        resistance:    d.resistance,
1400        name:          if d.name.is_empty() { None } else { Some(d.name.as_str().to_owned()) },
1401        light_level:   d.light_level,
1402        sound:         if d.sound.is_empty() { None } else { Some(d.sound.as_str().to_owned()) },
1403        requires_tool: d.requires_tool,
1404        no_collision:  d.no_collision,
1405        slipperiness:  d.slipperiness,
1406        shape:         if d.shape == [0.0f32; 6] { None } else { Some(d.shape) },
1407    });
1408}
1409
1410unsafe extern "C" fn api_schedule_once(ctx: *mut c_void, delay_ticks: u64, ud: *mut c_void, h: YogScheduledFn) {
1411    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1412    handlers.scheduler.lock().expect("scheduler poisoned").once_tasks.push(OnceTask { delay_remaining: delay_ticks, ud, f: h });
1413}
1414
1415unsafe extern "C" fn api_schedule_repeating(ctx: *mut c_void, period_ticks: u64, ud: *mut c_void, h: YogScheduledFn) {
1416    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1417    handlers.scheduler.lock().expect("scheduler poisoned").repeating_tasks.push(RepeatingTask { period: period_ticks, ticks_left: period_ticks, ud, f: h });
1418}
1419
1420unsafe extern "C" fn api_register_startup_grant(ctx: *mut c_void, grant: *const YogStartupGrantDef) {
1421    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1422    let g = &*grant;
1423    let items: Vec<String> = if g.items.is_empty() {
1424        Vec::new()
1425    } else {
1426        unsafe { g.items.as_str().split('|').map(|s: &str| s.to_owned()).collect() }
1427    };
1428    let book = if g.book.is_empty() { None } else { Some(unsafe { g.book.as_str().to_owned() }) };
1429    let command = if g.command.is_empty() { None } else { Some(unsafe { g.command.as_str().to_owned() }) };
1430    handlers.startup_grants.push(yog_registry::StartupGrant {
1431        id: unsafe { g.id.as_str().to_owned() },
1432        items,
1433        book,
1434        command,
1435    });
1436}
1437
1438unsafe extern "C" fn api_register_book(ctx: *mut c_void, book_id: YogStr, book_json: YogStr) {
1439    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1440    handlers.books.insert(unsafe { book_id.as_str().to_owned() }, unsafe { book_json.as_str().to_owned() });
1441}
1442
1443unsafe extern "C" fn api_register_ui(ctx: *mut c_void, ui_id: YogStr, _layout_json: YogStr,
1444                                     ud: *mut c_void, h: yog_abi::YogUIEventFn) {
1445    let handlers = &mut *(ctx as *mut RuntimeHandlers);
1446    let id = unsafe { ui_id.as_str().to_owned() };
1447    handlers.ui_handlers.insert(id.clone(), (ud, h));
1448    yog_logging::info!("registered UI handler: {}", id);
1449}
1450
1451
1452#[no_mangle]
1453pub extern "system" fn Java_dev_yog_NativeBridge_nativeBookJson<'l>(
1454    mut env: JNIEnv<'l>, _class: JClass<'l>, book_id: JString<'l>,
1455) -> jstring {
1456    let id = match env.get_string(&book_id) {
1457        Ok(s) => String::from(s),
1458        Err(_) => return std::ptr::null_mut(),
1459    };
1460    let json = handlers().books.get(&id).cloned().unwrap_or_else(|| "null".to_string());
1461    env.new_string(json).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
1462}
1463
1464// ── Table constructors ────────────────────────────────────────────────────────
1465
1466fn build_server_table() -> YogServer {
1467    YogServer {
1468        ctx:         std::ptr::null_mut(),
1469        abi_version: ABI_VERSION,
1470        size:        std::mem::size_of::<YogServer>() as u32,
1471        free_str:    yog_free_str,
1472        broadcast:   srv_broadcast,
1473        get_block:   srv_get_block,
1474        set_block:   srv_set_block,
1475        world_time:  srv_world_time,
1476        set_time:    srv_set_time,
1477        is_raining:  srv_is_raining,
1478        set_weather: srv_set_weather,
1479        give_item:   srv_give_item,
1480        player_teleport: srv_player_teleport,
1481        send_to_player: srv_send_to_player,
1482        send_to_server: srv_send_to_server,
1483        kick_player: srv_kick_player,
1484        set_gamemode: srv_set_gamemode,
1485        send_title:  srv_send_title,
1486        send_actionbar: srv_send_actionbar,
1487        play_sound:  srv_play_sound,
1488        play_sound_player: srv_play_sound_player,
1489        entity_teleport: srv_entity_teleport,
1490        entity_position: srv_entity_position,
1491        entity_health: srv_entity_health,
1492        entity_set_health: srv_entity_set_health,
1493        entity_kill: srv_entity_kill,
1494        spawn_entity: srv_spawn_entity,
1495        entity_add_effect: srv_entity_add_effect,
1496        entity_remove_effect: srv_entity_remove_effect,
1497        entity_clear_effects: srv_entity_clear_effects,
1498        entity_velocity: srv_entity_velocity,
1499        entity_set_velocity: srv_entity_set_velocity,
1500        entity_add_velocity: srv_entity_add_velocity,
1501        has_item_tag: srv_has_item_tag,
1502        has_block_tag: srv_has_block_tag,
1503        drop_loot: srv_drop_loot,
1504        scoreboard_get: srv_scoreboard_get,
1505        scoreboard_set: srv_scoreboard_set,
1506        scoreboard_add: srv_scoreboard_add,
1507        bossbar_create: srv_bossbar_create,
1508        bossbar_remove: srv_bossbar_remove,
1509        bossbar_set_title: srv_bossbar_set_title,
1510        bossbar_set_progress: srv_bossbar_set_progress,
1511        bossbar_set_color: srv_bossbar_set_color,
1512        bossbar_add_player: srv_bossbar_add_player,
1513        bossbar_remove_player: srv_bossbar_remove_player,
1514        bossbar_set_visible: srv_bossbar_set_visible,
1515        game_dir: srv_game_dir,
1516        get_block_nbt:       srv_get_block_nbt,
1517        set_block_nbt:       srv_set_block_nbt,
1518        player_inventory:    srv_player_inventory,
1519        player_set_slot:     srv_player_set_slot,
1520        player_teleport_dim: srv_player_teleport_dim,
1521        entity_teleport_dim: srv_entity_teleport_dim,
1522        online_players:      srv_online_players,
1523        world_entity_count:  srv_world_entity_count,
1524        entity_get_nbt:          srv_entity_get_nbt,
1525        entity_set_nbt:          srv_entity_set_nbt,
1526        spawn_particles:         srv_spawn_particles,
1527        entity_attribute_get:    srv_entity_attribute_get,
1528        entity_attribute_set:    srv_entity_attribute_set,
1529        get_held_item_nbt:       srv_get_held_item_nbt,
1530        set_held_item_nbt:       srv_set_held_item_nbt,
1531        get_offhand_item_nbt:    srv_get_offhand_item_nbt,
1532        set_offhand_item_nbt:    srv_set_offhand_item_nbt,
1533        get_slot_item:           srv_get_slot_item,
1534        set_slot_item:           srv_set_slot_item,
1535    }
1536}
1537
1538fn build_api_table(ctx: *mut RuntimeHandlers, server: *const YogServer) -> YogApi {
1539    YogApi {
1540        abi_version: ABI_VERSION,
1541        size:        std::mem::size_of::<YogApi>() as u32,
1542        ctx:         ctx as *mut c_void,
1543        server,
1544        on_block_break:     api_on_block_break,
1545        on_chat:            api_on_chat,
1546        on_player_join:     api_on_player_join,
1547        on_player_leave:    api_on_player_leave,
1548        on_use_item:        api_on_use_item,
1549        on_use_block:       api_on_use_block,
1550        on_attack_entity:   api_on_attack_entity,
1551        on_entity_damage:   api_on_entity_damage,
1552        on_entity_death:    api_on_entity_death,
1553        on_entity_spawn:         api_on_entity_spawn,
1554        on_player_place_block:   api_on_player_place_block,
1555        on_player_death:         api_on_player_death,
1556        on_player_respawn:       api_on_player_respawn,
1557        on_advancement:          api_on_advancement,
1558        on_entity_interact:      api_on_entity_interact,
1559        on_item_craft:           api_on_item_craft,
1560        on_explosion:            api_on_explosion,
1561        on_item_pickup:          api_on_item_pickup,
1562        on_player_move:          api_on_player_move,
1563        on_container_open:       api_on_container_open,
1564        on_container_close:      api_on_container_close,
1565        on_projectile_hit:       api_on_projectile_hit,
1566        on_server_tick:          api_on_server_tick,
1567        on_server_started:       api_on_server_started,
1568        on_server_stopping:      api_on_server_stopping,
1569        on_packet:               api_on_packet,
1570        on_client_packet:        api_on_client_packet,
1571        register_command:        api_register_command,
1572        register_typed_command:  api_register_typed_command,
1573        register_recipe_json:    api_register_recipe_json,
1574        register_item:          api_register_item,
1575        register_block:     api_register_block,
1576        schedule_once:      api_schedule_once,
1577        schedule_repeating: api_schedule_repeating,
1578        on_client_tick:     api_on_client_tick,
1579        on_hud_render:      api_on_hud_render,
1580        on_key_press:       api_on_key_press,
1581        on_screen_open:     api_on_screen_open,
1582        on_screen_close:    api_on_screen_close,
1583        on_world_render:    api_on_world_render,
1584        register_startup_grant: api_register_startup_grant,
1585        register_book:          api_register_book,
1586        register_ui:            api_register_ui,
1587    }
1588}
1589
1590// ── Mod loading ───────────────────────────────────────────────────────────────
1591
1592fn platform_tag() -> String {
1593    format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
1594}
1595
1596type AbiVersionFn   = unsafe extern "C" fn() -> u32;
1597type RegisterFn     = unsafe extern "C" fn(*const YogApi, *mut c_void);
1598
1599fn load_mods(dir: &Path, api: &YogApi) {
1600    let entries = match std::fs::read_dir(dir) {
1601        Ok(e) => e,
1602        Err(_) => {
1603            yog_logging::info!("no mods directory at {} — none loaded", dir.display());
1604            return;
1605        }
1606    };
1607    let mut count = 0u32;
1608    for entry in entries.flatten() {
1609        let path = entry.path();
1610        let lib_path = match path.extension().and_then(|e| e.to_str()) {
1611            Some("yog") => match extract_yog(&path) {
1612                Some(p) => p,
1613                None => {
1614                    yog_logging::error!("no native for {} in {}", platform_tag(), path.display());
1615                    continue;
1616                }
1617            },
1618            Some("so") | Some("dll") | Some("dylib") => path.clone(),
1619            _ => continue,
1620        };
1621        if load_mod_lib(&lib_path, api) { count += 1; }
1622    }
1623    yog_logging::info!("loaded {} mod(s) from {}", count, dir.display());
1624}
1625
1626fn load_mod_lib(path: &Path, api: &YogApi) -> bool {
1627    unsafe {
1628        let lib = match Library::new(path) {
1629            Ok(l) => l,
1630            Err(e) => { yog_logging::error!("failed to load {}: {}", path.display(), e); return false; }
1631        };
1632        let abi: Symbol<AbiVersionFn> = match lib.get(b"yog_abi_version") {
1633            Ok(s) => s,
1634            Err(_) => { yog_logging::error!("{} is not a Yog mod (no yog_abi_version)", path.display()); return false; }
1635        };
1636        let mod_abi = abi();
1637        if mod_abi != ABI_VERSION {
1638            yog_logging::error!("{}: ABI {} incompatible with runtime ABI {}", path.display(), mod_abi, ABI_VERSION);
1639            return false;
1640        }
1641        let register: Symbol<RegisterFn> = match lib.get(b"yog_mod_register") {
1642            Ok(s) => s,
1643            Err(_) => { yog_logging::error!("{} missing yog_mod_register", path.display()); return false; }
1644        };
1645        register(api as *const YogApi, std::ptr::null_mut());
1646        drop(register);
1647        drop(abi);
1648        LOADED_MODS.lock().expect("mods lock poisoned").push(lib);
1649    }
1650    true
1651}
1652
1653fn extract_yog(path: &Path) -> Option<PathBuf> {
1654    let file = std::fs::File::open(path).ok()?;
1655    let mut archive = zip::ZipArchive::new(file).ok()?;
1656    let prefix = format!("natives/{}/", platform_tag());
1657    let mut entry_name = None;
1658    for i in 0..archive.len() {
1659        let f = archive.by_index(i).ok()?;
1660        if f.name().starts_with(&prefix) && !f.name().ends_with('/') {
1661            entry_name = Some(f.name().to_string());
1662            break;
1663        }
1664    }
1665    let entry_name = entry_name?;
1666    let ext = Path::new(&entry_name).extension().and_then(|e| e.to_str()).unwrap_or("bin");
1667    let stem = path.file_stem()?.to_string_lossy().into_owned();
1668    let out = std::env::temp_dir().join(format!("yog-{}-{}.{}", stem, std::process::id(), ext));
1669    let mut entry = archive.by_name(&entry_name).ok()?;
1670    let mut out_file = std::fs::File::create(&out).ok()?;
1671    std::io::copy(&mut entry, &mut out_file).ok()?;
1672    Some(out)
1673}
1674
1675// ── Dispatcher helpers ────────────────────────────────────────────────────────
1676
1677fn srv_ptr() -> *const YogServer {
1678    SERVER.get().expect("yog: SERVER not initialised") as *const YogServer
1679}
1680
1681// ── JNI entry points ──────────────────────────────────────────────────────────
1682
1683#[no_mangle]
1684pub extern "system" fn Java_dev_yog_NativeBridge_nativeInit<'l>(
1685    mut env: JNIEnv<'l>,
1686    _class: JClass<'l>,
1687    mods_dir: JString<'l>,
1688) {
1689    if let Ok(vm) = env.get_java_vm() { let _ = JAVA_VM.set(vm); }
1690
1691    let dir = env.get_string(&mods_dir).map(String::from).unwrap_or_default();
1692
1693    // Build YogServer and store in static (gets a stable address).
1694    let _ = SERVER.set(build_server_table());
1695    let server_ptr = SERVER.get().unwrap() as *const YogServer;
1696
1697    // Build RuntimeHandlers on the heap temporarily so we have a stable pointer
1698    // to pass as ctx while mods register.
1699    let mut handlers = Box::new(RuntimeHandlers::new());
1700    let handlers_ptr = &mut *handlers as *mut RuntimeHandlers;
1701
1702    let api = build_api_table(handlers_ptr, server_ptr);
1703
1704    guard("mod loading", || {
1705        load_mods(Path::new(&dir), &api);
1706    });
1707
1708    // Move handlers out of Box and into the OnceLock.
1709    let _ = HANDLERS.set(*handlers);
1710
1711    yog_logging::info!("runtime initialised — the gate is open.");
1712}
1713
1714#[no_mangle]
1715pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreak<'l>(
1716    mut env: JNIEnv<'l>, _class: JClass<'l>,
1717    player: JString<'l>, block: JString<'l>, x: jint, y: jint, z: jint,
1718) {
1719    let (p, b) = (jstr!(env, player), jstr!(env, block));
1720    let ev = yog_abi::YogBlockBreakEvent {
1721        player: YogStr::from_str(&p), block: YogStr::from_str(&b),
1722        pos: YogBlockPos { x, y, z },
1723    };
1724    let srv = srv_ptr();
1725    guard("on_block_break", || {
1726        for (ud, f) in &handlers().block_break {
1727            unsafe { f(*ud, srv, &ev, 1) };
1728        }
1729    });
1730}
1731
1732#[no_mangle]
1733pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChat<'l>(
1734    mut env: JNIEnv<'l>, _class: JClass<'l>,
1735    player: JString<'l>, message: JString<'l>,
1736) {
1737    let (p, m) = (jstr!(env, player), jstr!(env, message));
1738    let ev = yog_abi::YogChatEvent { player: YogStr::from_str(&p), message: YogStr::from_str(&m) };
1739    let srv = srv_ptr();
1740    guard("on_chat", || {
1741        for (ud, f) in &handlers().chat {
1742            unsafe { f(*ud, srv, &ev, 1) };
1743        }
1744    });
1745}
1746
1747#[no_mangle]
1748pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerJoin<'l>(
1749    mut env: JNIEnv<'l>, _class: JClass<'l>,
1750    player: JString<'l>, uuid: JString<'l>,
1751) {
1752    let (p, u) = (jstr!(env, player), jstr!(env, uuid));
1753    let ev = yog_abi::YogPlayerEvent { player: YogStr::from_str(&p), uuid: YogStr::from_str(&u) };
1754    let srv = srv_ptr();
1755    guard("on_player_join", || {
1756        for (ud, f) in &handlers().player_join {
1757            unsafe { f(*ud, srv, &ev, 1) };
1758        }
1759    });
1760    // Process startup grants (give items/books on first join).
1761    let h = handlers();
1762    let mut granted = h.startup_granted.lock().expect("startup_granted poisoned");
1763    yog_logging::info!("processing {} startup grants for player {}", h.startup_grants.len(), p);
1764    for sg in &h.startup_grants {
1765        let key = format!("{}::{}", u, sg.id);
1766        if granted.contains_key(&key) {
1767            yog_logging::info!("startup grant {} already granted, skipping", sg.id);
1768            continue;
1769        }
1770        yog_logging::info!("granting startup grant {} with {} items", sg.id, sg.items.len());
1771        for item_id in &sg.items {
1772            let ok = unsafe { srv_give_item(std::ptr::null_mut(), YogStr::from_str(&p), YogStr::from_str(item_id), 1) };
1773            yog_logging::info!("gave {} to {} -> {}", item_id, p, ok);
1774        }
1775        if let Some(book) = &sg.book {
1776            let ok = unsafe { srv_give_item(std::ptr::null_mut(), YogStr::from_str(&p), YogStr::from_str("minecraft:written_book"), 1) };
1777            yog_logging::info!("gave book {} to {} -> {}", book, p, ok);
1778        }
1779        granted.insert(key, true);
1780    }
1781    drop(granted);
1782}
1783
1784#[no_mangle]
1785pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerLeave<'l>(
1786    mut env: JNIEnv<'l>, _class: JClass<'l>,
1787    player: JString<'l>, uuid: JString<'l>,
1788) {
1789    let (p, u) = (jstr!(env, player), jstr!(env, uuid));
1790    let ev = yog_abi::YogPlayerEvent { player: YogStr::from_str(&p), uuid: YogStr::from_str(&u) };
1791    let srv = srv_ptr();
1792    guard("on_player_leave", || {
1793        for (ud, f) in &handlers().player_leave {
1794            unsafe { f(*ud, srv, &ev, 1) };
1795        }
1796    });
1797}
1798
1799#[no_mangle]
1800pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseItem<'l>(
1801    mut env: JNIEnv<'l>, _class: JClass<'l>,
1802    player: JString<'l>, item: JString<'l>,
1803) {
1804    let (p, i) = (jstr!(env, player), jstr!(env, item));
1805    let ev = yog_abi::YogUseItemEvent { player: YogStr::from_str(&p), item: YogStr::from_str(&i) };
1806    let srv = srv_ptr();
1807    guard("on_use_item", || {
1808        for (ud, f) in &handlers().use_item {
1809            unsafe { f(*ud, srv, &ev, 1) };
1810        }
1811    });
1812}
1813
1814#[no_mangle]
1815pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnUseBlock<'l>(
1816    mut env: JNIEnv<'l>, _class: JClass<'l>,
1817    player: JString<'l>, block: JString<'l>, x: jint, y: jint, z: jint,
1818) {
1819    let (p, b) = (jstr!(env, player), jstr!(env, block));
1820    let ev = yog_abi::YogUseBlockEvent {
1821        player: YogStr::from_str(&p), block: YogStr::from_str(&b),
1822        pos: YogBlockPos { x, y, z },
1823    };
1824    let srv = srv_ptr();
1825    guard("on_use_block", || {
1826        for (ud, f) in &handlers().use_block {
1827            unsafe { f(*ud, srv, &ev, 1) };
1828        }
1829    });
1830}
1831
1832#[no_mangle]
1833pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAttackEntity<'l>(
1834    mut env: JNIEnv<'l>, _class: JClass<'l>,
1835    player: JString<'l>, target_type: JString<'l>, target_uuid: JString<'l>,
1836) {
1837    let (p, tt, tu) = (jstr!(env, player), jstr!(env, target_type), jstr!(env, target_uuid));
1838    let ev = yog_abi::YogAttackEntityEvent {
1839        player: YogStr::from_str(&p), target_type: YogStr::from_str(&tt), target_uuid: YogStr::from_str(&tu),
1840    };
1841    let srv = srv_ptr();
1842    guard("on_attack_entity", || {
1843        for (ud, f) in &handlers().attack_entity {
1844            unsafe { f(*ud, srv, &ev, 1) };
1845        }
1846    });
1847}
1848
1849#[no_mangle]
1850pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamage<'l>(
1851    mut env: JNIEnv<'l>, _class: JClass<'l>,
1852    entity_type: JString<'l>, uuid: JString<'l>, amount: jfloat, source: JString<'l>,
1853) {
1854    let (et, u, s) = (jstr!(env, entity_type), jstr!(env, uuid), jstr!(env, source));
1855    let ev = yog_abi::YogEntityDamageEvent {
1856        entity_type: YogStr::from_str(&et), uuid: YogStr::from_str(&u),
1857        amount, source: YogStr::from_str(&s),
1858    };
1859    let srv = srv_ptr();
1860    guard("on_entity_damage", || {
1861        for (ud, f) in &handlers().entity_damage {
1862            unsafe { f(*ud, srv, &ev, 1) };
1863        }
1864    });
1865}
1866
1867#[no_mangle]
1868pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDeath<'l>(
1869    mut env: JNIEnv<'l>, _class: JClass<'l>,
1870    entity_type: JString<'l>, uuid: JString<'l>, source: JString<'l>,
1871) {
1872    let (et, u, s) = (jstr!(env, entity_type), jstr!(env, uuid), jstr!(env, source));
1873    let ev = yog_abi::YogEntityDeathEvent {
1874        entity_type: YogStr::from_str(&et), uuid: YogStr::from_str(&u), source: YogStr::from_str(&s),
1875    };
1876    let srv = srv_ptr();
1877    guard("on_entity_death", || {
1878        for (ud, f) in &handlers().entity_death {
1879            unsafe { f(*ud, srv, &ev, 1) };
1880        }
1881    });
1882}
1883
1884#[no_mangle]
1885pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnTick<'l>(
1886    _env: JNIEnv<'l>, _class: JClass<'l>,
1887) {
1888    let h = handlers();
1889    let srv = srv_ptr();
1890    guard("on_tick", || {
1891        for (ud, f) in &h.server_tick {
1892            unsafe { f(*ud, srv) };
1893        }
1894    });
1895
1896    // Scheduler — once tasks
1897    {
1898        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
1899        let mut to_fire: Vec<(*mut c_void, YogScheduledFn)> = Vec::new();
1900        let mut remaining = Vec::new();
1901        for task in sched.once_tasks.drain(..) {
1902            if task.delay_remaining == 0 {
1903                to_fire.push((task.ud, task.f));
1904            } else {
1905                remaining.push(OnceTask { delay_remaining: task.delay_remaining - 1, ..task });
1906            }
1907        }
1908        sched.once_tasks = remaining;
1909        drop(sched);
1910        for (ud, f) in to_fire {
1911            guard("schedule_once", || unsafe { f(ud, srv) });
1912        }
1913    }
1914
1915    // Scheduler — repeating tasks
1916    {
1917        let mut sched = h.scheduler.lock().expect("scheduler poisoned");
1918        let mut to_fire: Vec<(*mut c_void, YogScheduledFn)> = Vec::new();
1919        for task in &mut sched.repeating_tasks {
1920            if task.ticks_left == 0 {
1921                to_fire.push((task.ud, task.f));
1922                task.ticks_left = task.period;
1923            } else {
1924                task.ticks_left -= 1;
1925            }
1926        }
1927        drop(sched);
1928        for (ud, f) in to_fire {
1929            guard("schedule_repeating", || unsafe { f(ud, srv) });
1930        }
1931    }
1932}
1933
1934#[no_mangle]
1935pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStarted<'l>(
1936    _env: JNIEnv<'l>, _class: JClass<'l>,
1937) {
1938    let srv = srv_ptr();
1939    guard("on_server_started", || {
1940        for (ud, f) in &handlers().server_started {
1941            unsafe { f(*ud, srv) };
1942        }
1943    });
1944}
1945
1946#[no_mangle]
1947pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnServerStopping<'l>(
1948    _env: JNIEnv<'l>, _class: JClass<'l>,
1949) {
1950    let srv = srv_ptr();
1951    guard("on_server_stopping", || {
1952        for (ud, f) in &handlers().server_stopping {
1953            unsafe { f(*ud, srv) };
1954        }
1955    });
1956}
1957
1958#[no_mangle]
1959pub extern "system" fn Java_dev_yog_NativeBridge_nativeCommandNames<'l>(
1960    env: JNIEnv<'l>, _class: JClass<'l>,
1961) -> jstring {
1962    let names = handlers().commands.keys().cloned().collect::<Vec<_>>().join("\n");
1963    env.new_string(names).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
1964}
1965
1966#[no_mangle]
1967pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnBlockBreakPre<'l>(
1968    mut env: JNIEnv<'l>, _class: JClass<'l>,
1969    player: JString<'l>, block: JString<'l>, x: jint, y: jint, z: jint,
1970) -> jni::sys::jboolean {
1971    let h = handlers();
1972    if h.block_break.is_empty() { return 1; }
1973    let p = match env.get_string(&player) { Ok(s) => String::from(s), Err(_) => return 1 };
1974    let b = match env.get_string(&block)  { Ok(s) => String::from(s), Err(_) => return 1 };
1975    let ev = yog_abi::YogBlockBreakEvent {
1976        player: YogStr::from_str(&p), block: YogStr::from_str(&b),
1977        pos: YogBlockPos { x, y, z },
1978    };
1979    let srv = srv_ptr();
1980    let mut allow = true;
1981    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1982        for (ud, f) in &h.block_break {
1983            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
1984        }
1985    })).ok();
1986    allow as jni::sys::jboolean
1987}
1988
1989#[no_mangle]
1990pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnChatPre<'l>(
1991    mut env: JNIEnv<'l>, _class: JClass<'l>,
1992    player: JString<'l>, message: JString<'l>,
1993) -> jni::sys::jboolean {
1994    let h = handlers();
1995    if h.chat.is_empty() { return 1; }
1996    let p = match env.get_string(&player)  { Ok(s) => String::from(s), Err(_) => return 1 };
1997    let m = match env.get_string(&message) { Ok(s) => String::from(s), Err(_) => return 1 };
1998    let ev = yog_abi::YogChatEvent { player: YogStr::from_str(&p), message: YogStr::from_str(&m) };
1999    let srv = srv_ptr();
2000    let mut allow = true;
2001    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2002        for (ud, f) in &h.chat {
2003            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2004        }
2005    })).ok();
2006    allow as jni::sys::jboolean
2007}
2008
2009#[no_mangle]
2010pub extern "system" fn Java_dev_yog_NativeBridge_nativeRecipeJsons<'l>(
2011    env: JNIEnv<'l>, _class: JClass<'l>,
2012) -> jstring {
2013    let s = handlers().recipes.iter()
2014        .map(|(ns, name, json)| format!("{}\t{}\t{}", ns, name, json))
2015        .collect::<Vec<_>>().join("\n");
2016    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2017}
2018
2019#[no_mangle]
2020pub extern "system" fn Java_dev_yog_NativeBridge_nativeTypedCommandSchemas<'l>(
2021    env: JNIEnv<'l>, _class: JClass<'l>,
2022) -> jstring {
2023    let s = handlers().typed_schemas.iter()
2024        .map(|(name, schema)| format!("{}\t{}", name, schema))
2025        .collect::<Vec<_>>().join("\n");
2026    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2027}
2028
2029#[no_mangle]
2030pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnCommand<'l>(
2031    mut env: JNIEnv<'l>, _class: JClass<'l>,
2032    name: JString<'l>, args: JString<'l>, source: JString<'l>, uuid: JString<'l>,
2033) -> jstring {
2034    let (n, a, s, u) = (
2035        env.get_string(&name).map(String::from).unwrap_or_default(),
2036        env.get_string(&args).map(String::from).unwrap_or_default(),
2037        env.get_string(&source).map(String::from).unwrap_or_default(),
2038        env.get_string(&uuid).map(String::from).unwrap_or_default(),
2039    );
2040    let ev = yog_abi::YogCommandEvent {
2041        name: YogStr::from_str(&n), args: YogStr::from_str(&a),
2042        source: YogStr::from_str(&s), uuid: YogStr::from_str(&u),
2043    };
2044    let h = handlers();
2045    let srv = srv_ptr();
2046    let reply = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2047        if let Some((ud, f)) = h.commands.get(&n) {
2048            let mut buf = [0u8; 4096];
2049            let mut reply_len: u32 = 0;
2050            unsafe { f(*ud, srv, &ev, buf.as_mut_ptr(), buf.len() as u32, &mut reply_len) };
2051            String::from_utf8_lossy(&buf[..reply_len as usize]).into_owned()
2052        } else {
2053            String::new()
2054        }
2055    }))
2056    .unwrap_or_else(|_| { yog_logging::error!("a mod panicked handling command `{}`", n); String::new() });
2057
2058    env.new_string(reply).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2059}
2060
2061#[no_mangle]
2062pub extern "system" fn Java_dev_yog_NativeBridge_nativeItemDefs<'l>(
2063    env: JNIEnv<'l>, _class: JClass<'l>,
2064) -> jstring {
2065    let s = handlers().items.iter().map(|d| {
2066        let mut parts = vec![d.id.clone()];
2067        parts.push(format!("max_stack={}", d.max_stack));
2068        if let Some(n) = &d.name    { parts.push(format!("name={n}")); }
2069        if let Some(t) = &d.tooltip { parts.push(format!("tooltip={t}")); }
2070        if d.max_damage > 0         { parts.push(format!("max_damage={}", d.max_damage)); }
2071        if d.fire_resistant         { parts.push("fire_resistant=1".into()); }
2072        if d.fuel_ticks > 0         { parts.push(format!("fuel_ticks={}", d.fuel_ticks)); }
2073        if let Some(f) = &d.food {
2074            parts.push(format!("food={}:{}:{}", f.nutrition, f.saturation, if f.can_always_eat { 1 } else { 0 }));
2075        }
2076        parts.join("\t")
2077    }).collect::<Vec<_>>().join("\n");
2078    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2079}
2080
2081#[no_mangle]
2082pub extern "system" fn Java_dev_yog_NativeBridge_nativeBlockDefs<'l>(
2083    env: JNIEnv<'l>, _class: JClass<'l>,
2084) -> jstring {
2085    let s = handlers().blocks.iter().map(|d| {
2086        let mut parts = vec![d.id.clone()];
2087        parts.push(format!("hardness={}", d.hardness));
2088        parts.push(format!("resistance={}", d.resistance));
2089        if let Some(n) = &d.name { parts.push(format!("name={n}")); }
2090        if let Some(sh) = d.shape {
2091            parts.push(format!("shape={}:{}:{}:{}:{}:{}", sh[0], sh[1], sh[2], sh[3], sh[4], sh[5]));
2092        }
2093        if d.light_level > 0    { parts.push(format!("light={}", d.light_level)); }
2094        if let Some(snd) = &d.sound { parts.push(format!("sound={snd}")); }
2095        if d.requires_tool       { parts.push("requires_tool=1".into()); }
2096        if d.no_collision        { parts.push("no_collision=1".into()); }
2097        if d.slipperiness > 0.0  { parts.push(format!("slipperiness={}", d.slipperiness)); }
2098        parts.join("\t")
2099    }).collect::<Vec<_>>().join("\n");
2100    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2101}
2102
2103#[no_mangle]
2104pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPacket<'l>(
2105    mut env: JNIEnv<'l>, _class: JClass<'l>,
2106    channel: JString<'l>, player: JString<'l>, payload: JByteArray<'l>,
2107) {
2108    let ch = env.get_string(&channel).map(String::from).unwrap_or_default();
2109    let pl = env.get_string(&player).map(String::from).unwrap_or_default();
2110    let data = env.convert_byte_array(&payload).unwrap_or_default();
2111    let ev = yog_abi::YogPacketEvent {
2112        channel: YogStr::from_str(&ch), player: YogStr::from_str(&pl),
2113        payload: data.as_ptr(), payload_len: data.len() as u32,
2114    };
2115    let h = handlers();
2116    let srv = srv_ptr();
2117    guard("on_packet", || {
2118        if let Some((ud, f)) = h.packets.get(&ch) {
2119            unsafe { f(*ud, srv, &ev) };
2120        }
2121    });
2122}
2123
2124#[no_mangle]
2125pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientPacket<'l>(
2126    mut env: JNIEnv<'l>, _class: JClass<'l>,
2127    channel: JString<'l>, payload: JByteArray<'l>,
2128) {
2129    let ch = env.get_string(&channel).map(String::from).unwrap_or_default();
2130    let data = env.convert_byte_array(&payload).unwrap_or_default();
2131    let ev = yog_abi::YogPacketEvent {
2132        channel: YogStr::from_str(&ch), player: YogStr::EMPTY,
2133        payload: data.as_ptr(), payload_len: data.len() as u32,
2134    };
2135    let h = handlers();
2136    let srv = srv_ptr();
2137    guard("on_client_packet", || {
2138        if let Some((ud, f)) = h.client_packets.get(&ch) {
2139            unsafe { f(*ud, srv, &ev) };
2140        }
2141    });
2142}
2143
2144#[no_mangle]
2145pub extern "system" fn Java_dev_yog_NativeBridge_nativePacketChannels<'l>(
2146    env: JNIEnv<'l>, _class: JClass<'l>,
2147) -> jstring {
2148    let s = handlers().packets.keys().cloned().collect::<Vec<_>>().join("\n");
2149    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2150}
2151
2152#[no_mangle]
2153pub extern "system" fn Java_dev_yog_NativeBridge_nativeClientPacketChannels<'l>(
2154    env: JNIEnv<'l>, _class: JClass<'l>,
2155) -> jstring {
2156    let s = handlers().client_packets.keys().cloned().collect::<Vec<_>>().join("\n");
2157    env.new_string(s).map(|s| s.into_raw()).unwrap_or(std::ptr::null_mut())
2158}
2159
2160#[no_mangle]
2161pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawn<'l>(
2162    mut env: JNIEnv<'l>, _class: JClass<'l>,
2163    entity_type: JString<'l>, uuid: JString<'l>, dimension: JString<'l>,
2164) {
2165    let h = handlers();
2166    if h.entity_spawn.is_empty() { return; }
2167    let (et, u, d) = (jstr!(env, entity_type), jstr!(env, uuid), jstr!(env, dimension));
2168    let ev = yog_abi::YogEntitySpawnEvent {
2169        entity_type: YogStr::from_str(&et), uuid: YogStr::from_str(&u),
2170        dimension: YogStr::from_str(&d),
2171    };
2172    let srv = srv_ptr();
2173    guard("on_entity_spawn", || {
2174        for (ud, f) in &h.entity_spawn {
2175            unsafe { f(*ud, srv, &ev, 1) };
2176        }
2177    });
2178}
2179
2180#[no_mangle]
2181pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntitySpawnPre<'l>(
2182    mut env: JNIEnv<'l>, _class: JClass<'l>,
2183    entity_type: JString<'l>, uuid: JString<'l>, dimension: JString<'l>,
2184) -> jni::sys::jboolean {
2185    let h = handlers();
2186    if h.entity_spawn.is_empty() { return 1; }
2187    let et = match env.get_string(&entity_type) { Ok(s) => String::from(s), Err(_) => return 1 };
2188    let u  = match env.get_string(&uuid)        { Ok(s) => String::from(s), Err(_) => return 1 };
2189    let d  = match env.get_string(&dimension)   { Ok(s) => String::from(s), Err(_) => return 1 };
2190    let ev = yog_abi::YogEntitySpawnEvent {
2191        entity_type: YogStr::from_str(&et), uuid: YogStr::from_str(&u),
2192        dimension: YogStr::from_str(&d),
2193    };
2194    let srv = srv_ptr();
2195    let mut allow = true;
2196    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2197        for (ud, f) in &h.entity_spawn {
2198            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2199        }
2200    })).ok();
2201    allow as jni::sys::jboolean
2202}
2203
2204#[no_mangle]
2205pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityDamagePre<'l>(
2206    mut env: JNIEnv<'l>, _class: JClass<'l>,
2207    entity_type: JString<'l>, uuid: JString<'l>, amount: jfloat, source: JString<'l>,
2208) -> jni::sys::jboolean {
2209    let h = handlers();
2210    if h.entity_damage.is_empty() { return 1; }
2211    let et = match env.get_string(&entity_type) { Ok(s) => String::from(s), Err(_) => return 1 };
2212    let u  = match env.get_string(&uuid)        { Ok(s) => String::from(s), Err(_) => return 1 };
2213    let s  = match env.get_string(&source)      { Ok(s) => String::from(s), Err(_) => return 1 };
2214    let ev = yog_abi::YogEntityDamageEvent {
2215        entity_type: YogStr::from_str(&et), uuid: YogStr::from_str(&u),
2216        amount, source: YogStr::from_str(&s),
2217    };
2218    let srv = srv_ptr();
2219    let mut allow = true;
2220    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2221        for (ud, f) in &h.entity_damage {
2222            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2223        }
2224    })).ok();
2225    allow as jni::sys::jboolean
2226}
2227
2228#[no_mangle]
2229pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlockPre<'l>(
2230    mut env: JNIEnv<'l>, _class: JClass<'l>,
2231    player: JString<'l>, block: JString<'l>, x: jint, y: jint, z: jint,
2232) -> jni::sys::jboolean {
2233    let h = handlers();
2234    if h.player_place_block.is_empty() { return 1; }
2235    let p = match env.get_string(&player) { Ok(s) => String::from(s), Err(_) => return 1 };
2236    let b = match env.get_string(&block)  { Ok(s) => String::from(s), Err(_) => return 1 };
2237    let ev = YogPlaceBlockEvent {
2238        player: YogStr::from_str(&p), block: YogStr::from_str(&b),
2239        pos: YogBlockPos { x, y, z },
2240    };
2241    let srv = srv_ptr();
2242    let mut allow = true;
2243    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2244        for (ud, f) in &h.player_place_block {
2245            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2246        }
2247    })).ok();
2248    allow as jni::sys::jboolean
2249}
2250
2251#[no_mangle]
2252pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlaceBlock<'l>(
2253    mut env: JNIEnv<'l>, _class: JClass<'l>,
2254    player: JString<'l>, block: JString<'l>, x: jint, y: jint, z: jint,
2255) {
2256    let h = handlers();
2257    if h.player_place_block.is_empty() { return; }
2258    let (p, b) = (jstr!(env, player), jstr!(env, block));
2259    let ev = YogPlaceBlockEvent {
2260        player: YogStr::from_str(&p), block: YogStr::from_str(&b),
2261        pos: YogBlockPos { x, y, z },
2262    };
2263    let srv = srv_ptr();
2264    guard("on_player_place_block", || {
2265        for (ud, f) in &h.player_place_block {
2266            unsafe { f(*ud, srv, &ev, 1) };
2267        }
2268    });
2269}
2270
2271#[no_mangle]
2272pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeathPre<'l>(
2273    mut env: JNIEnv<'l>, _class: JClass<'l>,
2274    player: JString<'l>, uuid: JString<'l>, source: JString<'l>,
2275) -> jni::sys::jboolean {
2276    let h = handlers();
2277    if h.player_death.is_empty() { return 1; }
2278    let p  = match env.get_string(&player) { Ok(s) => String::from(s), Err(_) => return 1 };
2279    let u  = match env.get_string(&uuid)   { Ok(s) => String::from(s), Err(_) => return 1 };
2280    let s  = match env.get_string(&source) { Ok(s) => String::from(s), Err(_) => return 1 };
2281    let ev = YogPlayerDeathEvent {
2282        player: YogStr::from_str(&p), uuid: YogStr::from_str(&u), source: YogStr::from_str(&s),
2283    };
2284    let srv = srv_ptr();
2285    let mut allow = true;
2286    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2287        for (ud, f) in &h.player_death {
2288            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2289        }
2290    })).ok();
2291    allow as jni::sys::jboolean
2292}
2293
2294#[no_mangle]
2295pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerDeath<'l>(
2296    mut env: JNIEnv<'l>, _class: JClass<'l>,
2297    player: JString<'l>, uuid: JString<'l>, source: JString<'l>,
2298) {
2299    let h = handlers();
2300    if h.player_death.is_empty() { return; }
2301    let (p, u, s) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, source));
2302    let ev = YogPlayerDeathEvent {
2303        player: YogStr::from_str(&p), uuid: YogStr::from_str(&u), source: YogStr::from_str(&s),
2304    };
2305    let srv = srv_ptr();
2306    guard("on_player_death", || {
2307        for (ud, f) in &h.player_death {
2308            unsafe { f(*ud, srv, &ev, 1) };
2309        }
2310    });
2311}
2312
2313#[no_mangle]
2314pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerRespawn<'l>(
2315    mut env: JNIEnv<'l>, _class: JClass<'l>,
2316    player: JString<'l>, uuid: JString<'l>, at_anchor: jni::sys::jboolean,
2317) {
2318    let h = handlers();
2319    if h.player_respawn.is_empty() { return; }
2320    let (p, u) = (jstr!(env, player), jstr!(env, uuid));
2321    let ev = YogPlayerRespawnEvent {
2322        player: YogStr::from_str(&p), uuid: YogStr::from_str(&u), at_anchor: at_anchor != 0,
2323    };
2324    let srv = srv_ptr();
2325    guard("on_player_respawn", || {
2326        for (ud, f) in &h.player_respawn {
2327            unsafe { f(*ud, srv, &ev, 1) };
2328        }
2329    });
2330}
2331
2332#[no_mangle]
2333pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnAdvancement<'l>(
2334    mut env: JNIEnv<'l>, _class: JClass<'l>,
2335    player: JString<'l>, uuid: JString<'l>, advancement: JString<'l>,
2336) {
2337    let h = handlers();
2338    if h.advancement.is_empty() { return; }
2339    let (p, u, a) = (jstr!(env, player), jstr!(env, uuid), jstr!(env, advancement));
2340    let ev = YogAdvancementEvent {
2341        player: YogStr::from_str(&p), uuid: YogStr::from_str(&u), advancement: YogStr::from_str(&a),
2342    };
2343    let srv = srv_ptr();
2344    guard("on_advancement", || {
2345        for (ud, f) in &h.advancement {
2346            unsafe { f(*ud, srv, &ev, 1) };
2347        }
2348    });
2349}
2350
2351#[no_mangle]
2352pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteractPre<'l>(
2353    mut env: JNIEnv<'l>, _class: JClass<'l>,
2354    player: JString<'l>, player_uuid: JString<'l>,
2355    entity_type: JString<'l>, entity_uuid: JString<'l>, hand: JString<'l>,
2356) -> jni::sys::jboolean {
2357    let h = handlers();
2358    if h.entity_interact.is_empty() { return 1; }
2359    let p  = match env.get_string(&player)      { Ok(s) => String::from(s), Err(_) => return 1 };
2360    let pu = match env.get_string(&player_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2361    let et = match env.get_string(&entity_type)  { Ok(s) => String::from(s), Err(_) => return 1 };
2362    let eu = match env.get_string(&entity_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2363    let ha = match env.get_string(&hand)         { Ok(s) => String::from(s), Err(_) => return 1 };
2364    let ev = YogEntityInteractEvent {
2365        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2366        entity_type: YogStr::from_str(&et), entity_uuid: YogStr::from_str(&eu),
2367        hand: YogStr::from_str(&ha),
2368    };
2369    let srv = srv_ptr();
2370    let mut allow = true;
2371    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2372        for (ud, f) in &h.entity_interact {
2373            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2374        }
2375    })).ok();
2376    allow as jni::sys::jboolean
2377}
2378
2379#[no_mangle]
2380pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnEntityInteract<'l>(
2381    mut env: JNIEnv<'l>, _class: JClass<'l>,
2382    player: JString<'l>, player_uuid: JString<'l>,
2383    entity_type: JString<'l>, entity_uuid: JString<'l>, hand: JString<'l>,
2384) {
2385    let h = handlers();
2386    if h.entity_interact.is_empty() { return; }
2387    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
2388    let (et, eu, ha) = (jstr!(env, entity_type), jstr!(env, entity_uuid), jstr!(env, hand));
2389    let ev = YogEntityInteractEvent {
2390        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2391        entity_type: YogStr::from_str(&et), entity_uuid: YogStr::from_str(&eu),
2392        hand: YogStr::from_str(&ha),
2393    };
2394    let srv = srv_ptr();
2395    guard("on_entity_interact", || {
2396        for (ud, f) in &h.entity_interact {
2397            unsafe { f(*ud, srv, &ev, 1) };
2398        }
2399    });
2400}
2401
2402#[no_mangle]
2403pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemCraft<'l>(
2404    mut env: JNIEnv<'l>, _class: JClass<'l>,
2405    player: JString<'l>, player_uuid: JString<'l>,
2406    result_item: JString<'l>, result_count: jint,
2407) {
2408    let h = handlers();
2409    if h.item_craft.is_empty() { return; }
2410    let (p, pu, ri) = (jstr!(env, player), jstr!(env, player_uuid), jstr!(env, result_item));
2411    let ev = YogCraftEvent {
2412        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2413        result_item: YogStr::from_str(&ri), result_count: result_count as u32,
2414    };
2415    let srv = srv_ptr();
2416    guard("on_item_craft", || {
2417        for (ud, f) in &h.item_craft {
2418            unsafe { f(*ud, srv, &ev, 1) };
2419        }
2420    });
2421}
2422
2423#[no_mangle]
2424pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosionPre<'l>(
2425    mut env: JNIEnv<'l>, _class: JClass<'l>,
2426    dimension: JString<'l>, x: jdouble, y: jdouble, z: jdouble,
2427    power: jfloat, cause_uuid: JString<'l>,
2428) -> jni::sys::jboolean {
2429    let h = handlers();
2430    if h.explosion.is_empty() { return 1; }
2431    let d  = match env.get_string(&dimension)  { Ok(s) => String::from(s), Err(_) => return 1 };
2432    let cu = match env.get_string(&cause_uuid) { Ok(s) => String::from(s), Err(_) => return 1 };
2433    let ev = YogExplosionEvent {
2434        dimension: YogStr::from_str(&d), x, y, z, power, cause_uuid: YogStr::from_str(&cu),
2435    };
2436    let srv = srv_ptr();
2437    let mut allow = true;
2438    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2439        for (ud, f) in &h.explosion {
2440            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2441        }
2442    })).ok();
2443    allow as jni::sys::jboolean
2444}
2445
2446#[no_mangle]
2447pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnExplosion<'l>(
2448    mut env: JNIEnv<'l>, _class: JClass<'l>,
2449    dimension: JString<'l>, x: jdouble, y: jdouble, z: jdouble,
2450    power: jfloat, cause_uuid: JString<'l>,
2451) {
2452    let h = handlers();
2453    if h.explosion.is_empty() { return; }
2454    let (d, cu) = (jstr!(env, dimension), jstr!(env, cause_uuid));
2455    let ev = YogExplosionEvent {
2456        dimension: YogStr::from_str(&d), x, y, z, power, cause_uuid: YogStr::from_str(&cu),
2457    };
2458    let srv = srv_ptr();
2459    guard("on_explosion", || {
2460        for (ud, f) in &h.explosion {
2461            unsafe { f(*ud, srv, &ev, 1) };
2462        }
2463    });
2464}
2465
2466// ── ABI minor 9 JNI entry points ─────────────────────────────────────────────
2467
2468#[no_mangle]
2469pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickupPre<'l>(
2470    mut env: JNIEnv<'l>, _class: JClass<'l>,
2471    player: JString<'l>, player_uuid: JString<'l>,
2472    item_id: JString<'l>, item_count: jint, entity_uuid: JString<'l>,
2473) -> jni::sys::jboolean {
2474    let h = handlers();
2475    if h.item_pickup.is_empty() { return 1; }
2476    let p   = match env.get_string(&player)      { Ok(s) => String::from(s), Err(_) => return 1 };
2477    let pu  = match env.get_string(&player_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2478    let ii  = match env.get_string(&item_id)      { Ok(s) => String::from(s), Err(_) => return 1 };
2479    let eu  = match env.get_string(&entity_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2480    let ev = YogItemPickupEvent {
2481        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2482        item_id: YogStr::from_str(&ii), item_count: item_count as u32,
2483        entity_uuid: YogStr::from_str(&eu),
2484    };
2485    let srv = srv_ptr();
2486    let mut allow = true;
2487    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2488        for (ud, f) in &h.item_pickup {
2489            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2490        }
2491    })).ok();
2492    allow as jni::sys::jboolean
2493}
2494
2495#[no_mangle]
2496pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnItemPickup<'l>(
2497    mut env: JNIEnv<'l>, _class: JClass<'l>,
2498    player: JString<'l>, player_uuid: JString<'l>,
2499    item_id: JString<'l>, item_count: jint, entity_uuid: JString<'l>,
2500) {
2501    let h = handlers();
2502    if h.item_pickup.is_empty() { return; }
2503    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
2504    let (ii, eu) = (jstr!(env, item_id), jstr!(env, entity_uuid));
2505    let ev = YogItemPickupEvent {
2506        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2507        item_id: YogStr::from_str(&ii), item_count: item_count as u32,
2508        entity_uuid: YogStr::from_str(&eu),
2509    };
2510    let srv = srv_ptr();
2511    guard("on_item_pickup", || {
2512        for (ud, f) in &h.item_pickup {
2513            unsafe { f(*ud, srv, &ev, 1) };
2514        }
2515    });
2516}
2517
2518#[no_mangle]
2519pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnPlayerMove<'l>(
2520    mut env: JNIEnv<'l>, _class: JClass<'l>,
2521    player: JString<'l>, player_uuid: JString<'l>,
2522    x: jdouble, y: jdouble, z: jdouble, yaw: jfloat, pitch: jfloat,
2523) {
2524    let h = handlers();
2525    if h.player_move.is_empty() { return; }
2526    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
2527    let ev = YogPlayerMoveEvent {
2528        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2529        x, y, z, yaw, pitch,
2530    };
2531    let srv = srv_ptr();
2532    guard("on_player_move", || {
2533        for (ud, f) in &h.player_move {
2534            unsafe { f(*ud, srv, &ev, 1) };
2535        }
2536    });
2537}
2538
2539#[no_mangle]
2540pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpenPre<'l>(
2541    mut env: JNIEnv<'l>, _class: JClass<'l>,
2542    player: JString<'l>, player_uuid: JString<'l>,
2543) -> jni::sys::jboolean {
2544    let h = handlers();
2545    if h.container_open.is_empty() { return 1; }
2546    let p  = match env.get_string(&player)      { Ok(s) => String::from(s), Err(_) => return 1 };
2547    let pu = match env.get_string(&player_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2548    let ev = YogContainerOpenEvent {
2549        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2550        container_type: YogStr::EMPTY,
2551    };
2552    let srv = srv_ptr();
2553    let mut allow = true;
2554    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2555        for (ud, f) in &h.container_open {
2556            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2557        }
2558    })).ok();
2559    allow as jni::sys::jboolean
2560}
2561
2562#[no_mangle]
2563pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerOpen<'l>(
2564    mut env: JNIEnv<'l>, _class: JClass<'l>,
2565    player: JString<'l>, player_uuid: JString<'l>, container_type: JString<'l>,
2566) {
2567    let h = handlers();
2568    if h.container_open.is_empty() { return; }
2569    let (p, pu, ct) = (jstr!(env, player), jstr!(env, player_uuid), jstr!(env, container_type));
2570    let ev = YogContainerOpenEvent {
2571        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2572        container_type: YogStr::from_str(&ct),
2573    };
2574    let srv = srv_ptr();
2575    guard("on_container_open", || {
2576        for (ud, f) in &h.container_open {
2577            unsafe { f(*ud, srv, &ev, 1) };
2578        }
2579    });
2580}
2581
2582#[no_mangle]
2583pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnContainerClose<'l>(
2584    mut env: JNIEnv<'l>, _class: JClass<'l>,
2585    player: JString<'l>, player_uuid: JString<'l>,
2586) {
2587    let h = handlers();
2588    if h.container_close.is_empty() { return; }
2589    let (p, pu) = (jstr!(env, player), jstr!(env, player_uuid));
2590    let ev = YogContainerCloseEvent {
2591        player: YogStr::from_str(&p), player_uuid: YogStr::from_str(&pu),
2592    };
2593    let srv = srv_ptr();
2594    guard("on_container_close", || {
2595        for (ud, f) in &h.container_close {
2596            unsafe { f(*ud, srv, &ev, 1) };
2597        }
2598    });
2599}
2600
2601#[no_mangle]
2602pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHitPre<'l>(
2603    mut env: JNIEnv<'l>, _class: JClass<'l>,
2604    projectile_type: JString<'l>, projectile_uuid: JString<'l>, shooter_uuid: JString<'l>,
2605    hit_type: JString<'l>, hit_entity_uuid: JString<'l>,
2606    x: jdouble, y: jdouble, z: jdouble, dimension: JString<'l>,
2607) -> jni::sys::jboolean {
2608    let h = handlers();
2609    if h.projectile_hit.is_empty() { return 1; }
2610    let pt  = match env.get_string(&projectile_type)  { Ok(s) => String::from(s), Err(_) => return 1 };
2611    let pu  = match env.get_string(&projectile_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2612    let su  = match env.get_string(&shooter_uuid)     { Ok(s) => String::from(s), Err(_) => return 1 };
2613    let ht  = match env.get_string(&hit_type)         { Ok(s) => String::from(s), Err(_) => return 1 };
2614    let heu = match env.get_string(&hit_entity_uuid)  { Ok(s) => String::from(s), Err(_) => return 1 };
2615    let dim = match env.get_string(&dimension)        { Ok(s) => String::from(s), Err(_) => return 1 };
2616    let ev = YogProjectileHitEvent {
2617        projectile_type: YogStr::from_str(&pt), projectile_uuid: YogStr::from_str(&pu),
2618        shooter_uuid: YogStr::from_str(&su), hit_type: YogStr::from_str(&ht),
2619        hit_entity_uuid: YogStr::from_str(&heu), x, y, z, dimension: YogStr::from_str(&dim),
2620    };
2621    let srv = srv_ptr();
2622    let mut allow = true;
2623    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2624        for (ud, f) in &h.projectile_hit {
2625            if !unsafe { f(*ud, srv, &ev, 0) } { allow = false; break; }
2626        }
2627    })).ok();
2628    allow as jni::sys::jboolean
2629}
2630
2631#[no_mangle]
2632pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnProjectileHit<'l>(
2633    mut env: JNIEnv<'l>, _class: JClass<'l>,
2634    projectile_type: JString<'l>, projectile_uuid: JString<'l>, shooter_uuid: JString<'l>,
2635    hit_type: JString<'l>, hit_entity_uuid: JString<'l>,
2636    x: jdouble, y: jdouble, z: jdouble, dimension: JString<'l>,
2637) {
2638    let h = handlers();
2639    if h.projectile_hit.is_empty() { return; }
2640    let (pt, pu) = (jstr!(env, projectile_type), jstr!(env, projectile_uuid));
2641    let (su, ht) = (jstr!(env, shooter_uuid), jstr!(env, hit_type));
2642    let (heu, dim) = (jstr!(env, hit_entity_uuid), jstr!(env, dimension));
2643    let ev = YogProjectileHitEvent {
2644        projectile_type: YogStr::from_str(&pt), projectile_uuid: YogStr::from_str(&pu),
2645        shooter_uuid: YogStr::from_str(&su), hit_type: YogStr::from_str(&ht),
2646        hit_entity_uuid: YogStr::from_str(&heu), x, y, z, dimension: YogStr::from_str(&dim),
2647    };
2648    let srv = srv_ptr();
2649    guard("on_projectile_hit", || {
2650        for (ud, f) in &h.projectile_hit {
2651            unsafe { f(*ud, srv, &ev, 1) };
2652        }
2653    });
2654}
2655
2656// ── ABI minor 10 — client-side JNI entry points ───────────────────────────────
2657
2658#[no_mangle]
2659pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnClientTick<'l>(
2660    _env: JNIEnv<'l>, _class: JClass<'l>,
2661) {
2662    let h = handlers();
2663    if h.client_tick.is_empty() { return; }
2664    guard("on_client_tick", || {
2665        for (ud, f) in &h.client_tick {
2666            unsafe { f(*ud) };
2667        }
2668    });
2669}
2670
2671#[no_mangle]
2672pub extern "system" fn Java_dev_yog_NativeBridge_nativeGlInit<'l>(
2673    _env: JNIEnv<'l>, _class: JClass<'l>,
2674) {
2675    if GL.get().is_some() { return; }
2676    let mut raw_get_binary: usize = 0;
2677    let mut raw_prog_binary: usize = 0;
2678    let gl = unsafe {
2679        glow::Context::from_loader_function(|sym| {
2680            let Some(mut env) = get_env() else { return std::ptr::null() };
2681            let jsym = match env.new_string(sym) {
2682                Ok(s) => s,
2683                Err(_) => return std::ptr::null(),
2684            };
2685            let jsym_obj: JObject = jsym.into();
2686            let val = env.call_static_method(
2687                "dev/yog/NativeBridge",
2688                "glProcAddress",
2689                "(Ljava/lang/String;)J",
2690                &[JValue::Object(&jsym_obj)],
2691            );
2692            let ptr = match val.and_then(|v| v.j()) {
2693                Ok(p) if p != 0 => p as usize as *const _,
2694                _ => std::ptr::null(),
2695            };
2696            // Capture extension pointers while the loader runs.
2697            match sym {
2698                "glGetProgramBinary" => raw_get_binary = ptr as usize,
2699                "glProgramBinary"    => raw_prog_binary = ptr as usize,
2700                _ => {}
2701            }
2702            ptr
2703        })
2704    };
2705    let _ = GL.set(GlCtx(gl));
2706    let _ = GL_GET_PROGRAM_BINARY.set(if raw_get_binary != 0 { Some(raw_get_binary) } else { None });
2707    let _ = GL_PROGRAM_BINARY.set(if raw_prog_binary != 0 { Some(raw_prog_binary) } else { None });
2708    // `glGetProgramiv` is a core function; always available.  We look it up once here
2709    // to avoid depending on glow internals for the PROGRAM_BINARY_LENGTH query.
2710    if let Some(mut env) = get_env() {
2711        if let Ok(jsym) = env.new_string("glGetProgramiv") {
2712            let jsym_obj: JObject = jsym.into();
2713            if let Ok(jv) = env.call_static_method(
2714                "dev/yog/NativeBridge", "glProcAddress", "(Ljava/lang/String;)J",
2715                &[JValue::Object(&jsym_obj)],
2716            ) {
2717                if let Ok(ptr) = jv.j() {
2718                    let _ = GL_GET_PROGRAM_IV.set(if ptr != 0 { Some(ptr as usize) } else { None });
2719                }
2720            }
2721        }
2722    }
2723}
2724
2725#[no_mangle]
2726pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnHudRender<'l>(
2727    _env: JNIEnv<'l>, _class: JClass<'l>,
2728    delta_tick: jfloat,
2729    screen_w: jint,
2730    screen_h: jint,
2731    scale_factor: jfloat,
2732    player_x: jfloat, player_y: jfloat, player_z: jfloat,
2733) {
2734    let h = handlers();
2735    if h.hud_render.is_empty() { return; }
2736    let mut gfx = GFX_FN_TABLE;
2737    gfx.screen_w = screen_w;
2738    gfx.screen_h = screen_h;
2739    gfx.delta_tick = delta_tick;
2740    gfx.scale_factor = scale_factor;
2741    gfx.player_pos = [player_x, player_y, player_z];
2742    guard("on_hud_render", || {
2743        for (ud, f) in &h.hud_render {
2744            unsafe { f(*ud, &gfx) };
2745        }
2746    });
2747}
2748
2749#[no_mangle]
2750pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnWorldRender<'l>(
2751    env: JNIEnv<'l>, _class: JClass<'l>,
2752    delta_tick: jfloat,
2753    screen_w: jint,
2754    screen_h: jint,
2755    scale_factor: jfloat,
2756    view_proj_arr: JFloatArray<'l>,
2757    cam_x: jfloat, cam_y: jfloat, cam_z: jfloat,
2758    player_x: jfloat, player_y: jfloat, player_z: jfloat,
2759) {
2760    let h = handlers();
2761    if h.world_render.is_empty() { return; }
2762    let mut view_proj = [0f32; 16];
2763    if env.get_float_array_region(&view_proj_arr, 0, &mut view_proj).is_err() { return; }
2764    let mut gfx = GFX_FN_TABLE;
2765    gfx.screen_w = screen_w;
2766    gfx.screen_h = screen_h;
2767    gfx.delta_tick = delta_tick;
2768    gfx.scale_factor = scale_factor;
2769    gfx.view_proj = view_proj;
2770    gfx.camera_pos = [cam_x, cam_y, cam_z];
2771    gfx.player_pos = [player_x, player_y, player_z];
2772    guard("on_world_render", || {
2773        for (ud, f) in &h.world_render {
2774            unsafe { f(*ud, &gfx) };
2775        }
2776    });
2777}
2778
2779#[no_mangle]
2780pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnKeyPress<'l>(
2781    _env: JNIEnv<'l>, _class: JClass<'l>,
2782    key_code: jint, scan_code: jint, action: jint, modifiers: jint,
2783) -> jni::sys::jboolean {
2784    let h = handlers();
2785    if h.key_press.is_empty() { return 1; }
2786    let ev = YogKeyPressEvent { key_code, scan_code, action, modifiers };
2787    let mut allow = true;
2788    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2789        for (ud, f) in &h.key_press {
2790            if !unsafe { f(*ud, &ev) } { allow = false; break; }
2791        }
2792    })).ok();
2793    allow as jni::sys::jboolean
2794}
2795
2796#[no_mangle]
2797pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenOpen<'l>(
2798    mut env: JNIEnv<'l>, _class: JClass<'l>,
2799    screen_class: JString<'l>,
2800) {
2801    let h = handlers();
2802    if h.screen_open.is_empty() { return; }
2803    let sc = match env.get_string(&screen_class) { Ok(s) => String::from(s), Err(_) => return };
2804    guard("on_screen_open", || {
2805        for (ud, f) in &h.screen_open {
2806            unsafe { f(*ud, YogStr::from_str(&sc)) };
2807        }
2808    });
2809}
2810
2811#[no_mangle]
2812pub extern "system" fn Java_dev_yog_NativeBridge_nativeOnScreenClose<'l>(
2813    mut env: JNIEnv<'l>, _class: JClass<'l>,
2814    screen_class: JString<'l>,
2815) {
2816    let h = handlers();
2817    if h.screen_close.is_empty() { return; }
2818    let sc = match env.get_string(&screen_class) { Ok(s) => String::from(s), Err(_) => return };
2819    guard("on_screen_close", || {
2820        for (ud, f) in &h.screen_close {
2821            unsafe { f(*ud, YogStr::from_str(&sc)) };
2822        }
2823    });
2824}
2825
2826
2827mod ui_jni;