Skip to main content

yog_api/
registry.rs

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