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, String)> {
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 mut it = text.splitn(3, '\t');
580        let item_id = it.next()?.to_owned();
581        let count = it.next()?.parse().ok()?;
582        let snbt = it.next().unwrap_or("{}").to_owned();
583        Some((item_id, count, snbt))
584    }
585
586    fn set_inventory_slot(
587        &self,
588        dimension: &str,
589        pos: yog_core::BlockPos,
590        slot: u32,
591        item_id: &str,
592        count: u32,
593        snbt: &str,
594    ) -> bool {
595        // The ABI's item id carries the SNBT after a tab.
596        let item = if snbt.is_empty() { item_id.to_string() } else { format!("{item_id}\t{snbt}") };
597        let s = srv!(self);
598        unsafe {
599            (s.set_inventory_slot)(
600                s.ctx,
601                YogStr::from_str(dimension),
602                yog_abi::YogBlockPos {
603                    x: pos.x,
604                    y: pos.y,
605                    z: pos.z,
606                },
607                slot,
608                YogStr::from_str(&item),
609                count,
610            )
611        }
612    }
613
614    fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
615        let s = srv!(self);
616        let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
617        let Some(text) = take_owned(s.free_str, owned) else {
618            return Vec::new();
619        };
620        text.lines()
621            .filter_map(|line| {
622                let mut it = line.split('\t');
623                let slot: u32 = it.next()?.parse().ok()?;
624                let item_id = it.next()?.to_owned();
625                let count: u32 = it.next()?.parse().ok()?;
626                Some((slot, item_id, count))
627            })
628            .collect()
629    }
630
631    fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
632        let s = srv!(self);
633        unsafe {
634            (s.player_set_slot)(
635                s.ctx,
636                YogStr::from_str(player),
637                slot,
638                YogStr::from_str(item_id),
639                count,
640            )
641        }
642    }
643
644    fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
645        let s = srv!(self);
646        unsafe {
647            (s.player_teleport_dim)(
648                s.ctx,
649                YogStr::from_str(player),
650                YogStr::from_str(dimension),
651                yog_abi::YogVec3 { x, y, z },
652            )
653        }
654    }
655
656    fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
657        let s = srv!(self);
658        unsafe {
659            (s.entity_teleport_dim)(
660                s.ctx,
661                YogStr::from_str(uuid),
662                YogStr::from_str(dimension),
663                yog_abi::YogVec3 { x, y, z },
664            )
665        }
666    }
667
668    fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
669        let s = srv!(self);
670        unsafe {
671            (s.world_entity_count)(
672                s.ctx,
673                YogStr::from_str(dimension),
674                YogStr::from_str(entity_type),
675            )
676        }
677    }
678
679    fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
680        let s = srv!(self);
681        let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
682
683        take_owned(s.free_str, owned)
684    }
685
686    fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
687        let s = srv!(self);
688        unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
689    }
690
691    fn spawn_particles(
692        &self,
693        dimension: &str,
694        x: f64,
695        y: f64,
696        z: f64,
697        particle_type: &str,
698        count: i32,
699        dx: f64,
700        dy: f64,
701        dz: f64,
702        speed: f64,
703    ) -> bool {
704        let s = srv!(self);
705        unsafe {
706            (s.spawn_particles)(
707                s.ctx,
708                YogStr::from_str(dimension),
709                yog_abi::YogVec3 { x, y, z },
710                YogStr::from_str(particle_type),
711                count,
712                dx,
713                dy,
714                dz,
715                speed,
716            )
717        }
718    }
719
720    fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
721        let s = srv!(self);
722        let v = unsafe {
723            (s.entity_attribute_get)(
724                s.ctx,
725                YogStr::from_str(uuid),
726                YogStr::from_str(attribute_id),
727            )
728        };
729        if v.is_nan() { None } else { Some(v) }
730    }
731
732    fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
733        let s = srv!(self);
734        unsafe {
735            (s.entity_attribute_set)(
736                s.ctx,
737                YogStr::from_str(uuid),
738                YogStr::from_str(attribute_id),
739                value,
740            )
741        }
742    }
743
744    fn get_held_item_nbt(&self, player: &str) -> Option<String> {
745        let s = srv!(self);
746        let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
747
748        take_owned(s.free_str, owned)
749    }
750
751    fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
752        let s = srv!(self);
753        unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
754    }
755
756    fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
757        let s = srv!(self);
758        let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
759
760        take_owned(s.free_str, owned)
761    }
762
763    fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
764        let s = srv!(self);
765        unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
766    }
767
768    fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
769        let s = srv!(self);
770        let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
771        let text = take_owned(s.free_str, owned)?;
772        let mut it = text.splitn(3, '\t');
773        let item_id = it.next()?.to_owned();
774        let count: u32 = it.next()?.parse().ok()?;
775        let nbt = it.next().unwrap_or("{}").to_owned();
776        Some((item_id, count, nbt))
777    }
778
779    fn set_slot_item(
780        &self,
781        player: &str,
782        slot: u32,
783        item_id: &str,
784        count: u32,
785        snbt: &str,
786    ) -> bool {
787        let s = srv!(self);
788        unsafe {
789            (s.set_slot_item)(
790                s.ctx,
791                YogStr::from_str(player),
792                slot,
793                YogStr::from_str(item_id),
794                count,
795                YogStr::from_str(snbt),
796            )
797        }
798    }
799
800    // ── world queries & effects (ABI minor 1) ────────────────────────────────
801
802    fn entity_info(&self, uuid: &str) -> Option<yog_core::EntityInfo> {
803        let s = srv!(self);
804        let owned = unsafe { (s.entity_info)(s.ctx, YogStr::from_str(uuid)) };
805        take_owned(s.free_str, owned).and_then(|l| yog_core::EntityInfo::decode(&l))
806    }
807
808    fn entities_in_box(
809        &self,
810        dimension: &str,
811        min: (f64, f64, f64),
812        max: (f64, f64, f64),
813    ) -> Vec<String> {
814        let s = srv!(self);
815        let owned =
816            unsafe { (s.entities_in_box)(s.ctx, YogStr::from_str(dimension), v3(min), v3(max)) };
817        take_owned(s.free_str, owned)
818            .map(|t| {
819                t.lines()
820                    .filter(|l| !l.is_empty())
821                    .map(str::to_owned)
822                    .collect()
823            })
824            .unwrap_or_default()
825    }
826
827    fn raycast_block(
828        &self,
829        dimension: &str,
830        from: (f64, f64, f64),
831        to: (f64, f64, f64),
832    ) -> Option<yog_core::BlockHit> {
833        let s = srv!(self);
834        let owned =
835            unsafe { (s.raycast_block)(s.ctx, YogStr::from_str(dimension), v3(from), v3(to)) };
836        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockHit::decode(&l))
837    }
838
839    fn raycast_entity(
840        &self,
841        dimension: &str,
842        from: (f64, f64, f64),
843        to: (f64, f64, f64),
844        exclude: &str,
845    ) -> Option<String> {
846        let s = srv!(self);
847        let owned = unsafe {
848            (s.raycast_entity)(
849                s.ctx,
850                YogStr::from_str(dimension),
851                v3(from),
852                v3(to),
853                YogStr::from_str(exclude),
854            )
855        };
856        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
857    }
858
859    fn block_info(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<yog_core::BlockInfo> {
860        let s = srv!(self);
861        let owned = unsafe { (s.block_info)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
862        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockInfo::decode(&l))
863    }
864
865    fn break_block(
866        &self,
867        dimension: &str,
868        pos: yog_core::BlockPos,
869        drop: bool,
870        breaker: &str,
871    ) -> bool {
872        let s = srv!(self);
873        unsafe {
874            (s.break_block)(
875                s.ctx,
876                YogStr::from_str(dimension),
877                bp(pos),
878                drop,
879                YogStr::from_str(breaker),
880            )
881        }
882    }
883
884    fn place_block_from_slot(
885        &self,
886        player: &str,
887        slot: u32,
888        dimension: &str,
889        pos: yog_core::BlockPos,
890    ) -> bool {
891        let s = srv!(self);
892        unsafe {
893            (s.place_block_from_slot)(
894                s.ctx,
895                YogStr::from_str(player),
896                slot,
897                YogStr::from_str(dimension),
898                bp(pos),
899            )
900        }
901    }
902
903    fn bonemeal(&self, dimension: &str, pos: yog_core::BlockPos) -> bool {
904        let s = srv!(self);
905        unsafe { (s.bonemeal)(s.ctx, YogStr::from_str(dimension), bp(pos)) }
906    }
907
908    fn explode(
909        &self,
910        dimension: &str,
911        x: f64,
912        y: f64,
913        z: f64,
914        power: f32,
915        fire: bool,
916        source: &str,
917    ) -> bool {
918        let s = srv!(self);
919        unsafe {
920            (s.explode)(
921                s.ctx,
922                YogStr::from_str(dimension),
923                v3((x, y, z)),
924                power,
925                fire,
926                YogStr::from_str(source),
927            )
928        }
929    }
930
931    fn tellraw(&self, player: &str, json: &str) -> bool {
932        let s = srv!(self);
933        unsafe { (s.tellraw)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
934    }
935
936    fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool {
937        let s = srv!(self);
938        unsafe {
939            (s.entity_damage)(
940                s.ctx,
941                YogStr::from_str(uuid),
942                amount,
943                YogStr::from_str(damage_type),
944                YogStr::from_str(attacker),
945            )
946        }
947    }
948
949    fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool {
950        let s = srv!(self);
951        unsafe { (s.entity_set_fire_ticks)(s.ctx, YogStr::from_str(uuid), ticks) }
952    }
953
954    fn player_add_xp(&self, player: &str, amount: i32) -> bool {
955        let s = srv!(self);
956        unsafe { (s.player_add_xp)(s.ctx, YogStr::from_str(player), amount) }
957    }
958
959    fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool {
960        let s = srv!(self);
961        unsafe { (s.player_set_may_fly)(s.ctx, YogStr::from_str(player), may_fly) }
962    }
963
964    fn player_info(&self, player: &str) -> Option<yog_core::PlayerInfo> {
965        let s = srv!(self);
966        let owned = unsafe { (s.player_info)(s.ctx, YogStr::from_str(player)) };
967        take_owned(s.free_str, owned).and_then(|l| yog_core::PlayerInfo::decode(&l))
968    }
969
970    fn spawn_item(
971        &self,
972        dimension: &str,
973        pos: (f64, f64, f64),
974        vel: (f64, f64, f64),
975        item_id: &str,
976        count: u32,
977        snbt: &str,
978    ) -> Option<String> {
979        let s = srv!(self);
980        let owned = unsafe {
981            (s.spawn_item)(
982                s.ctx,
983                YogStr::from_str(dimension),
984                v3(pos),
985                v3(vel),
986                YogStr::from_str(item_id),
987                count,
988                YogStr::from_str(snbt),
989            )
990        };
991        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
992    }
993
994    fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)> {
995        let s = srv!(self);
996        let owned = unsafe { (s.item_entity_stack)(s.ctx, YogStr::from_str(uuid)) };
997        let text = take_owned(s.free_str, owned)?;
998        let mut it = text.splitn(3, '\t');
999        let item_id = it.next()?.to_owned();
1000        let count = it.next()?.parse().ok()?;
1001        let nbt = it.next().unwrap_or("{}").to_owned();
1002        Some((item_id, count, nbt))
1003    }
1004
1005    fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool {
1006        let s = srv!(self);
1007        unsafe {
1008            (s.item_entity_set_stack)(
1009                s.ctx,
1010                YogStr::from_str(uuid),
1011                YogStr::from_str(item_id),
1012                count,
1013                YogStr::from_str(snbt),
1014            )
1015        }
1016    }
1017
1018    fn actionbar_json(&self, player: &str, json: &str) -> bool {
1019        let s = srv!(self);
1020        unsafe { (s.actionbar_json)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
1021    }
1022
1023    fn use_item_on_block(
1024        &self,
1025        dimension: &str,
1026        pos: yog_core::BlockPos,
1027        face: yog_core::Face,
1028        item_id: &str,
1029        player: &str,
1030    ) -> bool {
1031        let s = srv!(self);
1032        unsafe {
1033            (s.use_item_on_block)(
1034                s.ctx,
1035                YogStr::from_str(dimension),
1036                bp(pos),
1037                face.index(),
1038                YogStr::from_str(item_id),
1039                YogStr::from_str(player),
1040            )
1041        }
1042    }
1043
1044    fn pour_fluid(&self, dimension: &str, pos: yog_core::BlockPos, bucket_item_id: &str) -> bool {
1045        let s = srv!(self);
1046        unsafe {
1047            (s.pour_fluid)(
1048                s.ctx,
1049                YogStr::from_str(dimension),
1050                bp(pos),
1051                YogStr::from_str(bucket_item_id),
1052            )
1053        }
1054    }
1055
1056    fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool {
1057        let s = srv!(self);
1058        unsafe {
1059            (s.has_entity_tag)(
1060                s.ctx,
1061                YogStr::from_str(entity_type),
1062                YogStr::from_str(tag_id),
1063            )
1064        }
1065    }
1066
1067    fn item_block(&self, item_id: &str) -> Option<String> {
1068        let s = srv!(self);
1069        let owned = unsafe { (s.item_block)(s.ctx, YogStr::from_str(item_id)) };
1070        take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1071    }
1072
1073    fn place_feature(&self, dimension: &str, pos: yog_core::BlockPos, feature_id: &str) -> bool {
1074        let s = srv!(self);
1075        unsafe {
1076            (s.place_feature)(
1077                s.ctx,
1078                YogStr::from_str(dimension),
1079                bp(pos),
1080                YogStr::from_str(feature_id),
1081            )
1082        }
1083    }
1084
1085    fn set_item_cooldown(&self, player: &str, item_id: &str, ticks: u32) -> bool {
1086        let s = srv!(self);
1087        unsafe { (s.set_item_cooldown)(s.ctx, YogStr::from_str(player), YogStr::from_str(item_id), ticks.min(i32::MAX as u32) as i32) }
1088    }
1089
1090    fn entity_teleport_with_passengers(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
1091        let s = srv!(self);
1092        unsafe {
1093            (s.entity_teleport_with_passengers)(
1094                s.ctx,
1095                YogStr::from_str(uuid),
1096                YogStr::from_str(dimension),
1097                yog_abi::YogVec3 { x, y, z },
1098            )
1099        }
1100    }
1101
1102    fn redstone_power(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
1103        let s = srv!(self);
1104        let p = unsafe { (s.redstone_power)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1105        (0..=15).contains(&p).then_some(p as u8)
1106    }
1107
1108    fn set_redstone_output(&self, dimension: &str, pos: yog_core::BlockPos, level: u8) -> bool {
1109        let s = srv!(self);
1110        unsafe { (s.set_redstone_output)(s.ctx, YogStr::from_str(dimension), bp(pos), level.min(15) as i32) }
1111    }
1112
1113    fn get_block_state(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
1114        let s = srv!(self);
1115        let owned = unsafe { (s.get_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1116        take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1117    }
1118
1119    fn set_block_state(&self, dimension: &str, pos: yog_core::BlockPos, state: &str) -> bool {
1120        let s = srv!(self);
1121        unsafe { (s.set_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos), YogStr::from_str(state)) }
1122    }
1123
1124    fn world_is_thundering(&self, dimension: &str) -> bool {
1125        let s = srv!(self);
1126        unsafe { (s.world_is_thundering)(s.ctx, YogStr::from_str(dimension)) }
1127    }
1128
1129    fn world_set_thunder(&self, dimension: &str, thundering: bool, duration_ticks: i32) -> bool {
1130        let s = srv!(self);
1131        unsafe { (s.world_set_thunder)(s.ctx, YogStr::from_str(dimension), thundering, duration_ticks) }
1132    }
1133
1134    fn fill_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos, state: &str) -> Option<u32> {
1135        let s = srv!(self);
1136        let n = unsafe { (s.fill_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to), YogStr::from_str(state)) };
1137        u32::try_from(n).ok()
1138    }
1139
1140    fn scan_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos) -> Vec<(yog_core::BlockPos, String)> {
1141        let s = srv!(self);
1142        let owned = unsafe { (s.scan_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to)) };
1143        let Some(text) = take_owned(s.free_str, owned) else {
1144            return Vec::new();
1145        };
1146        text.lines()
1147            .filter_map(|line| {
1148                let mut it = line.splitn(4, ' ');
1149                let (x, y, z) = (it.next()?.parse().ok()?, it.next()?.parse().ok()?, it.next()?.parse().ok()?);
1150                Some((yog_core::BlockPos { x, y, z }, it.next()?.to_string()))
1151            })
1152            .collect()
1153    }
1154}
1155
1156/// Copy a runtime-owned string out and free it; `None` for NONE.
1157pub(crate) fn take_owned(
1158    free_str: unsafe extern "C" fn(ptr: *mut u8, len: u32),
1159    owned: yog_abi::YogOwnedStr,
1160) -> Option<String> {
1161    if owned.is_none() {
1162        return None;
1163    }
1164    // SAFETY: a non-NONE owned string is `len` valid bytes until freed.
1165    let text = unsafe {
1166        String::from_utf8_lossy(std::slice::from_raw_parts(owned.ptr, owned.len as usize))
1167            .into_owned()
1168    };
1169    unsafe { free_str(owned.ptr, owned.len) };
1170    Some(text)
1171}
1172
1173fn v3((x, y, z): (f64, f64, f64)) -> yog_abi::YogVec3 {
1174    yog_abi::YogVec3 { x, y, z }
1175}
1176
1177fn bp(pos: yog_core::BlockPos) -> yog_abi::YogBlockPos {
1178    yog_abi::YogBlockPos {
1179        x: pos.x,
1180        y: pos.y,
1181        z: pos.z,
1182    }
1183}
1184
1185// ── Trampoline helpers ────────────────────────────────────────────────────────
1186//
1187// Each trampoline is a `unsafe extern "C" fn` that:
1188//   1. Casts `ud` back to the original boxed closure.
1189//   2. Converts ABI C structs to Rust event types.
1190//   3. Builds a `CServer` from the `*const YogServer`.
1191//   4. Calls the closure.
1192//
1193// Closures are Box::into_raw'd in Registry methods — they are INTENTIONALLY
1194// leaked and live for the process lifetime.
1195
1196// ── Trampoline helpers ────────────────────────────────────────────────────────
1197//
1198// All phased event trampolines share the same pattern:
1199//   `phase: u8`  0 = EventPhase::Pre, 1 = EventPhase::Post
1200//   Return value is meaningful only in Pre phase.
1201
1202macro_rules! trampoline_phased {
1203    ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr_2021) => {
1204        unsafe extern "C" fn $name<F>(
1205            ud: *mut c_void,
1206            srv: *const YogServer,
1207            ev: *const $abi_ev,
1208            phase: u8,
1209        ) -> bool
1210        where
1211            F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
1212        {
1213            unsafe {
1214                let f = &*(ud as *const F);
1215                let $ev = &*ev;
1216                let rust_ev = $build;
1217                let p = if phase == 0 {
1218                    EventPhase::Pre
1219                } else {
1220                    EventPhase::Post
1221                };
1222                f(&rust_ev, p, &CServer(srv))
1223            }
1224        }
1225    };
1226}
1227
1228trampoline_phased!(
1229    trampoline_block_break,
1230    YogBlockBreakEvent,
1231    BlockBreakEvent,
1232    |ev| BlockBreakEvent {
1233        player_name: ev.player.as_str().to_owned(),
1234        block_id: ev.block.as_str().to_owned(),
1235        pos: yog_core::BlockPos {
1236            x: ev.pos.x,
1237            y: ev.pos.y,
1238            z: ev.pos.z
1239        },
1240        dimension: ev.dimension.as_str().to_owned(),
1241    }
1242);
1243
1244trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
1245    player_name: ev.player.as_str().to_owned(),
1246    message: ev.message.as_str().to_owned(),
1247    dimension: ev.dimension.as_str().to_owned(),
1248});
1249
1250trampoline_phased!(
1251    trampoline_player_join,
1252    YogPlayerEvent,
1253    PlayerJoinEvent,
1254    |ev| PlayerJoinEvent {
1255        player_name: ev.player.as_str().to_owned(),
1256        uuid: ev.uuid.as_str().to_owned(),
1257        dimension: ev.dimension.as_str().to_owned(),
1258    }
1259);
1260
1261trampoline_phased!(
1262    trampoline_player_leave,
1263    YogPlayerEvent,
1264    PlayerLeaveEvent,
1265    |ev| PlayerLeaveEvent {
1266        player_name: ev.player.as_str().to_owned(),
1267        uuid: ev.uuid.as_str().to_owned(),
1268        dimension: ev.dimension.as_str().to_owned(),
1269    }
1270);
1271
1272trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| {
1273    UseItemEvent {
1274        player_name: ev.player.as_str().to_owned(),
1275        item_id: ev.item.as_str().to_owned(),
1276        sneaking: ev.sneaking,
1277        dimension: ev.dimension.as_str().to_owned(),
1278    }
1279});
1280
1281trampoline_phased!(
1282    trampoline_use_block,
1283    YogUseBlockEvent,
1284    UseBlockEvent,
1285    |ev| UseBlockEvent {
1286        player_name: ev.player.as_str().to_owned(),
1287        block_id: ev.block.as_str().to_owned(),
1288        pos: yog_core::BlockPos {
1289            x: ev.pos.x,
1290            y: ev.pos.y,
1291            z: ev.pos.z
1292        },
1293        dimension: ev.dimension.as_str().to_owned(),
1294    }
1295);
1296
1297trampoline_phased!(
1298    trampoline_attack_entity,
1299    YogAttackEntityEvent,
1300    AttackEntityEvent,
1301    |ev| AttackEntityEvent {
1302        player_name: ev.player.as_str().to_owned(),
1303        target_type: ev.target_type.as_str().to_owned(),
1304        target_uuid: ev.target_uuid.as_str().to_owned(),
1305        dimension: ev.dimension.as_str().to_owned(),
1306    }
1307);
1308
1309trampoline_phased!(
1310    trampoline_entity_damage,
1311    YogEntityDamageEvent,
1312    EntityDamageEvent,
1313    |ev| EntityDamageEvent {
1314        entity_type: ev.entity_type.as_str().to_owned(),
1315        uuid: ev.uuid.as_str().to_owned(),
1316        amount: ev.amount,
1317        source: ev.source.as_str().to_owned(),
1318        dimension: ev.dimension.as_str().to_owned(),
1319    }
1320);
1321
1322trampoline_phased!(
1323    trampoline_entity_death,
1324    YogEntityDeathEvent,
1325    EntityDeathEvent,
1326    |ev| EntityDeathEvent {
1327        entity_type: ev.entity_type.as_str().to_owned(),
1328        uuid: ev.uuid.as_str().to_owned(),
1329        source: ev.source.as_str().to_owned(),
1330        dimension: ev.dimension.as_str().to_owned(),
1331    }
1332);
1333
1334trampoline_phased!(
1335    trampoline_entity_spawn,
1336    YogEntitySpawnEvent,
1337    EntitySpawnEvent,
1338    |ev| EntitySpawnEvent {
1339        entity_type: ev.entity_type.as_str().to_owned(),
1340        uuid: ev.uuid.as_str().to_owned(),
1341        dimension: ev.dimension.as_str().to_owned(),
1342    }
1343);
1344
1345trampoline_phased!(
1346    trampoline_place_block,
1347    YogPlaceBlockEvent,
1348    PlaceBlockEvent,
1349    |ev| PlaceBlockEvent {
1350        player_name: ev.player.as_str().to_owned(),
1351        block_id: ev.block.as_str().to_owned(),
1352        pos: yog_core::BlockPos {
1353            x: ev.pos.x,
1354            y: ev.pos.y,
1355            z: ev.pos.z
1356        },
1357        dimension: ev.dimension.as_str().to_owned(),
1358    }
1359);
1360
1361trampoline_phased!(
1362    trampoline_player_death,
1363    YogPlayerDeathEvent,
1364    PlayerDeathEvent,
1365    |ev| PlayerDeathEvent {
1366        player_name: ev.player.as_str().to_owned(),
1367        uuid: ev.uuid.as_str().to_owned(),
1368        source: ev.source.as_str().to_owned(),
1369        dimension: ev.dimension.as_str().to_owned(),
1370    }
1371);
1372
1373trampoline_phased!(
1374    trampoline_player_respawn,
1375    YogPlayerRespawnEvent,
1376    PlayerRespawnEvent,
1377    |ev| PlayerRespawnEvent {
1378        player_name: ev.player.as_str().to_owned(),
1379        uuid: ev.uuid.as_str().to_owned(),
1380        at_anchor: ev.at_anchor,
1381        dimension: ev.dimension.as_str().to_owned(),
1382    }
1383);
1384
1385trampoline_phased!(
1386    trampoline_advancement,
1387    YogAdvancementEvent,
1388    AdvancementEvent,
1389    |ev| AdvancementEvent {
1390        player_name: ev.player.as_str().to_owned(),
1391        uuid: ev.uuid.as_str().to_owned(),
1392        advancement_id: ev.advancement.as_str().to_owned(),
1393        dimension: ev.dimension.as_str().to_owned(),
1394    }
1395);
1396
1397trampoline_phased!(
1398    trampoline_entity_interact,
1399    YogEntityInteractEvent,
1400    EntityInteractEvent,
1401    |ev| EntityInteractEvent {
1402        player_name: ev.player.as_str().to_owned(),
1403        player_uuid: ev.player_uuid.as_str().to_owned(),
1404        entity_type: ev.entity_type.as_str().to_owned(),
1405        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1406        hand: ev.hand.as_str().to_owned(),
1407        dimension: ev.dimension.as_str().to_owned(),
1408    }
1409);
1410
1411trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| {
1412    CraftEvent {
1413        player_name: ev.player.as_str().to_owned(),
1414        player_uuid: ev.player_uuid.as_str().to_owned(),
1415        result_item: ev.result_item.as_str().to_owned(),
1416        result_count: ev.result_count,
1417        dimension: ev.dimension.as_str().to_owned(),
1418    }
1419});
1420
1421trampoline_phased!(
1422    trampoline_explosion,
1423    YogExplosionEvent,
1424    ExplosionEvent,
1425    |ev| ExplosionEvent {
1426        dimension: ev.dimension.as_str().to_owned(),
1427        x: ev.x,
1428        y: ev.y,
1429        z: ev.z,
1430        power: ev.power,
1431        cause_uuid: ev.cause_uuid.as_str().to_owned(),
1432    }
1433);
1434
1435trampoline_phased!(
1436    trampoline_item_pickup,
1437    YogItemPickupEvent,
1438    ItemPickupEvent,
1439    |ev| ItemPickupEvent {
1440        player_name: ev.player.as_str().to_owned(),
1441        player_uuid: ev.player_uuid.as_str().to_owned(),
1442        item_id: ev.item_id.as_str().to_owned(),
1443        item_count: ev.item_count,
1444        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1445        dimension: ev.dimension.as_str().to_owned(),
1446    }
1447);
1448
1449trampoline_phased!(
1450    trampoline_player_move,
1451    YogPlayerMoveEvent,
1452    PlayerMoveEvent,
1453    |ev| PlayerMoveEvent {
1454        player_name: ev.player.as_str().to_owned(),
1455        player_uuid: ev.player_uuid.as_str().to_owned(),
1456        x: ev.x,
1457        y: ev.y,
1458        z: ev.z,
1459        yaw: ev.yaw,
1460        pitch: ev.pitch,
1461        dimension: ev.dimension.as_str().to_owned(),
1462    }
1463);
1464
1465trampoline_phased!(
1466    trampoline_container_open,
1467    YogContainerOpenEvent,
1468    ContainerOpenEvent,
1469    |ev| ContainerOpenEvent {
1470        player_name: ev.player.as_str().to_owned(),
1471        player_uuid: ev.player_uuid.as_str().to_owned(),
1472        container_type: ev.container_type.as_str().to_owned(),
1473        dimension: ev.dimension.as_str().to_owned(),
1474    }
1475);
1476
1477trampoline_phased!(
1478    trampoline_container_close,
1479    YogContainerCloseEvent,
1480    ContainerCloseEvent,
1481    |ev| ContainerCloseEvent {
1482        player_name: ev.player.as_str().to_owned(),
1483        player_uuid: ev.player_uuid.as_str().to_owned(),
1484        dimension: ev.dimension.as_str().to_owned(),
1485    }
1486);
1487
1488trampoline_phased!(
1489    trampoline_projectile_hit,
1490    YogProjectileHitEvent,
1491    ProjectileHitEvent,
1492    |ev| ProjectileHitEvent {
1493        projectile_type: ev.projectile_type.as_str().to_owned(),
1494        projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
1495        shooter_uuid: ev.shooter_uuid.as_str().to_owned(),
1496        hit_type: ev.hit_type.as_str().to_owned(),
1497        hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
1498        x: ev.x,
1499        y: ev.y,
1500        z: ev.z,
1501        dimension: ev.dimension.as_str().to_owned(),
1502    }
1503);
1504
1505unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
1506where
1507    F: Fn(&dyn Server) + Send + Sync,
1508{
1509    unsafe {
1510        let f = &*(ud as *const F);
1511        f(&CServer(srv));
1512    }
1513}
1514
1515unsafe extern "C" fn trampoline_packet<F>(
1516    ud: *mut c_void,
1517    srv: *const YogServer,
1518    ev: *const YogPacketEvent,
1519) where
1520    F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
1521{
1522    unsafe {
1523        let f = &*(ud as *const F);
1524        let ev = &*ev;
1525        let rust_ev = PacketEvent {
1526            channel: ev.channel.as_str().to_owned(),
1527            player: ev.player.as_str().to_owned(),
1528            payload: std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec(),
1529        };
1530        f(&rust_ev, &CServer(srv));
1531    }
1532}
1533
1534unsafe extern "C" fn trampoline_command<F>(
1535    ud: *mut c_void,
1536    srv: *const YogServer,
1537    ev: *const YogCommandEvent,
1538    reply_buf: *mut u8,
1539    reply_cap: u32,
1540    reply_len: *mut u32,
1541) where
1542    F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
1543{
1544    unsafe {
1545        let f = &*(ud as *const F);
1546        let ev = &*ev;
1547        let ctx = CommandContext {
1548            name: ev.name.as_str().to_owned(),
1549            args: ev.args.as_str().to_owned(),
1550            source: ev.source.as_str().to_owned(),
1551            uuid: ev.uuid.as_str().to_owned(),
1552            dimension: ev.dimension.as_str().to_owned(),
1553        };
1554        *reply_len = 0;
1555        if let Some(reply) = f(&ctx, &CServer(srv)) {
1556            let bytes = reply.as_bytes();
1557            let n = bytes.len().min(reply_cap as usize);
1558            std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
1559            *reply_len = n as u32;
1560        }
1561    }
1562}
1563
1564unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
1565where
1566    F: Fn(&dyn Server) + Send + Sync,
1567{
1568    unsafe {
1569        let f = &*(ud as *const F);
1570        f(&CServer(srv));
1571    }
1572}
1573
1574// ── ABI minor 10 — client-side trampolines ────────────────────────────────────
1575
1576unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
1577where
1578    F: Fn(&ClientTickEvent) + Send + Sync,
1579{
1580    unsafe {
1581        let f = &*(ud as *const F);
1582        f(&ClientTickEvent {});
1583    }
1584}
1585
1586unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1587where
1588    F: Fn(&GfxContext) + Send + Sync,
1589{
1590    unsafe {
1591        let f = &*(ud as *const F);
1592        f(&GfxContext::from_raw(gfx));
1593    }
1594}
1595
1596unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1597where
1598    F: Fn(&GfxContext) + Send + Sync,
1599{
1600    unsafe {
1601        let f = &*(ud as *const F);
1602        f(&GfxContext::from_raw(gfx));
1603    }
1604}
1605
1606unsafe extern "C" fn trampoline_key_press<F>(ud: *mut c_void, ev: *const YogKeyPressEvent) -> bool
1607where
1608    F: Fn(&KeyPressEvent) -> bool + Send + Sync,
1609{
1610    unsafe {
1611        let f = &*(ud as *const F);
1612        let e = &*ev;
1613        f(&KeyPressEvent {
1614            key_code: e.key_code,
1615            scan_code: e.scan_code,
1616            action: e.action,
1617            modifiers: e.modifiers,
1618        })
1619    }
1620}
1621
1622unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
1623where
1624    F: Fn(&ScreenEvent) + Send + Sync,
1625{
1626    unsafe {
1627        let f = &*(ud as *const F);
1628        f(&ScreenEvent {
1629            screen_class: screen_class.as_str().to_owned(),
1630        });
1631        true
1632    }
1633}
1634
1635// ── Registry ─────────────────────────────────────────────────────────────────
1636
1637/// Wraps the [`YogApi`] table and provides an ergonomic registration API.
1638///
1639/// Obtained inside `yog_mod_register` via `export_mod!`.  Closures registered
1640/// here are boxed and leaked — they live as long as the process (which is the
1641/// correct lifetime for a server mod).
1642/// Pointer to the runtime's `YogApi` table (a process-lifetime static in the
1643/// runtime), captured when the mod registers. Lets free functions like
1644/// [`installed_mods`] work outside of `register()` — e.g. in UI handlers.
1645static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
1646    std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
1647
1648/// The runtime's `YogApi` table, null before this mod registered.
1649pub(crate) fn global_api() -> *mut YogApi {
1650    GLOBAL_API.load(std::sync::atomic::Ordering::Acquire)
1651}
1652
1653/// Metadata of an installed mod, as reported by the loader.
1654#[derive(Debug, Clone)]
1655pub struct ModInfo {
1656    /// `"yog"` for .yog mods, `"platform"` for loader (Java) mods.
1657    pub source: String,
1658    pub id: String,
1659    pub name: String,
1660    pub version: String,
1661    /// Comma-separated author list (may be empty).
1662    pub authors: String,
1663    pub description: String,
1664}
1665
1666/// All installed mods known to the loader: .yog mods plus, where the host
1667/// exposes them, platform (Java) mods. Callable at any time after this mod's
1668/// `register()` ran — including client-side UI handlers. Note that during
1669/// `register()` mods that load after this one are not in the list yet; query
1670/// lazily (e.g. on first render) for a complete view.
1671pub fn installed_mods() -> Vec<ModInfo> {
1672    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1673    if api.is_null() {
1674        return Vec::new();
1675    }
1676    let owned = unsafe { ((*api).mods_list)((*api).ctx) };
1677    let Some(text) = take_owned(unsafe { (*api).free_str }, owned) else {
1678        return Vec::new();
1679    };
1680    text.lines()
1681        .filter_map(|line| {
1682            let mut f = line.split('\t');
1683            Some(ModInfo {
1684                source: f.next()?.to_string(),
1685                id: f.next()?.to_string(),
1686                name: f.next().unwrap_or_default().to_string(),
1687                version: f.next().unwrap_or_default().to_string(),
1688                authors: f.next().unwrap_or_default().to_string(),
1689                description: f.next().unwrap_or_default().to_string(),
1690            })
1691        })
1692        .collect()
1693}
1694
1695/// Open the Yog UI registered as `ui_id` (client side; no-op on dedicated
1696/// servers). `modal` blocks game input, `pause` pauses singleplayer. Callable
1697/// from any handler after registration — e.g. an `on_client_packet` handler.
1698pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
1699    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1700    if api.is_null() {
1701        return;
1702    }
1703    unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
1704}
1705
1706/// Close the Yog UI screen showing `ui_id` — e.g. from that screen's own
1707/// "Done" button. Does nothing when another screen is open. Deferred onto the
1708/// render thread, so the handler that calls it finishes first. Client side;
1709/// a no-op on dedicated servers.
1710pub fn close_ui(ui_id: &str) {
1711    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1712    if api.is_null() {
1713        return;
1714    }
1715    unsafe { ((*api).ui_close)((*api).ctx, YogStr::from_str(ui_id)) }
1716}
1717
1718/// What a [`Registry::register_ui`] handler returns: `()` (never marks
1719/// events as used) or `bool` (`true` = the handler used this `key:`/`char:`
1720/// event — the host then doesn't, e.g. Esc doesn't close the screen).
1721pub trait UiHandled {
1722    fn handled(self) -> bool;
1723}
1724
1725impl UiHandled for () {
1726    fn handled(self) -> bool {
1727        false
1728    }
1729}
1730
1731impl UiHandled for bool {
1732    fn handled(self) -> bool {
1733        self
1734    }
1735}
1736
1737/// Handle to the runtime's server-action table, usable from any handler after
1738/// registration — including client-side ones that don't receive `&dyn Server`
1739/// (UI event handlers, client tick). Server-world actions are no-ops client
1740/// side, but networking (`send_to_server`) and similar client-safe calls work.
1741pub fn server() -> Option<CServer> {
1742    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1743    if api.is_null() {
1744        return None;
1745    }
1746    let srv = unsafe { (*api).server };
1747    if srv.is_null() {
1748        return None;
1749    }
1750    Some(CServer(srv))
1751}
1752
1753pub struct Registry {
1754    api: *const YogApi,
1755}
1756
1757// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
1758unsafe impl Send for Registry {}
1759unsafe impl Sync for Registry {}
1760
1761impl Registry {
1762    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
1763    ///
1764    /// # Safety
1765    ///
1766    /// `api` must point to the runtime's `YogApi` table, valid for the rest of
1767    /// the process.
1768    pub unsafe fn from_raw(api: *const YogApi) -> Self {
1769        GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
1770        Self { api }
1771    }
1772
1773    #[inline]
1774    fn ctx(&self) -> *mut c_void {
1775        unsafe { (*self.api).ctx }
1776    }
1777
1778    // ── helpers ──────────────────────────────────────────────────────────────
1779
1780    fn leak<F: 'static>(f: F) -> *mut c_void {
1781        Box::into_raw(Box::new(f)) as *mut c_void
1782    }
1783
1784    // ── events ───────────────────────────────────────────────────────────────
1785    //
1786    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
1787    // In `Pre` phase, returning `false` cancels the action.
1788    // In `Post` phase, the return value is ignored.
1789    // A single registration fires for BOTH phases.
1790
1791    pub fn on_block_break<F>(&mut self, handler: F)
1792    where
1793        F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1794    {
1795        let ud = Self::leak(handler);
1796        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
1797    }
1798
1799    pub fn on_chat<F>(&mut self, handler: F)
1800    where
1801        F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1802    {
1803        let ud = Self::leak(handler);
1804        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
1805    }
1806
1807    pub fn on_player_join<F>(&mut self, handler: F)
1808    where
1809        F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1810    {
1811        let ud = Self::leak(handler);
1812        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
1813    }
1814
1815    pub fn on_player_leave<F>(&mut self, handler: F)
1816    where
1817        F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1818    {
1819        let ud = Self::leak(handler);
1820        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
1821    }
1822
1823    pub fn on_use_item<F>(&mut self, handler: F)
1824    where
1825        F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1826    {
1827        let ud = Self::leak(handler);
1828        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
1829    }
1830
1831    pub fn on_use_block<F>(&mut self, handler: F)
1832    where
1833        F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1834    {
1835        let ud = Self::leak(handler);
1836        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
1837    }
1838
1839    pub fn on_attack_entity<F>(&mut self, handler: F)
1840    where
1841        F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1842    {
1843        let ud = Self::leak(handler);
1844        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
1845    }
1846
1847    pub fn on_entity_damage<F>(&mut self, handler: F)
1848    where
1849        F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1850    {
1851        let ud = Self::leak(handler);
1852        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
1853    }
1854
1855    pub fn on_entity_death<F>(&mut self, handler: F)
1856    where
1857        F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1858    {
1859        let ud = Self::leak(handler);
1860        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
1861    }
1862
1863    pub fn on_entity_spawn<F>(&mut self, handler: F)
1864    where
1865        F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1866    {
1867        let ud = Self::leak(handler);
1868        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
1869    }
1870
1871    pub fn on_player_place_block<F>(&mut self, handler: F)
1872    where
1873        F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1874    {
1875        let ud = Self::leak(handler);
1876        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
1877    }
1878
1879    pub fn on_player_death<F>(&mut self, handler: F)
1880    where
1881        F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1882    {
1883        let ud = Self::leak(handler);
1884        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
1885    }
1886
1887    pub fn on_player_respawn<F>(&mut self, handler: F)
1888    where
1889        F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1890    {
1891        let ud = Self::leak(handler);
1892        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
1893    }
1894
1895    pub fn on_advancement<F>(&mut self, handler: F)
1896    where
1897        F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1898    {
1899        let ud = Self::leak(handler);
1900        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
1901    }
1902
1903    pub fn on_entity_interact<F>(&mut self, handler: F)
1904    where
1905        F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1906    {
1907        let ud = Self::leak(handler);
1908        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
1909    }
1910
1911    pub fn on_item_craft<F>(&mut self, handler: F)
1912    where
1913        F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1914    {
1915        let ud = Self::leak(handler);
1916        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
1917    }
1918
1919    pub fn on_explosion<F>(&mut self, handler: F)
1920    where
1921        F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1922    {
1923        let ud = Self::leak(handler);
1924        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
1925    }
1926
1927    pub fn on_item_pickup<F>(&mut self, handler: F)
1928    where
1929        F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1930    {
1931        let ud = Self::leak(handler);
1932        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
1933    }
1934
1935    pub fn on_player_move<F>(&mut self, handler: F)
1936    where
1937        F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1938    {
1939        let ud = Self::leak(handler);
1940        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
1941    }
1942
1943    pub fn on_container_open<F>(&mut self, handler: F)
1944    where
1945        F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1946    {
1947        let ud = Self::leak(handler);
1948        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
1949    }
1950
1951    pub fn on_container_close<F>(&mut self, handler: F)
1952    where
1953        F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1954    {
1955        let ud = Self::leak(handler);
1956        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
1957    }
1958
1959    pub fn on_projectile_hit<F>(&mut self, handler: F)
1960    where
1961        F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
1962    {
1963        let ud = Self::leak(handler);
1964        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
1965    }
1966
1967    pub fn on_tick<F>(&mut self, listener: F)
1968    where
1969        F: Fn(&dyn Server) + Send + Sync + 'static,
1970    {
1971        let ud = Self::leak(listener);
1972        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
1973    }
1974
1975    pub fn on_server_started<F>(&mut self, listener: F)
1976    where
1977        F: Fn(&dyn Server) + Send + Sync + 'static,
1978    {
1979        let ud = Self::leak(listener);
1980        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
1981    }
1982
1983    pub fn on_server_stopping<F>(&mut self, listener: F)
1984    where
1985        F: Fn(&dyn Server) + Send + Sync + 'static,
1986    {
1987        let ud = Self::leak(listener);
1988        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
1989    }
1990
1991    // ── commands ─────────────────────────────────────────────────────────────
1992
1993    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
1994    where
1995        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
1996    {
1997        let name_ys = YogStr::from_str(name.as_ref());
1998        let ud = Self::leak(handler);
1999        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
2000    }
2001
2002    /// Register a command with Brigadier-typed arguments.
2003    ///
2004    /// `schema` is a space-separated list of argument types:
2005    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
2006    ///
2007    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
2008    pub fn on_typed_command<F>(
2009        &mut self,
2010        name: impl AsRef<str>,
2011        schema: impl AsRef<str>,
2012        handler: F,
2013    ) where
2014        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
2015    {
2016        let name_ys = YogStr::from_str(name.as_ref());
2017        let schema_ys = YogStr::from_str(schema.as_ref());
2018        let ud = Self::leak(handler);
2019        unsafe {
2020            ((*self.api).register_typed_command)(
2021                self.ctx(),
2022                name_ys,
2023                schema_ys,
2024                ud,
2025                trampoline_command::<F>,
2026            );
2027        }
2028    }
2029
2030    // ── networking ───────────────────────────────────────────────────────────
2031
2032    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2033    where
2034        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2035    {
2036        let ch = YogStr::from_str(channel.as_ref());
2037        let ud = Self::leak(handler);
2038        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2039    }
2040
2041    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2042    where
2043        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2044    {
2045        let ch = YogStr::from_str(channel.as_ref());
2046        let ud = Self::leak(handler);
2047        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2048    }
2049
2050    /// Register a typed-packet handler.
2051    ///
2052    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
2053    /// Malformed payloads are silently dropped.
2054    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
2055    where
2056        P: Packet + Send + Sync + 'static,
2057        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
2058    {
2059        self.on_packet(channel, move |ev, srv| {
2060            if let Some(pkt) = P::decode(&ev.payload) {
2061                handler(&pkt, srv);
2062            }
2063        });
2064    }
2065
2066    // ── recipes ──────────────────────────────────────────────────────────────
2067
2068    fn recipe(&mut self, ns: &str, name: &str, json: &str) {
2069        unsafe {
2070            ((*self.api).register_recipe_json)(
2071                self.ctx(),
2072                YogStr::from_str(ns),
2073                YogStr::from_str(name),
2074                YogStr::from_str(json),
2075            );
2076        }
2077    }
2078
2079    /// Register a shaped crafting recipe.
2080    pub fn add_shaped_recipe(&mut self, recipe: ShapedRecipe) {
2081        let (ns, name) = recipe.ns_name();
2082        self.recipe(ns, name, &recipe.to_json());
2083    }
2084
2085    /// Register a shapeless crafting recipe.
2086    pub fn add_shapeless_recipe(&mut self, recipe: ShapelessRecipe) {
2087        let (ns, name) = recipe.ns_name();
2088        self.recipe(ns, name, &recipe.to_json());
2089    }
2090
2091    /// Register a furnace smelting recipe.
2092    pub fn add_furnace_recipe(&mut self, recipe: FurnaceRecipe) {
2093        let (ns, name) = recipe.ns_name();
2094        self.recipe(ns, name, &recipe.to_json());
2095    }
2096
2097    // ── content ──────────────────────────────────────────────────────────────
2098
2099    pub fn register_item(&mut self, def: ItemDef) {
2100        // Build a C-compatible YogItemDef whose YogStr fields point into `def`'s
2101        // String storage.  We then call register_item which must copy the data
2102        // before returning (the runtime stores owned Strings).
2103        let food_nutrition = def.food.as_ref().map_or(0, |f| f.nutrition);
2104        let food_saturation = def.food.as_ref().map_or(0.0, |f| f.saturation);
2105        let food_always_eat = def.food.as_ref().is_some_and(|f| f.can_always_eat);
2106        let c = YogItemDef {
2107            id: YogStr::from_str(&def.id),
2108            max_stack: def.max_stack as u32,
2109            name: def
2110                .name
2111                .as_deref()
2112                .map(YogStr::from_str)
2113                .unwrap_or(YogStr::EMPTY),
2114            tooltip: def
2115                .tooltip
2116                .as_deref()
2117                .map(YogStr::from_str)
2118                .unwrap_or(YogStr::EMPTY),
2119            max_damage: def.max_damage,
2120            fire_resistant: def.fire_resistant,
2121            fuel_ticks: def.fuel_ticks,
2122            food_nutrition,
2123            food_saturation,
2124            food_always_eat,
2125        };
2126        unsafe { ((*self.api).register_item)(self.ctx(), &c) }
2127    }
2128
2129    pub fn register_block(&mut self, def: BlockDef) {
2130        let shape = def.shape.unwrap_or([0.0; 6]);
2131        let groups_joined = def.connect_groups.join(",");
2132        let c = YogBlockDef {
2133            id: YogStr::from_str(&def.id),
2134            hardness: def.hardness,
2135            resistance: def.resistance,
2136            name: def
2137                .name
2138                .as_deref()
2139                .map(YogStr::from_str)
2140                .unwrap_or(YogStr::EMPTY),
2141            light_level: def.light_level,
2142            sound: def
2143                .sound
2144                .as_deref()
2145                .map(YogStr::from_str)
2146                .unwrap_or(YogStr::EMPTY),
2147            requires_tool: def.requires_tool,
2148            no_collision: def.no_collision,
2149            slipperiness: def.slipperiness,
2150            shape,
2151            connects: def.connects,
2152            connect_groups: YogStr::from_str(&groups_joined),
2153            inventory_id: def
2154                .inventory_id
2155                .as_deref()
2156                .map(YogStr::from_str)
2157                .unwrap_or(YogStr::EMPTY),
2158            redstone_source: def.redstone_source,
2159        };
2160        unsafe { ((*self.api).register_block)(self.ctx(), &c) }
2161    }
2162
2163    /// Register a real Container/Menu inventory screen — see
2164    /// `yog_inventory::InventoryDef`. Attach it to a block via
2165    /// `BlockDef::inventory(id)`.
2166    pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
2167        let layout = yog_inventory::encode_layout(&def.resolved_layout());
2168        let c = YogInventoryDef {
2169            id: YogStr::from_str(&def.id),
2170            slot_count: def.slot_count as u32,
2171            layout: YogStr::from_str(&layout),
2172            include_player_inventory: def.include_player_inventory,
2173            player_inv_x: def.player_inv_offset.0,
2174            player_inv_y: def.player_inv_offset.1,
2175            background_texture: def
2176                .background_texture
2177                .as_deref()
2178                .map(YogStr::from_str)
2179                .unwrap_or(YogStr::EMPTY),
2180            title: YogStr::from_str(&def.title),
2181            background_w: def.background_size.map(|(w, _)| w).unwrap_or(0.0),
2182            background_h: def.background_size.map(|(_, h)| h).unwrap_or(0.0),
2183        };
2184        unsafe { ((*self.api).register_inventory)(self.ctx(), &c) }
2185    }
2186
2187    // ── startup grants ───────────────────────────────────────────────────────
2188
2189    /// Register a startup grant: items/books to give once when a player first joins.
2190    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
2191        let items_str = grant.items.join("|");
2192        let c = YogStartupGrantDef {
2193            id: YogStr::from_str(&grant.id),
2194            items: YogStr::from_str(&items_str),
2195            book: grant
2196                .book
2197                .as_deref()
2198                .map(YogStr::from_str)
2199                .unwrap_or(YogStr::EMPTY),
2200            command: grant
2201                .command
2202                .as_deref()
2203                .map(YogStr::from_str)
2204                .unwrap_or(YogStr::EMPTY),
2205        };
2206        unsafe { ((*self.api).register_startup_grant)(self.ctx(), &c) }
2207    }
2208
2209    /// Register a book with its JSON-serialized content.
2210    pub fn register_book(&mut self, book: &Book) {
2211        let json = book.to_json();
2212        let id = YogStr::from_str(&book.id);
2213        let j = YogStr::from_str(&json);
2214        unsafe { ((*self.api).register_book)(self.ctx(), id, j) }
2215    }
2216
2217    /// Register (or patch) a dimension's *type* (sky, lighting, physics, ...).
2218    ///
2219    /// `def.id` need not be a new custom id — pass a vanilla id
2220    /// (`"minecraft:overworld"`, `"minecraft:the_nether"`,
2221    /// `"minecraft:the_end"`) to patch that dimension's type/generator
2222    /// instead of creating a new one (e.g. to replace the overworld's
2223    /// terrain with your own generator type).
2224    ///
2225    /// Must be called from `register()` — the same mod-init window as
2226    /// blocks/items — so the host can declare/patch the dimension before
2227    /// Minecraft's registries freeze. See the `yog-dimensions` crate docs;
2228    /// set `def.generator_type` (via [`YogDimensionDef::generator_type`]) to
2229    /// pick a generator type registered with
2230    /// [`Registry::register_chunk_generator`].
2231    pub fn register_dimension(&mut self, def: &YogDimensionDef) {
2232        let json = def.to_json();
2233        let id = YogStr::from_str(&def.id);
2234        let j = YogStr::from_str(&json);
2235        unsafe { ((*self.api).register_dimension_json)(self.ctx(), id, j) }
2236    }
2237
2238    /// Register a named, reusable chunk-generator type under
2239    /// `generator_type_id` (e.g. `"mymod:custom_terrain"`) — analogous to
2240    /// how Biomes O'Plenty registers selectable world-type presets. Any
2241    /// dimension (custom or vanilla) can opt into this generator via
2242    /// [`YogDimensionDef::generator_type`]; the platform host may also list
2243    /// registered generator type ids as options on the world-creation screen.
2244    ///
2245    /// `generator` is called once per chunk column that needs generating in
2246    /// any dimension using this type — write whatever terrain logic you want
2247    /// using the [`ChunkWriter`] handle (combine as many noise layers as you
2248    /// like; this crate doesn't prescribe a generation style).
2249    pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
2250    where
2251        F: Fn(&ChunkWriter) + Send + Sync + 'static,
2252    {
2253        let ud = Self::leak(generator);
2254        let id = YogStr::from_str(generator_type_id);
2255        unsafe {
2256            ((*self.api).register_chunk_generator)(
2257                self.ctx(),
2258                id,
2259                ud,
2260                trampoline_chunk_generator::<F>,
2261            );
2262        }
2263    }
2264
2265    /// Register a menu entry — the host renders a button on vanilla screens
2266    /// (TitleScreen on Fabric, ModListScreen on Forge/NeoForge) that opens `ui_id`.
2267    /// `label` is the human-readable button text (e.g. "Yog Mods").
2268    /// `ui_id` is the UI to open when clicked (e.g. "example:menu").
2269    /// See [`installed_mods`]. During `register()` the list only contains
2270    /// mods loaded before this one.
2271    pub fn installed_mods(&self) -> Vec<ModInfo> {
2272        installed_mods()
2273    }
2274
2275    /// Access the inter-mod communication layer.
2276    ///
2277    /// Allows exporting function pointers from this mod and importing
2278    /// function pointers from other mods.
2279    pub fn interop(&self) -> crate::Interop {
2280        crate::Interop::new(self.api)
2281    }
2282
2283    /// Raw `YogApi` pointer — for passing to interop-imported functions
2284    /// that register content through the C ABI table directly.
2285    pub fn raw_api(&self) -> *const yog_abi::YogApi {
2286        self.api
2287    }
2288
2289    pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
2290        let l = YogStr::from_str(label);
2291        let u = YogStr::from_str(ui_id);
2292        unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
2293    }
2294
2295    /// Register a UI tree with an event callback.
2296    /// `ui_id` is the unique identifier (e.g. "mymod:menu").
2297    /// `handler` receives `(ui_id, event)` for the screen's input: `click:X:Y`,
2298    /// `drag:X:Y`, `release:X:Y`, `scroll:DY`, `key:KEY:MODS` (GLFW key code
2299    /// and modifier bits, on press and repeat) and `char:CODEPOINT` (a typed
2300    /// character). It returns `()` or a `bool`: `true` marks a `key:`/`char:`
2301    /// event as used, so the host skips its default handling of the key
2302    /// (Esc closing the screen, ...) — see [`UiHandled`].
2303    pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
2304    where
2305        F: Fn(&str, &str) -> R + Send + Sync + 'static,
2306        R: UiHandled,
2307    {
2308        let ud = Self::leak(handler);
2309        let id = YogStr::from_str(ui_id);
2310        let empty = YogStr::from_str("{}");
2311        unsafe { ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F, R>) }
2312    }
2313
2314    // ── scheduler ────────────────────────────────────────────────────────────
2315
2316    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
2317    where
2318        F: Fn(&dyn Server) + Send + Sync + 'static,
2319    {
2320        let ud = Self::leak(handler);
2321        unsafe {
2322            ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>);
2323        }
2324    }
2325
2326    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
2327    where
2328        F: Fn(&dyn Server) + Send + Sync + 'static,
2329    {
2330        let ud = Self::leak(handler);
2331        unsafe {
2332            ((*self.api).schedule_repeating)(
2333                self.ctx(),
2334                period_ticks,
2335                ud,
2336                trampoline_scheduled::<F>,
2337            );
2338        }
2339    }
2340
2341    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
2342
2343    /// Register a handler called every client tick (render thread, no server).
2344    pub fn on_client_tick<F>(&mut self, handler: F)
2345    where
2346        F: Fn(&ClientTickEvent) + Send + Sync + 'static,
2347    {
2348        let ud = Self::leak(handler);
2349        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
2350    }
2351
2352    /// Register a render callback for a specific UI screen (`ui_id`).
2353    ///
2354    /// Called from `YogUIScreen.render()` — i.e. AFTER `renderBackground()` darkens
2355    /// the screen — so your UI draws on top of the dimmed world view.
2356    /// Use this instead of `on_hud_render` for book/inventory screens.
2357    ///
2358    /// Clicks are forwarded as `"click:X:Y"` via the `register_ui` handler so you
2359    /// can do hit-testing on your own stored layout.
2360    pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
2361    where
2362        F: Fn(&GfxContext) + Send + Sync + 'static,
2363    {
2364        let ud = Self::leak(handler);
2365        let id = YogStr::from_str(ui_id);
2366        unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
2367    }
2368
2369    /// Register a handler called every frame when the HUD is rendered.
2370    ///
2371    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
2372    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
2373    pub fn on_hud_render<F>(&mut self, handler: F)
2374    where
2375        F: Fn(&GfxContext) + Send + Sync + 'static,
2376    {
2377        let ud = Self::leak(handler);
2378        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
2379    }
2380
2381    /// Register a handler called every frame at the end of world rendering.
2382    ///
2383    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
2384    /// `gfx.camera_pos()` is the camera world position.
2385    /// To render at world position P, translate by `P - camera_pos` before drawing.
2386    pub fn on_world_render<F>(&mut self, handler: F)
2387    where
2388        F: Fn(&GfxContext) + Send + Sync + 'static,
2389    {
2390        let ud = Self::leak(handler);
2391        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
2392    }
2393
2394    /// Register a handler for keyboard input (client-side).
2395    /// Return `false` to prevent Minecraft from processing the key.
2396    pub fn on_key_press<F>(&mut self, handler: F)
2397    where
2398        F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static,
2399    {
2400        let ud = Self::leak(handler);
2401        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
2402    }
2403
2404    /// Register a handler called when a GUI screen is opened.
2405    pub fn on_screen_open<F>(&mut self, handler: F)
2406    where
2407        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2408    {
2409        let ud = Self::leak(handler);
2410        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
2411    }
2412
2413    /// Register a handler called when a GUI screen is closed.
2414    pub fn on_screen_close<F>(&mut self, handler: F)
2415    where
2416        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2417    {
2418        let ud = Self::leak(handler);
2419        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
2420    }
2421}
2422
2423unsafe extern "C" fn trampoline_ui_event<F, R>(
2424    ud: *mut c_void,
2425    ui_id: yog_abi::YogStr,
2426    event_id: yog_abi::YogStr,
2427) where
2428    F: Fn(&str, &str) -> R + Send + Sync + 'static,
2429    R: UiHandled,
2430{
2431    let f = unsafe { &*(ud as *const F) };
2432    let ui = unsafe { ui_id.as_str() };
2433    let ev = unsafe { event_id.as_str() };
2434    if f(ui, ev).handled() {
2435        let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2436        if !api.is_null() {
2437            unsafe { ((*api).ui_consume_event)((*api).ctx) }
2438        }
2439    }
2440}
2441
2442unsafe extern "C" fn trampoline_chunk_generator<F>(
2443    ud: *mut c_void,
2444    writer: *const yog_abi::YogChunkWriterApi,
2445) where
2446    F: Fn(&ChunkWriter) + Send + Sync + 'static,
2447{
2448    let f = unsafe { &*(ud as *const F) };
2449    let writer = unsafe { ChunkWriter::from_raw(writer) };
2450    f(&writer);
2451}
2452
2453// ── Mod trait ─────────────────────────────────────────────────────────────────
2454
2455/// Implemented by every Yog mod. Called once at startup to register handlers.
2456pub trait Mod {
2457    fn register(registry: &mut Registry);
2458}