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