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, 
14    YogChatEvent, YogCommandEvent, YogContainerCloseEvent, YogContainerOpenEvent, YogCraftEvent,
15    YogEntityDamageEvent, YogEntityDeathEvent, YogEntityInteractEvent, YogEntitySpawnEvent,
16    YogExplosionEvent, YogGfxApi, YogItemInsertEvent, YogItemPickupEvent, YogMachineCraftEvent,
17    YogKeyPressEvent, YogPacketEvent, YogPlaceBlockEvent, YogPlayerDeathEvent, YogPlayerEvent,
18    YogPlayerMoveEvent, YogPlayerRespawnEvent, YogProjectileHitEvent, YogServer,
19    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, ItemInsertEvent, 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::{ArmorMaterial, BlockDef, ItemDef, Recipe, SoundGroup, StartupGrant, ToolMaterial};
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 world_dir(&self) -> String {
377        let s = srv!(self);
378        let owned = unsafe { (s.world_dir)(s.ctx) };
379        take_owned(s.free_str, owned).unwrap_or_default()
380    }
381
382    fn play_sound(
383        &self,
384        dimension: &str,
385        x: f64,
386        y: f64,
387        z: f64,
388        sound_id: &str,
389        volume: f32,
390        pitch: f32,
391    ) -> bool {
392        let s = srv!(self);
393        unsafe {
394            (s.play_sound)(
395                s.ctx,
396                YogStr::from_str(dimension),
397                yog_abi::YogVec3 { x, y, z },
398                YogStr::from_str(sound_id),
399                volume,
400                pitch,
401            )
402        }
403    }
404
405    fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool {
406        let s = srv!(self);
407        unsafe {
408            (s.play_sound_player)(
409                s.ctx,
410                YogStr::from_str(player),
411                YogStr::from_str(sound_id),
412                volume,
413                pitch,
414            )
415        }
416    }
417
418    fn send_title(
419        &self,
420        player: &str,
421        title: &str,
422        subtitle: &str,
423        fadein: i32,
424        stay: i32,
425        fadeout: i32,
426    ) -> bool {
427        let s = srv!(self);
428        unsafe {
429            (s.send_title)(
430                s.ctx,
431                YogStr::from_str(player),
432                YogStr::from_str(title),
433                YogStr::from_str(subtitle),
434                fadein,
435                stay,
436                fadeout,
437            )
438        }
439    }
440
441    fn entity_rotation(&self, uuid: &str) -> Option<(f32, f32)> {
442        let s = srv!(self);
443        let mut out = yog_abi::YogVec3 {
444            x: 0.0,
445            y: 0.0,
446            z: 0.0,
447        };
448        let ok = unsafe { (s.entity_rotation)(s.ctx, YogStr::from_str(uuid), &mut out) };
449        if ok {
450            Some((out.x as f32, out.y as f32))
451        } else {
452            None
453        }
454    }
455
456    fn send_actionbar(&self, player: &str, message: &str) -> bool {
457        let s = srv!(self);
458        unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
459    }
460
461    fn kick_player(&self, player: &str, reason: &str) -> bool {
462        let s = srv!(self);
463        unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
464    }
465
466    fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
467        let s = srv!(self);
468        unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
469    }
470
471    fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
472        let s = srv!(self);
473        unsafe {
474            (s.bossbar_create)(
475                s.ctx,
476                YogStr::from_str(id),
477                YogStr::from_str(title),
478                YogStr::from_str(color),
479                YogStr::from_str(style),
480            )
481        }
482    }
483
484    fn bossbar_remove(&self, id: &str) -> bool {
485        let s = srv!(self);
486        unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
487    }
488
489    fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
490        let s = srv!(self);
491        unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
492    }
493
494    fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
495        let s = srv!(self);
496        unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
497    }
498
499    fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
500        let s = srv!(self);
501        unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
502    }
503
504    fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
505        let s = srv!(self);
506        unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
507    }
508
509    fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
510        let s = srv!(self);
511        unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
512    }
513
514    fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
515        let s = srv!(self);
516        unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
517    }
518
519    fn online_players(&self) -> Vec<String> {
520        let s = srv!(self);
521        let owned = unsafe { (s.online_players)(s.ctx) };
522        let Some(text) = take_owned(s.free_str, owned) else {
523            return Vec::new();
524        };
525        if text.is_empty() {
526            Vec::new()
527        } else {
528            text.lines().map(str::to_owned).collect()
529        }
530    }
531
532    fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
533        let s = srv!(self);
534        let owned = unsafe {
535            (s.get_block_nbt)(
536                s.ctx,
537                YogStr::from_str(dimension),
538                yog_abi::YogBlockPos {
539                    x: pos.x,
540                    y: pos.y,
541                    z: pos.z,
542                },
543            )
544        };
545
546        take_owned(s.free_str, owned)
547    }
548
549    fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
550        let s = srv!(self);
551        unsafe {
552            (s.set_block_nbt)(
553                s.ctx,
554                YogStr::from_str(dimension),
555                yog_abi::YogBlockPos {
556                    x: pos.x,
557                    y: pos.y,
558                    z: pos.z,
559                },
560                YogStr::from_str(snbt),
561            )
562        }
563    }
564
565    fn get_inventory_slot(
566        &self,
567        dimension: &str,
568        pos: yog_core::BlockPos,
569        slot: u32,
570    ) -> Option<(String, u32, String)> {
571        let s = srv!(self);
572        let owned = unsafe {
573            (s.get_inventory_slot)(
574                s.ctx,
575                YogStr::from_str(dimension),
576                yog_abi::YogBlockPos {
577                    x: pos.x,
578                    y: pos.y,
579                    z: pos.z,
580                },
581                slot,
582            )
583        };
584        let text = take_owned(s.free_str, owned)?;
585        let mut it = text.splitn(3, '\t');
586        let item_id = it.next()?.to_owned();
587        let count = it.next()?.parse().ok()?;
588        let snbt = it.next().unwrap_or("{}").to_owned();
589        Some((item_id, count, snbt))
590    }
591
592    fn set_inventory_slot(
593        &self,
594        dimension: &str,
595        pos: yog_core::BlockPos,
596        slot: u32,
597        item_id: &str,
598        count: u32,
599        snbt: &str,
600    ) -> bool {
601        // The ABI's item id carries the SNBT after a tab.
602        let item = if snbt.is_empty() { item_id.to_string() } else { format!("{item_id}\t{snbt}") };
603        let s = srv!(self);
604        unsafe {
605            (s.set_inventory_slot)(
606                s.ctx,
607                YogStr::from_str(dimension),
608                yog_abi::YogBlockPos {
609                    x: pos.x,
610                    y: pos.y,
611                    z: pos.z,
612                },
613                slot,
614                YogStr::from_str(&item),
615                count,
616            )
617        }
618    }
619
620    fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
621        let s = srv!(self);
622        let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
623        let Some(text) = take_owned(s.free_str, owned) else {
624            return Vec::new();
625        };
626        text.lines()
627            .filter_map(|line| {
628                let mut it = line.split('\t');
629                let slot: u32 = it.next()?.parse().ok()?;
630                let item_id = it.next()?.to_owned();
631                let count: u32 = it.next()?.parse().ok()?;
632                Some((slot, item_id, count))
633            })
634            .collect()
635    }
636
637    fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
638        let s = srv!(self);
639        unsafe {
640            (s.player_set_slot)(
641                s.ctx,
642                YogStr::from_str(player),
643                slot,
644                YogStr::from_str(item_id),
645                count,
646            )
647        }
648    }
649
650    fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
651        let s = srv!(self);
652        unsafe {
653            (s.player_teleport_dim)(
654                s.ctx,
655                YogStr::from_str(player),
656                YogStr::from_str(dimension),
657                yog_abi::YogVec3 { x, y, z },
658            )
659        }
660    }
661
662    fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
663        let s = srv!(self);
664        unsafe {
665            (s.entity_teleport_dim)(
666                s.ctx,
667                YogStr::from_str(uuid),
668                YogStr::from_str(dimension),
669                yog_abi::YogVec3 { x, y, z },
670            )
671        }
672    }
673
674    fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
675        let s = srv!(self);
676        unsafe {
677            (s.world_entity_count)(
678                s.ctx,
679                YogStr::from_str(dimension),
680                YogStr::from_str(entity_type),
681            )
682        }
683    }
684
685    fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
686        let s = srv!(self);
687        let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
688
689        take_owned(s.free_str, owned)
690    }
691
692    fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
693        let s = srv!(self);
694        unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
695    }
696
697    fn spawn_particles(
698        &self,
699        dimension: &str,
700        x: f64,
701        y: f64,
702        z: f64,
703        particle_type: &str,
704        count: i32,
705        dx: f64,
706        dy: f64,
707        dz: f64,
708        speed: f64,
709    ) -> bool {
710        let s = srv!(self);
711        unsafe {
712            (s.spawn_particles)(
713                s.ctx,
714                YogStr::from_str(dimension),
715                yog_abi::YogVec3 { x, y, z },
716                YogStr::from_str(particle_type),
717                count,
718                dx,
719                dy,
720                dz,
721                speed,
722            )
723        }
724    }
725
726    fn entity_attribute_value(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
727        let s = srv!(self);
728        let v = unsafe { (s.entity_attribute_value)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(attribute_id)) };
729        (!v.is_nan()).then_some(v)
730    }
731
732    fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
733        let s = srv!(self);
734        let v = unsafe {
735            (s.entity_attribute_get)(
736                s.ctx,
737                YogStr::from_str(uuid),
738                YogStr::from_str(attribute_id),
739            )
740        };
741        if v.is_nan() { None } else { Some(v) }
742    }
743
744    fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
745        let s = srv!(self);
746        unsafe {
747            (s.entity_attribute_set)(
748                s.ctx,
749                YogStr::from_str(uuid),
750                YogStr::from_str(attribute_id),
751                value,
752            )
753        }
754    }
755
756    fn get_held_item_nbt(&self, player: &str) -> Option<String> {
757        let s = srv!(self);
758        let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
759
760        take_owned(s.free_str, owned)
761    }
762
763    fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
764        let s = srv!(self);
765        unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
766    }
767
768    fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
769        let s = srv!(self);
770        let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
771
772        take_owned(s.free_str, owned)
773    }
774
775    fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
776        let s = srv!(self);
777        unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
778    }
779
780    fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
781        let s = srv!(self);
782        let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
783        let text = take_owned(s.free_str, owned)?;
784        let mut it = text.splitn(3, '\t');
785        let item_id = it.next()?.to_owned();
786        let count: u32 = it.next()?.parse().ok()?;
787        let nbt = it.next().unwrap_or("{}").to_owned();
788        Some((item_id, count, nbt))
789    }
790
791    fn set_slot_item(
792        &self,
793        player: &str,
794        slot: u32,
795        item_id: &str,
796        count: u32,
797        snbt: &str,
798    ) -> bool {
799        let s = srv!(self);
800        unsafe {
801            (s.set_slot_item)(
802                s.ctx,
803                YogStr::from_str(player),
804                slot,
805                YogStr::from_str(item_id),
806                count,
807                YogStr::from_str(snbt),
808            )
809        }
810    }
811
812    // ── world queries & effects (ABI minor 1) ────────────────────────────────
813
814    fn entity_info(&self, uuid: &str) -> Option<yog_core::EntityInfo> {
815        let s = srv!(self);
816        let owned = unsafe { (s.entity_info)(s.ctx, YogStr::from_str(uuid)) };
817        take_owned(s.free_str, owned).and_then(|l| yog_core::EntityInfo::decode(&l))
818    }
819
820    fn entities_in_box(
821        &self,
822        dimension: &str,
823        min: (f64, f64, f64),
824        max: (f64, f64, f64),
825    ) -> Vec<String> {
826        let s = srv!(self);
827        let owned =
828            unsafe { (s.entities_in_box)(s.ctx, YogStr::from_str(dimension), v3(min), v3(max)) };
829        take_owned(s.free_str, owned)
830            .map(|t| {
831                t.lines()
832                    .filter(|l| !l.is_empty())
833                    .map(str::to_owned)
834                    .collect()
835            })
836            .unwrap_or_default()
837    }
838
839    fn raycast_block(
840        &self,
841        dimension: &str,
842        from: (f64, f64, f64),
843        to: (f64, f64, f64),
844    ) -> Option<yog_core::BlockHit> {
845        let s = srv!(self);
846        let owned =
847            unsafe { (s.raycast_block)(s.ctx, YogStr::from_str(dimension), v3(from), v3(to)) };
848        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockHit::decode(&l))
849    }
850
851    fn raycast_entity(
852        &self,
853        dimension: &str,
854        from: (f64, f64, f64),
855        to: (f64, f64, f64),
856        exclude: &str,
857    ) -> Option<String> {
858        let s = srv!(self);
859        let owned = unsafe {
860            (s.raycast_entity)(
861                s.ctx,
862                YogStr::from_str(dimension),
863                v3(from),
864                v3(to),
865                YogStr::from_str(exclude),
866            )
867        };
868        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
869    }
870
871    fn block_info(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<yog_core::BlockInfo> {
872        let s = srv!(self);
873        let owned = unsafe { (s.block_info)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
874        take_owned(s.free_str, owned).and_then(|l| yog_core::BlockInfo::decode(&l))
875    }
876
877    fn break_block(
878        &self,
879        dimension: &str,
880        pos: yog_core::BlockPos,
881        drop: bool,
882        breaker: &str,
883    ) -> bool {
884        let s = srv!(self);
885        unsafe {
886            (s.break_block)(
887                s.ctx,
888                YogStr::from_str(dimension),
889                bp(pos),
890                drop,
891                YogStr::from_str(breaker),
892            )
893        }
894    }
895
896    fn place_block_from_slot(
897        &self,
898        player: &str,
899        slot: u32,
900        dimension: &str,
901        pos: yog_core::BlockPos,
902    ) -> bool {
903        let s = srv!(self);
904        unsafe {
905            (s.place_block_from_slot)(
906                s.ctx,
907                YogStr::from_str(player),
908                slot,
909                YogStr::from_str(dimension),
910                bp(pos),
911            )
912        }
913    }
914
915    fn bonemeal(&self, dimension: &str, pos: yog_core::BlockPos) -> bool {
916        let s = srv!(self);
917        unsafe { (s.bonemeal)(s.ctx, YogStr::from_str(dimension), bp(pos)) }
918    }
919
920    fn explode(
921        &self,
922        dimension: &str,
923        x: f64,
924        y: f64,
925        z: f64,
926        power: f32,
927        fire: bool,
928        source: &str,
929    ) -> bool {
930        let s = srv!(self);
931        unsafe {
932            (s.explode)(
933                s.ctx,
934                YogStr::from_str(dimension),
935                v3((x, y, z)),
936                power,
937                fire,
938                YogStr::from_str(source),
939            )
940        }
941    }
942
943    fn tellraw(&self, player: &str, json: &str) -> bool {
944        let s = srv!(self);
945        unsafe { (s.tellraw)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
946    }
947
948    fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool {
949        let s = srv!(self);
950        unsafe {
951            (s.entity_damage)(
952                s.ctx,
953                YogStr::from_str(uuid),
954                amount,
955                YogStr::from_str(damage_type),
956                YogStr::from_str(attacker),
957            )
958        }
959    }
960
961    fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool {
962        let s = srv!(self);
963        unsafe { (s.entity_set_fire_ticks)(s.ctx, YogStr::from_str(uuid), ticks) }
964    }
965
966    fn player_add_xp(&self, player: &str, amount: i32) -> bool {
967        let s = srv!(self);
968        unsafe { (s.player_add_xp)(s.ctx, YogStr::from_str(player), amount) }
969    }
970
971    fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool {
972        let s = srv!(self);
973        unsafe { (s.player_set_may_fly)(s.ctx, YogStr::from_str(player), may_fly) }
974    }
975
976    fn player_info(&self, player: &str) -> Option<yog_core::PlayerInfo> {
977        let s = srv!(self);
978        let owned = unsafe { (s.player_info)(s.ctx, YogStr::from_str(player)) };
979        take_owned(s.free_str, owned).and_then(|l| yog_core::PlayerInfo::decode(&l))
980    }
981
982    fn spawn_item(
983        &self,
984        dimension: &str,
985        pos: (f64, f64, f64),
986        vel: (f64, f64, f64),
987        item_id: &str,
988        count: u32,
989        snbt: &str,
990    ) -> Option<String> {
991        let s = srv!(self);
992        let owned = unsafe {
993            (s.spawn_item)(
994                s.ctx,
995                YogStr::from_str(dimension),
996                v3(pos),
997                v3(vel),
998                YogStr::from_str(item_id),
999                count,
1000                YogStr::from_str(snbt),
1001            )
1002        };
1003        take_owned(s.free_str, owned).filter(|u| !u.is_empty())
1004    }
1005
1006    fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)> {
1007        let s = srv!(self);
1008        let owned = unsafe { (s.item_entity_stack)(s.ctx, YogStr::from_str(uuid)) };
1009        let text = take_owned(s.free_str, owned)?;
1010        let mut it = text.splitn(3, '\t');
1011        let item_id = it.next()?.to_owned();
1012        let count = it.next()?.parse().ok()?;
1013        let nbt = it.next().unwrap_or("{}").to_owned();
1014        Some((item_id, count, nbt))
1015    }
1016
1017    fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool {
1018        let s = srv!(self);
1019        unsafe {
1020            (s.item_entity_set_stack)(
1021                s.ctx,
1022                YogStr::from_str(uuid),
1023                YogStr::from_str(item_id),
1024                count,
1025                YogStr::from_str(snbt),
1026            )
1027        }
1028    }
1029
1030    fn actionbar_json(&self, player: &str, json: &str) -> bool {
1031        let s = srv!(self);
1032        unsafe { (s.actionbar_json)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
1033    }
1034
1035    fn use_item_on_block(
1036        &self,
1037        dimension: &str,
1038        pos: yog_core::BlockPos,
1039        face: yog_core::Face,
1040        item_id: &str,
1041        player: &str,
1042    ) -> bool {
1043        let s = srv!(self);
1044        unsafe {
1045            (s.use_item_on_block)(
1046                s.ctx,
1047                YogStr::from_str(dimension),
1048                bp(pos),
1049                face.index(),
1050                YogStr::from_str(item_id),
1051                YogStr::from_str(player),
1052            )
1053        }
1054    }
1055
1056    fn pour_fluid(&self, dimension: &str, pos: yog_core::BlockPos, bucket_item_id: &str) -> bool {
1057        let s = srv!(self);
1058        unsafe {
1059            (s.pour_fluid)(
1060                s.ctx,
1061                YogStr::from_str(dimension),
1062                bp(pos),
1063                YogStr::from_str(bucket_item_id),
1064            )
1065        }
1066    }
1067
1068    fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool {
1069        let s = srv!(self);
1070        unsafe {
1071            (s.has_entity_tag)(
1072                s.ctx,
1073                YogStr::from_str(entity_type),
1074                YogStr::from_str(tag_id),
1075            )
1076        }
1077    }
1078
1079    fn item_block(&self, item_id: &str) -> Option<String> {
1080        let s = srv!(self);
1081        let owned = unsafe { (s.item_block)(s.ctx, YogStr::from_str(item_id)) };
1082        take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1083    }
1084
1085    fn place_feature(&self, dimension: &str, pos: yog_core::BlockPos, feature_id: &str) -> bool {
1086        let s = srv!(self);
1087        unsafe {
1088            (s.place_feature)(
1089                s.ctx,
1090                YogStr::from_str(dimension),
1091                bp(pos),
1092                YogStr::from_str(feature_id),
1093            )
1094        }
1095    }
1096
1097    fn set_item_cooldown(&self, player: &str, item_id: &str, ticks: u32) -> bool {
1098        let s = srv!(self);
1099        unsafe { (s.set_item_cooldown)(s.ctx, YogStr::from_str(player), YogStr::from_str(item_id), ticks.min(i32::MAX as u32) as i32) }
1100    }
1101
1102    fn entity_vehicle(&self, uuid: &str) -> Option<String> {
1103        let s = srv!(self);
1104        let owned = unsafe { (s.entity_vehicle)(s.ctx, YogStr::from_str(uuid)) };
1105        take_owned(s.free_str, owned).filter(|v| !v.is_empty())
1106    }
1107
1108    fn entity_passengers(&self, uuid: &str) -> Vec<String> {
1109        let s = srv!(self);
1110        let owned = unsafe { (s.entity_passengers)(s.ctx, YogStr::from_str(uuid)) };
1111        take_owned(s.free_str, owned)
1112            .map(|t| t.lines().filter(|l| !l.is_empty()).map(str::to_string).collect())
1113            .unwrap_or_default()
1114    }
1115
1116    fn entity_mount(&self, rider: &str, vehicle: &str, force: bool) -> bool {
1117        let s = srv!(self);
1118        unsafe { (s.entity_mount)(s.ctx, YogStr::from_str(rider), YogStr::from_str(vehicle), force) }
1119    }
1120
1121    fn entity_dismount(&self, uuid: &str) -> bool {
1122        let s = srv!(self);
1123        unsafe { (s.entity_dismount)(s.ctx, YogStr::from_str(uuid)) }
1124    }
1125
1126    fn entity_data(&self, uuid: &str, key: &str) -> Option<Vec<u8>> {
1127        let s = srv!(self);
1128        let mut out = Vec::new();
1129        let found = unsafe {
1130            (s.entity_data_get)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(key), &mut out as *mut Vec<u8> as *mut c_void, crate::body::collect)
1131        };
1132        found.then_some(out)
1133    }
1134
1135    fn set_entity_data(&self, uuid: &str, key: &str, value: Option<&[u8]>, persist: bool) -> bool {
1136        let s = srv!(self);
1137        let flags = value.map_or(0, |_| 1) | if persist { 2 } else { 0 };
1138        let v = value.unwrap_or(&[]);
1139        unsafe { (s.entity_data_set)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(key), v.as_ptr(), v.len() as u32, flags) }
1140    }
1141
1142    fn entity_set_dimensions(&self, uuid: &str, size: Option<(f32, f32, f32)>) -> bool {
1143        let s = srv!(self);
1144        let (w, h, e) = size.unwrap_or((-1.0, 0.0, 0.0));
1145        unsafe { (s.entity_set_dimensions)(s.ctx, YogStr::from_str(uuid), w, h, e) }
1146    }
1147
1148    fn entity_set_fall_distance(&self, uuid: &str, distance: f32) -> bool {
1149        let s = srv!(self);
1150        unsafe { (s.entity_set_fall_distance)(s.ctx, YogStr::from_str(uuid), distance) }
1151    }
1152
1153    fn redstone_signal(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
1154        let s = srv!(self);
1155        let p = unsafe { (s.redstone_signal)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1156        (0..=15).contains(&p).then_some(p as u8)
1157    }
1158
1159    fn redstone_power(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
1160        let s = srv!(self);
1161        let p = unsafe { (s.redstone_power)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1162        (0..=15).contains(&p).then_some(p as u8)
1163    }
1164
1165    fn set_redstone_output(&self, dimension: &str, pos: yog_core::BlockPos, level: u8) -> bool {
1166        let s = srv!(self);
1167        unsafe { (s.set_redstone_output)(s.ctx, YogStr::from_str(dimension), bp(pos), level.min(15) as i32) }
1168    }
1169
1170    fn get_block_state(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
1171        let s = srv!(self);
1172        let owned = unsafe { (s.get_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1173        take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1174    }
1175
1176    fn set_block_state(&self, dimension: &str, pos: yog_core::BlockPos, state: &str) -> bool {
1177        let s = srv!(self);
1178        unsafe { (s.set_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos), YogStr::from_str(state)) }
1179    }
1180
1181    fn world_is_thundering(&self, dimension: &str) -> bool {
1182        let s = srv!(self);
1183        unsafe { (s.world_is_thundering)(s.ctx, YogStr::from_str(dimension)) }
1184    }
1185
1186    fn world_set_thunder(&self, dimension: &str, thundering: bool, duration_ticks: i32) -> bool {
1187        let s = srv!(self);
1188        unsafe { (s.world_set_thunder)(s.ctx, YogStr::from_str(dimension), thundering, duration_ticks) }
1189    }
1190
1191    fn fill_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos, state: &str) -> Option<u32> {
1192        let s = srv!(self);
1193        let n = unsafe { (s.fill_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to), YogStr::from_str(state)) };
1194        u32::try_from(n).ok()
1195    }
1196
1197    fn scan_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos) -> Vec<(yog_core::BlockPos, String)> {
1198        let s = srv!(self);
1199        let owned = unsafe { (s.scan_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to)) };
1200        let Some(text) = take_owned(s.free_str, owned) else {
1201            return Vec::new();
1202        };
1203        text.lines()
1204            .filter_map(|line| {
1205                let mut it = line.splitn(4, ' ');
1206                let (x, y, z) = (it.next()?.parse().ok()?, it.next()?.parse().ok()?, it.next()?.parse().ok()?);
1207                Some((yog_core::BlockPos { x, y, z }, it.next()?.to_string()))
1208            })
1209            .collect()
1210    }
1211}
1212
1213/// Copy a runtime-owned string out and free it; `None` for NONE.
1214pub(crate) fn take_owned(
1215    free_str: unsafe extern "C" fn(ptr: *mut u8, len: u32),
1216    owned: yog_abi::YogOwnedStr,
1217) -> Option<String> {
1218    if owned.is_none() {
1219        return None;
1220    }
1221    // SAFETY: a non-NONE owned string is `len` valid bytes until freed.
1222    let text = unsafe {
1223        String::from_utf8_lossy(std::slice::from_raw_parts(owned.ptr, owned.len as usize))
1224            .into_owned()
1225    };
1226    unsafe { free_str(owned.ptr, owned.len) };
1227    Some(text)
1228}
1229
1230fn v3((x, y, z): (f64, f64, f64)) -> yog_abi::YogVec3 {
1231    yog_abi::YogVec3 { x, y, z }
1232}
1233
1234fn bp(pos: yog_core::BlockPos) -> yog_abi::YogBlockPos {
1235    yog_abi::YogBlockPos {
1236        x: pos.x,
1237        y: pos.y,
1238        z: pos.z,
1239    }
1240}
1241
1242// ── Trampoline helpers ────────────────────────────────────────────────────────
1243//
1244// A panic must never unwind out of a trampoline: the runtime calls them
1245// through `extern "C"`, so it would abort the whole game. `guard` stops it
1246// at the boundary — the panic message is already in the log — and the
1247// event goes on as if the mod hadn't handled it.
1248
1249fn guard<R>(fallback: R, call: impl FnOnce() -> R) -> R {
1250    std::panic::catch_unwind(std::panic::AssertUnwindSafe(call)).unwrap_or_else(|_| {
1251        crate::error!("a mod's callback panicked (see above); carrying on without it");
1252        fallback
1253    })
1254}
1255//
1256// Each trampoline is a `unsafe extern "C" fn` that:
1257//   1. Casts `ud` back to the original boxed closure.
1258//   2. Converts ABI C structs to Rust event types.
1259//   3. Builds a `CServer` from the `*const YogServer`.
1260//   4. Calls the closure.
1261//
1262// Closures are Box::into_raw'd in Registry methods — they are INTENTIONALLY
1263// leaked and live for the process lifetime.
1264
1265// ── Trampoline helpers ────────────────────────────────────────────────────────
1266//
1267// All phased event trampolines share the same pattern:
1268//   `phase: u8`  0 = EventPhase::Pre, 1 = EventPhase::Post
1269//   Return value is meaningful only in Pre phase.
1270
1271macro_rules! trampoline_phased {
1272    ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr_2021) => {
1273        unsafe extern "C" fn $name<F>(
1274            ud: *mut c_void,
1275            srv: *const YogServer,
1276            ev: *const $abi_ev,
1277            phase: u8,
1278        ) -> bool
1279        where
1280            F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
1281        {
1282            unsafe {
1283                let f = &*(ud as *const F);
1284                let $ev = &*ev;
1285                let rust_ev = $build;
1286                let p = if phase == 0 {
1287                    EventPhase::Pre
1288                } else {
1289                    EventPhase::Post
1290                };
1291                guard(true, || f(&rust_ev, p, &CServer(srv)))
1292            }
1293        }
1294    };
1295}
1296
1297trampoline_phased!(
1298    trampoline_block_break,
1299    YogBlockBreakEvent,
1300    BlockBreakEvent,
1301    |ev| BlockBreakEvent {
1302        player_name: ev.player.as_str().to_owned(),
1303        block_id: ev.block.as_str().to_owned(),
1304        pos: yog_core::BlockPos {
1305            x: ev.pos.x,
1306            y: ev.pos.y,
1307            z: ev.pos.z
1308        },
1309        dimension: ev.dimension.as_str().to_owned(),
1310    }
1311);
1312
1313trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
1314    player_name: ev.player.as_str().to_owned(),
1315    message: ev.message.as_str().to_owned(),
1316    dimension: ev.dimension.as_str().to_owned(),
1317});
1318
1319trampoline_phased!(
1320    trampoline_player_join,
1321    YogPlayerEvent,
1322    PlayerJoinEvent,
1323    |ev| PlayerJoinEvent {
1324        player_name: ev.player.as_str().to_owned(),
1325        uuid: ev.uuid.as_str().to_owned(),
1326        dimension: ev.dimension.as_str().to_owned(),
1327    }
1328);
1329
1330trampoline_phased!(
1331    trampoline_player_leave,
1332    YogPlayerEvent,
1333    PlayerLeaveEvent,
1334    |ev| PlayerLeaveEvent {
1335        player_name: ev.player.as_str().to_owned(),
1336        uuid: ev.uuid.as_str().to_owned(),
1337        dimension: ev.dimension.as_str().to_owned(),
1338    }
1339);
1340
1341trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| {
1342    UseItemEvent {
1343        player_name: ev.player.as_str().to_owned(),
1344        item_id: ev.item.as_str().to_owned(),
1345        sneaking: ev.sneaking,
1346        dimension: ev.dimension.as_str().to_owned(),
1347    }
1348});
1349
1350trampoline_phased!(
1351    trampoline_use_block,
1352    YogUseBlockEvent,
1353    UseBlockEvent,
1354    |ev| UseBlockEvent {
1355        player_name: ev.player.as_str().to_owned(),
1356        block_id: ev.block.as_str().to_owned(),
1357        pos: yog_core::BlockPos {
1358            x: ev.pos.x,
1359            y: ev.pos.y,
1360            z: ev.pos.z
1361        },
1362        dimension: ev.dimension.as_str().to_owned(),
1363        sneaking: ev.sneaking,
1364    }
1365);
1366
1367trampoline_phased!(
1368    trampoline_attack_entity,
1369    YogAttackEntityEvent,
1370    AttackEntityEvent,
1371    |ev| AttackEntityEvent {
1372        player_name: ev.player.as_str().to_owned(),
1373        target_type: ev.target_type.as_str().to_owned(),
1374        target_uuid: ev.target_uuid.as_str().to_owned(),
1375        dimension: ev.dimension.as_str().to_owned(),
1376    }
1377);
1378
1379trampoline_phased!(
1380    trampoline_entity_damage,
1381    YogEntityDamageEvent,
1382    EntityDamageEvent,
1383    |ev| EntityDamageEvent {
1384        entity_type: ev.entity_type.as_str().to_owned(),
1385        uuid: ev.uuid.as_str().to_owned(),
1386        amount: ev.amount,
1387        source: ev.source.as_str().to_owned(),
1388        dimension: ev.dimension.as_str().to_owned(),
1389    }
1390);
1391
1392trampoline_phased!(
1393    trampoline_entity_death,
1394    YogEntityDeathEvent,
1395    EntityDeathEvent,
1396    |ev| EntityDeathEvent {
1397        entity_type: ev.entity_type.as_str().to_owned(),
1398        uuid: ev.uuid.as_str().to_owned(),
1399        source: ev.source.as_str().to_owned(),
1400        dimension: ev.dimension.as_str().to_owned(),
1401    }
1402);
1403
1404trampoline_phased!(
1405    trampoline_entity_spawn,
1406    YogEntitySpawnEvent,
1407    EntitySpawnEvent,
1408    |ev| EntitySpawnEvent {
1409        entity_type: ev.entity_type.as_str().to_owned(),
1410        uuid: ev.uuid.as_str().to_owned(),
1411        dimension: ev.dimension.as_str().to_owned(),
1412    }
1413);
1414
1415trampoline_phased!(
1416    trampoline_place_block,
1417    YogPlaceBlockEvent,
1418    PlaceBlockEvent,
1419    |ev| PlaceBlockEvent {
1420        player_name: ev.player.as_str().to_owned(),
1421        block_id: ev.block.as_str().to_owned(),
1422        pos: yog_core::BlockPos {
1423            x: ev.pos.x,
1424            y: ev.pos.y,
1425            z: ev.pos.z
1426        },
1427        dimension: ev.dimension.as_str().to_owned(),
1428        sneaking: ev.sneaking,
1429    }
1430);
1431
1432trampoline_phased!(
1433    trampoline_player_death,
1434    YogPlayerDeathEvent,
1435    PlayerDeathEvent,
1436    |ev| PlayerDeathEvent {
1437        player_name: ev.player.as_str().to_owned(),
1438        uuid: ev.uuid.as_str().to_owned(),
1439        source: ev.source.as_str().to_owned(),
1440        dimension: ev.dimension.as_str().to_owned(),
1441    }
1442);
1443
1444trampoline_phased!(
1445    trampoline_player_respawn,
1446    YogPlayerRespawnEvent,
1447    PlayerRespawnEvent,
1448    |ev| PlayerRespawnEvent {
1449        player_name: ev.player.as_str().to_owned(),
1450        uuid: ev.uuid.as_str().to_owned(),
1451        at_anchor: ev.at_anchor,
1452        dimension: ev.dimension.as_str().to_owned(),
1453    }
1454);
1455
1456trampoline_phased!(
1457    trampoline_advancement,
1458    YogAdvancementEvent,
1459    AdvancementEvent,
1460    |ev| AdvancementEvent {
1461        player_name: ev.player.as_str().to_owned(),
1462        uuid: ev.uuid.as_str().to_owned(),
1463        advancement_id: ev.advancement.as_str().to_owned(),
1464        dimension: ev.dimension.as_str().to_owned(),
1465    }
1466);
1467
1468trampoline_phased!(
1469    trampoline_entity_interact,
1470    YogEntityInteractEvent,
1471    EntityInteractEvent,
1472    |ev| EntityInteractEvent {
1473        player_name: ev.player.as_str().to_owned(),
1474        player_uuid: ev.player_uuid.as_str().to_owned(),
1475        entity_type: ev.entity_type.as_str().to_owned(),
1476        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1477        hand: ev.hand.as_str().to_owned(),
1478        dimension: ev.dimension.as_str().to_owned(),
1479    }
1480);
1481
1482trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| {
1483    CraftEvent {
1484        player_name: ev.player.as_str().to_owned(),
1485        player_uuid: ev.player_uuid.as_str().to_owned(),
1486        result_item: ev.result_item.as_str().to_owned(),
1487        result_count: ev.result_count,
1488        dimension: ev.dimension.as_str().to_owned(),
1489    }
1490});
1491
1492/// A machine — an inventory whose `.yui` has a `<RecipeArea kind="…">` —
1493/// making a recipe of its kind, like a crafting table: the result shows as
1494/// soon as its slots hold a recipe's ingredients, and taking it takes them.
1495///
1496/// - `Pre` — whether it may: `false` and the result doesn't show (a
1497///   workbench short of what the recipe's `values` cost). `player` is empty.
1498/// - `Post` — a player took the result; the host takes the ingredients, the mod
1499///   its share (the workbench's resources).
1500#[derive(Debug, Clone)]
1501pub struct MachineCraftEvent {
1502    /// The machine's inventory, e.g. `"mymod:press"`.
1503    pub machine: String,
1504    /// The recipe's kind (`"mymod:pressing"`) and id.
1505    pub kind: String,
1506    pub recipe: String,
1507    pub result_item: String,
1508    pub result_count: u32,
1509    /// The recipe's values: `{ "energy": 4 }`.
1510    pub values: yog_data::Value,
1511    pub pos: yog_core::BlockPos,
1512    pub dimension: String,
1513    pub player: String,
1514}
1515
1516trampoline_phased!(trampoline_machine_craft, YogMachineCraftEvent, MachineCraftEvent, |ev| {
1517    MachineCraftEvent {
1518        machine: ev.machine.as_str().to_owned(),
1519        kind: ev.kind.as_str().to_owned(),
1520        recipe: ev.recipe.as_str().to_owned(),
1521        result_item: ev.result_item.as_str().to_owned(),
1522        result_count: ev.result_count,
1523        values: yog_data::json::from_json_text(ev.values.as_str()).unwrap_or(yog_data::Value::Null),
1524        pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
1525        dimension: ev.dimension.as_str().to_owned(),
1526        player: ev.player.as_str().to_owned(),
1527    }
1528});
1529
1530trampoline_phased!(
1531    trampoline_explosion,
1532    YogExplosionEvent,
1533    ExplosionEvent,
1534    |ev| ExplosionEvent {
1535        dimension: ev.dimension.as_str().to_owned(),
1536        x: ev.x,
1537        y: ev.y,
1538        z: ev.z,
1539        power: ev.power,
1540        cause_uuid: ev.cause_uuid.as_str().to_owned(),
1541    }
1542);
1543
1544trampoline_phased!(
1545    trampoline_item_insert,
1546    YogItemInsertEvent,
1547    ItemInsertEvent,
1548    |ev| ItemInsertEvent {
1549        block_id: ev.block.as_str().to_owned(),
1550        pos: yog_core::BlockPos {
1551            x: ev.pos.x,
1552            y: ev.pos.y,
1553            z: ev.pos.z
1554        },
1555        dimension: ev.dimension.as_str().to_owned(),
1556        item_id: ev.item_id.as_str().to_owned(),
1557        count: ev.count,
1558        nbt: ev.nbt.as_str().to_owned(),
1559    }
1560);
1561
1562trampoline_phased!(
1563    trampoline_item_pickup,
1564    YogItemPickupEvent,
1565    ItemPickupEvent,
1566    |ev| ItemPickupEvent {
1567        player_name: ev.player.as_str().to_owned(),
1568        player_uuid: ev.player_uuid.as_str().to_owned(),
1569        item_id: ev.item_id.as_str().to_owned(),
1570        item_count: ev.item_count,
1571        entity_uuid: ev.entity_uuid.as_str().to_owned(),
1572        dimension: ev.dimension.as_str().to_owned(),
1573    }
1574);
1575
1576trampoline_phased!(
1577    trampoline_player_move,
1578    YogPlayerMoveEvent,
1579    PlayerMoveEvent,
1580    |ev| PlayerMoveEvent {
1581        player_name: ev.player.as_str().to_owned(),
1582        player_uuid: ev.player_uuid.as_str().to_owned(),
1583        x: ev.x,
1584        y: ev.y,
1585        z: ev.z,
1586        yaw: ev.yaw,
1587        pitch: ev.pitch,
1588        dimension: ev.dimension.as_str().to_owned(),
1589    }
1590);
1591
1592trampoline_phased!(
1593    trampoline_container_open,
1594    YogContainerOpenEvent,
1595    ContainerOpenEvent,
1596    |ev| ContainerOpenEvent {
1597        player_name: ev.player.as_str().to_owned(),
1598        player_uuid: ev.player_uuid.as_str().to_owned(),
1599        container_type: ev.container_type.as_str().to_owned(),
1600        dimension: ev.dimension.as_str().to_owned(),
1601    }
1602);
1603
1604trampoline_phased!(
1605    trampoline_container_close,
1606    YogContainerCloseEvent,
1607    ContainerCloseEvent,
1608    |ev| ContainerCloseEvent {
1609        player_name: ev.player.as_str().to_owned(),
1610        player_uuid: ev.player_uuid.as_str().to_owned(),
1611        dimension: ev.dimension.as_str().to_owned(),
1612    }
1613);
1614
1615trampoline_phased!(
1616    trampoline_projectile_hit,
1617    YogProjectileHitEvent,
1618    ProjectileHitEvent,
1619    |ev| ProjectileHitEvent {
1620        projectile_type: ev.projectile_type.as_str().to_owned(),
1621        projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
1622        shooter_uuid: ev.shooter_uuid.as_str().to_owned(),
1623        hit_type: ev.hit_type.as_str().to_owned(),
1624        hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
1625        x: ev.x,
1626        y: ev.y,
1627        z: ev.z,
1628        dimension: ev.dimension.as_str().to_owned(),
1629    }
1630);
1631
1632unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
1633where
1634    F: Fn(&dyn Server) + Send + Sync,
1635{
1636    unsafe {
1637        let f = &*(ud as *const F);
1638        guard((), || f(&CServer(srv)));
1639    }
1640}
1641
1642unsafe extern "C" fn trampoline_packet<F>(
1643    ud: *mut c_void,
1644    srv: *const YogServer,
1645    ev: *const YogPacketEvent,
1646) where
1647    F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
1648{
1649    unsafe {
1650        let f = &*(ud as *const F);
1651        let ev = &*ev;
1652        let rust_ev = PacketEvent {
1653            channel: ev.channel.as_str().to_owned(),
1654            player: ev.player.as_str().to_owned(),
1655            // (A null pointer with no bytes is fine for C, not for a slice.)
1656            payload: if ev.payload.is_null() || ev.payload_len == 0 {
1657                Vec::new()
1658            } else {
1659                std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec()
1660            },
1661        };
1662        guard((), || f(&rust_ev, &CServer(srv)));
1663    }
1664}
1665
1666unsafe extern "C" fn trampoline_command<F>(
1667    ud: *mut c_void,
1668    srv: *const YogServer,
1669    ev: *const YogCommandEvent,
1670    reply_buf: *mut u8,
1671    reply_cap: u32,
1672    reply_len: *mut u32,
1673) where
1674    F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
1675{
1676    unsafe {
1677        let f = &*(ud as *const F);
1678        let ev = &*ev;
1679        let ctx = CommandContext {
1680            name: ev.name.as_str().to_owned(),
1681            args: ev.args.as_str().to_owned(),
1682            source: ev.source.as_str().to_owned(),
1683            uuid: ev.uuid.as_str().to_owned(),
1684            dimension: ev.dimension.as_str().to_owned(),
1685        };
1686        *reply_len = 0;
1687        if let Some(reply) = guard(None, || f(&ctx, &CServer(srv))) {
1688            let bytes = reply.as_bytes();
1689            // As much as fits, cut between characters (it's read as UTF-8).
1690            let mut n = bytes.len().min(reply_cap as usize);
1691            while !reply.is_char_boundary(n) {
1692                n -= 1;
1693            }
1694            std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
1695            *reply_len = n as u32;
1696        }
1697    }
1698}
1699
1700unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
1701where
1702    F: Fn(&dyn Server) + Send + Sync,
1703{
1704    unsafe {
1705        let f = &*(ud as *const F);
1706        guard((), || f(&CServer(srv)));
1707    }
1708}
1709
1710// ── ABI minor 10 — client-side trampolines ────────────────────────────────────
1711
1712unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
1713where
1714    F: Fn(&ClientTickEvent) + Send + Sync,
1715{
1716    unsafe {
1717        let f = &*(ud as *const F);
1718        guard((), || f(&ClientTickEvent {}));
1719    }
1720}
1721
1722unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1723where
1724    F: Fn(&GfxContext) + Send + Sync,
1725{
1726    unsafe {
1727        let f = &*(ud as *const F);
1728        guard((), || f(&GfxContext::from_raw(gfx)));
1729    }
1730}
1731
1732/// What `Registry::on_item_tooltip` keeps: the size and the draw closures.
1733struct TooltipPicture<S, D> {
1734    size: S,
1735    draw: D,
1736}
1737
1738unsafe extern "C" fn trampoline_tooltip_size<S, D>(ud: *mut c_void, item: YogStr, snbt: YogStr, w: *mut f32, h: *mut f32) -> bool
1739where
1740    S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync,
1741{
1742    unsafe {
1743        let t = &*(ud as *const TooltipPicture<S, D>);
1744        match guard(None, || (t.size)(item.as_str(), snbt.as_str())) {
1745            Some((pw, ph)) => {
1746                (*w, *h) = (pw, ph);
1747                true
1748            }
1749            None => false,
1750        }
1751    }
1752}
1753
1754unsafe extern "C" fn trampoline_item_text<F>(ud: *mut c_void, item: YogStr, snbt: YogStr, sink: *mut c_void, write: yog_abi::YogTextSink) -> bool
1755where
1756    F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync,
1757{
1758    unsafe {
1759        let f = &*(ud as *const F);
1760        let Some((name, lines)) = guard(None, || f(item.as_str(), snbt.as_str())) else { return false };
1761        let mut text = name.unwrap_or_default();
1762        for l in lines {
1763            text.push('\n');
1764            text.push_str(&l.replace('\n', " "));
1765        }
1766        write(sink, YogStr::from_str(&text));
1767        true
1768    }
1769}
1770
1771unsafe extern "C" fn trampoline_entity_tick<F>(ud: *mut c_void, srv: *const yog_abi::YogServer, uuid: YogStr)
1772where
1773    F: Fn(&dyn Server, &str) + Send + Sync,
1774{
1775    unsafe {
1776        let f = &*(ud as *const F);
1777        let s = CServer(srv);
1778        guard((), || f(&s, uuid.as_str()));
1779    }
1780}
1781
1782unsafe extern "C" fn trampoline_loot<F>(
1783    ud: *mut c_void,
1784    srv: *const yog_abi::YogServer,
1785    loot: *const u8,
1786    len: usize,
1787    sink: *mut c_void,
1788    write: yog_abi::YogBytesSink,
1789) where
1790    F: Fn(&dyn Server, &mut crate::loot::Loot) + Send + Sync,
1791{
1792    use yog_data::Schema;
1793    unsafe {
1794        let f = &*(ud as *const F);
1795        let Ok(mut loot) = yog_data::read_bytes::<crate::loot::Loot>(std::slice::from_raw_parts(loot, len)) else { return };
1796        let before = loot.items.clone();
1797        let s = CServer(srv);
1798        guard((), || f(&s, &mut loot));
1799        if loot.items != before {
1800            let bytes = yog_data::binary::encode(&loot.items.write());
1801            write(sink, bytes.as_ptr(), bytes.len());
1802        }
1803    }
1804}
1805
1806unsafe extern "C" fn trampoline_body<F>(ud: *mut c_void, frame: *const u8, len: usize, sink: *mut c_void, write: yog_abi::YogBytesSink)
1807where
1808    F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync,
1809{
1810    unsafe {
1811        let f = &*(ud as *const F);
1812        let Some(frame) = yog_body::Frame::decode(std::slice::from_raw_parts(frame, len)) else { return };
1813        let mut ops = yog_body::Ops::default();
1814        guard((), || f(&frame, &mut ops));
1815        if !ops.is_empty() {
1816            write(sink, ops.0.as_ptr(), ops.0.len());
1817        }
1818    }
1819}
1820
1821unsafe extern "C" fn trampoline_camera<F>(ud: *mut c_void, frame: *const yog_abi::YogCameraFrame, out: *mut yog_abi::YogCameraOut)
1822where
1823    F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync,
1824{
1825    unsafe {
1826        let f = &*(ud as *const F);
1827        let c = &*frame;
1828        let frame = yog_body::CameraFrame {
1829            uuid: c.uuid.as_str().to_owned(),
1830            partial: c.partial,
1831            pos: c.pos,
1832            yaw: c.yaw,
1833            pitch: c.pitch,
1834            detached: c.detached,
1835            flags: c.flags,
1836        };
1837        let o = &mut *out;
1838        let mut cam = yog_body::CameraOut {
1839            local_offset: yog_body::Vec3::from_array(o.local_offset),
1840            world_offset: yog_body::Vec3::from_array(o.world_offset),
1841            yaw: o.yaw,
1842            pitch: o.pitch,
1843            roll: o.roll,
1844            fov: o.fov,
1845        };
1846        guard((), || f(&frame, &mut cam));
1847        *o = yog_abi::YogCameraOut {
1848            local_offset: cam.local_offset.to_array(),
1849            world_offset: cam.world_offset.to_array(),
1850            yaw: cam.yaw,
1851            pitch: cam.pitch,
1852            roll: cam.roll,
1853            fov: cam.fov,
1854        };
1855    }
1856}
1857
1858unsafe extern "C" fn trampoline_travel<F>(ud: *mut c_void, frame: *const yog_abi::YogTravelFrame, velocity: *mut [f32; 3]) -> bool
1859where
1860    F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync,
1861{
1862    unsafe {
1863        let f = &*(ud as *const F);
1864        let t = &*frame;
1865        let frame = yog_body::TravelFrame {
1866            uuid: t.uuid.as_str().to_owned(),
1867            velocity: yog_body::Vec3::from_array(t.velocity),
1868            look: yog_body::Vec3::from_array(t.look),
1869            yaw: t.yaw,
1870            pitch: t.pitch,
1871            forward: t.forward,
1872            strafe: t.strafe,
1873            jumping: t.jumping,
1874            sneaking: t.sneaking,
1875            gravity: t.gravity,
1876            flags: t.flags,
1877        };
1878        match guard(None, || f(&frame)) {
1879            Some(v) => {
1880                *velocity = v.to_array();
1881                true
1882            }
1883            None => false,
1884        }
1885    }
1886}
1887
1888#[allow(clippy::too_many_arguments)]
1889unsafe extern "C" fn trampoline_tooltip_draw<S, D>(ud: *mut c_void, gfx: *const YogGfxApi, item: YogStr, snbt: YogStr, x: f32, y: f32, w: f32, h: f32)
1890where
1891    D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync,
1892{
1893    unsafe {
1894        let t = &*(ud as *const TooltipPicture<S, D>);
1895        guard((), || (t.draw)(&GfxContext::from_raw(gfx), item.as_str(), snbt.as_str(), [x, y, w, h]));
1896    }
1897}
1898
1899#[allow(clippy::too_many_arguments)]
1900unsafe extern "C" fn trampoline_book_page<F>(ud: *mut c_void, gfx: *const YogGfxApi, data: *const u8, len: usize, x: f32, y: f32, w: f32, h: f32)
1901where
1902    F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync,
1903{
1904    unsafe {
1905        let f = &*(ud as *const F);
1906        let bytes = if data.is_null() { &[][..] } else { std::slice::from_raw_parts(data, len) };
1907        let value = yog_data::binary::decode(bytes).unwrap_or(yog_data::Value::Null);
1908        guard((), || f(&GfxContext::from_raw(gfx), &value, [x, y, w, h]));
1909    }
1910}
1911
1912unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1913where
1914    F: Fn(&GfxContext) + Send + Sync,
1915{
1916    unsafe {
1917        let f = &*(ud as *const F);
1918        guard((), || f(&GfxContext::from_raw(gfx)));
1919    }
1920}
1921
1922unsafe extern "C" fn trampoline_key_press<F>(ud: *mut c_void, ev: *const YogKeyPressEvent) -> bool
1923where
1924    F: Fn(&KeyPressEvent) -> bool + Send + Sync,
1925{
1926    unsafe {
1927        let f = &*(ud as *const F);
1928        let e = &*ev;
1929        let ev = KeyPressEvent { key_code: e.key_code, scan_code: e.scan_code, action: e.action, modifiers: e.modifiers };
1930        guard(false, || f(&ev))
1931    }
1932}
1933
1934unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
1935where
1936    F: Fn(&ScreenEvent) + Send + Sync,
1937{
1938    unsafe {
1939        let f = &*(ud as *const F);
1940        let ev = ScreenEvent { screen_class: screen_class.as_str().to_owned() };
1941        guard((), || f(&ev));
1942        true
1943    }
1944}
1945
1946// ── Registry ─────────────────────────────────────────────────────────────────
1947
1948/// Wraps the [`YogApi`] table and provides an ergonomic registration API.
1949///
1950/// Obtained inside `yog_mod_register` via `export_mod!`.  Closures registered
1951/// here are boxed and leaked — they live as long as the process (which is the
1952/// correct lifetime for a server mod).
1953/// Pointer to the runtime's `YogApi` table (a process-lifetime static in the
1954/// runtime), captured when the mod registers. Lets free functions like
1955/// [`installed_mods`] work outside of `register()` — e.g. in UI handlers.
1956static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
1957    std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
1958
1959/// The runtime's `YogApi` table, null before this mod registered.
1960pub(crate) fn global_api() -> *mut YogApi {
1961    GLOBAL_API.load(std::sync::atomic::Ordering::Acquire)
1962}
1963
1964/// Metadata of an installed mod, as reported by the loader.
1965#[derive(Debug, Clone)]
1966pub struct ModInfo {
1967    /// `"yog"` for .yog mods, `"platform"` for loader (Java) mods.
1968    pub source: String,
1969    pub id: String,
1970    pub name: String,
1971    pub version: String,
1972    /// Comma-separated author list (may be empty).
1973    pub authors: String,
1974    pub description: String,
1975}
1976
1977/// All installed mods known to the loader: .yog mods plus, where the host
1978/// exposes them, platform (Java) mods. Callable at any time after this mod's
1979/// `register()` ran — including client-side UI handlers. Note that during
1980/// `register()` mods that load after this one are not in the list yet; query
1981/// lazily (e.g. on first render) for a complete view.
1982pub fn installed_mods() -> Vec<ModInfo> {
1983    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1984    if api.is_null() {
1985        return Vec::new();
1986    }
1987    let owned = unsafe { ((*api).mods_list)((*api).ctx) };
1988    let Some(text) = take_owned(unsafe { (*api).free_str }, owned) else {
1989        return Vec::new();
1990    };
1991    text.lines()
1992        .filter_map(|line| {
1993            let mut f = line.split('\t');
1994            Some(ModInfo {
1995                source: f.next()?.to_string(),
1996                id: f.next()?.to_string(),
1997                name: f.next().unwrap_or_default().to_string(),
1998                version: f.next().unwrap_or_default().to_string(),
1999                authors: f.next().unwrap_or_default().to_string(),
2000                description: f.next().unwrap_or_default().to_string(),
2001            })
2002        })
2003        .collect()
2004}
2005
2006/// Open the Yog UI registered as `ui_id` (client side; no-op on dedicated
2007/// servers). `modal` blocks game input, `pause` pauses singleplayer. Callable
2008/// from any handler after registration — e.g. an `on_client_packet` handler.
2009pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
2010    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2011    if api.is_null() {
2012        return;
2013    }
2014    unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
2015}
2016
2017/// Close the Yog UI screen showing `ui_id` — e.g. from that screen's own
2018/// "Done" button. Does nothing when another screen is open. Deferred onto the
2019/// render thread, so the handler that calls it finishes first. Client side;
2020/// a no-op on dedicated servers.
2021pub fn close_ui(ui_id: &str) {
2022    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2023    if api.is_null() {
2024        return;
2025    }
2026    unsafe { ((*api).ui_close)((*api).ctx, YogStr::from_str(ui_id)) }
2027}
2028
2029/// What a [`Registry::register_ui`] handler returns: `()` (never marks
2030/// events as used) or `bool` (`true` = the handler used this `key:`/`char:`
2031/// event — the host then doesn't, e.g. Esc doesn't close the screen).
2032pub trait UiHandled {
2033    fn handled(self) -> bool;
2034}
2035
2036impl UiHandled for () {
2037    fn handled(self) -> bool {
2038        false
2039    }
2040}
2041
2042impl UiHandled for bool {
2043    fn handled(self) -> bool {
2044        self
2045    }
2046}
2047
2048/// Handle to the runtime's server-action table, usable from any handler after
2049/// registration — including client-side ones that don't receive `&dyn Server`
2050/// (UI event handlers, client tick). Server-world actions are no-ops client
2051/// side, but networking (`send_to_server`) and similar client-safe calls work.
2052/// The server's table, if there's a server.
2053pub(crate) fn server_table() -> Option<&'static yog_abi::YogServer> {
2054    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2055    if api.is_null() {
2056        return None;
2057    }
2058    let srv = unsafe { (*api).server };
2059    (!srv.is_null()).then(|| unsafe { &*srv })
2060}
2061
2062pub fn server() -> Option<CServer> {
2063    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2064    if api.is_null() {
2065        return None;
2066    }
2067    let srv = unsafe { (*api).server };
2068    if srv.is_null() {
2069        return None;
2070    }
2071    Some(CServer(srv))
2072}
2073
2074pub struct Registry {
2075    api: *const YogApi,
2076}
2077
2078// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
2079unsafe impl Send for Registry {}
2080unsafe impl Sync for Registry {}
2081
2082impl Registry {
2083    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
2084    ///
2085    /// # Safety
2086    ///
2087    /// `api` must point to the runtime's `YogApi` table, valid for the rest of
2088    /// the process.
2089    pub unsafe fn from_raw(api: *const YogApi) -> Self {
2090        GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
2091        Self { api }
2092    }
2093
2094    #[inline]
2095    fn ctx(&self) -> *mut c_void {
2096        unsafe { (*self.api).ctx }
2097    }
2098
2099    // ── helpers ──────────────────────────────────────────────────────────────
2100
2101    fn leak<F: 'static>(f: F) -> *mut c_void {
2102        Box::into_raw(Box::new(f)) as *mut c_void
2103    }
2104
2105    // ── events ───────────────────────────────────────────────────────────────
2106    //
2107    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
2108    // In `Pre` phase, returning `false` cancels the action.
2109    // In `Post` phase, the return value is ignored.
2110    // A single registration fires for BOTH phases.
2111
2112    pub fn on_block_break<F>(&mut self, handler: F)
2113    where
2114        F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2115    {
2116        let ud = Self::leak(handler);
2117        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
2118    }
2119
2120    pub fn on_chat<F>(&mut self, handler: F)
2121    where
2122        F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2123    {
2124        let ud = Self::leak(handler);
2125        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
2126    }
2127
2128    pub fn on_player_join<F>(&mut self, handler: F)
2129    where
2130        F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2131    {
2132        let ud = Self::leak(handler);
2133        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
2134    }
2135
2136    pub fn on_player_leave<F>(&mut self, handler: F)
2137    where
2138        F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2139    {
2140        let ud = Self::leak(handler);
2141        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
2142    }
2143
2144    pub fn on_use_item<F>(&mut self, handler: F)
2145    where
2146        F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2147    {
2148        let ud = Self::leak(handler);
2149        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
2150    }
2151
2152    pub fn on_use_block<F>(&mut self, handler: F)
2153    where
2154        F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2155    {
2156        let ud = Self::leak(handler);
2157        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
2158    }
2159
2160    pub fn on_attack_entity<F>(&mut self, handler: F)
2161    where
2162        F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2163    {
2164        let ud = Self::leak(handler);
2165        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
2166    }
2167
2168    pub fn on_entity_damage<F>(&mut self, handler: F)
2169    where
2170        F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2171    {
2172        let ud = Self::leak(handler);
2173        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
2174    }
2175
2176    pub fn on_entity_death<F>(&mut self, handler: F)
2177    where
2178        F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2179    {
2180        let ud = Self::leak(handler);
2181        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
2182    }
2183
2184    pub fn on_entity_spawn<F>(&mut self, handler: F)
2185    where
2186        F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2187    {
2188        let ud = Self::leak(handler);
2189        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
2190    }
2191
2192    pub fn on_player_place_block<F>(&mut self, handler: F)
2193    where
2194        F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2195    {
2196        let ud = Self::leak(handler);
2197        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
2198    }
2199
2200    pub fn on_player_death<F>(&mut self, handler: F)
2201    where
2202        F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2203    {
2204        let ud = Self::leak(handler);
2205        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
2206    }
2207
2208    pub fn on_player_respawn<F>(&mut self, handler: F)
2209    where
2210        F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2211    {
2212        let ud = Self::leak(handler);
2213        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
2214    }
2215
2216    pub fn on_advancement<F>(&mut self, handler: F)
2217    where
2218        F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2219    {
2220        let ud = Self::leak(handler);
2221        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
2222    }
2223
2224    pub fn on_entity_interact<F>(&mut self, handler: F)
2225    where
2226        F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2227    {
2228        let ud = Self::leak(handler);
2229        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
2230    }
2231
2232    pub fn on_item_craft<F>(&mut self, handler: F)
2233    where
2234        F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2235    {
2236        let ud = Self::leak(handler);
2237        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
2238    }
2239
2240    /// A machine making a recipe — see [`MachineCraftEvent`].
2241    pub fn on_machine_craft<F>(&mut self, handler: F)
2242    where
2243        F: Fn(&MachineCraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2244    {
2245        let ud = Self::leak(handler);
2246        unsafe { ((*self.api).on_machine_craft)(self.ctx(), ud, trampoline_machine_craft::<F>) }
2247    }
2248
2249    pub fn on_explosion<F>(&mut self, handler: F)
2250    where
2251        F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2252    {
2253        let ud = Self::leak(handler);
2254        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
2255    }
2256
2257    /// A hopper or dropper offers an item to a block that accepts items
2258    /// (`block.accepts_items`): `Pre` returns whether it takes it, `Post`
2259    /// takes it.
2260    pub fn on_item_insert<F>(&mut self, handler: F)
2261    where
2262        F: Fn(&ItemInsertEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2263    {
2264        let ud = Self::leak(handler);
2265        unsafe { ((*self.api).on_item_insert)(self.ctx(), ud, trampoline_item_insert::<F>) }
2266    }
2267
2268    pub fn on_item_pickup<F>(&mut self, handler: F)
2269    where
2270        F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2271    {
2272        let ud = Self::leak(handler);
2273        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
2274    }
2275
2276    pub fn on_player_move<F>(&mut self, handler: F)
2277    where
2278        F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2279    {
2280        let ud = Self::leak(handler);
2281        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
2282    }
2283
2284    pub fn on_container_open<F>(&mut self, handler: F)
2285    where
2286        F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2287    {
2288        let ud = Self::leak(handler);
2289        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
2290    }
2291
2292    pub fn on_container_close<F>(&mut self, handler: F)
2293    where
2294        F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2295    {
2296        let ud = Self::leak(handler);
2297        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
2298    }
2299
2300    pub fn on_projectile_hit<F>(&mut self, handler: F)
2301    where
2302        F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2303    {
2304        let ud = Self::leak(handler);
2305        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
2306    }
2307
2308    pub fn on_tick<F>(&mut self, listener: F)
2309    where
2310        F: Fn(&dyn Server) + Send + Sync + 'static,
2311    {
2312        let ud = Self::leak(listener);
2313        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
2314    }
2315
2316    pub fn on_server_started<F>(&mut self, listener: F)
2317    where
2318        F: Fn(&dyn Server) + Send + Sync + 'static,
2319    {
2320        let ud = Self::leak(listener);
2321        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
2322    }
2323
2324    pub fn on_server_stopping<F>(&mut self, listener: F)
2325    where
2326        F: Fn(&dyn Server) + Send + Sync + 'static,
2327    {
2328        let ud = Self::leak(listener);
2329        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
2330    }
2331
2332    // ── commands ─────────────────────────────────────────────────────────────
2333
2334    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
2335    where
2336        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
2337    {
2338        let name_ys = YogStr::from_str(name.as_ref());
2339        let ud = Self::leak(handler);
2340        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
2341    }
2342
2343    /// Register a command with Brigadier-typed arguments.
2344    ///
2345    /// `schema` is a space-separated list of argument types:
2346    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
2347    ///
2348    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
2349    pub fn on_typed_command<F>(
2350        &mut self,
2351        name: impl AsRef<str>,
2352        schema: impl AsRef<str>,
2353        handler: F,
2354    ) where
2355        F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
2356    {
2357        let name_ys = YogStr::from_str(name.as_ref());
2358        let schema_ys = YogStr::from_str(schema.as_ref());
2359        let ud = Self::leak(handler);
2360        unsafe {
2361            ((*self.api).register_typed_command)(
2362                self.ctx(),
2363                name_ys,
2364                schema_ys,
2365                ud,
2366                trampoline_command::<F>,
2367            );
2368        }
2369    }
2370
2371    // ── networking ───────────────────────────────────────────────────────────
2372
2373    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2374    where
2375        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2376    {
2377        let ch = YogStr::from_str(channel.as_ref());
2378        let ud = Self::leak(handler);
2379        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2380    }
2381
2382    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2383    where
2384        F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2385    {
2386        let ch = YogStr::from_str(channel.as_ref());
2387        let ud = Self::leak(handler);
2388        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2389    }
2390
2391    /// Register a typed-packet handler.
2392    ///
2393    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
2394    /// Malformed payloads are silently dropped.
2395    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
2396    where
2397        P: Packet + Send + Sync + 'static,
2398        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
2399    {
2400        self.on_packet(channel, move |ev, srv| {
2401            if let Some(pkt) = P::decode(&ev.payload) {
2402                handler(&pkt, srv);
2403            }
2404        });
2405    }
2406
2407    // ── content ──────────────────────────────────────────────────────────────
2408
2409    /// Hands the runtime content of `kind` named `id`: its definition as a
2410    /// Yog Data blob (`register_content`, one ABI call for every kind).
2411    fn content(&mut self, kind: &str, id: &str, def: &yog_data::Value) {
2412        let blob = yog_data::binary::encode(def);
2413        unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str(kind), YogStr::from_str(id), blob.as_ptr(), blob.len()) }
2414    }
2415
2416    /// JSON text as Yog Data (recipes and books are written as JSON by their crates).
2417    fn json(text: &str) -> yog_data::Value {
2418        yog_data::json::from_json_text(text).unwrap_or_default()
2419    }
2420
2421    /// Register a recipe (Yog's format — see [`Recipe`]; the host writes
2422    /// its Minecraft version's JSON).
2423    pub fn add_recipe(&mut self, recipe: Recipe) {
2424        let (ns, name) = recipe.ns_name();
2425        let id = format!("{ns}:{name}");
2426        self.content("recipe", &id, &recipe.to_data());
2427    }
2428
2429    /// Register a recipe given as Yog's recipe JSON — how `yog build`
2430    /// registers the mod's own kinds (`{"type": "mymod:infusion", "slots":
2431    /// …}`), which stay the mod's data: for its machines and its books.
2432    pub fn add_recipe_json(&mut self, id: &str, json: &str) {
2433        let id = if id.contains(':') { id.to_string() } else { format!("minecraft:{id}") };
2434        self.content("recipe", &id, &Self::json(json));
2435    }
2436
2437    pub fn register_item(&mut self, def: ItemDef) {
2438        use yog_data::Schema;
2439        self.content("item", &def.id.clone(), &def.write());
2440    }
2441
2442    pub fn register_block(&mut self, def: BlockDef) {
2443        use yog_data::Schema;
2444        self.content("block", &def.id.clone(), &def.write());
2445    }
2446
2447    /// Registers an entity type (what a `.yentity` makes). With a `model`,
2448    /// the loader draws it — its body model with the clip of its state
2449    /// (`clips`: `idle`, `walk`, or entity data `yog:state`).
2450    pub fn register_entity(&mut self, def: yog_registry::EntityDef) {
2451        use yog_data::Schema;
2452        if crate::entities::drawn(&def) {
2453            self.on_body(crate::entities::draw);
2454        }
2455        self.content("entity", &def.id.clone(), &def.write());
2456    }
2457
2458    /// A compiled `.yentity` (the generated code calls this).
2459    pub fn register_entity_data(&mut self, id: &str, data: &[u8]) {
2460        match yog_data::read_bytes::<yog_registry::EntityDef>(data) {
2461            Ok(def) => self.register_entity(def),
2462            Err(e) => crate::error!("entity {id}: {e:?}"),
2463        }
2464    }
2465
2466    /// Calls `handler` each tick of every entity of `entity_type` (a mod's
2467    /// type with `ticks`), with its uuid — server side.
2468    pub fn on_entity_tick<F>(&mut self, entity_type: &str, handler: F)
2469    where
2470        F: Fn(&dyn Server, &str) + Send + Sync + 'static,
2471    {
2472        let ud = Self::leak(handler);
2473        unsafe { ((*self.api).on_entity_tick)(self.ctx(), YogStr::from_str(entity_type), ud, trampoline_entity_tick::<F>) }
2474    }
2475
2476    /// Add placed feature `feature` (the mod's datapack's, like an ore's
2477    /// `mymod:mythril_ore`) to the biomes of tag `biomes` at generation
2478    /// `step` (`underground_ores`). An ore `.ydef` does this for you.
2479    pub fn add_biome_feature(&mut self, feature: &str, biomes: &str, step: &str) {
2480        use yog_data::Schema;
2481        let f = yog_registry::BiomeFeature { feature: feature.into(), biomes: biomes.into(), step: step.into() };
2482        self.content("biome_feature", feature, &f.write());
2483    }
2484
2485    /// Register a tool material — what tool items naming it
2486    /// (`ItemDef::tool`) are made of. The game's are built in.
2487    pub fn register_tool_material(&mut self, material: ToolMaterial) {
2488        use yog_data::Schema;
2489        self.content("tool_material", &material.id.clone(), &material.write());
2490    }
2491
2492    /// Register an armor material — what armor items naming it
2493    /// (`ItemDef::armor`) are made of. The game's are built in.
2494    pub fn register_armor_material(&mut self, material: ArmorMaterial) {
2495        use yog_data::Schema;
2496        self.content("armor_material", &material.id.clone(), &material.write());
2497    }
2498
2499    /// Register a sound group — what blocks naming it (`BlockDef::sound`)
2500    /// sound like. The game's groups are built in.
2501    pub fn register_sound_group(&mut self, group: SoundGroup) {
2502        use yog_data::Schema;
2503        self.content("sound_group", &group.id.clone(), &group.write());
2504    }
2505
2506    /// Register a real Container/Menu inventory screen — see
2507    /// `yog_inventory::InventoryDef`. Attach it to a block via
2508    /// `BlockDef::inventory(id)`.
2509    pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
2510        use yog_data::Schema;
2511        self.content("inventory", &def.id.clone(), &def.write());
2512    }
2513
2514    // ── startup grants ───────────────────────────────────────────────────────
2515
2516    /// Register a startup grant: items/books to give once when a player first joins.
2517    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
2518        use yog_data::Schema;
2519        self.content("startup_grant", &grant.id.clone(), &grant.write());
2520    }
2521
2522    /// Register a book.
2523    pub fn register_book(&mut self, book: &Book) {
2524        use yog_data::Schema;
2525        self.content("book", &book.id, &book.write());
2526    }
2527
2528    /// Register a book given as Yog Data, binary — what `yog build` makes
2529    /// of a `content/**/*.ybook`; the bytes go to the runtime as they are.
2530    pub fn register_book_data(&mut self, id: &str, data: &[u8]) {
2531        unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("book"), YogStr::from_str(id), data.as_ptr(), data.len()) }
2532    }
2533
2534    /// Register (or patch) a dimension's *type* (sky, lighting, physics, ...).
2535    ///
2536    /// `def.id` need not be a new custom id — pass a vanilla id
2537    /// (`"minecraft:overworld"`, `"minecraft:the_nether"`,
2538    /// `"minecraft:the_end"`) to patch that dimension's type/generator
2539    /// instead of creating a new one (e.g. to replace the overworld's
2540    /// terrain with your own generator type).
2541    ///
2542    /// Must be called from `register()` — the same mod-init window as
2543    /// blocks/items — so the host can declare/patch the dimension before
2544    /// Minecraft's registries freeze. See the `yog-dimensions` crate docs;
2545    /// set `def.generator_type` (via [`YogDimensionDef::generator_type`]) to
2546    /// pick a generator type registered with
2547    /// [`Registry::register_chunk_generator`].
2548    pub fn register_dimension(&mut self, def: &YogDimensionDef) {
2549        let value = Self::json(&def.to_json());
2550        self.content("dimension", &def.id.clone(), &value);
2551    }
2552
2553    /// Register a dimension given as Yog Data, binary — what `yog build`
2554    /// makes of a `.ydef` of kind `dimension` (its `terrain` generated by
2555    /// the runtime itself); the bytes go to the runtime as they are.
2556    pub fn register_dimension_data(&mut self, id: &str, data: &[u8]) {
2557        unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("dimension"), YogStr::from_str(id), data.as_ptr(), data.len()) }
2558    }
2559
2560    /// Register a named, reusable chunk-generator type under
2561    /// `generator_type_id` (e.g. `"mymod:custom_terrain"`) — analogous to
2562    /// how Biomes O'Plenty registers selectable world-type presets. Any
2563    /// dimension (custom or vanilla) can opt into this generator via
2564    /// [`YogDimensionDef::generator_type`]; the platform host may also list
2565    /// registered generator type ids as options on the world-creation screen.
2566    ///
2567    /// `generator` is called once per chunk column that needs generating in
2568    /// any dimension using this type — write whatever terrain logic you want
2569    /// using the [`ChunkWriter`] handle (combine as many noise layers as you
2570    /// like; this crate doesn't prescribe a generation style).
2571    pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
2572    where
2573        F: Fn(&ChunkWriter) + Send + Sync + 'static,
2574    {
2575        let ud = Self::leak(generator);
2576        let id = YogStr::from_str(generator_type_id);
2577        unsafe {
2578            ((*self.api).register_chunk_generator)(
2579                self.ctx(),
2580                id,
2581                ud,
2582                trampoline_chunk_generator::<F>,
2583            );
2584        }
2585    }
2586
2587    /// Register a menu entry — the host renders a button on vanilla screens
2588    /// (TitleScreen on Fabric, ModListScreen on Forge/NeoForge) that opens `ui_id`.
2589    /// `label` is the human-readable button text (e.g. "Yog Mods").
2590    /// `ui_id` is the UI to open when clicked (e.g. "example:menu").
2591    /// See [`installed_mods`]. During `register()` the list only contains
2592    /// mods loaded before this one.
2593    pub fn installed_mods(&self) -> Vec<ModInfo> {
2594        installed_mods()
2595    }
2596
2597    /// Access the inter-mod communication layer.
2598    ///
2599    /// Allows exporting function pointers from this mod and importing
2600    /// function pointers from other mods.
2601    pub fn interop(&self) -> crate::Interop {
2602        crate::Interop::new(self.api)
2603    }
2604
2605    /// Raw `YogApi` pointer — for passing to interop-imported functions
2606    /// that register content through the C ABI table directly.
2607    pub fn raw_api(&self) -> *const yog_abi::YogApi {
2608        self.api
2609    }
2610
2611    pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
2612        let l = YogStr::from_str(label);
2613        let u = YogStr::from_str(ui_id);
2614        unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
2615    }
2616
2617    /// Register a UI tree with an event callback.
2618    /// `ui_id` is the unique identifier (e.g. "mymod:menu").
2619    /// `handler` receives `(ui_id, event)` for the screen's input: `click:X:Y`,
2620    /// `drag:X:Y`, `release:X:Y`, `scroll:DY`, `key:KEY:MODS` (GLFW key code
2621    /// and modifier bits, on press and repeat) and `char:CODEPOINT` (a typed
2622    /// character). It returns `()` or a `bool`: `true` marks a `key:`/`char:`
2623    /// event as used, so the host skips its default handling of the key
2624    /// (Esc closing the screen, ...) — see [`UiHandled`].
2625    pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
2626    where
2627        F: Fn(&str, &str) -> R + Send + Sync + 'static,
2628        R: UiHandled,
2629    {
2630        let ud = Self::leak(handler);
2631        let id = YogStr::from_str(ui_id);
2632        let empty = YogStr::from_str("{}");
2633        unsafe { ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F, R>) }
2634    }
2635
2636    // ── scheduler ────────────────────────────────────────────────────────────
2637
2638    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
2639    where
2640        F: Fn(&dyn Server) + Send + Sync + 'static,
2641    {
2642        let ud = Self::leak(handler);
2643        unsafe {
2644            ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>);
2645        }
2646    }
2647
2648    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
2649    where
2650        F: Fn(&dyn Server) + Send + Sync + 'static,
2651    {
2652        let ud = Self::leak(handler);
2653        unsafe {
2654            ((*self.api).schedule_repeating)(
2655                self.ctx(),
2656                period_ticks,
2657                ud,
2658                trampoline_scheduled::<F>,
2659            );
2660        }
2661    }
2662
2663    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
2664
2665    /// Register a handler called every client tick (render thread, no server).
2666    pub fn on_client_tick<F>(&mut self, handler: F)
2667    where
2668        F: Fn(&ClientTickEvent) + Send + Sync + 'static,
2669    {
2670        let ud = Self::leak(handler);
2671        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
2672    }
2673
2674    /// Register a render callback for a specific UI screen (`ui_id`).
2675    ///
2676    /// Called from `YogUIScreen.render()` — i.e. AFTER `renderBackground()` darkens
2677    /// the screen — so your UI draws on top of the dimmed world view.
2678    /// Use this instead of `on_hud_render` for book/inventory screens.
2679    ///
2680    /// Clicks are forwarded as `"click:X:Y"` via the `register_ui` handler so you
2681    /// can do hit-testing on your own stored layout.
2682    pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
2683    where
2684        F: Fn(&GfxContext) + Send + Sync + 'static,
2685    {
2686        let ud = Self::leak(handler);
2687        let id = YogStr::from_str(ui_id);
2688        unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
2689    }
2690
2691    /// Draw the pictures of book pages of type `drawn` whose `drawer` is
2692    /// `drawer` (`"mymod:diagram"`): `handler` gets the page's `data` and
2693    /// the picture's box `[x, y, w, h]` in screen pixels, every frame the
2694    /// page is open.
2695    ///
2696    /// ```text
2697    /// { type: "drawn", drawer: "mymod:diagram", title: "Wiring", height: 64,
2698    ///   caption: "{u|in} → {u|out}", text: "…", data: { nodes: 3 } }
2699    /// ```
2700    pub fn on_book_page<F>(&mut self, drawer: &str, handler: F)
2701    where
2702        F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync + 'static,
2703    {
2704        let ud = Self::leak(handler);
2705        let id = YogStr::from_str(drawer);
2706        unsafe { ((*self.api).on_book_page)(self.ctx(), id, ud, trampoline_book_page::<F>) }
2707    }
2708
2709    /// A picture under the tooltips of `item` (as Hex Casting's scrolls show
2710    /// their pattern): `size(item, snbt)` says whether a stack — its custom
2711    /// data as SNBT — gets one and its size in GUI pixels; `draw` draws it
2712    /// in the box `[x, y, w, h]` (screen pixels) while the tooltip shows.
2713    pub fn on_item_tooltip<S, D>(&mut self, item: &str, size: S, draw: D)
2714    where
2715        S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync + 'static,
2716        D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync + 'static,
2717    {
2718        let ud = Self::leak(TooltipPicture { size, draw });
2719        let id = YogStr::from_str(item);
2720        unsafe {
2721            ((*self.api).on_item_tooltip)(self.ctx(), id, ud, trampoline_tooltip_size::<S, D>, trampoline_tooltip_draw::<S, D>)
2722        }
2723    }
2724
2725    /// A stack's own name and tooltip, from its custom data (SNBT): `text(item,
2726    /// snbt)` gives `Some((name, lines))` — `name` `None` keeps the item's;
2727    /// `lines` stand instead of the item's description — or `None` for the
2728    /// item's own. A written blueprint names its design:
2729    ///
2730    /// ```ignore
2731    /// registry.on_item_text(Items::BLUEPRINT, |_, snbt| {
2732    ///     let bp = Blueprint::from_nbt(snbt)?;
2733    ///     Some((Some(bp.name.clone()), vec![format!("{} ports", bp.ports)]))
2734    /// });
2735    /// ```
2736    pub fn on_item_text<F>(&mut self, item: &str, text: F)
2737    where
2738        F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync + 'static,
2739    {
2740        let ud = Self::leak(text);
2741        let id = YogStr::from_str(item);
2742        unsafe { ((*self.api).on_item_text)(self.ctx(), id, ud, trampoline_item_text::<F>) }
2743    }
2744
2745    /// A compiled `.ymodel` (the generated code calls this): found later by
2746    /// [`crate::body::model`].
2747    pub fn register_body_model(&mut self, id: &str, data: &[u8]) {
2748        if let Err(e) = yog_body::register_model(id, data) {
2749            crate::error!("{e}");
2750        }
2751    }
2752
2753    /// A compiled `.yanim` (the generated code calls this): its clips found
2754    /// later by [`crate::body::clip`].
2755    pub fn register_body_anims(&mut self, id: &str, data: &[u8]) {
2756        if let Err(e) = yog_body::register_anims(id, data) {
2757            crate::error!("{e}");
2758        }
2759    }
2760
2761    /// Register a handler asked each frame about the body of every tracked
2762    /// entity ([`crate::body::track`]): it answers with [`crate::body::Ops`]
2763    /// — pose vanilla parts, turn the body, draw meshes (rigs, morphs),
2764    /// disguise, hide, capture. Every mod's ops add up.
2765    pub fn on_body<F>(&mut self, handler: F)
2766    where
2767        F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync + 'static,
2768    {
2769        let ud = Self::leak(handler);
2770        unsafe { ((*self.api).on_body)(self.ctx(), ud, trampoline_body::<F>) }
2771    }
2772
2773    /// Register a handler that sees every loot table rolled — a chest's, a
2774    /// block's drops, a mob's — and may change what it gives
2775    /// ([`crate::loot::Loot::items`]); each handler sees the one before's
2776    /// result. Server side. For adding and scaling items on some tables, a
2777    /// [`crate::loot::LootModifier`] says it as data.
2778    pub fn on_loot<F>(&mut self, handler: F)
2779    where
2780        F: Fn(&dyn Server, &mut crate::loot::Loot) + Send + Sync + 'static,
2781    {
2782        let ud = Self::leak(handler);
2783        unsafe { ((*self.api).on_loot)(self.ctx(), ud, trampoline_loot::<F>) }
2784    }
2785
2786    /// Change the loot of some tables as `modifier` says.
2787    pub fn add_loot_modifier(&mut self, modifier: crate::loot::LootModifier) {
2788        self.on_loot(move |srv, loot| crate::loot::apply(srv, &modifier, loot));
2789    }
2790
2791    /// Register an attribute every player has — what effects change and
2792    /// [`Server::entity_attribute_value`] reads.
2793    pub fn register_attribute(&mut self, def: yog_registry::AttributeDef) {
2794        use yog_data::Schema;
2795        self.content("attribute", &def.id.clone(), &def.write());
2796    }
2797
2798    /// Register a mob effect.
2799    pub fn register_effect(&mut self, def: yog_registry::EffectDef) {
2800        use yog_data::Schema;
2801        self.content("effect", &def.id.clone(), &def.write());
2802    }
2803
2804    /// Register a potion and its brewing.
2805    pub fn register_potion(&mut self, def: yog_registry::PotionDef) {
2806        use yog_data::Schema;
2807        self.content("potion", &def.id.clone(), &def.write());
2808    }
2809
2810    /// A compiled `.ydef` the game registers (`attribute`, `effect`,
2811    /// `potion`) — the generated code calls this.
2812    pub fn register_definition_data(&mut self, kind: &str, id: &str, data: &[u8]) {
2813        unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str(kind), YogStr::from_str(id), data.as_ptr(), data.len()) }
2814    }
2815
2816    /// A compiled `.ydef` loot modifier (the generated code calls this).
2817    pub fn add_loot_modifier_data(&mut self, id: &str, data: &[u8]) {
2818        match yog_data::read_bytes::<crate::loot::LootModifier>(data) {
2819            Ok(m) => self.add_loot_modifier(m),
2820            Err(e) => crate::error!("loot modifier {id}: {e:?}"),
2821        }
2822    }
2823
2824    /// Register loot function `name` (`mymod:pattern_scroll`): what loot
2825    /// modifiers naming it (`function`) run on each stack they add — set
2826    /// its custom data, say.
2827    pub fn loot_function<F>(&mut self, name: &str, function: F)
2828    where
2829        F: Fn(&dyn Server, &mut crate::loot::LootStack, &crate::loot::Loot) + Send + Sync + 'static,
2830    {
2831        if let Ok(mut f) = crate::loot::functions().write() {
2832            f.insert(name.to_string(), std::sync::Arc::new(function));
2833        }
2834    }
2835
2836    /// Register a handler that moves, turns or zooms the camera each frame;
2837    /// it changes the [`crate::body::CameraOut`] the handlers before it made.
2838    pub fn on_camera<F>(&mut self, handler: F)
2839    where
2840        F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync + 'static,
2841    {
2842        let ud = Self::leak(handler);
2843        unsafe { ((*self.api).on_camera)(self.ctx(), ud, trampoline_camera::<F>) }
2844    }
2845
2846    /// Register a handler that may replace the local player's travel step:
2847    /// `Some(velocity)` (blocks per tick, drag applied) moves the player by
2848    /// it instead of vanilla's walking, swimming, falling or gliding.
2849    pub fn on_travel<F>(&mut self, handler: F)
2850    where
2851        F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync + 'static,
2852    {
2853        let ud = Self::leak(handler);
2854        unsafe { ((*self.api).on_travel)(self.ctx(), ud, trampoline_travel::<F>) }
2855    }
2856
2857    /// Register a handler called every frame when the HUD is rendered.
2858    ///
2859    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
2860    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
2861    pub fn on_hud_render<F>(&mut self, handler: F)
2862    where
2863        F: Fn(&GfxContext) + Send + Sync + 'static,
2864    {
2865        let ud = Self::leak(handler);
2866        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
2867    }
2868
2869    /// Register a handler called every frame at the end of world rendering.
2870    ///
2871    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
2872    /// `gfx.camera_pos()` is the camera world position.
2873    /// To render at world position P, translate by `P - camera_pos` before drawing.
2874    pub fn on_world_render<F>(&mut self, handler: F)
2875    where
2876        F: Fn(&GfxContext) + Send + Sync + 'static,
2877    {
2878        let ud = Self::leak(handler);
2879        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
2880    }
2881
2882    /// Register a handler for keyboard input (client-side).
2883    /// Return `false` to prevent Minecraft from processing the key.
2884    pub fn on_key_press<F>(&mut self, handler: F)
2885    where
2886        F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static,
2887    {
2888        let ud = Self::leak(handler);
2889        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
2890    }
2891
2892    /// Register a handler called when a GUI screen is opened.
2893    pub fn on_screen_open<F>(&mut self, handler: F)
2894    where
2895        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2896    {
2897        let ud = Self::leak(handler);
2898        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
2899    }
2900
2901    /// Register a handler called when a GUI screen is closed.
2902    pub fn on_screen_close<F>(&mut self, handler: F)
2903    where
2904        F: Fn(&ScreenEvent) + Send + Sync + 'static,
2905    {
2906        let ud = Self::leak(handler);
2907        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
2908    }
2909}
2910
2911unsafe extern "C" fn trampoline_ui_event<F, R>(
2912    ud: *mut c_void,
2913    ui_id: yog_abi::YogStr,
2914    event_id: yog_abi::YogStr,
2915) where
2916    F: Fn(&str, &str) -> R + Send + Sync + 'static,
2917    R: UiHandled,
2918{
2919    let f = unsafe { &*(ud as *const F) };
2920    let ui = unsafe { ui_id.as_str() };
2921    let ev = unsafe { event_id.as_str() };
2922    if guard(false, || f(ui, ev).handled()) {
2923        let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2924        if !api.is_null() {
2925            unsafe { ((*api).ui_consume_event)((*api).ctx) }
2926        }
2927    }
2928}
2929
2930unsafe extern "C" fn trampoline_chunk_generator<F>(
2931    ud: *mut c_void,
2932    writer: *const yog_abi::YogChunkWriterApi,
2933) where
2934    F: Fn(&ChunkWriter) + Send + Sync + 'static,
2935{
2936    let f = unsafe { &*(ud as *const F) };
2937    let writer = unsafe { ChunkWriter::from_raw(writer) };
2938    guard((), || f(&writer));
2939}
2940
2941// ── Mod trait ─────────────────────────────────────────────────────────────────
2942
2943/// Implemented by every Yog mod. Called once at startup to register handlers.
2944pub trait Mod {
2945    fn register(registry: &mut Registry);
2946}
2947
2948#[cfg(test)]
2949mod trampoline_tests {
2950    use super::*;
2951    use yog_abi::{YogBlockPos, YogChatEvent, YogCommandEvent, YogPacketEvent};
2952
2953    fn ud<F>(f: &F) -> *mut c_void {
2954        f as *const F as *mut c_void
2955    }
2956
2957    fn chat<F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogChatEvent, phase: u8) -> bool {
2958        unsafe { trampoline_chat::<F>(ud(f), std::ptr::null(), ev, phase) }
2959    }
2960
2961    fn block_break<F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogBlockBreakEvent) -> bool {
2962        unsafe { trampoline_block_break::<F>(ud(f), std::ptr::null(), ev, 0) }
2963    }
2964
2965    fn command<F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync>(f: &F, ev: &YogCommandEvent, buf: &mut [u8]) -> u32 {
2966        let mut len = 99u32;
2967        unsafe { trampoline_command::<F>(ud(f), std::ptr::null(), ev, buf.as_mut_ptr(), buf.len() as u32, &mut len) };
2968        len
2969    }
2970
2971    fn packet<F: Fn(&PacketEvent, &dyn Server) + Send + Sync>(f: &F, ev: &YogPacketEvent) {
2972        unsafe { trampoline_packet::<F>(ud(f), std::ptr::null(), ev) }
2973    }
2974
2975    #[test]
2976    fn events_arrive_converted_with_their_phase() {
2977        let seen = std::sync::Mutex::new(Vec::new());
2978        let handler = |ev: &ChatEvent, p: EventPhase, _: &dyn Server| {
2979            seen.lock().unwrap().push((ev.player_name.clone(), ev.message.clone(), p));
2980            ev.message != "cancel me"
2981        };
2982        let ev = |msg: &'static str| YogChatEvent {
2983            player: YogStr::from_str("Dev"),
2984            message: YogStr::from_str(msg),
2985            dimension: YogStr::from_str("minecraft:overworld"),
2986        };
2987        assert!(chat(&handler, &ev("hi"), 0));
2988        assert!(!chat(&handler, &ev("cancel me"), 0));
2989        chat(&handler, &ev("after"), 1);
2990        let seen = seen.into_inner().unwrap();
2991        assert_eq!(seen[0], ("Dev".into(), "hi".into(), EventPhase::Pre));
2992        assert_eq!(seen[2].2, EventPhase::Post);
2993    }
2994
2995    #[test]
2996    fn a_panicking_handler_allows_the_action() {
2997        let handler = |_: &BlockBreakEvent, _: EventPhase, _: &dyn Server| -> bool { panic!("mod bug") };
2998        let ev = YogBlockBreakEvent {
2999            player: YogStr::from_str("Dev"),
3000            block: YogStr::from_str("minecraft:stone"),
3001            pos: YogBlockPos { x: 1, y: 2, z: 3 },
3002            dimension: YogStr::from_str("minecraft:overworld"),
3003        };
3004        assert!(block_break(&handler, &ev));
3005    }
3006
3007    #[test]
3008    fn command_replies_fit_the_buffer_whole_characters_only() {
3009        let handler = |c: &CommandContext, _: &dyn Server| Some(format!("{}: привет", c.args));
3010        let ev = YogCommandEvent {
3011            name: YogStr::from_str("hex"),
3012            args: YogStr::from_str("x"),
3013            source: YogStr::from_str("Dev"),
3014            uuid: YogStr::EMPTY,
3015            dimension: YogStr::EMPTY,
3016        };
3017        let mut buf = [0u8; 6];
3018        let len = command(&handler, &ev, &mut buf);
3019        // "x: п" is 5 bytes; "пр" wouldn't fit whole in 6.
3020        assert_eq!(std::str::from_utf8(&buf[..len as usize]), Ok("x: п"));
3021    }
3022
3023    #[test]
3024    fn empty_packets_need_no_payload_pointer() {
3025        let got = std::sync::Mutex::new(None);
3026        let handler = |p: &PacketEvent, _: &dyn Server| *got.lock().unwrap() = Some(p.payload.len());
3027        let ev = YogPacketEvent {
3028            channel: YogStr::from_str("mymod:ping"),
3029            player: YogStr::from_str("Dev"),
3030            payload: std::ptr::null(),
3031            payload_len: 0,
3032        };
3033        packet(&handler, &ev);
3034        assert_eq!(*got.lock().unwrap(), Some(0));
3035    }
3036}