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