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