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