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, YogItemDef, YogItemPickupEvent, YogKeyPressEvent,
17    YogPacketEvent, YogPlaceBlockEvent, YogPlayerDeathEvent, YogPlayerEvent, YogPlayerMoveEvent,
18    YogPlayerRespawnEvent, YogProjectileHitEvent, YogServer, YogStr, YogStartupGrantDef,
19    YogUseBlockEvent, YogUseItemEvent,
20};
21use yog_book::Book;
22use yog_gfx::GfxContext;
23use yog_command::CommandContext;
24use yog_core::Server;
25use yog_event::{
26    AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
27    ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
28    EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent,
29    ItemPickupEvent, KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent,
30    PlayerLeaveEvent, PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent,
31    UseBlockEvent, UseItemEvent,
32};
33use yog_network::{Packet, PacketEvent};
34use yog_registry::{BlockDef, FurnaceRecipe, ItemDef, ShapedRecipe, ShapelessRecipe, StartupGrant};
35
36// ── CServer — implements Server via the YogServer function table ──────────────
37
38/// A handle to the runtime's server actions, backed by [`YogServer`] fn pointers.
39/// Given to every handler as `&dyn Server`.
40pub struct CServer(pub *const YogServer);
41
42unsafe impl Send for CServer {}
43unsafe impl Sync for CServer {}
44
45macro_rules! srv {
46    ($self:ident) => { unsafe { &*$self.0 } };
47}
48
49impl Server for CServer {
50    fn broadcast(&self, message: &str) {
51        let s = srv!(self);
52        unsafe { (s.broadcast)(s.ctx, YogStr::from_str(message)) }
53    }
54
55    fn get_block(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
56        let s = srv!(self);
57        let owned = unsafe {
58            (s.get_block)(s.ctx, YogStr::from_str(dimension),
59                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z })
60        };
61        if owned.is_none() { return None; }
62        let result = unsafe {
63            String::from_utf8(
64                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
65            ).ok()
66        };
67        unsafe { (s.free_str)(owned.ptr, owned.len) };
68        result
69    }
70
71    fn set_block(&self, dimension: &str, pos: yog_core::BlockPos, block_id: &str) -> bool {
72        let s = srv!(self);
73        unsafe {
74            (s.set_block)(s.ctx, YogStr::from_str(dimension),
75                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z },
76                YogStr::from_str(block_id))
77        }
78    }
79
80    fn give_item(&self, player: &str, item_id: &str, count: u32) -> bool {
81        let s = srv!(self);
82        unsafe { (s.give_item)(s.ctx, YogStr::from_str(player), YogStr::from_str(item_id), count) }
83    }
84
85    fn teleport(&self, player: &str, x: f64, y: f64, z: f64) -> bool {
86        let s = srv!(self);
87        unsafe { (s.player_teleport)(s.ctx, YogStr::from_str(player), yog_abi::YogVec3 { x, y, z }) }
88    }
89
90    fn send_to_player(&self, player: &str, channel: &str, payload: &[u8]) -> bool {
91        let s = srv!(self);
92        unsafe {
93            (s.send_to_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(channel),
94                payload.as_ptr(), payload.len() as u32)
95        }
96    }
97
98    fn send_to_server(&self, channel: &str, payload: &[u8]) -> bool {
99        let s = srv!(self);
100        unsafe {
101            (s.send_to_server)(s.ctx, YogStr::from_str(channel),
102                payload.as_ptr(), payload.len() as u32)
103        }
104    }
105
106    fn entity_teleport(&self, uuid: &str, x: f64, y: f64, z: f64) -> bool {
107        let s = srv!(self);
108        unsafe { (s.entity_teleport)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x, y, z }) }
109    }
110
111    fn entity_position(&self, uuid: &str) -> Option<(f64, f64, f64)> {
112        let s = srv!(self);
113        let mut out = yog_abi::YogVec3 { x: 0.0, y: 0.0, z: 0.0 };
114        if unsafe { (s.entity_position)(s.ctx, YogStr::from_str(uuid), &mut out) } {
115            Some((out.x, out.y, out.z))
116        } else {
117            None
118        }
119    }
120
121    fn entity_health(&self, uuid: &str) -> Option<f32> {
122        let s = srv!(self);
123        let mut hp = 0f32;
124        if unsafe { (s.entity_health)(s.ctx, YogStr::from_str(uuid), &mut hp) } { Some(hp) } else { None }
125    }
126
127    fn entity_set_health(&self, uuid: &str, health: f32) -> bool {
128        let s = srv!(self);
129        unsafe { (s.entity_set_health)(s.ctx, YogStr::from_str(uuid), health) }
130    }
131
132    fn entity_kill(&self, uuid: &str) -> bool {
133        let s = srv!(self);
134        unsafe { (s.entity_kill)(s.ctx, YogStr::from_str(uuid)) }
135    }
136
137    fn spawn_entity(&self, entity_type: &str, dimension: &str, x: f64, y: f64, z: f64) -> Option<String> {
138        let s = srv!(self);
139        let owned = unsafe {
140            (s.spawn_entity)(s.ctx, YogStr::from_str(entity_type),
141                YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z })
142        };
143        if owned.is_none() { return None; }
144        let result = unsafe {
145            String::from_utf8(
146                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
147            ).ok()
148        };
149        unsafe { (s.free_str)(owned.ptr, owned.len) };
150        result
151    }
152
153    fn entity_add_effect(&self, uuid: &str, effect_id: &str, duration_ticks: i32, amplifier: u8, show_particles: bool) -> bool {
154        let s = srv!(self);
155        unsafe { (s.entity_add_effect)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(effect_id), duration_ticks, amplifier, show_particles) }
156    }
157
158    fn entity_remove_effect(&self, uuid: &str, effect_id: &str) -> bool {
159        let s = srv!(self);
160        unsafe { (s.entity_remove_effect)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(effect_id)) }
161    }
162
163    fn entity_clear_effects(&self, uuid: &str) -> bool {
164        let s = srv!(self);
165        unsafe { (s.entity_clear_effects)(s.ctx, YogStr::from_str(uuid)) }
166    }
167
168    fn drop_loot(&self, table_id: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
169        let s = srv!(self);
170        unsafe { (s.drop_loot)(s.ctx, YogStr::from_str(table_id), YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z }) }
171    }
172
173    fn has_item_tag(&self, item_id: &str, tag_id: &str) -> bool {
174        let s = srv!(self);
175        unsafe { (s.has_item_tag)(s.ctx, YogStr::from_str(item_id), YogStr::from_str(tag_id)) }
176    }
177
178    fn has_block_tag(&self, block_id: &str, tag_id: &str) -> bool {
179        let s = srv!(self);
180        unsafe { (s.has_block_tag)(s.ctx, YogStr::from_str(block_id), YogStr::from_str(tag_id)) }
181    }
182
183    fn world_time(&self, dimension: &str) -> Option<i64> {
184        let s = srv!(self);
185        let mut t = 0i64;
186        if unsafe { (s.world_time)(s.ctx, YogStr::from_str(dimension), &mut t) } { Some(t) } else { None }
187    }
188
189    fn world_set_time(&self, dimension: &str, time: i64) -> bool {
190        let s = srv!(self);
191        unsafe { (s.set_time)(s.ctx, YogStr::from_str(dimension), time) }
192    }
193
194    fn world_is_raining(&self, dimension: &str) -> bool {
195        let s = srv!(self);
196        unsafe { (s.is_raining)(s.ctx, YogStr::from_str(dimension)) }
197    }
198
199    fn world_set_weather(&self, dimension: &str, raining: bool, duration_ticks: i32) -> bool {
200        let s = srv!(self);
201        unsafe { (s.set_weather)(s.ctx, YogStr::from_str(dimension), raining, duration_ticks) }
202    }
203
204    fn entity_velocity(&self, uuid: &str) -> Option<(f64, f64, f64)> {
205        let s = srv!(self);
206        let mut v = yog_abi::YogVec3 { x: 0.0, y: 0.0, z: 0.0 };
207        if unsafe { (s.entity_velocity)(s.ctx, YogStr::from_str(uuid), &mut v) } {
208            Some((v.x, v.y, v.z))
209        } else {
210            None
211        }
212    }
213
214    fn entity_set_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
215        let s = srv!(self);
216        unsafe { (s.entity_set_velocity)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x: vx, y: vy, z: vz }) }
217    }
218
219    fn entity_add_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
220        let s = srv!(self);
221        unsafe { (s.entity_add_velocity)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x: vx, y: vy, z: vz }) }
222    }
223
224    fn scoreboard_get(&self, objective: &str, player: &str) -> Option<i32> {
225        let s = srv!(self);
226        let mut score = 0i32;
227        if unsafe { (s.scoreboard_get)(s.ctx, YogStr::from_str(objective), YogStr::from_str(player), &mut score) } { Some(score) } else { None }
228    }
229
230    fn scoreboard_set(&self, objective: &str, player: &str, score: i32) -> bool {
231        let s = srv!(self);
232        unsafe { (s.scoreboard_set)(s.ctx, YogStr::from_str(objective), YogStr::from_str(player), score) }
233    }
234
235    fn scoreboard_add(&self, objective: &str, player: &str, delta: i32) -> Option<i32> {
236        let s = srv!(self);
237        let mut new_score = 0i32;
238        if unsafe { (s.scoreboard_add)(s.ctx, YogStr::from_str(objective), YogStr::from_str(player), delta, &mut new_score) } { Some(new_score) } else { None }
239    }
240
241    fn game_dir(&self) -> String {
242        let s = srv!(self);
243        let owned = unsafe { (s.game_dir)(s.ctx) };
244        if owned.is_none() { return String::new(); }
245        let result = unsafe {
246            String::from_utf8(
247                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
248            ).unwrap_or_default()
249        };
250        unsafe { (s.free_str)(owned.ptr, owned.len) };
251        result
252    }
253
254    fn play_sound(&self, dimension: &str, x: f64, y: f64, z: f64, sound_id: &str, volume: f32, pitch: f32) -> bool {
255        let s = srv!(self);
256        unsafe { (s.play_sound)(s.ctx, YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z }, YogStr::from_str(sound_id), volume, pitch) }
257    }
258
259    fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool {
260        let s = srv!(self);
261        unsafe { (s.play_sound_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(sound_id), volume, pitch) }
262    }
263
264    fn send_title(&self, player: &str, title: &str, subtitle: &str, fadein: i32, stay: i32, fadeout: i32) -> bool {
265        let s = srv!(self);
266        unsafe { (s.send_title)(s.ctx, YogStr::from_str(player), YogStr::from_str(title), YogStr::from_str(subtitle), fadein, stay, fadeout) }
267    }
268
269    fn send_actionbar(&self, player: &str, message: &str) -> bool {
270        let s = srv!(self);
271        unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
272    }
273
274    fn kick_player(&self, player: &str, reason: &str) -> bool {
275        let s = srv!(self);
276        unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
277    }
278
279    fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
280        let s = srv!(self);
281        unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
282    }
283
284    fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
285        let s = srv!(self);
286        unsafe { (s.bossbar_create)(s.ctx, YogStr::from_str(id), YogStr::from_str(title), YogStr::from_str(color), YogStr::from_str(style)) }
287    }
288
289    fn bossbar_remove(&self, id: &str) -> bool {
290        let s = srv!(self);
291        unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
292    }
293
294    fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
295        let s = srv!(self);
296        unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
297    }
298
299    fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
300        let s = srv!(self);
301        unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
302    }
303
304    fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
305        let s = srv!(self);
306        unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
307    }
308
309    fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
310        let s = srv!(self);
311        unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
312    }
313
314    fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
315        let s = srv!(self);
316        unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
317    }
318
319    fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
320        let s = srv!(self);
321        unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
322    }
323
324    fn online_players(&self) -> Vec<String> {
325        let s = srv!(self);
326        let owned = unsafe { (s.online_players)(s.ctx) };
327        if owned.is_none() { return Vec::new(); }
328        let text = unsafe {
329            String::from_utf8(
330                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
331            ).unwrap_or_default()
332        };
333        unsafe { (s.free_str)(owned.ptr, owned.len) };
334        if text.is_empty() { Vec::new() } else { text.lines().map(str::to_owned).collect() }
335    }
336
337    fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
338        let s = srv!(self);
339        let owned = unsafe {
340            (s.get_block_nbt)(s.ctx, YogStr::from_str(dimension),
341                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z })
342        };
343        if owned.is_none() { return None; }
344        let result = unsafe {
345            String::from_utf8(
346                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
347            ).ok()
348        };
349        unsafe { (s.free_str)(owned.ptr, owned.len) };
350        result
351    }
352
353    fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
354        let s = srv!(self);
355        unsafe {
356            (s.set_block_nbt)(s.ctx, YogStr::from_str(dimension),
357                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z },
358                YogStr::from_str(snbt))
359        }
360    }
361
362    fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
363        let s = srv!(self);
364        let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
365        if owned.is_none() { return Vec::new(); }
366        let text = unsafe {
367            String::from_utf8(
368                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
369            ).unwrap_or_default()
370        };
371        unsafe { (s.free_str)(owned.ptr, owned.len) };
372        text.lines().filter_map(|line| {
373            let mut it = line.split('\t');
374            let slot: u32 = it.next()?.parse().ok()?;
375            let item_id = it.next()?.to_owned();
376            let count: u32 = it.next()?.parse().ok()?;
377            Some((slot, item_id, count))
378        }).collect()
379    }
380
381    fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
382        let s = srv!(self);
383        unsafe {
384            (s.player_set_slot)(s.ctx, YogStr::from_str(player), slot, YogStr::from_str(item_id), count)
385        }
386    }
387
388    fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
389        let s = srv!(self);
390        unsafe {
391            (s.player_teleport_dim)(s.ctx, YogStr::from_str(player),
392                YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z })
393        }
394    }
395
396    fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
397        let s = srv!(self);
398        unsafe {
399            (s.entity_teleport_dim)(s.ctx, YogStr::from_str(uuid),
400                YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z })
401        }
402    }
403
404    fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
405        let s = srv!(self);
406        unsafe { (s.world_entity_count)(s.ctx, YogStr::from_str(dimension), YogStr::from_str(entity_type)) }
407    }
408
409    fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
410        let s = srv!(self);
411        let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
412        if owned.is_none() { return None; }
413        let result = unsafe {
414            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
415        };
416        unsafe { (s.free_str)(owned.ptr, owned.len) };
417        result
418    }
419
420    fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
421        let s = srv!(self);
422        unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
423    }
424
425    fn spawn_particles(&self, dimension: &str, x: f64, y: f64, z: f64, particle_type: &str, count: i32, dx: f64, dy: f64, dz: f64, speed: f64) -> bool {
426        let s = srv!(self);
427        unsafe {
428            (s.spawn_particles)(s.ctx, YogStr::from_str(dimension),
429                yog_abi::YogVec3 { x, y, z }, YogStr::from_str(particle_type),
430                count, dx, dy, dz, speed)
431        }
432    }
433
434    fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
435        let s = srv!(self);
436        let v = unsafe { (s.entity_attribute_get)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(attribute_id)) };
437        if v.is_nan() { None } else { Some(v) }
438    }
439
440    fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
441        let s = srv!(self);
442        unsafe { (s.entity_attribute_set)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(attribute_id), value) }
443    }
444
445    fn get_held_item_nbt(&self, player: &str) -> Option<String> {
446        let s = srv!(self);
447        let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
448        if owned.is_none() { return None; }
449        let result = unsafe {
450            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
451        };
452        unsafe { (s.free_str)(owned.ptr, owned.len) };
453        result
454    }
455
456    fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
457        let s = srv!(self);
458        unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
459    }
460
461    fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
462        let s = srv!(self);
463        let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
464        if owned.is_none() { return None; }
465        let result = unsafe {
466            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
467        };
468        unsafe { (s.free_str)(owned.ptr, owned.len) };
469        result
470    }
471
472    fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
473        let s = srv!(self);
474        unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
475    }
476
477    fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
478        let s = srv!(self);
479        let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
480        if owned.is_none() { return None; }
481        let text = unsafe {
482            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec())
483                .unwrap_or_default()
484        };
485        unsafe { (s.free_str)(owned.ptr, owned.len) };
486        let mut it = text.splitn(3, '\t');
487        let item_id = it.next()?.to_owned();
488        let count: u32 = it.next()?.parse().ok()?;
489        let nbt = it.next().unwrap_or("{}").to_owned();
490        Some((item_id, count, nbt))
491    }
492
493    fn set_slot_item(&self, player: &str, slot: u32, item_id: &str, count: u32, snbt: &str) -> bool {
494        let s = srv!(self);
495        unsafe {
496            (s.set_slot_item)(s.ctx, YogStr::from_str(player), slot,
497                YogStr::from_str(item_id), count, YogStr::from_str(snbt))
498        }
499    }
500}
501
502// ── Trampoline helpers ────────────────────────────────────────────────────────
503//
504// Each trampoline is a `unsafe extern "C" fn` that:
505//   1. Casts `ud` back to the original boxed closure.
506//   2. Converts ABI C structs to Rust event types.
507//   3. Builds a `CServer` from the `*const YogServer`.
508//   4. Calls the closure.
509//
510// Closures are Box::into_raw'd in Registry methods — they are INTENTIONALLY
511// leaked and live for the process lifetime.
512
513// ── Trampoline helpers ────────────────────────────────────────────────────────
514//
515// All phased event trampolines share the same pattern:
516//   `phase: u8`  0 = EventPhase::Pre, 1 = EventPhase::Post
517//   Return value is meaningful only in Pre phase.
518
519macro_rules! trampoline_phased {
520    ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr) => {
521        unsafe extern "C" fn $name<F>(
522            ud: *mut c_void, srv: *const YogServer, ev: *const $abi_ev, phase: u8,
523        ) -> bool
524        where F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
525        {
526            let f = &*(ud as *const F);
527            let $ev = &*ev;
528            let rust_ev = $build;
529            let p = if phase == 0 { EventPhase::Pre } else { EventPhase::Post };
530            f(&rust_ev, p, &CServer(srv))
531        }
532    };
533}
534
535trampoline_phased!(trampoline_block_break, YogBlockBreakEvent, BlockBreakEvent, |ev| BlockBreakEvent {
536    player_name: ev.player.as_str().to_owned(),
537    block_id:    ev.block.as_str().to_owned(),
538    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
539});
540
541trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
542    player_name: ev.player.as_str().to_owned(),
543    message:     ev.message.as_str().to_owned(),
544});
545
546trampoline_phased!(trampoline_player_join, YogPlayerEvent, PlayerJoinEvent, |ev| PlayerJoinEvent {
547    player_name: ev.player.as_str().to_owned(),
548    uuid:        ev.uuid.as_str().to_owned(),
549});
550
551trampoline_phased!(trampoline_player_leave, YogPlayerEvent, PlayerLeaveEvent, |ev| PlayerLeaveEvent {
552    player_name: ev.player.as_str().to_owned(),
553    uuid:        ev.uuid.as_str().to_owned(),
554});
555
556trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| UseItemEvent {
557    player_name: ev.player.as_str().to_owned(),
558    item_id:     ev.item.as_str().to_owned(),
559});
560
561trampoline_phased!(trampoline_use_block, YogUseBlockEvent, UseBlockEvent, |ev| UseBlockEvent {
562    player_name: ev.player.as_str().to_owned(),
563    block_id:    ev.block.as_str().to_owned(),
564    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
565});
566
567trampoline_phased!(trampoline_attack_entity, YogAttackEntityEvent, AttackEntityEvent, |ev| AttackEntityEvent {
568    player_name: ev.player.as_str().to_owned(),
569    target_type: ev.target_type.as_str().to_owned(),
570    target_uuid: ev.target_uuid.as_str().to_owned(),
571});
572
573trampoline_phased!(trampoline_entity_damage, YogEntityDamageEvent, EntityDamageEvent, |ev| EntityDamageEvent {
574    entity_type: ev.entity_type.as_str().to_owned(),
575    uuid:        ev.uuid.as_str().to_owned(),
576    amount:      ev.amount,
577    source:      ev.source.as_str().to_owned(),
578});
579
580trampoline_phased!(trampoline_entity_death, YogEntityDeathEvent, EntityDeathEvent, |ev| EntityDeathEvent {
581    entity_type: ev.entity_type.as_str().to_owned(),
582    uuid:        ev.uuid.as_str().to_owned(),
583    source:      ev.source.as_str().to_owned(),
584});
585
586trampoline_phased!(trampoline_entity_spawn, YogEntitySpawnEvent, EntitySpawnEvent, |ev| EntitySpawnEvent {
587    entity_type: ev.entity_type.as_str().to_owned(),
588    uuid:        ev.uuid.as_str().to_owned(),
589    dimension:   ev.dimension.as_str().to_owned(),
590});
591
592trampoline_phased!(trampoline_place_block, YogPlaceBlockEvent, PlaceBlockEvent, |ev| PlaceBlockEvent {
593    player_name: ev.player.as_str().to_owned(),
594    block_id:    ev.block.as_str().to_owned(),
595    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
596});
597
598trampoline_phased!(trampoline_player_death, YogPlayerDeathEvent, PlayerDeathEvent, |ev| PlayerDeathEvent {
599    player_name: ev.player.as_str().to_owned(),
600    uuid:        ev.uuid.as_str().to_owned(),
601    source:      ev.source.as_str().to_owned(),
602});
603
604trampoline_phased!(trampoline_player_respawn, YogPlayerRespawnEvent, PlayerRespawnEvent, |ev| PlayerRespawnEvent {
605    player_name: ev.player.as_str().to_owned(),
606    uuid:        ev.uuid.as_str().to_owned(),
607    at_anchor:   ev.at_anchor,
608});
609
610trampoline_phased!(trampoline_advancement, YogAdvancementEvent, AdvancementEvent, |ev| AdvancementEvent {
611    player_name:    ev.player.as_str().to_owned(),
612    uuid:           ev.uuid.as_str().to_owned(),
613    advancement_id: ev.advancement.as_str().to_owned(),
614});
615
616trampoline_phased!(trampoline_entity_interact, YogEntityInteractEvent, EntityInteractEvent, |ev| EntityInteractEvent {
617    player_name: ev.player.as_str().to_owned(),
618    player_uuid: ev.player_uuid.as_str().to_owned(),
619    entity_type: ev.entity_type.as_str().to_owned(),
620    entity_uuid: ev.entity_uuid.as_str().to_owned(),
621    hand:        ev.hand.as_str().to_owned(),
622});
623
624trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| CraftEvent {
625    player_name:  ev.player.as_str().to_owned(),
626    player_uuid:  ev.player_uuid.as_str().to_owned(),
627    result_item:  ev.result_item.as_str().to_owned(),
628    result_count: ev.result_count,
629});
630
631trampoline_phased!(trampoline_explosion, YogExplosionEvent, ExplosionEvent, |ev| ExplosionEvent {
632    dimension:  ev.dimension.as_str().to_owned(),
633    x:          ev.x,
634    y:          ev.y,
635    z:          ev.z,
636    power:      ev.power,
637    cause_uuid: ev.cause_uuid.as_str().to_owned(),
638});
639
640trampoline_phased!(trampoline_item_pickup, YogItemPickupEvent, ItemPickupEvent, |ev| ItemPickupEvent {
641    player_name: ev.player.as_str().to_owned(),
642    player_uuid: ev.player_uuid.as_str().to_owned(),
643    item_id:     ev.item_id.as_str().to_owned(),
644    item_count:  ev.item_count,
645    entity_uuid: ev.entity_uuid.as_str().to_owned(),
646});
647
648trampoline_phased!(trampoline_player_move, YogPlayerMoveEvent, PlayerMoveEvent, |ev| PlayerMoveEvent {
649    player_name: ev.player.as_str().to_owned(),
650    player_uuid: ev.player_uuid.as_str().to_owned(),
651    x:     ev.x,
652    y:     ev.y,
653    z:     ev.z,
654    yaw:   ev.yaw,
655    pitch: ev.pitch,
656});
657
658trampoline_phased!(trampoline_container_open, YogContainerOpenEvent, ContainerOpenEvent, |ev| ContainerOpenEvent {
659    player_name:    ev.player.as_str().to_owned(),
660    player_uuid:    ev.player_uuid.as_str().to_owned(),
661    container_type: ev.container_type.as_str().to_owned(),
662});
663
664trampoline_phased!(trampoline_container_close, YogContainerCloseEvent, ContainerCloseEvent, |ev| ContainerCloseEvent {
665    player_name: ev.player.as_str().to_owned(),
666    player_uuid: ev.player_uuid.as_str().to_owned(),
667});
668
669trampoline_phased!(trampoline_projectile_hit, YogProjectileHitEvent, ProjectileHitEvent, |ev| ProjectileHitEvent {
670    projectile_type: ev.projectile_type.as_str().to_owned(),
671    projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
672    shooter_uuid:    ev.shooter_uuid.as_str().to_owned(),
673    hit_type:        ev.hit_type.as_str().to_owned(),
674    hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
675    x:               ev.x,
676    y:               ev.y,
677    z:               ev.z,
678    dimension:       ev.dimension.as_str().to_owned(),
679});
680
681unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
682where F: Fn(&dyn Server) + Send + Sync,
683{
684    let f = &*(ud as *const F);
685    f(&CServer(srv));
686}
687
688unsafe extern "C" fn trampoline_packet<F>(ud: *mut c_void, srv: *const YogServer, ev: *const YogPacketEvent)
689where F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
690{
691    let f = &*(ud as *const F);
692    let ev = &*ev;
693    let rust_ev = PacketEvent {
694        channel: ev.channel.as_str().to_owned(),
695        player:  ev.player.as_str().to_owned(),
696        payload: std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec(),
697    };
698    f(&rust_ev, &CServer(srv));
699}
700
701unsafe extern "C" fn trampoline_command<F>(
702    ud: *mut c_void,
703    srv: *const YogServer,
704    ev: *const YogCommandEvent,
705    reply_buf: *mut u8,
706    reply_cap: u32,
707    reply_len: *mut u32,
708) where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
709{
710    let f = &*(ud as *const F);
711    let ev = &*ev;
712    let ctx = CommandContext {
713        name:   ev.name.as_str().to_owned(),
714        args:   ev.args.as_str().to_owned(),
715        source: ev.source.as_str().to_owned(),
716        uuid:   ev.uuid.as_str().to_owned(),
717    };
718    *reply_len = 0;
719    if let Some(reply) = f(&ctx, &CServer(srv)) {
720        let bytes = reply.as_bytes();
721        let n = bytes.len().min(reply_cap as usize);
722        std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
723        *reply_len = n as u32;
724    }
725}
726
727unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
728where F: Fn(&dyn Server) + Send + Sync,
729{
730    let f = &*(ud as *const F);
731    f(&CServer(srv));
732}
733
734// ── ABI minor 10 — client-side trampolines ────────────────────────────────────
735
736unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
737where F: Fn(&ClientTickEvent) + Send + Sync,
738{
739    let f = &*(ud as *const F);
740    f(&ClientTickEvent {});
741}
742
743unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
744where F: Fn(&GfxContext) + Send + Sync,
745{
746    let f = &*(ud as *const F);
747    f(&GfxContext::from_raw(gfx));
748}
749
750unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
751where F: Fn(&GfxContext) + Send + Sync,
752{
753    let f = &*(ud as *const F);
754    f(&GfxContext::from_raw(gfx));
755}
756
757unsafe extern "C" fn trampoline_key_press<F>(
758    ud: *mut c_void,
759    ev: *const YogKeyPressEvent,
760) -> bool
761where F: Fn(&KeyPressEvent) -> bool + Send + Sync,
762{
763    let f = &*(ud as *const F);
764    let e = &*ev;
765    f(&KeyPressEvent {
766        key_code:  e.key_code,
767        scan_code: e.scan_code,
768        action:    e.action,
769        modifiers: e.modifiers,
770    })
771}
772
773unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
774where F: Fn(&ScreenEvent) + Send + Sync,
775{
776    let f = &*(ud as *const F);
777    f(&ScreenEvent { screen_class: screen_class.as_str().to_owned() });
778    true
779}
780
781// ── Registry ─────────────────────────────────────────────────────────────────
782
783/// Wraps the [`YogApi`] table and provides an ergonomic registration API.
784///
785/// Obtained inside `yog_mod_register` via `export_mod!`.  Closures registered
786/// here are boxed and leaked — they live as long as the process (which is the
787/// correct lifetime for a server mod).
788pub struct Registry {
789    api: *const YogApi,
790}
791
792// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
793unsafe impl Send for Registry {}
794unsafe impl Sync for Registry {}
795
796impl Registry {
797    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
798    pub unsafe fn from_raw(api: *const YogApi) -> Self {
799        Self { api }
800    }
801
802    #[inline]
803    fn ctx(&self) -> *mut c_void { unsafe { (*self.api).ctx } }
804
805    // ── helpers ──────────────────────────────────────────────────────────────
806
807    fn leak<F: 'static>(f: F) -> *mut c_void {
808        Box::into_raw(Box::new(f)) as *mut c_void
809    }
810
811    // ── events ───────────────────────────────────────────────────────────────
812    //
813    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
814    // In `Pre` phase, returning `false` cancels the action.
815    // In `Post` phase, the return value is ignored.
816    // A single registration fires for BOTH phases.
817
818    pub fn on_block_break<F>(&mut self, handler: F)
819    where F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
820        let ud = Self::leak(handler);
821        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
822    }
823
824    pub fn on_chat<F>(&mut self, handler: F)
825    where F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
826        let ud = Self::leak(handler);
827        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
828    }
829
830    pub fn on_player_join<F>(&mut self, handler: F)
831    where F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
832        let ud = Self::leak(handler);
833        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
834    }
835
836    pub fn on_player_leave<F>(&mut self, handler: F)
837    where F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
838        let ud = Self::leak(handler);
839        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
840    }
841
842    pub fn on_use_item<F>(&mut self, handler: F)
843    where F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
844        let ud = Self::leak(handler);
845        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
846    }
847
848    pub fn on_use_block<F>(&mut self, handler: F)
849    where F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
850        let ud = Self::leak(handler);
851        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
852    }
853
854    pub fn on_attack_entity<F>(&mut self, handler: F)
855    where F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
856        let ud = Self::leak(handler);
857        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
858    }
859
860    pub fn on_entity_damage<F>(&mut self, handler: F)
861    where F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
862        let ud = Self::leak(handler);
863        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
864    }
865
866    pub fn on_entity_death<F>(&mut self, handler: F)
867    where F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
868        let ud = Self::leak(handler);
869        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
870    }
871
872    pub fn on_entity_spawn<F>(&mut self, handler: F)
873    where F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
874        let ud = Self::leak(handler);
875        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
876    }
877
878    pub fn on_player_place_block<F>(&mut self, handler: F)
879    where F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
880        let ud = Self::leak(handler);
881        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
882    }
883
884    pub fn on_player_death<F>(&mut self, handler: F)
885    where F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
886        let ud = Self::leak(handler);
887        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
888    }
889
890    pub fn on_player_respawn<F>(&mut self, handler: F)
891    where F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
892        let ud = Self::leak(handler);
893        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
894    }
895
896    pub fn on_advancement<F>(&mut self, handler: F)
897    where F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
898        let ud = Self::leak(handler);
899        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
900    }
901
902    pub fn on_entity_interact<F>(&mut self, handler: F)
903    where F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
904        let ud = Self::leak(handler);
905        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
906    }
907
908    pub fn on_item_craft<F>(&mut self, handler: F)
909    where F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
910        let ud = Self::leak(handler);
911        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
912    }
913
914    pub fn on_explosion<F>(&mut self, handler: F)
915    where F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
916        let ud = Self::leak(handler);
917        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
918    }
919
920    pub fn on_item_pickup<F>(&mut self, handler: F)
921    where F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
922        let ud = Self::leak(handler);
923        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
924    }
925
926    pub fn on_player_move<F>(&mut self, handler: F)
927    where F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
928        let ud = Self::leak(handler);
929        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
930    }
931
932    pub fn on_container_open<F>(&mut self, handler: F)
933    where F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
934        let ud = Self::leak(handler);
935        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
936    }
937
938    pub fn on_container_close<F>(&mut self, handler: F)
939    where F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
940        let ud = Self::leak(handler);
941        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
942    }
943
944    pub fn on_projectile_hit<F>(&mut self, handler: F)
945    where F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
946        let ud = Self::leak(handler);
947        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
948    }
949
950    pub fn on_tick<F>(&mut self, listener: F)
951    where F: Fn(&dyn Server) + Send + Sync + 'static {
952        let ud = Self::leak(listener);
953        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
954    }
955
956    pub fn on_server_started<F>(&mut self, listener: F)
957    where F: Fn(&dyn Server) + Send + Sync + 'static {
958        let ud = Self::leak(listener);
959        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
960    }
961
962    pub fn on_server_stopping<F>(&mut self, listener: F)
963    where F: Fn(&dyn Server) + Send + Sync + 'static {
964        let ud = Self::leak(listener);
965        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
966    }
967
968    // ── commands ─────────────────────────────────────────────────────────────
969
970    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
971    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
972        let name_ys = YogStr::from_str(name.as_ref());
973        let ud = Self::leak(handler);
974        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
975    }
976
977    /// Register a command with Brigadier-typed arguments.
978    ///
979    /// `schema` is a space-separated list of argument types:
980    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
981    ///
982    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
983    pub fn on_typed_command<F>(&mut self, name: impl AsRef<str>, schema: impl AsRef<str>, handler: F)
984    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
985        let name_ys   = YogStr::from_str(name.as_ref());
986        let schema_ys = YogStr::from_str(schema.as_ref());
987        let ud = Self::leak(handler);
988        unsafe { ((*self.api).register_typed_command)(self.ctx(), name_ys, schema_ys, ud, trampoline_command::<F>) }
989    }
990
991    // ── networking ───────────────────────────────────────────────────────────
992
993    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
994    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
995        let ch = YogStr::from_str(channel.as_ref());
996        let ud = Self::leak(handler);
997        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
998    }
999
1000    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1001    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
1002        let ch = YogStr::from_str(channel.as_ref());
1003        let ud = Self::leak(handler);
1004        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1005    }
1006
1007    /// Register a typed-packet handler.
1008    ///
1009    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
1010    /// Malformed payloads are silently dropped.
1011    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
1012    where
1013        P: Packet + Send + Sync + 'static,
1014        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
1015    {
1016        self.on_packet(channel, move |ev, srv| {
1017            if let Some(pkt) = P::decode(&ev.payload) {
1018                handler(&pkt, srv);
1019            }
1020        });
1021    }
1022
1023    // ── recipes ──────────────────────────────────────────────────────────────
1024
1025    fn recipe(&mut self, ns: &str, name: &str, json: &str) {
1026        unsafe {
1027            ((*self.api).register_recipe_json)(
1028                self.ctx(),
1029                YogStr::from_str(ns), YogStr::from_str(name), YogStr::from_str(json),
1030            )
1031        }
1032    }
1033
1034    /// Register a shaped crafting recipe.
1035    pub fn add_shaped_recipe(&mut self, recipe: ShapedRecipe) {
1036        let json = recipe.to_json();
1037        let (ns, name) = recipe.ns_name();
1038        self.recipe(ns, name, &json);
1039    }
1040
1041    /// Register a shapeless crafting recipe.
1042    pub fn add_shapeless_recipe(&mut self, recipe: ShapelessRecipe) {
1043        let json = recipe.to_json();
1044        let (ns, name) = recipe.ns_name();
1045        self.recipe(ns, name, &json);
1046    }
1047
1048    /// Register a furnace smelting recipe.
1049    pub fn add_furnace_recipe(&mut self, recipe: FurnaceRecipe) {
1050        let json = recipe.to_json();
1051        let (ns, name) = recipe.ns_name();
1052        self.recipe(ns, name, &json);
1053    }
1054
1055    // ── content ──────────────────────────────────────────────────────────────
1056
1057    pub fn register_item(&mut self, def: ItemDef) {
1058        // Build a C-compatible YogItemDef whose YogStr fields point into `def`'s
1059        // String storage.  We then call register_item which must copy the data
1060        // before returning (the runtime stores owned Strings).
1061        let food_nutrition  = def.food.as_ref().map_or(0, |f| f.nutrition);
1062        let food_saturation = def.food.as_ref().map_or(0.0, |f| f.saturation);
1063        let food_always_eat = def.food.as_ref().map_or(false, |f| f.can_always_eat);
1064        let c = YogItemDef {
1065            id:              YogStr::from_str(&def.id),
1066            max_stack:       def.max_stack as u32,
1067            name:            def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1068            tooltip:         def.tooltip.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1069            max_damage:      def.max_damage,
1070            fire_resistant:  def.fire_resistant,
1071            fuel_ticks:      def.fuel_ticks,
1072            food_nutrition,
1073            food_saturation,
1074            food_always_eat,
1075        };
1076        unsafe { ((*self.api).register_item)(self.ctx(), &c) }
1077    }
1078
1079    pub fn register_block(&mut self, def: BlockDef) {
1080        let shape = def.shape.unwrap_or([0.0; 6]);
1081        let c = YogBlockDef {
1082            id:            YogStr::from_str(&def.id),
1083            hardness:      def.hardness,
1084            resistance:    def.resistance,
1085            name:          def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1086            light_level:   def.light_level,
1087            sound:         def.sound.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1088            requires_tool: def.requires_tool,
1089            no_collision:  def.no_collision,
1090            slipperiness:  def.slipperiness,
1091            shape,
1092        };
1093        unsafe { ((*self.api).register_block)(self.ctx(), &c) }
1094    }
1095
1096    // ── startup grants ───────────────────────────────────────────────────────
1097
1098    /// Register a startup grant: items/books to give once when a player first joins.
1099    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
1100        let items_str = grant.items.join("|");
1101        let c = YogStartupGrantDef {
1102            id:      YogStr::from_str(&grant.id),
1103            items:   YogStr::from_str(&items_str),
1104            book:    grant.book.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1105            command: grant.command.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1106        };
1107        unsafe { ((*self.api).register_startup_grant)(self.ctx(), &c) }
1108    }
1109
1110    /// Register a book with its JSON-serialized content.
1111    pub fn register_book(&mut self, book: &Book) {
1112        let json = book.to_json();
1113        let id = YogStr::from_str(&book.id);
1114        let j = YogStr::from_str(&json);
1115        unsafe { ((*self.api).register_book)(self.ctx(), id, j) }
1116    }
1117
1118    // ── scheduler ────────────────────────────────────────────────────────────
1119
1120    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
1121    where F: Fn(&dyn Server) + Send + Sync + 'static {
1122        let ud = Self::leak(handler);
1123        unsafe { ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>) }
1124    }
1125
1126    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
1127    where F: Fn(&dyn Server) + Send + Sync + 'static {
1128        let ud = Self::leak(handler);
1129        unsafe { ((*self.api).schedule_repeating)(self.ctx(), period_ticks, ud, trampoline_scheduled::<F>) }
1130    }
1131
1132    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
1133
1134    /// Register a handler called every client tick (render thread, no server).
1135    pub fn on_client_tick<F>(&mut self, handler: F)
1136    where F: Fn(&ClientTickEvent) + Send + Sync + 'static {
1137        let ud = Self::leak(handler);
1138        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
1139    }
1140
1141    /// Register a handler called every frame when the HUD is rendered.
1142    ///
1143    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
1144    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
1145    pub fn on_hud_render<F>(&mut self, handler: F)
1146    where F: Fn(&GfxContext) + Send + Sync + 'static {
1147        let ud = Self::leak(handler);
1148        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
1149    }
1150
1151    /// Register a handler called every frame at the end of world rendering.
1152    ///
1153    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
1154    /// `gfx.camera_pos()` is the camera world position.
1155    /// To render at world position P, translate by `P - camera_pos` before drawing.
1156    pub fn on_world_render<F>(&mut self, handler: F)
1157    where F: Fn(&GfxContext) + Send + Sync + 'static {
1158        let ud = Self::leak(handler);
1159        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
1160    }
1161
1162    /// Register a handler for keyboard input (client-side).
1163    /// Return `false` to prevent Minecraft from processing the key.
1164    pub fn on_key_press<F>(&mut self, handler: F)
1165    where F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static {
1166        let ud = Self::leak(handler);
1167        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
1168    }
1169
1170    /// Register a handler called when a GUI screen is opened.
1171    pub fn on_screen_open<F>(&mut self, handler: F)
1172    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1173        let ud = Self::leak(handler);
1174        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
1175    }
1176
1177    /// Register a handler called when a GUI screen is closed.
1178    pub fn on_screen_close<F>(&mut self, handler: F)
1179    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1180        let ud = Self::leak(handler);
1181        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
1182    }
1183}
1184
1185// ── Mod trait ─────────────────────────────────────────────────────────────────
1186
1187/// Implemented by every Yog mod. Called once at startup to register handlers.
1188pub trait Mod {
1189    fn register(registry: &mut Registry);
1190}