Skip to main content

yog_api/
registry.rs

1//! Registration hub and mod entry-point trait.
2//!
3//! [`Registry`] wraps the [`YogApi`] C table passed to `yog_mod_register` and
4//! provides an ergonomic Rust API over it.  All closures registered here are
5//! boxed on the heap and their raw pointers handed to the runtime; the runtime
6//! then drives them via C function-pointer calls.  Closures are intentionally
7//! leaked (never freed) — they live for the entire process lifetime, which is
8//! correct for a game server.
9
10use std::os::raw::c_void;
11
12use yog_abi::{
13    YogAdvancementEvent, YogApi, YogAttackEntityEvent, YogBlockBreakEvent, YogBlockDef,
14    YogChatEvent, YogCommandEvent, YogContainerCloseEvent, YogContainerOpenEvent, YogCraftEvent,
15    YogEntityDamageEvent, YogEntityDeathEvent, YogEntityInteractEvent, YogEntitySpawnEvent,
16    YogExplosionEvent, YogGfxApi, YogInventoryDef, YogItemDef, YogItemPickupEvent,
17    YogKeyPressEvent, YogPacketEvent, YogPlaceBlockEvent, YogPlayerDeathEvent, YogPlayerEvent,
18    YogPlayerMoveEvent, YogPlayerRespawnEvent, YogProjectileHitEvent, YogServer,
19    YogStartupGrantDef, YogStr, YogUseBlockEvent, YogUseItemEvent,
20};
21use yog_book::Book;
22use yog_command::CommandContext;
23use yog_core::Server;
24use yog_dimensions::{ChunkWriter, YogDimensionDef};
25use yog_event::{
26    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
27    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
28    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemPickupEvent,
29    KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
30    PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
31    UseItemEvent,
32};
33use yog_gfx::GfxContext;
34use yog_network::{Packet, PacketEvent};
35use yog_registry::{BlockDef, FurnaceRecipe, ItemDef, ShapedRecipe, ShapelessRecipe, StartupGrant};
36
37// ── CServer — implements Server via the YogServer function table ──────────────
38
39/// A handle to the runtime's server actions, backed by [`YogServer`] fn pointers.
40/// Given to every handler as `&dyn Server`.
41pub struct CServer(pub *const YogServer);
42
43unsafe impl Send for CServer {}
44unsafe impl Sync for CServer {}
45
46macro_rules! srv {
47    ($self:ident) => {
48        unsafe { &*$self.0 }
49    };
50}
51
52impl Server for CServer {
53    fn broadcast(&self, message: &str) {
54        let s = srv!(self);
55        unsafe { (s.broadcast)(s.ctx, YogStr::from_str(message)) }
56    }
57
58    fn get_block(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
59        let s = srv!(self);
60        let owned = unsafe {
61            (s.get_block)(
62                s.ctx,
63                YogStr::from_str(dimension),
64                yog_abi::YogBlockPos {
65                    x: pos.x,
66                    y: pos.y,
67                    z: pos.z,
68                },
69            )
70        };
71
72        take_owned(s.free_str, owned)
73    }
74
75    fn set_block(&self, dimension: &str, pos: yog_core::BlockPos, block_id: &str) -> bool {
76        let s = srv!(self);
77        unsafe {
78            (s.set_block)(
79                s.ctx,
80                YogStr::from_str(dimension),
81                yog_abi::YogBlockPos {
82                    x: pos.x,
83                    y: pos.y,
84                    z: pos.z,
85                },
86                YogStr::from_str(block_id),
87            )
88        }
89    }
90
91    fn give_item(&self, player: &str, item_id: &str, count: u32) -> bool {
92        let s = srv!(self);
93        unsafe {
94            (s.give_item)(
95                s.ctx,
96                YogStr::from_str(player),
97                YogStr::from_str(item_id),
98                count,
99            )
100        }
101    }
102
103    fn teleport(&self, player: &str, x: f64, y: f64, z: f64) -> bool {
104        let s = srv!(self);
105        unsafe {
106            (s.player_teleport)(
107                s.ctx,
108                YogStr::from_str(player),
109                yog_abi::YogVec3 { x, y, z },
110            )
111        }
112    }
113
114    fn send_to_player(&self, player: &str, channel: &str, payload: &[u8]) -> bool {
115        let s = srv!(self);
116        unsafe {
117            (s.send_to_player)(
118                s.ctx,
119                YogStr::from_str(player),
120                YogStr::from_str(channel),
121                payload.as_ptr(),
122                payload.len() as u32,
123            )
124        }
125    }
126
127    fn send_to_server(&self, channel: &str, payload: &[u8]) -> bool {
128        let s = srv!(self);
129        unsafe {
130            (s.send_to_server)(
131                s.ctx,
132                YogStr::from_str(channel),
133                payload.as_ptr(),
134                payload.len() as u32,
135            )
136        }
137    }
138
139    fn entity_teleport(&self, uuid: &str, x: f64, y: f64, z: f64) -> bool {
140        let s = srv!(self);
141        unsafe { (s.entity_teleport)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x, y, z }) }
142    }
143
144    fn entity_position(&self, uuid: &str) -> Option<(f64, f64, f64)> {
145        let s = srv!(self);
146        let mut out = yog_abi::YogVec3 {
147            x: 0.0,
148            y: 0.0,
149            z: 0.0,
150        };
151        if unsafe { (s.entity_position)(s.ctx, YogStr::from_str(uuid), &mut out) } {
152            Some((out.x, out.y, out.z))
153        } else {
154            None
155        }
156    }
157
158    fn entity_health(&self, uuid: &str) -> Option<f32> {
159        let s = srv!(self);
160        let mut hp = 0f32;
161        if unsafe { (s.entity_health)(s.ctx, YogStr::from_str(uuid), &mut hp) } {
162            Some(hp)
163        } else {
164            None
165        }
166    }
167
168    fn entity_set_health(&self, uuid: &str, health: f32) -> bool {
169        let s = srv!(self);
170        unsafe { (s.entity_set_health)(s.ctx, YogStr::from_str(uuid), health) }
171    }
172
173    fn entity_kill(&self, uuid: &str) -> bool {
174        let s = srv!(self);
175        unsafe { (s.entity_kill)(s.ctx, YogStr::from_str(uuid)) }
176    }
177
178    fn spawn_entity(
179        &self,
180        entity_type: &str,
181        dimension: &str,
182        x: f64,
183        y: f64,
184        z: f64,
185    ) -> Option<String> {
186        let s = srv!(self);
187        let owned = unsafe {
188            (s.spawn_entity)(
189                s.ctx,
190                YogStr::from_str(entity_type),
191                YogStr::from_str(dimension),
192                yog_abi::YogVec3 { x, y, z },
193            )
194        };
195
196        take_owned(s.free_str, owned)
197    }
198
199    fn entity_add_effect(
200        &self,
201        uuid: &str,
202        effect_id: &str,
203        duration_ticks: i32,
204        amplifier: u8,
205        show_particles: bool,
206    ) -> bool {
207        let s = srv!(self);
208        unsafe {
209            (s.entity_add_effect)(
210                s.ctx,
211                YogStr::from_str(uuid),
212                YogStr::from_str(effect_id),
213                duration_ticks,
214                amplifier,
215                show_particles,
216            )
217        }
218    }
219
220    fn entity_remove_effect(&self, uuid: &str, effect_id: &str) -> bool {
221        let s = srv!(self);
222        unsafe {
223            (s.entity_remove_effect)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(effect_id))
224        }
225    }
226
227    fn entity_clear_effects(&self, uuid: &str) -> bool {
228        let s = srv!(self);
229        unsafe { (s.entity_clear_effects)(s.ctx, YogStr::from_str(uuid)) }
230    }
231
232    fn drop_loot(&self, table_id: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
233        let s = srv!(self);
234        unsafe {
235            (s.drop_loot)(
236                s.ctx,
237                YogStr::from_str(table_id),
238                YogStr::from_str(dimension),
239                yog_abi::YogVec3 { x, y, z },
240            )
241        }
242    }
243
244    fn has_item_tag(&self, item_id: &str, tag_id: &str) -> bool {
245        let s = srv!(self);
246        unsafe { (s.has_item_tag)(s.ctx, YogStr::from_str(item_id), YogStr::from_str(tag_id)) }
247    }
248
249    fn has_block_tag(&self, block_id: &str, tag_id: &str) -> bool {
250        let s = srv!(self);
251        unsafe { (s.has_block_tag)(s.ctx, YogStr::from_str(block_id), YogStr::from_str(tag_id)) }
252    }
253
254    fn world_time(&self, dimension: &str) -> Option<i64> {
255        let s = srv!(self);
256        let mut t = 0i64;
257        if unsafe { (s.world_time)(s.ctx, YogStr::from_str(dimension), &mut t) } {
258            Some(t)
259        } else {
260            None
261        }
262    }
263
264    fn world_set_time(&self, dimension: &str, time: i64) -> bool {
265        let s = srv!(self);
266        unsafe { (s.set_time)(s.ctx, YogStr::from_str(dimension), time) }
267    }
268
269    fn world_is_raining(&self, dimension: &str) -> bool {
270        let s = srv!(self);
271        unsafe { (s.is_raining)(s.ctx, YogStr::from_str(dimension)) }
272    }
273
274    fn world_set_weather(&self, dimension: &str, raining: bool, duration_ticks: i32) -> bool {
275        let s = srv!(self);
276        unsafe { (s.set_weather)(s.ctx, YogStr::from_str(dimension), raining, duration_ticks) }
277    }
278
279    fn entity_velocity(&self, uuid: &str) -> Option<(f64, f64, f64)> {
280        let s = srv!(self);
281        let mut v = yog_abi::YogVec3 {
282            x: 0.0,
283            y: 0.0,
284            z: 0.0,
285        };
286        if unsafe { (s.entity_velocity)(s.ctx, YogStr::from_str(uuid), &mut v) } {
287            Some((v.x, v.y, v.z))
288        } else {
289            None
290        }
291    }
292
293    fn entity_set_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
294        let s = srv!(self);
295        unsafe {
296            (s.entity_set_velocity)(
297                s.ctx,
298                YogStr::from_str(uuid),
299                yog_abi::YogVec3 {
300                    x: vx,
301                    y: vy,
302                    z: vz,
303                },
304            )
305        }
306    }
307
308    fn entity_add_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
309        let s = srv!(self);
310        unsafe {
311            (s.entity_add_velocity)(
312                s.ctx,
313                YogStr::from_str(uuid),
314                yog_abi::YogVec3 {
315                    x: vx,
316                    y: vy,
317                    z: vz,
318                },
319            )
320        }
321    }
322
323    fn scoreboard_get(&self, objective: &str, player: &str) -> Option<i32> {
324        let s = srv!(self);
325        let mut score = 0i32;
326        if unsafe {
327            (s.scoreboard_get)(
328                s.ctx,
329                YogStr::from_str(objective),
330                YogStr::from_str(player),
331                &mut score,
332            )
333        } {
334            Some(score)
335        } else {
336            None
337        }
338    }
339
340    fn scoreboard_set(&self, objective: &str, player: &str, score: i32) -> bool {
341        let s = srv!(self);
342        unsafe {
343            (s.scoreboard_set)(
344                s.ctx,
345                YogStr::from_str(objective),
346                YogStr::from_str(player),
347                score,
348            )
349        }
350    }
351
352    fn scoreboard_add(&self, objective: &str, player: &str, delta: i32) -> Option<i32> {
353        let s = srv!(self);
354        let mut new_score = 0i32;
355        if unsafe {
356            (s.scoreboard_add)(
357                s.ctx,
358                YogStr::from_str(objective),
359                YogStr::from_str(player),
360                delta,
361                &mut new_score,
362            )
363        } {
364            Some(new_score)
365        } else {
366            None
367        }
368    }
369
370    fn game_dir(&self) -> String {
371        let s = srv!(self);
372        let owned = unsafe { (s.game_dir)(s.ctx) };
373        take_owned(s.free_str, owned).unwrap_or_default()
374    }
375
376    fn play_sound(
377        &self,
378        dimension: &str,
379        x: f64,
380        y: f64,
381        z: f64,
382        sound_id: &str,
383        volume: f32,
384        pitch: f32,
385    ) -> bool {
386        let s = srv!(self);
387        unsafe {
388            (s.play_sound)(
389                s.ctx,
390                YogStr::from_str(dimension),
391                yog_abi::YogVec3 { x, y, z },
392                YogStr::from_str(sound_id),
393                volume,
394                pitch,
395            )
396        }
397    }
398
399    fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool {
400        let s = srv!(self);
401        unsafe {
402            (s.play_sound_player)(
403                s.ctx,
404                YogStr::from_str(player),
405                YogStr::from_str(sound_id),
406                volume,
407                pitch,
408            )
409        }
410    }
411
412    fn send_title(
413        &self,
414        player: &str,
415        title: &str,
416        subtitle: &str,
417        fadein: i32,
418        stay: i32,
419        fadeout: i32,
420    ) -> bool {
421        let s = srv!(self);
422        unsafe {
423            (s.send_title)(
424                s.ctx,
425                YogStr::from_str(player),
426                YogStr::from_str(title),
427                YogStr::from_str(subtitle),
428                fadein,
429                stay,
430                fadeout,
431            )
432        }
433    }
434
435    fn entity_rotation(&self, uuid: &str) -> Option<(f32, f32)> {
436        let s = srv!(self);
437        let mut out = yog_abi::YogVec3 {
438            x: 0.0,
439            y: 0.0,
440            z: 0.0,
441        };
442        let ok = unsafe { (s.entity_rotation)(s.ctx, YogStr::from_str(uuid), &mut out) };
443        if ok {
444            Some((out.x as f32, out.y as f32))
445        } else {
446            None
447        }
448    }
449
450    fn send_actionbar(&self, player: &str, message: &str) -> bool {
451        let s = srv!(self);
452        unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
453    }
454
455    fn kick_player(&self, player: &str, reason: &str) -> bool {
456        let s = srv!(self);
457        unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
458    }
459
460    fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
461        let s = srv!(self);
462        unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
463    }
464
465    fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
466        let s = srv!(self);
467        unsafe {
468            (s.bossbar_create)(
469                s.ctx,
470                YogStr::from_str(id),
471                YogStr::from_str(title),
472                YogStr::from_str(color),
473                YogStr::from_str(style),
474            )
475        }
476    }
477
478    fn bossbar_remove(&self, id: &str) -> bool {
479        let s = srv!(self);
480        unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
481    }
482
483    fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
484        let s = srv!(self);
485        unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
486    }
487
488    fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
489        let s = srv!(self);
490        unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
491    }
492
493    fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
494        let s = srv!(self);
495        unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
496    }
497
498    fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
499        let s = srv!(self);
500        unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
501    }
502
503    fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
504        let s = srv!(self);
505        unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
506    }
507
508    fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
509        let s = srv!(self);
510        unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
511    }
512
513    fn online_players(&self) -> Vec<String> {
514        let s = srv!(self);
515        let owned = unsafe { (s.online_players)(s.ctx) };
516        let Some(text) = take_owned(s.free_str, owned) else {
517            return Vec::new();
518        };
519        if text.is_empty() {
520            Vec::new()
521        } else {
522            text.lines().map(str::to_owned).collect()
523        }
524    }
525
526    fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
527        let s = srv!(self);
528        let owned = unsafe {
529            (s.get_block_nbt)(
530                s.ctx,
531                YogStr::from_str(dimension),
532                yog_abi::YogBlockPos {
533                    x: pos.x,
534                    y: pos.y,
535                    z: pos.z,
536                },
537            )
538        };
539
540        take_owned(s.free_str, owned)
541    }
542
543    fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
544        let s = srv!(self);
545        unsafe {
546            (s.set_block_nbt)(
547                s.ctx,
548                YogStr::from_str(dimension),
549                yog_abi::YogBlockPos {
550                    x: pos.x,
551                    y: pos.y,
552                    z: pos.z,
553                },
554                YogStr::from_str(snbt),
555            )
556        }
557    }
558
559    fn get_inventory_slot(
560        &self,
561        dimension: &str,
562        pos: yog_core::BlockPos,
563        slot: u32,
564    ) -> Option<(String, u32)> {
565        let s = srv!(self);
566        let owned = unsafe {
567            (s.get_inventory_slot)(
568                s.ctx,
569                YogStr::from_str(dimension),
570                yog_abi::YogBlockPos {
571                    x: pos.x,
572                    y: pos.y,
573                    z: pos.z,
574                },
575                slot,
576            )
577        };
578        let text = take_owned(s.free_str, owned)?;
579        let (item_id, count) = text.split_once('\t')?;
580        Some((item_id.to_owned(), count.parse().ok()?))
581    }
582
583    fn set_inventory_slot(
584        &self,
585        dimension: &str,
586        pos: yog_core::BlockPos,
587        slot: u32,
588        item_id: &str,
589        count: u32,
590    ) -> bool {
591        let s = srv!(self);
592        unsafe {
593            (s.set_inventory_slot)(
594                s.ctx,
595                YogStr::from_str(dimension),
596                yog_abi::YogBlockPos {
597                    x: pos.x,
598                    y: pos.y,
599                    z: pos.z,
600                },
601                slot,
602                YogStr::from_str(item_id),
603                count,
604            )
605        }
606    }
607
608    fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
609        let s = srv!(self);
610        let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
611        let Some(text) = take_owned(s.free_str, owned) else {
612            return Vec::new();
613        };
614        text.lines()
615            .filter_map(|line| {
616                let mut it = line.split('\t');
617                let slot: u32 = it.next()?.parse().ok()?;
618                let item_id = it.next()?.to_owned();
619                let count: u32 = it.next()?.parse().ok()?;
620                Some((slot, item_id, count))
621            })
622            .collect()
623    }
624
625    fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
626        let s = srv!(self);
627        unsafe {
628            (s.player_set_slot)(
629                s.ctx,
630                YogStr::from_str(player),
631                slot,
632                YogStr::from_str(item_id),
633                count,
634            )
635        }
636    }
637
638    fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
639        let s = srv!(self);
640        unsafe {
641            (s.player_teleport_dim)(
642                s.ctx,
643                YogStr::from_str(player),
644                YogStr::from_str(dimension),
645                yog_abi::YogVec3 { x, y, z },
646            )
647        }
648    }
649
650    fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
651        let s = srv!(self);
652        unsafe {
653            (s.entity_teleport_dim)(
654                s.ctx,
655                YogStr::from_str(uuid),
656                YogStr::from_str(dimension),
657                yog_abi::YogVec3 { x, y, z },
658            )
659        }
660    }
661
662    fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
663        let s = srv!(self);
664        unsafe {
665            (s.world_entity_count)(
666                s.ctx,
667                YogStr::from_str(dimension),
668                YogStr::from_str(entity_type),
669            )
670        }
671    }
672
673    fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
674        let s = srv!(self);
675        let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
676
677        take_owned(s.free_str, owned)
678    }
679
680    fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
681        let s = srv!(self);
682        unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
683    }
684
685    fn spawn_particles(
686        &self,
687        dimension: &str,
688        x: f64,
689        y: f64,
690        z: f64,
691        particle_type: &str,
692        count: i32,
693        dx: f64,
694        dy: f64,
695        dz: f64,
696        speed: f64,
697    ) -> bool {
698        let s = srv!(self);
699        unsafe {
700            (s.spawn_particles)(
701                s.ctx,
702                YogStr::from_str(dimension),
703                yog_abi::YogVec3 { x, y, z },
704                YogStr::from_str(particle_type),
705                count,
706                dx,
707                dy,
708                dz,
709                speed,
710            )
711        }
712    }
713
714    fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
715        let s = srv!(self);
716        let v = unsafe {
717            (s.entity_attribute_get)(
718                s.ctx,
719                YogStr::from_str(uuid),
720                YogStr::from_str(attribute_id),
721            )
722        };
723        if v.is_nan() { None } else { Some(v) }
724    }
725
726    fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
727        let s = srv!(self);
728        unsafe {
729            (s.entity_attribute_set)(
730                s.ctx,
731                YogStr::from_str(uuid),
732                YogStr::from_str(attribute_id),
733                value,
734            )
735        }
736    }
737
738    fn get_held_item_nbt(&self, player: &str) -> Option<String> {
739        let s = srv!(self);
740        let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
741
742        take_owned(s.free_str, owned)
743    }
744
745    fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
746        let s = srv!(self);
747        unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
748    }
749
750    fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
751        let s = srv!(self);
752        let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
753
754        take_owned(s.free_str, owned)
755    }
756
757    fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
758        let s = srv!(self);
759        unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
760    }
761
762    fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
763        let s = srv!(self);
764        let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
765        let text = take_owned(s.free_str, owned)?;
766        let mut it = text.splitn(3, '\t');
767        let item_id = it.next()?.to_owned();
768        let count: u32 = it.next()?.parse().ok()?;
769        let nbt = it.next().unwrap_or("{}").to_owned();
770        Some((item_id, count, nbt))
771    }
772
773    fn set_slot_item(
774        &self,
775        player: &str,
776        slot: u32,
777        item_id: &str,
778        count: u32,
779        snbt: &str,
780    ) -> bool {
781        let s = srv!(self);
782        unsafe {
783            (s.set_slot_item)(
784                s.ctx,
785                YogStr::from_str(player),
786                slot,
787                YogStr::from_str(item_id),
788                count,
789                YogStr::from_str(snbt),
790            )
791        }
792    }
793
794    // ── world queries & effects (ABI minor 1) ────────────────────────────────
795
796    fn entity_info(&self, uuid: &str) -> Option<yog_core::EntityInfo> {
797        let s = srv!(self);
798        let owned = unsafe { (s.entity_info)(s.ctx, YogStr::from_str(uuid)) };
799        take_owned(s.free_str, owned).and_then(|l| yog_core::EntityInfo::decode(&l))
800    }
801
802    fn entities_in_box(
803        &self,
804        dimension: &str,
805        min: (f64, f64, f64),
806        max: (f64, f64, f64),
807    ) -> Vec<String> {
808        let s = srv!(self);
809        let owned =
810            unsafe { (s.entities_in_box)(s.ctx, YogStr::from_str(dimension), v3(min), v3(max)) };
811        take_owned(s.free_str, owned)
812            .map(|t| {
813                t.lines()
814                    .filter(|l| !l.is_empty())
815                    .map(str::to_owned)
816                    .collect()
817            })
818            .unwrap_or_default()
819    }
820
821    fn raycast_block(
822        &self,
823        dimension: &str,
824        from: (f64, f64, f64),
825        to: (f64, f64, f64),
826    ) -> Option<yog_core::BlockHit> {
827        let s = srv!(self);
828        let owned =
829            unsafe { (s.raycast_block)(s.ctx, YogStr::from_str(dimension), v3(from), v3(to)) };
830        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockHit::decode(&l))
831    }
832
833    fn raycast_entity(
834        &self,
835        dimension: &str,
836        from: (f64, f64, f64),
837        to: (f64, f64, f64),
838        exclude: &str,
839    ) -> Option<String> {
840        let s = srv!(self);
841        let owned = unsafe {
842            (s.raycast_entity)(
843                s.ctx,
844                YogStr::from_str(dimension),
845                v3(from),
846                v3(to),
847                YogStr::from_str(exclude),
848            )
849        };
850        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
851    }
852
853    fn block_info(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<yog_core::BlockInfo> {
854        let s = srv!(self);
855        let owned = unsafe { (s.block_info)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
856        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockInfo::decode(&l))
857    }
858
859    fn break_block(
860        &self,
861        dimension: &str,
862        pos: yog_core::BlockPos,
863        drop: bool,
864        breaker: &str,
865    ) -> bool {
866        let s = srv!(self);
867        unsafe {
868            (s.break_block)(
869                s.ctx,
870                YogStr::from_str(dimension),
871                bp(pos),
872                drop,
873                YogStr::from_str(breaker),
874            )
875        }
876    }
877
878    fn place_block_from_slot(
879        &self,
880        player: &str,
881        slot: u32,
882        dimension: &str,
883        pos: yog_core::BlockPos,
884    ) -> bool {
885        let s = srv!(self);
886        unsafe {
887            (s.place_block_from_slot)(
888                s.ctx,
889                YogStr::from_str(player),
890                slot,
891                YogStr::from_str(dimension),
892                bp(pos),
893            )
894        }
895    }
896
897    fn bonemeal(&self, dimension: &str, pos: yog_core::BlockPos) -> bool {
898        let s = srv!(self);
899        unsafe { (s.bonemeal)(s.ctx, YogStr::from_str(dimension), bp(pos)) }
900    }
901
902    fn explode(
903        &self,
904        dimension: &str,
905        x: f64,
906        y: f64,
907        z: f64,
908        power: f32,
909        fire: bool,
910        source: &str,
911    ) -> bool {
912        let s = srv!(self);
913        unsafe {
914            (s.explode)(
915                s.ctx,
916                YogStr::from_str(dimension),
917                v3((x, y, z)),
918                power,
919                fire,
920                YogStr::from_str(source),
921            )
922        }
923    }
924
925    fn tellraw(&self, player: &str, json: &str) -> bool {
926        let s = srv!(self);
927        unsafe { (s.tellraw)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
928    }
929
930    fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool {
931        let s = srv!(self);
932        unsafe {
933            (s.entity_damage)(
934                s.ctx,
935                YogStr::from_str(uuid),
936                amount,
937                YogStr::from_str(damage_type),
938                YogStr::from_str(attacker),
939            )
940        }
941    }
942
943    fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool {
944        let s = srv!(self);
945        unsafe { (s.entity_set_fire_ticks)(s.ctx, YogStr::from_str(uuid), ticks) }
946    }
947
948    fn player_add_xp(&self, player: &str, amount: i32) -> bool {
949        let s = srv!(self);
950        unsafe { (s.player_add_xp)(s.ctx, YogStr::from_str(player), amount) }
951    }
952
953    fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool {
954        let s = srv!(self);
955        unsafe { (s.player_set_may_fly)(s.ctx, YogStr::from_str(player), may_fly) }
956    }
957
958    fn player_info(&self, player: &str) -> Option<yog_core::PlayerInfo> {
959        let s = srv!(self);
960        let owned = unsafe { (s.player_info)(s.ctx, YogStr::from_str(player)) };
961        take_owned(s.free_str, owned).and_then(|l| yog_core::PlayerInfo::decode(&l))
962    }
963
964    fn spawn_item(
965        &self,
966        dimension: &str,
967        pos: (f64, f64, f64),
968        vel: (f64, f64, f64),
969        item_id: &str,
970        count: u32,
971        snbt: &str,
972    ) -> Option<String> {
973        let s = srv!(self);
974        let owned = unsafe {
975            (s.spawn_item)(
976                s.ctx,
977                YogStr::from_str(dimension),
978                v3(pos),
979                v3(vel),
980                YogStr::from_str(item_id),
981                count,
982                YogStr::from_str(snbt),
983            )
984        };
985        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
986    }
987
988    fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)> {
989        let s = srv!(self);
990        let owned = unsafe { (s.item_entity_stack)(s.ctx, YogStr::from_str(uuid)) };
991        let text = take_owned(s.free_str, owned)?;
992        let mut it = text.splitn(3, '\t');
993        let item_id = it.next()?.to_owned();
994        let count = it.next()?.parse().ok()?;
995        let nbt = it.next().unwrap_or("{}").to_owned();
996        Some((item_id, count, nbt))
997    }
998
999    fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool {
1000        let s = srv!(self);
1001        unsafe {
1002            (s.item_entity_set_stack)(
1003                s.ctx,
1004                YogStr::from_str(uuid),
1005                YogStr::from_str(item_id),
1006                count,
1007                YogStr::from_str(snbt),
1008            )
1009        }
1010    }
1011
1012    fn actionbar_json(&self, player: &str, json: &str) -> bool {
1013        let s = srv!(self);
1014        unsafe { (s.actionbar_json)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
1015    }
1016
1017    fn use_item_on_block(
1018        &self,
1019        dimension: &str,
1020        pos: yog_core::BlockPos,
1021        face: yog_core::Face,
1022        item_id: &str,
1023        player: &str,
1024    ) -> bool {
1025        let s = srv!(self);
1026        unsafe {
1027            (s.use_item_on_block)(
1028                s.ctx,
1029                YogStr::from_str(dimension),
1030                bp(pos),
1031                face.index(),
1032                YogStr::from_str(item_id),
1033                YogStr::from_str(player),
1034            )
1035        }
1036    }
1037
1038    fn pour_fluid(&self, dimension: &str, pos: yog_core::BlockPos, bucket_item_id: &str) -> bool {
1039        let s = srv!(self);
1040        unsafe {
1041            (s.pour_fluid)(
1042                s.ctx,
1043                YogStr::from_str(dimension),
1044                bp(pos),
1045                YogStr::from_str(bucket_item_id),
1046            )
1047        }
1048    }
1049
1050    fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool {
1051        let s = srv!(self);
1052        unsafe {
1053            (s.has_entity_tag)(
1054                s.ctx,
1055                YogStr::from_str(entity_type),
1056                YogStr::from_str(tag_id),
1057            )
1058        }
1059    }
1060
1061    fn item_block(&self, item_id: &str) -> Option<String> {
1062        let s = srv!(self);
1063        let owned = unsafe { (s.item_block)(s.ctx, YogStr::from_str(item_id)) };
1064        take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1065    }
1066
1067    fn place_feature(&self, dimension: &str, pos: yog_core::BlockPos, feature_id: &str) -> bool {
1068        let s = srv!(self);
1069        unsafe {
1070            (s.place_feature)(
1071                s.ctx,
1072                YogStr::from_str(dimension),
1073                bp(pos),
1074                YogStr::from_str(feature_id),
1075            )
1076        }
1077    }
1078}
1079
1080/// Copy a runtime-owned string out and free it; `None` for NONE.
1081pub(crate) fn take_owned(
1082    free_str: unsafe extern "C" fn(ptr: *mut u8, len: u32),
1083    owned: yog_abi::YogOwnedStr,
1084) -> Option<String> {
1085    if owned.is_none() {
1086        return None;
1087    }
1088    // SAFETY: a non-NONE owned string is `len` valid bytes until freed.
1089    let text = unsafe {
1090        String::from_utf8_lossy(std::slice::from_raw_parts(owned.ptr, owned.len as usize))
1091            .into_owned()
1092    };
1093    unsafe { free_str(owned.ptr, owned.len) };
1094    Some(text)
1095}
1096
1097fn v3((x, y, z): (f64, f64, f64)) -> yog_abi::YogVec3 {
1098    yog_abi::YogVec3 { x, y, z }
1099}
1100
1101fn bp(pos: yog_core::BlockPos) -> yog_abi::YogBlockPos {
1102    yog_abi::YogBlockPos {
1103        x: pos.x,
1104        y: pos.y,
1105        z: pos.z,
1106    }
1107}
1108
1109// ── Trampoline helpers ────────────────────────────────────────────────────────
1110//
1111// Each trampoline is a `unsafe extern "C" fn` that:
1112//   1. Casts `ud` back to the original boxed closure.
1113//   2. Converts ABI C structs to Rust event types.
1114//   3. Builds a `CServer` from the `*const YogServer`.
1115//   4. Calls the closure.
1116//
1117// Closures are Box::into_raw'd in Registry methods — they are INTENTIONALLY
1118// leaked and live for the process lifetime.
1119
1120// ── Trampoline helpers ────────────────────────────────────────────────────────
1121//
1122// All phased event trampolines share the same pattern:
1123//   `phase: u8`  0 = EventPhase::Pre, 1 = EventPhase::Post
1124//   Return value is meaningful only in Pre phase.
1125
1126macro_rules! trampoline_phased {
1127    ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr_2021) => {
1128        unsafe extern "C" fn $name<F>(
1129            ud: *mut c_void,
1130            srv: *const YogServer,
1131            ev: *const $abi_ev,
1132            phase: u8,
1133        ) -> bool
1134        where
1135            F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
1136        {
1137            unsafe {
1138                let f = &*(ud as *const F);
1139                let $ev = &*ev;
1140                let rust_ev = $build;
1141                let p = if phase == 0 {
1142                    EventPhase::Pre
1143                } else {
1144                    EventPhase::Post
1145                };
1146                f(&rust_ev, p, &CServer(srv))
1147            }
1148        }
1149    };
1150}
1151
1152trampoline_phased!(
1153    trampoline_block_break,
1154    YogBlockBreakEvent,
1155    BlockBreakEvent,
1156    |ev| BlockBreakEvent {
1157        player_name: ev.player.as_str().to_owned(),
1158        block_id: ev.block.as_str().to_owned(),
1159        pos: yog_core::BlockPos {
1160            x: ev.pos.x,
1161            y: ev.pos.y,
1162            z: ev.pos.z
1163        },
1164        dimension: ev.dimension.as_str().to_owned(),
1165    }
1166);
1167
1168trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
1169    player_name: ev.player.as_str().to_owned(),
1170    message: ev.message.as_str().to_owned(),
1171    dimension: ev.dimension.as_str().to_owned(),
1172});
1173
1174trampoline_phased!(
1175    trampoline_player_join,
1176    YogPlayerEvent,
1177    PlayerJoinEvent,
1178    |ev| PlayerJoinEvent {
1179        player_name: ev.player.as_str().to_owned(),
1180        uuid: ev.uuid.as_str().to_owned(),
1181        dimension: ev.dimension.as_str().to_owned(),
1182    }
1183);
1184
1185trampoline_phased!(
1186    trampoline_player_leave,
1187    YogPlayerEvent,
1188    PlayerLeaveEvent,
1189    |ev| PlayerLeaveEvent {
1190        player_name: ev.player.as_str().to_owned(),
1191        uuid: ev.uuid.as_str().to_owned(),
1192        dimension: ev.dimension.as_str().to_owned(),
1193    }
1194);
1195
1196trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| {
1197    UseItemEvent {
1198        player_name: ev.player.as_str().to_owned(),
1199        item_id: ev.item.as_str().to_owned(),
1200        sneaking: ev.sneaking,
1201        dimension: ev.dimension.as_str().to_owned(),
1202    }
1203});
1204
1205trampoline_phased!(
1206    trampoline_use_block,
1207    YogUseBlockEvent,
1208    UseBlockEvent,
1209    |ev| UseBlockEvent {
1210        player_name: ev.player.as_str().to_owned(),
1211        block_id: ev.block.as_str().to_owned(),
1212        pos: yog_core::BlockPos {
1213            x: ev.pos.x,
1214            y: ev.pos.y,
1215            z: ev.pos.z
1216        },
1217        dimension: ev.dimension.as_str().to_owned(),
1218    }
1219);
1220
1221trampoline_phased!(
1222    trampoline_attack_entity,
1223    YogAttackEntityEvent,
1224    AttackEntityEvent,
1225    |ev| AttackEntityEvent {
1226        player_name: ev.player.as_str().to_owned(),
1227        target_type: ev.target_type.as_str().to_owned(),
1228        target_uuid: ev.target_uuid.as_str().to_owned(),
1229        dimension: ev.dimension.as_str().to_owned(),
1230    }
1231);
1232
1233trampoline_phased!(
1234    trampoline_entity_damage,
1235    YogEntityDamageEvent,
1236    EntityDamageEvent,
1237    |ev| EntityDamageEvent {
1238        entity_type: ev.entity_type.as_str().to_owned(),
1239        uuid: ev.uuid.as_str().to_owned(),
1240        amount: ev.amount,
1241        source: ev.source.as_str().to_owned(),
1242        dimension: ev.dimension.as_str().to_owned(),
1243    }
1244);
1245
1246trampoline_phased!(
1247    trampoline_entity_death,
1248    YogEntityDeathEvent,
1249    EntityDeathEvent,
1250    |ev| EntityDeathEvent {
1251        entity_type: ev.entity_type.as_str().to_owned(),
1252        uuid: ev.uuid.as_str().to_owned(),
1253        source: ev.source.as_str().to_owned(),
1254        dimension: ev.dimension.as_str().to_owned(),
1255    }
1256);
1257
1258trampoline_phased!(
1259    trampoline_entity_spawn,
1260    YogEntitySpawnEvent,
1261    EntitySpawnEvent,
1262    |ev| EntitySpawnEvent {
1263        entity_type: ev.entity_type.as_str().to_owned(),
1264        uuid: ev.uuid.as_str().to_owned(),
1265        dimension: ev.dimension.as_str().to_owned(),
1266    }
1267);
1268
1269trampoline_phased!(
1270    trampoline_place_block,
1271    YogPlaceBlockEvent,
1272    PlaceBlockEvent,
1273    |ev| PlaceBlockEvent {
1274        player_name: ev.player.as_str().to_owned(),
1275        block_id: ev.block.as_str().to_owned(),
1276        pos: yog_core::BlockPos {
1277            x: ev.pos.x,
1278            y: ev.pos.y,
1279            z: ev.pos.z
1280        },
1281        dimension: ev.dimension.as_str().to_owned(),
1282    }
1283);
1284
1285trampoline_phased!(
1286    trampoline_player_death,
1287    YogPlayerDeathEvent,
1288    PlayerDeathEvent,
1289    |ev| PlayerDeathEvent {
1290        player_name: ev.player.as_str().to_owned(),
1291        uuid: ev.uuid.as_str().to_owned(),
1292        source: ev.source.as_str().to_owned(),
1293        dimension: ev.dimension.as_str().to_owned(),
1294    }
1295);
1296
1297trampoline_phased!(
1298    trampoline_player_respawn,
1299    YogPlayerRespawnEvent,
1300    PlayerRespawnEvent,
1301    |ev| PlayerRespawnEvent {
1302        player_name: ev.player.as_str().to_owned(),
1303        uuid: ev.uuid.as_str().to_owned(),
1304        at_anchor: ev.at_anchor,
1305        dimension: ev.dimension.as_str().to_owned(),
1306    }
1307);
1308
1309trampoline_phased!(
1310    trampoline_advancement,
1311    YogAdvancementEvent,
1312    AdvancementEvent,
1313    |ev| AdvancementEvent {
1314        player_name: ev.player.as_str().to_owned(),
1315        uuid: ev.uuid.as_str().to_owned(),
1316        advancement_id: ev.advancement.as_str().to_owned(),
1317        dimension: ev.dimension.as_str().to_owned(),
1318    }
1319);
1320
1321trampoline_phased!(
1322    trampoline_entity_interact,
1323    YogEntityInteractEvent,
1324    EntityInteractEvent,
1325    |ev| EntityInteractEvent {
1326        player_name: ev.player.as_str().to_owned(),
1327        player_uuid: ev.player_uuid.as_str().to_owned(),
1328        entity_type: ev.entity_type.as_str().to_owned(),
1329        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1330        hand: ev.hand.as_str().to_owned(),
1331        dimension: ev.dimension.as_str().to_owned(),
1332    }
1333);
1334
1335trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| {
1336    CraftEvent {
1337        player_name: ev.player.as_str().to_owned(),
1338        player_uuid: ev.player_uuid.as_str().to_owned(),
1339        result_item: ev.result_item.as_str().to_owned(),
1340        result_count: ev.result_count,
1341        dimension: ev.dimension.as_str().to_owned(),
1342    }
1343});
1344
1345trampoline_phased!(
1346    trampoline_explosion,
1347    YogExplosionEvent,
1348    ExplosionEvent,
1349    |ev| ExplosionEvent {
1350        dimension: ev.dimension.as_str().to_owned(),
1351        x: ev.x,
1352        y: ev.y,
1353        z: ev.z,
1354        power: ev.power,
1355        cause_uuid: ev.cause_uuid.as_str().to_owned(),
1356    }
1357);
1358
1359trampoline_phased!(
1360    trampoline_item_pickup,
1361    YogItemPickupEvent,
1362    ItemPickupEvent,
1363    |ev| ItemPickupEvent {
1364        player_name: ev.player.as_str().to_owned(),
1365        player_uuid: ev.player_uuid.as_str().to_owned(),
1366        item_id: ev.item_id.as_str().to_owned(),
1367        item_count: ev.item_count,
1368        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1369        dimension: ev.dimension.as_str().to_owned(),
1370    }
1371);
1372
1373trampoline_phased!(
1374    trampoline_player_move,
1375    YogPlayerMoveEvent,
1376    PlayerMoveEvent,
1377    |ev| PlayerMoveEvent {
1378        player_name: ev.player.as_str().to_owned(),
1379        player_uuid: ev.player_uuid.as_str().to_owned(),
1380        x: ev.x,
1381        y: ev.y,
1382        z: ev.z,
1383        yaw: ev.yaw,
1384        pitch: ev.pitch,
1385        dimension: ev.dimension.as_str().to_owned(),
1386    }
1387);
1388
1389trampoline_phased!(
1390    trampoline_container_open,
1391    YogContainerOpenEvent,
1392    ContainerOpenEvent,
1393    |ev| ContainerOpenEvent {
1394        player_name: ev.player.as_str().to_owned(),
1395        player_uuid: ev.player_uuid.as_str().to_owned(),
1396        container_type: ev.container_type.as_str().to_owned(),
1397        dimension: ev.dimension.as_str().to_owned(),
1398    }
1399);
1400
1401trampoline_phased!(
1402    trampoline_container_close,
1403    YogContainerCloseEvent,
1404    ContainerCloseEvent,
1405    |ev| ContainerCloseEvent {
1406        player_name: ev.player.as_str().to_owned(),
1407        player_uuid: ev.player_uuid.as_str().to_owned(),
1408        dimension: ev.dimension.as_str().to_owned(),
1409    }
1410);
1411
1412trampoline_phased!(
1413    trampoline_projectile_hit,
1414    YogProjectileHitEvent,
1415    ProjectileHitEvent,
1416    |ev| ProjectileHitEvent {
1417        projectile_type: ev.projectile_type.as_str().to_owned(),
1418        projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
1419        shooter_uuid: ev.shooter_uuid.as_str().to_owned(),
1420        hit_type: ev.hit_type.as_str().to_owned(),
1421        hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
1422        x: ev.x,
1423        y: ev.y,
1424        z: ev.z,
1425        dimension: ev.dimension.as_str().to_owned(),
1426    }
1427);
1428
1429unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
1430where
1431    F: Fn(&dyn Server) + Send + Sync,
1432{
1433    unsafe {
1434        let f = &*(ud as *const F);
1435        f(&CServer(srv));
1436    }
1437}
1438
1439unsafe extern "C" fn trampoline_packet<F>(
1440    ud: *mut c_void,
1441    srv: *const YogServer,
1442    ev: *const YogPacketEvent,
1443) where
1444    F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
1445{
1446    unsafe {
1447        let f = &*(ud as *const F);
1448        let ev = &*ev;
1449        let rust_ev = PacketEvent {
1450            channel: ev.channel.as_str().to_owned(),
1451            player: ev.player.as_str().to_owned(),
1452            payload: std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec(),
1453        };
1454        f(&rust_ev, &CServer(srv));
1455    }
1456}
1457
1458unsafe extern "C" fn trampoline_command<F>(
1459    ud: *mut c_void,
1460    srv: *const YogServer,
1461    ev: *const YogCommandEvent,
1462    reply_buf: *mut u8,
1463    reply_cap: u32,
1464    reply_len: *mut u32,
1465) where
1466    F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
1467{
1468    unsafe {
1469        let f = &*(ud as *const F);
1470        let ev = &*ev;
1471        let ctx = CommandContext {
1472            name: ev.name.as_str().to_owned(),
1473            args: ev.args.as_str().to_owned(),
1474            source: ev.source.as_str().to_owned(),
1475            uuid: ev.uuid.as_str().to_owned(),
1476            dimension: ev.dimension.as_str().to_owned(),
1477        };
1478        *reply_len = 0;
1479        if let Some(reply) = f(&ctx, &CServer(srv)) {
1480            let bytes = reply.as_bytes();
1481            let n = bytes.len().min(reply_cap as usize);
1482            std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
1483            *reply_len = n as u32;
1484        }
1485    }
1486}
1487
1488unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
1489where
1490    F: Fn(&dyn Server) + Send + Sync,
1491{
1492    unsafe {
1493        let f = &*(ud as *const F);
1494        f(&CServer(srv));
1495    }
1496}
1497
1498// ── ABI minor 10 — client-side trampolines ────────────────────────────────────
1499
1500unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
1501where
1502    F: Fn(&ClientTickEvent) + Send + Sync,
1503{
1504    unsafe {
1505        let f = &*(ud as *const F);
1506        f(&ClientTickEvent {});
1507    }
1508}
1509
1510unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1511where
1512    F: Fn(&GfxContext) + Send + Sync,
1513{
1514    unsafe {
1515        let f = &*(ud as *const F);
1516        f(&GfxContext::from_raw(gfx));
1517    }
1518}
1519
1520unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1521where
1522    F: Fn(&GfxContext) + Send + Sync,
1523{
1524    unsafe {
1525        let f = &*(ud as *const F);
1526        f(&GfxContext::from_raw(gfx));
1527    }
1528}
1529
1530unsafe extern "C" fn trampoline_key_press<F>(ud: *mut c_void, ev: *const YogKeyPressEvent) -> bool
1531where
1532    F: Fn(&KeyPressEvent) -> bool + Send + Sync,
1533{
1534    unsafe {
1535        let f = &*(ud as *const F);
1536        let e = &*ev;
1537        f(&KeyPressEvent {
1538            key_code: e.key_code,
1539            scan_code: e.scan_code,
1540            action: e.action,
1541            modifiers: e.modifiers,
1542        })
1543    }
1544}
1545
1546unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
1547where
1548    F: Fn(&ScreenEvent) + Send + Sync,
1549{
1550    unsafe {
1551        let f = &*(ud as *const F);
1552        f(&ScreenEvent {
1553            screen_class: screen_class.as_str().to_owned(),
1554        });
1555        true
1556    }
1557}
1558
1559// ── Registry ─────────────────────────────────────────────────────────────────
1560
1561/// Wraps the [`YogApi`] table and provides an ergonomic registration API.
1562///
1563/// Obtained inside `yog_mod_register` via `export_mod!`.  Closures registered
1564/// here are boxed and leaked — they live as long as the process (which is the
1565/// correct lifetime for a server mod).
1566/// Pointer to the runtime's `YogApi` table (a process-lifetime static in the
1567/// runtime), captured when the mod registers. Lets free functions like
1568/// [`installed_mods`] work outside of `register()` — e.g. in UI handlers.
1569static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
1570    std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
1571
1572/// The runtime's `YogApi` table, null before this mod registered.
1573pub(crate) fn global_api() -> *mut YogApi {
1574    GLOBAL_API.load(std::sync::atomic::Ordering::Acquire)
1575}
1576
1577/// Metadata of an installed mod, as reported by the loader.
1578#[derive(Debug, Clone)]
1579pub struct ModInfo {
1580    /// `"yog"` for .yog mods, `"platform"` for loader (Java) mods.
1581    pub source: String,
1582    pub id: String,
1583    pub name: String,
1584    pub version: String,
1585    /// Comma-separated author list (may be empty).
1586    pub authors: String,
1587    pub description: String,
1588}
1589
1590/// All installed mods known to the loader: .yog mods plus, where the host
1591/// exposes them, platform (Java) mods. Callable at any time after this mod's
1592/// `register()` ran — including client-side UI handlers. Note that during
1593/// `register()` mods that load after this one are not in the list yet; query
1594/// lazily (e.g. on first render) for a complete view.
1595pub fn installed_mods() -> Vec<ModInfo> {
1596    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1597    if api.is_null() {
1598        return Vec::new();
1599    }
1600    let owned = unsafe { ((*api).mods_list)((*api).ctx) };
1601    let Some(text) = take_owned(unsafe { (*api).free_str }, owned) else {
1602        return Vec::new();
1603    };
1604    text.lines()
1605        .filter_map(|line| {
1606            let mut f = line.split('\t');
1607            Some(ModInfo {
1608                source: f.next()?.to_string(),
1609                id: f.next()?.to_string(),
1610                name: f.next().unwrap_or_default().to_string(),
1611                version: f.next().unwrap_or_default().to_string(),
1612                authors: f.next().unwrap_or_default().to_string(),
1613                description: f.next().unwrap_or_default().to_string(),
1614            })
1615        })
1616        .collect()
1617}
1618
1619/// Open the Yog UI registered as `ui_id` (client side; no-op on dedicated
1620/// servers). `modal` blocks game input, `pause` pauses singleplayer. Callable
1621/// from any handler after registration — e.g. an `on_client_packet` handler.
1622pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
1623    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1624    if api.is_null() {
1625        return;
1626    }
1627    unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
1628}
1629
1630/// Close the Yog UI screen showing `ui_id` — e.g. from that screen's own
1631/// "Done" button. Does nothing when another screen is open. Deferred onto the
1632/// render thread, so the handler that calls it finishes first. Client side;
1633/// a no-op on dedicated servers.
1634pub fn close_ui(ui_id: &str) {
1635    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1636    if api.is_null() {
1637        return;
1638    }
1639    unsafe { ((*api).ui_close)((*api).ctx, YogStr::from_str(ui_id)) }
1640}
1641
1642/// What a [`Registry::register_ui`] handler returns: `()` (never marks
1643/// events as used) or `bool` (`true` = the handler used this `key:`/`char:`
1644/// event — the host then doesn't, e.g. Esc doesn't close the screen).
1645pub trait UiHandled {
1646    fn handled(self) -> bool;
1647}
1648
1649impl UiHandled for () {
1650    fn handled(self) -> bool {
1651        false
1652    }
1653}
1654
1655impl UiHandled for bool {
1656    fn handled(self) -> bool {
1657        self
1658    }
1659}
1660
1661/// Handle to the runtime's server-action table, usable from any handler after
1662/// registration — including client-side ones that don't receive `&dyn Server`
1663/// (UI event handlers, client tick). Server-world actions are no-ops client
1664/// side, but networking (`send_to_server`) and similar client-safe calls work.
1665pub fn server() -> Option<CServer> {
1666    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1667    if api.is_null() {
1668        return None;
1669    }
1670    let srv = unsafe { (*api).server };
1671    if srv.is_null() {
1672        return None;
1673    }
1674    Some(CServer(srv))
1675}
1676
1677pub struct Registry {
1678    api: *const YogApi,
1679}
1680
1681// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
1682unsafe impl Send for Registry {}
1683unsafe impl Sync for Registry {}
1684
1685impl Registry {
1686    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
1687    ///
1688    /// # Safety
1689    ///
1690    /// `api` must point to the runtime's `YogApi` table, valid for the rest of
1691    /// the process.
1692    pub unsafe fn from_raw(api: *const YogApi) -> Self {
1693        GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
1694        Self { api }
1695    }
1696
1697    #[inline]
1698    fn ctx(&self) -> *mut c_void {
1699        unsafe { (*self.api).ctx }
1700    }
1701
1702    // ── helpers ──────────────────────────────────────────────────────────────
1703
1704    fn leak<F: 'static>(f: F) -> *mut c_void {
1705        Box::into_raw(Box::new(f)) as *mut c_void
1706    }
1707
1708    // ── events ───────────────────────────────────────────────────────────────
1709    //
1710    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
1711    // In `Pre` phase, returning `false` cancels the action.
1712    // In `Post` phase, the return value is ignored.
1713    // A single registration fires for BOTH phases.
1714
1715    pub fn on_block_break<F>(&mut self, handler: F)
1716    where
1717        F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1718    {
1719        let ud = Self::leak(handler);
1720        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
1721    }
1722
1723    pub fn on_chat<F>(&mut self, handler: F)
1724    where
1725        F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1726    {
1727        let ud = Self::leak(handler);
1728        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
1729    }
1730
1731    pub fn on_player_join<F>(&mut self, handler: F)
1732    where
1733        F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1734    {
1735        let ud = Self::leak(handler);
1736        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
1737    }
1738
1739    pub fn on_player_leave<F>(&mut self, handler: F)
1740    where
1741        F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1742    {
1743        let ud = Self::leak(handler);
1744        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
1745    }
1746
1747    pub fn on_use_item<F>(&mut self, handler: F)
1748    where
1749        F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1750    {
1751        let ud = Self::leak(handler);
1752        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
1753    }
1754
1755    pub fn on_use_block<F>(&mut self, handler: F)
1756    where
1757        F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1758    {
1759        let ud = Self::leak(handler);
1760        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
1761    }
1762
1763    pub fn on_attack_entity<F>(&mut self, handler: F)
1764    where
1765        F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1766    {
1767        let ud = Self::leak(handler);
1768        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
1769    }
1770
1771    pub fn on_entity_damage<F>(&mut self, handler: F)
1772    where
1773        F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1774    {
1775        let ud = Self::leak(handler);
1776        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
1777    }
1778
1779    pub fn on_entity_death<F>(&mut self, handler: F)
1780    where
1781        F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1782    {
1783        let ud = Self::leak(handler);
1784        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
1785    }
1786
1787    pub fn on_entity_spawn<F>(&mut self, handler: F)
1788    where
1789        F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1790    {
1791        let ud = Self::leak(handler);
1792        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
1793    }
1794
1795    pub fn on_player_place_block<F>(&mut self, handler: F)
1796    where
1797        F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1798    {
1799        let ud = Self::leak(handler);
1800        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
1801    }
1802
1803    pub fn on_player_death<F>(&mut self, handler: F)
1804    where
1805        F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1806    {
1807        let ud = Self::leak(handler);
1808        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
1809    }
1810
1811    pub fn on_player_respawn<F>(&mut self, handler: F)
1812    where
1813        F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1814    {
1815        let ud = Self::leak(handler);
1816        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
1817    }
1818
1819    pub fn on_advancement<F>(&mut self, handler: F)
1820    where
1821        F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1822    {
1823        let ud = Self::leak(handler);
1824        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
1825    }
1826
1827    pub fn on_entity_interact<F>(&mut self, handler: F)
1828    where
1829        F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1830    {
1831        let ud = Self::leak(handler);
1832        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
1833    }
1834
1835    pub fn on_item_craft<F>(&mut self, handler: F)
1836    where
1837        F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1838    {
1839        let ud = Self::leak(handler);
1840        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
1841    }
1842
1843    pub fn on_explosion<F>(&mut self, handler: F)
1844    where
1845        F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1846    {
1847        let ud = Self::leak(handler);
1848        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
1849    }
1850
1851    pub fn on_item_pickup<F>(&mut self, handler: F)
1852    where
1853        F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1854    {
1855        let ud = Self::leak(handler);
1856        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
1857    }
1858
1859    pub fn on_player_move<F>(&mut self, handler: F)
1860    where
1861        F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1862    {
1863        let ud = Self::leak(handler);
1864        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
1865    }
1866
1867    pub fn on_container_open<F>(&mut self, handler: F)
1868    where
1869        F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1870    {
1871        let ud = Self::leak(handler);
1872        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
1873    }
1874
1875    pub fn on_container_close<F>(&mut self, handler: F)
1876    where
1877        F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1878    {
1879        let ud = Self::leak(handler);
1880        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
1881    }
1882
1883    pub fn on_projectile_hit<F>(&mut self, handler: F)
1884    where
1885        F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1886    {
1887        let ud = Self::leak(handler);
1888        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
1889    }
1890
1891    pub fn on_tick<F>(&mut self, listener: F)
1892    where
1893        F: Fn(&dyn Server) + Send + Sync + 'static,
1894    {
1895        let ud = Self::leak(listener);
1896        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
1897    }
1898
1899    pub fn on_server_started<F>(&mut self, listener: F)
1900    where
1901        F: Fn(&dyn Server) + Send + Sync + 'static,
1902    {
1903        let ud = Self::leak(listener);
1904        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
1905    }
1906
1907    pub fn on_server_stopping<F>(&mut self, listener: F)
1908    where
1909        F: Fn(&dyn Server) + Send + Sync + 'static,
1910    {
1911        let ud = Self::leak(listener);
1912        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
1913    }
1914
1915    // ── commands ─────────────────────────────────────────────────────────────
1916
1917    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
1918    where
1919        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
1920    {
1921        let name_ys = YogStr::from_str(name.as_ref());
1922        let ud = Self::leak(handler);
1923        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
1924    }
1925
1926    /// Register a command with Brigadier-typed arguments.
1927    ///
1928    /// `schema` is a space-separated list of argument types:
1929    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
1930    ///
1931    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
1932    pub fn on_typed_command<F>(
1933        &mut self,
1934        name: impl AsRef<str>,
1935        schema: impl AsRef<str>,
1936        handler: F,
1937    ) where
1938        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
1939    {
1940        let name_ys = YogStr::from_str(name.as_ref());
1941        let schema_ys = YogStr::from_str(schema.as_ref());
1942        let ud = Self::leak(handler);
1943        unsafe {
1944            ((*self.api).register_typed_command)(
1945                self.ctx(),
1946                name_ys,
1947                schema_ys,
1948                ud,
1949                trampoline_command::<F>,
1950            );
1951        }
1952    }
1953
1954    // ── networking ───────────────────────────────────────────────────────────
1955
1956    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1957    where
1958        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
1959    {
1960        let ch = YogStr::from_str(channel.as_ref());
1961        let ud = Self::leak(handler);
1962        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1963    }
1964
1965    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1966    where
1967        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
1968    {
1969        let ch = YogStr::from_str(channel.as_ref());
1970        let ud = Self::leak(handler);
1971        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1972    }
1973
1974    /// Register a typed-packet handler.
1975    ///
1976    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
1977    /// Malformed payloads are silently dropped.
1978    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
1979    where
1980        P: Packet + Send + Sync + 'static,
1981        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
1982    {
1983        self.on_packet(channel, move |ev, srv| {
1984            if let Some(pkt) = P::decode(&ev.payload) {
1985                handler(&pkt, srv);
1986            }
1987        });
1988    }
1989
1990    // ── recipes ──────────────────────────────────────────────────────────────
1991
1992    fn recipe(&mut self, ns: &str, name: &str, json: &str) {
1993        unsafe {
1994            ((*self.api).register_recipe_json)(
1995                self.ctx(),
1996                YogStr::from_str(ns),
1997                YogStr::from_str(name),
1998                YogStr::from_str(json),
1999            );
2000        }
2001    }
2002
2003    /// Register a shaped crafting recipe.
2004    pub fn add_shaped_recipe(&mut self, recipe: ShapedRecipe) {
2005        let (ns, name) = recipe.ns_name();
2006        self.recipe(ns, name, &recipe.to_json());
2007    }
2008
2009    /// Register a shapeless crafting recipe.
2010    pub fn add_shapeless_recipe(&mut self, recipe: ShapelessRecipe) {
2011        let (ns, name) = recipe.ns_name();
2012        self.recipe(ns, name, &recipe.to_json());
2013    }
2014
2015    /// Register a furnace smelting recipe.
2016    pub fn add_furnace_recipe(&mut self, recipe: FurnaceRecipe) {
2017        let (ns, name) = recipe.ns_name();
2018        self.recipe(ns, name, &recipe.to_json());
2019    }
2020
2021    // ── content ──────────────────────────────────────────────────────────────
2022
2023    pub fn register_item(&mut self, def: ItemDef) {
2024        // Build a C-compatible YogItemDef whose YogStr fields point into `def`'s
2025        // String storage.  We then call register_item which must copy the data
2026        // before returning (the runtime stores owned Strings).
2027        let food_nutrition = def.food.as_ref().map_or(0, |f| f.nutrition);
2028        let food_saturation = def.food.as_ref().map_or(0.0, |f| f.saturation);
2029        let food_always_eat = def.food.as_ref().is_some_and(|f| f.can_always_eat);
2030        let c = YogItemDef {
2031            id: YogStr::from_str(&def.id),
2032            max_stack: def.max_stack as u32,
2033            name: def
2034                .name
2035                .as_deref()
2036                .map(YogStr::from_str)
2037                .unwrap_or(YogStr::EMPTY),
2038            tooltip: def
2039                .tooltip
2040                .as_deref()
2041                .map(YogStr::from_str)
2042                .unwrap_or(YogStr::EMPTY),
2043            max_damage: def.max_damage,
2044            fire_resistant: def.fire_resistant,
2045            fuel_ticks: def.fuel_ticks,
2046            food_nutrition,
2047            food_saturation,
2048            food_always_eat,
2049        };
2050        unsafe { ((*self.api).register_item)(self.ctx(), &c) }
2051    }
2052
2053    pub fn register_block(&mut self, def: BlockDef) {
2054        let shape = def.shape.unwrap_or([0.0; 6]);
2055        let groups_joined = def.connect_groups.join(",");
2056        let c = YogBlockDef {
2057            id: YogStr::from_str(&def.id),
2058            hardness: def.hardness,
2059            resistance: def.resistance,
2060            name: def
2061                .name
2062                .as_deref()
2063                .map(YogStr::from_str)
2064                .unwrap_or(YogStr::EMPTY),
2065            light_level: def.light_level,
2066            sound: def
2067                .sound
2068                .as_deref()
2069                .map(YogStr::from_str)
2070                .unwrap_or(YogStr::EMPTY),
2071            requires_tool: def.requires_tool,
2072            no_collision: def.no_collision,
2073            slipperiness: def.slipperiness,
2074            shape,
2075            connects: def.connects,
2076            connect_groups: YogStr::from_str(&groups_joined),
2077            inventory_id: def
2078                .inventory_id
2079                .as_deref()
2080                .map(YogStr::from_str)
2081                .unwrap_or(YogStr::EMPTY),
2082        };
2083        unsafe { ((*self.api).register_block)(self.ctx(), &c) }
2084    }
2085
2086    /// Register a real Container/Menu inventory screen — see
2087    /// `yog_inventory::InventoryDef`. Attach it to a block via
2088    /// `BlockDef::inventory(id)`.
2089    pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
2090        let layout = yog_inventory::encode_layout(&def.resolved_layout());
2091        let c = YogInventoryDef {
2092            id: YogStr::from_str(&def.id),
2093            slot_count: def.slot_count as u32,
2094            layout: YogStr::from_str(&layout),
2095            include_player_inventory: def.include_player_inventory,
2096            player_inv_x: def.player_inv_offset.0,
2097            player_inv_y: def.player_inv_offset.1,
2098            background_texture: def
2099                .background_texture
2100                .as_deref()
2101                .map(YogStr::from_str)
2102                .unwrap_or(YogStr::EMPTY),
2103            title: YogStr::from_str(&def.title),
2104            background_w: def.background_size.map(|(w, _)| w).unwrap_or(0.0),
2105            background_h: def.background_size.map(|(_, h)| h).unwrap_or(0.0),
2106        };
2107        unsafe { ((*self.api).register_inventory)(self.ctx(), &c) }
2108    }
2109
2110    // ── startup grants ───────────────────────────────────────────────────────
2111
2112    /// Register a startup grant: items/books to give once when a player first joins.
2113    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
2114        let items_str = grant.items.join("|");
2115        let c = YogStartupGrantDef {
2116            id: YogStr::from_str(&grant.id),
2117            items: YogStr::from_str(&items_str),
2118            book: grant
2119                .book
2120                .as_deref()
2121                .map(YogStr::from_str)
2122                .unwrap_or(YogStr::EMPTY),
2123            command: grant
2124                .command
2125                .as_deref()
2126                .map(YogStr::from_str)
2127                .unwrap_or(YogStr::EMPTY),
2128        };
2129        unsafe { ((*self.api).register_startup_grant)(self.ctx(), &c) }
2130    }
2131
2132    /// Register a book with its JSON-serialized content.
2133    pub fn register_book(&mut self, book: &Book) {
2134        let json = book.to_json();
2135        let id = YogStr::from_str(&book.id);
2136        let j = YogStr::from_str(&json);
2137        unsafe { ((*self.api).register_book)(self.ctx(), id, j) }
2138    }
2139
2140    /// Register (or patch) a dimension's *type* (sky, lighting, physics, ...).
2141    ///
2142    /// `def.id` need not be a new custom id — pass a vanilla id
2143    /// (`"minecraft:overworld"`, `"minecraft:the_nether"`,
2144    /// `"minecraft:the_end"`) to patch that dimension's type/generator
2145    /// instead of creating a new one (e.g. to replace the overworld's
2146    /// terrain with your own generator type).
2147    ///
2148    /// Must be called from `register()` — the same mod-init window as
2149    /// blocks/items — so the host can declare/patch the dimension before
2150    /// Minecraft's registries freeze. See the `yog-dimensions` crate docs;
2151    /// set `def.generator_type` (via [`YogDimensionDef::generator_type`]) to
2152    /// pick a generator type registered with
2153    /// [`Registry::register_chunk_generator`].
2154    pub fn register_dimension(&mut self, def: &YogDimensionDef) {
2155        let json = def.to_json();
2156        let id = YogStr::from_str(&def.id);
2157        let j = YogStr::from_str(&json);
2158        unsafe { ((*self.api).register_dimension_json)(self.ctx(), id, j) }
2159    }
2160
2161    /// Register a named, reusable chunk-generator type under
2162    /// `generator_type_id` (e.g. `"mymod:custom_terrain"`) — analogous to
2163    /// how Biomes O'Plenty registers selectable world-type presets. Any
2164    /// dimension (custom or vanilla) can opt into this generator via
2165    /// [`YogDimensionDef::generator_type`]; the platform host may also list
2166    /// registered generator type ids as options on the world-creation screen.
2167    ///
2168    /// `generator` is called once per chunk column that needs generating in
2169    /// any dimension using this type — write whatever terrain logic you want
2170    /// using the [`ChunkWriter`] handle (combine as many noise layers as you
2171    /// like; this crate doesn't prescribe a generation style).
2172    pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
2173    where
2174        F: Fn(&ChunkWriter) + Send + Sync + 'static,
2175    {
2176        let ud = Self::leak(generator);
2177        let id = YogStr::from_str(generator_type_id);
2178        unsafe {
2179            ((*self.api).register_chunk_generator)(
2180                self.ctx(),
2181                id,
2182                ud,
2183                trampoline_chunk_generator::<F>,
2184            );
2185        }
2186    }
2187
2188    /// Register a menu entry — the host renders a button on vanilla screens
2189    /// (TitleScreen on Fabric, ModListScreen on Forge/NeoForge) that opens `ui_id`.
2190    /// `label` is the human-readable button text (e.g. "Yog Mods").
2191    /// `ui_id` is the UI to open when clicked (e.g. "example:menu").
2192    /// See [`installed_mods`]. During `register()` the list only contains
2193    /// mods loaded before this one.
2194    pub fn installed_mods(&self) -> Vec<ModInfo> {
2195        installed_mods()
2196    }
2197
2198    /// Access the inter-mod communication layer.
2199    ///
2200    /// Allows exporting function pointers from this mod and importing
2201    /// function pointers from other mods.
2202    pub fn interop(&self) -> crate::Interop {
2203        crate::Interop::new(self.api)
2204    }
2205
2206    /// Raw `YogApi` pointer — for passing to interop-imported functions
2207    /// that register content through the C ABI table directly.
2208    pub fn raw_api(&self) -> *const yog_abi::YogApi {
2209        self.api
2210    }
2211
2212    pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
2213        let l = YogStr::from_str(label);
2214        let u = YogStr::from_str(ui_id);
2215        unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
2216    }
2217
2218    /// Register a UI tree with an event callback.
2219    /// `ui_id` is the unique identifier (e.g. "mymod:menu").
2220    /// `handler` receives `(ui_id, event)` for the screen's input: `click:X:Y`,
2221    /// `drag:X:Y`, `release:X:Y`, `scroll:DY`, `key:KEY:MODS` (GLFW key code
2222    /// and modifier bits, on press and repeat) and `char:CODEPOINT` (a typed
2223    /// character). It returns `()` or a `bool`: `true` marks a `key:`/`char:`
2224    /// event as used, so the host skips its default handling of the key
2225    /// (Esc closing the screen, ...) — see [`UiHandled`].
2226    pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
2227    where
2228        F: Fn(&str, &str) -> R + Send + Sync + 'static,
2229        R: UiHandled,
2230    {
2231        let ud = Self::leak(handler);
2232        let id = YogStr::from_str(ui_id);
2233        let empty = YogStr::from_str("{}");
2234        unsafe { ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F, R>) }
2235    }
2236
2237    // ── scheduler ────────────────────────────────────────────────────────────
2238
2239    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
2240    where
2241        F: Fn(&dyn Server) + Send + Sync + 'static,
2242    {
2243        let ud = Self::leak(handler);
2244        unsafe {
2245            ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>);
2246        }
2247    }
2248
2249    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
2250    where
2251        F: Fn(&dyn Server) + Send + Sync + 'static,
2252    {
2253        let ud = Self::leak(handler);
2254        unsafe {
2255            ((*self.api).schedule_repeating)(
2256                self.ctx(),
2257                period_ticks,
2258                ud,
2259                trampoline_scheduled::<F>,
2260            );
2261        }
2262    }
2263
2264    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
2265
2266    /// Register a handler called every client tick (render thread, no server).
2267    pub fn on_client_tick<F>(&mut self, handler: F)
2268    where
2269        F: Fn(&ClientTickEvent) + Send + Sync + 'static,
2270    {
2271        let ud = Self::leak(handler);
2272        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
2273    }
2274
2275    /// Register a render callback for a specific UI screen (`ui_id`).
2276    ///
2277    /// Called from `YogUIScreen.render()` — i.e. AFTER `renderBackground()` darkens
2278    /// the screen — so your UI draws on top of the dimmed world view.
2279    /// Use this instead of `on_hud_render` for book/inventory screens.
2280    ///
2281    /// Clicks are forwarded as `"click:X:Y"` via the `register_ui` handler so you
2282    /// can do hit-testing on your own stored layout.
2283    pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
2284    where
2285        F: Fn(&GfxContext) + Send + Sync + 'static,
2286    {
2287        let ud = Self::leak(handler);
2288        let id = YogStr::from_str(ui_id);
2289        unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
2290    }
2291
2292    /// Register a handler called every frame when the HUD is rendered.
2293    ///
2294    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
2295    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
2296    pub fn on_hud_render<F>(&mut self, handler: F)
2297    where
2298        F: Fn(&GfxContext) + Send + Sync + 'static,
2299    {
2300        let ud = Self::leak(handler);
2301        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
2302    }
2303
2304    /// Register a handler called every frame at the end of world rendering.
2305    ///
2306    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
2307    /// `gfx.camera_pos()` is the camera world position.
2308    /// To render at world position P, translate by `P - camera_pos` before drawing.
2309    pub fn on_world_render<F>(&mut self, handler: F)
2310    where
2311        F: Fn(&GfxContext) + Send + Sync + 'static,
2312    {
2313        let ud = Self::leak(handler);
2314        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
2315    }
2316
2317    /// Register a handler for keyboard input (client-side).
2318    /// Return `false` to prevent Minecraft from processing the key.
2319    pub fn on_key_press<F>(&mut self, handler: F)
2320    where
2321        F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static,
2322    {
2323        let ud = Self::leak(handler);
2324        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
2325    }
2326
2327    /// Register a handler called when a GUI screen is opened.
2328    pub fn on_screen_open<F>(&mut self, handler: F)
2329    where
2330        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2331    {
2332        let ud = Self::leak(handler);
2333        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
2334    }
2335
2336    /// Register a handler called when a GUI screen is closed.
2337    pub fn on_screen_close<F>(&mut self, handler: F)
2338    where
2339        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2340    {
2341        let ud = Self::leak(handler);
2342        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
2343    }
2344}
2345
2346unsafe extern "C" fn trampoline_ui_event<F, R>(
2347    ud: *mut c_void,
2348    ui_id: yog_abi::YogStr,
2349    event_id: yog_abi::YogStr,
2350) where
2351    F: Fn(&str, &str) -> R + Send + Sync + 'static,
2352    R: UiHandled,
2353{
2354    let f = unsafe { &*(ud as *const F) };
2355    let ui = unsafe { ui_id.as_str() };
2356    let ev = unsafe { event_id.as_str() };
2357    if f(ui, ev).handled() {
2358        let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2359        if !api.is_null() {
2360            unsafe { ((*api).ui_consume_event)((*api).ctx) }
2361        }
2362    }
2363}
2364
2365unsafe extern "C" fn trampoline_chunk_generator<F>(
2366    ud: *mut c_void,
2367    writer: *const yog_abi::YogChunkWriterApi,
2368) where
2369    F: Fn(&ChunkWriter) + Send + Sync + 'static,
2370{
2371    let f = unsafe { &*(ud as *const F) };
2372    let writer = unsafe { ChunkWriter::from_raw(writer) };
2373    f(&writer);
2374}
2375
2376// ── Mod trait ─────────────────────────────────────────────────────────────────
2377
2378/// Implemented by every Yog mod. Called once at startup to register handlers.
2379pub trait Mod {
2380    fn register(registry: &mut Registry);
2381}