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).
788/// Pointer to the runtime's `YogApi` table (a process-lifetime static in the
789/// runtime), captured when the mod registers. Lets free functions like
790/// [`installed_mods`] work outside of `register()` — e.g. in UI handlers.
791static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
792    std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
793
794/// Metadata of an installed mod, as reported by the loader.
795#[derive(Debug, Clone)]
796pub struct ModInfo {
797    /// `"yog"` for .yog mods, `"platform"` for loader (Java) mods.
798    pub source:      String,
799    pub id:          String,
800    pub name:        String,
801    pub version:     String,
802    /// Comma-separated author list (may be empty).
803    pub authors:     String,
804    pub description: String,
805}
806
807/// All installed mods known to the loader: .yog mods plus, where the host
808/// exposes them, platform (Java) mods. Callable at any time after this mod's
809/// `register()` ran — including client-side UI handlers. Note that during
810/// `register()` mods that load after this one are not in the list yet; query
811/// lazily (e.g. on first render) for a complete view.
812pub fn installed_mods() -> Vec<ModInfo> {
813    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
814    if api.is_null() { return Vec::new(); }
815    let owned = unsafe { ((*api).mods_list)((*api).ctx) };
816    if owned.is_none() { return Vec::new(); }
817    let text = unsafe {
818        String::from_utf8(
819            std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
820        ).unwrap_or_default()
821    };
822    unsafe { ((*api).free_str)(owned.ptr, owned.len) };
823    text.lines()
824        .filter_map(|line| {
825            let mut f = line.split('\t');
826            Some(ModInfo {
827                source:      f.next()?.to_string(),
828                id:          f.next()?.to_string(),
829                name:        f.next().unwrap_or_default().to_string(),
830                version:     f.next().unwrap_or_default().to_string(),
831                authors:     f.next().unwrap_or_default().to_string(),
832                description: f.next().unwrap_or_default().to_string(),
833            })
834        })
835        .collect()
836}
837
838pub struct Registry {
839    api: *const YogApi,
840}
841
842// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
843unsafe impl Send for Registry {}
844unsafe impl Sync for Registry {}
845
846impl Registry {
847    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
848    pub unsafe fn from_raw(api: *const YogApi) -> Self {
849        GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
850        Self { api }
851    }
852
853    #[inline]
854    fn ctx(&self) -> *mut c_void { unsafe { (*self.api).ctx } }
855
856    // ── helpers ──────────────────────────────────────────────────────────────
857
858    fn leak<F: 'static>(f: F) -> *mut c_void {
859        Box::into_raw(Box::new(f)) as *mut c_void
860    }
861
862    // ── events ───────────────────────────────────────────────────────────────
863    //
864    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
865    // In `Pre` phase, returning `false` cancels the action.
866    // In `Post` phase, the return value is ignored.
867    // A single registration fires for BOTH phases.
868
869    pub fn on_block_break<F>(&mut self, handler: F)
870    where F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
871        let ud = Self::leak(handler);
872        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
873    }
874
875    pub fn on_chat<F>(&mut self, handler: F)
876    where F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
877        let ud = Self::leak(handler);
878        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
879    }
880
881    pub fn on_player_join<F>(&mut self, handler: F)
882    where F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
883        let ud = Self::leak(handler);
884        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
885    }
886
887    pub fn on_player_leave<F>(&mut self, handler: F)
888    where F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
889        let ud = Self::leak(handler);
890        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
891    }
892
893    pub fn on_use_item<F>(&mut self, handler: F)
894    where F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
895        let ud = Self::leak(handler);
896        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
897    }
898
899    pub fn on_use_block<F>(&mut self, handler: F)
900    where F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
901        let ud = Self::leak(handler);
902        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
903    }
904
905    pub fn on_attack_entity<F>(&mut self, handler: F)
906    where F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
907        let ud = Self::leak(handler);
908        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
909    }
910
911    pub fn on_entity_damage<F>(&mut self, handler: F)
912    where F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
913        let ud = Self::leak(handler);
914        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
915    }
916
917    pub fn on_entity_death<F>(&mut self, handler: F)
918    where F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
919        let ud = Self::leak(handler);
920        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
921    }
922
923    pub fn on_entity_spawn<F>(&mut self, handler: F)
924    where F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
925        let ud = Self::leak(handler);
926        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
927    }
928
929    pub fn on_player_place_block<F>(&mut self, handler: F)
930    where F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
931        let ud = Self::leak(handler);
932        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
933    }
934
935    pub fn on_player_death<F>(&mut self, handler: F)
936    where F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
937        let ud = Self::leak(handler);
938        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
939    }
940
941    pub fn on_player_respawn<F>(&mut self, handler: F)
942    where F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
943        let ud = Self::leak(handler);
944        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
945    }
946
947    pub fn on_advancement<F>(&mut self, handler: F)
948    where F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
949        let ud = Self::leak(handler);
950        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
951    }
952
953    pub fn on_entity_interact<F>(&mut self, handler: F)
954    where F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
955        let ud = Self::leak(handler);
956        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
957    }
958
959    pub fn on_item_craft<F>(&mut self, handler: F)
960    where F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
961        let ud = Self::leak(handler);
962        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
963    }
964
965    pub fn on_explosion<F>(&mut self, handler: F)
966    where F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
967        let ud = Self::leak(handler);
968        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
969    }
970
971    pub fn on_item_pickup<F>(&mut self, handler: F)
972    where F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
973        let ud = Self::leak(handler);
974        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
975    }
976
977    pub fn on_player_move<F>(&mut self, handler: F)
978    where F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
979        let ud = Self::leak(handler);
980        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
981    }
982
983    pub fn on_container_open<F>(&mut self, handler: F)
984    where F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
985        let ud = Self::leak(handler);
986        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
987    }
988
989    pub fn on_container_close<F>(&mut self, handler: F)
990    where F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
991        let ud = Self::leak(handler);
992        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
993    }
994
995    pub fn on_projectile_hit<F>(&mut self, handler: F)
996    where F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
997        let ud = Self::leak(handler);
998        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
999    }
1000
1001    pub fn on_tick<F>(&mut self, listener: F)
1002    where F: Fn(&dyn Server) + Send + Sync + 'static {
1003        let ud = Self::leak(listener);
1004        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
1005    }
1006
1007    pub fn on_server_started<F>(&mut self, listener: F)
1008    where F: Fn(&dyn Server) + Send + Sync + 'static {
1009        let ud = Self::leak(listener);
1010        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
1011    }
1012
1013    pub fn on_server_stopping<F>(&mut self, listener: F)
1014    where F: Fn(&dyn Server) + Send + Sync + 'static {
1015        let ud = Self::leak(listener);
1016        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
1017    }
1018
1019    // ── commands ─────────────────────────────────────────────────────────────
1020
1021    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
1022    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
1023        let name_ys = YogStr::from_str(name.as_ref());
1024        let ud = Self::leak(handler);
1025        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
1026    }
1027
1028    /// Register a command with Brigadier-typed arguments.
1029    ///
1030    /// `schema` is a space-separated list of argument types:
1031    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
1032    ///
1033    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
1034    pub fn on_typed_command<F>(&mut self, name: impl AsRef<str>, schema: impl AsRef<str>, handler: F)
1035    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
1036        let name_ys   = YogStr::from_str(name.as_ref());
1037        let schema_ys = YogStr::from_str(schema.as_ref());
1038        let ud = Self::leak(handler);
1039        unsafe { ((*self.api).register_typed_command)(self.ctx(), name_ys, schema_ys, ud, trampoline_command::<F>) }
1040    }
1041
1042    // ── networking ───────────────────────────────────────────────────────────
1043
1044    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1045    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
1046        let ch = YogStr::from_str(channel.as_ref());
1047        let ud = Self::leak(handler);
1048        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1049    }
1050
1051    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1052    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
1053        let ch = YogStr::from_str(channel.as_ref());
1054        let ud = Self::leak(handler);
1055        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1056    }
1057
1058    /// Register a typed-packet handler.
1059    ///
1060    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
1061    /// Malformed payloads are silently dropped.
1062    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
1063    where
1064        P: Packet + Send + Sync + 'static,
1065        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
1066    {
1067        self.on_packet(channel, move |ev, srv| {
1068            if let Some(pkt) = P::decode(&ev.payload) {
1069                handler(&pkt, srv);
1070            }
1071        });
1072    }
1073
1074    // ── recipes ──────────────────────────────────────────────────────────────
1075
1076    fn recipe(&mut self, ns: &str, name: &str, json: &str) {
1077        unsafe {
1078            ((*self.api).register_recipe_json)(
1079                self.ctx(),
1080                YogStr::from_str(ns), YogStr::from_str(name), YogStr::from_str(json),
1081            )
1082        }
1083    }
1084
1085    /// Register a shaped crafting recipe.
1086    pub fn add_shaped_recipe(&mut self, recipe: ShapedRecipe) {
1087        let json = recipe.to_json();
1088        let (ns, name) = recipe.ns_name();
1089        self.recipe(ns, name, &json);
1090    }
1091
1092    /// Register a shapeless crafting recipe.
1093    pub fn add_shapeless_recipe(&mut self, recipe: ShapelessRecipe) {
1094        let json = recipe.to_json();
1095        let (ns, name) = recipe.ns_name();
1096        self.recipe(ns, name, &json);
1097    }
1098
1099    /// Register a furnace smelting recipe.
1100    pub fn add_furnace_recipe(&mut self, recipe: FurnaceRecipe) {
1101        let json = recipe.to_json();
1102        let (ns, name) = recipe.ns_name();
1103        self.recipe(ns, name, &json);
1104    }
1105
1106    // ── content ──────────────────────────────────────────────────────────────
1107
1108    pub fn register_item(&mut self, def: ItemDef) {
1109        // Build a C-compatible YogItemDef whose YogStr fields point into `def`'s
1110        // String storage.  We then call register_item which must copy the data
1111        // before returning (the runtime stores owned Strings).
1112        let food_nutrition  = def.food.as_ref().map_or(0, |f| f.nutrition);
1113        let food_saturation = def.food.as_ref().map_or(0.0, |f| f.saturation);
1114        let food_always_eat = def.food.as_ref().map_or(false, |f| f.can_always_eat);
1115        let c = YogItemDef {
1116            id:              YogStr::from_str(&def.id),
1117            max_stack:       def.max_stack as u32,
1118            name:            def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1119            tooltip:         def.tooltip.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1120            max_damage:      def.max_damage,
1121            fire_resistant:  def.fire_resistant,
1122            fuel_ticks:      def.fuel_ticks,
1123            food_nutrition,
1124            food_saturation,
1125            food_always_eat,
1126        };
1127        unsafe { ((*self.api).register_item)(self.ctx(), &c) }
1128    }
1129
1130    pub fn register_block(&mut self, def: BlockDef) {
1131        let shape = def.shape.unwrap_or([0.0; 6]);
1132        let c = YogBlockDef {
1133            id:            YogStr::from_str(&def.id),
1134            hardness:      def.hardness,
1135            resistance:    def.resistance,
1136            name:          def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1137            light_level:   def.light_level,
1138            sound:         def.sound.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1139            requires_tool: def.requires_tool,
1140            no_collision:  def.no_collision,
1141            slipperiness:  def.slipperiness,
1142            shape,
1143        };
1144        unsafe { ((*self.api).register_block)(self.ctx(), &c) }
1145    }
1146
1147    // ── startup grants ───────────────────────────────────────────────────────
1148
1149    /// Register a startup grant: items/books to give once when a player first joins.
1150    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
1151        let items_str = grant.items.join("|");
1152        let c = YogStartupGrantDef {
1153            id:      YogStr::from_str(&grant.id),
1154            items:   YogStr::from_str(&items_str),
1155            book:    grant.book.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1156            command: grant.command.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1157        };
1158        unsafe { ((*self.api).register_startup_grant)(self.ctx(), &c) }
1159    }
1160
1161    /// Register a book with its JSON-serialized content.
1162    pub fn register_book(&mut self, book: &Book) {
1163        let json = book.to_json();
1164        let id = YogStr::from_str(&book.id);
1165        let j = YogStr::from_str(&json);
1166        unsafe { ((*self.api).register_book)(self.ctx(), id, j) }
1167    }
1168
1169
1170    /// Register a menu entry — the host renders a button on vanilla screens
1171    /// (TitleScreen on Fabric, ModListScreen on Forge/NeoForge) that opens `ui_id`.
1172    /// `label` is the human-readable button text (e.g. "Yog Mods").
1173    /// `ui_id` is the UI to open when clicked (e.g. "yog:modlist").
1174    /// See [`installed_mods`]. During `register()` the list only contains
1175    /// mods loaded before this one.
1176    pub fn installed_mods(&self) -> Vec<ModInfo> {
1177        installed_mods()
1178    }
1179
1180    pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
1181        let l = YogStr::from_str(label);
1182        let u = YogStr::from_str(ui_id);
1183        unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
1184    }
1185
1186    /// Register a UI tree with an event callback.
1187    /// `ui_id` is the unique identifier (e.g. "mymod:menu").
1188    /// `handler` receives `(ui_id, event_id)` when a widget is clicked.
1189    pub fn register_ui<F>(&mut self, ui_id: &str, handler: F)
1190    where F: Fn(&str, &str) + Send + Sync + 'static {
1191        let ud = Self::leak(handler);
1192        let id = YogStr::from_str(ui_id);
1193        let empty = YogStr::from_str("{}");
1194        unsafe {
1195            ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F>)
1196        }
1197    }
1198
1199    // ── scheduler ────────────────────────────────────────────────────────────
1200
1201    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
1202    where F: Fn(&dyn Server) + Send + Sync + 'static {
1203        let ud = Self::leak(handler);
1204        unsafe { ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>) }
1205    }
1206
1207    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
1208    where F: Fn(&dyn Server) + Send + Sync + 'static {
1209        let ud = Self::leak(handler);
1210        unsafe { ((*self.api).schedule_repeating)(self.ctx(), period_ticks, ud, trampoline_scheduled::<F>) }
1211    }
1212
1213    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
1214
1215    /// Register a handler called every client tick (render thread, no server).
1216    pub fn on_client_tick<F>(&mut self, handler: F)
1217    where F: Fn(&ClientTickEvent) + Send + Sync + 'static {
1218        let ud = Self::leak(handler);
1219        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
1220    }
1221
1222    /// Register a render callback for a specific UI screen (`ui_id`).
1223    ///
1224    /// Called from `YogUIScreen.render()` — i.e. AFTER `renderBackground()` darkens
1225    /// the screen — so your UI draws on top of the dimmed world view.
1226    /// Use this instead of `on_hud_render` for book/inventory screens.
1227    ///
1228    /// Clicks are forwarded as `"click:X:Y"` via the `register_ui` handler so you
1229    /// can do hit-testing on your own stored layout.
1230    pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
1231    where F: Fn(&GfxContext) + Send + Sync + 'static {
1232        let ud = Self::leak(handler);
1233        let id = YogStr::from_str(ui_id);
1234        unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
1235    }
1236
1237    /// Register a handler called every frame when the HUD is rendered.
1238    ///
1239    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
1240    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
1241    pub fn on_hud_render<F>(&mut self, handler: F)
1242    where F: Fn(&GfxContext) + Send + Sync + 'static {
1243        let ud = Self::leak(handler);
1244        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
1245    }
1246
1247    /// Register a handler called every frame at the end of world rendering.
1248    ///
1249    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
1250    /// `gfx.camera_pos()` is the camera world position.
1251    /// To render at world position P, translate by `P - camera_pos` before drawing.
1252    pub fn on_world_render<F>(&mut self, handler: F)
1253    where F: Fn(&GfxContext) + Send + Sync + 'static {
1254        let ud = Self::leak(handler);
1255        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
1256    }
1257
1258    /// Register a handler for keyboard input (client-side).
1259    /// Return `false` to prevent Minecraft from processing the key.
1260    pub fn on_key_press<F>(&mut self, handler: F)
1261    where F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static {
1262        let ud = Self::leak(handler);
1263        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
1264    }
1265
1266    /// Register a handler called when a GUI screen is opened.
1267    pub fn on_screen_open<F>(&mut self, handler: F)
1268    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1269        let ud = Self::leak(handler);
1270        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
1271    }
1272
1273    /// Register a handler called when a GUI screen is closed.
1274    pub fn on_screen_close<F>(&mut self, handler: F)
1275    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1276        let ud = Self::leak(handler);
1277        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
1278    }
1279}
1280
1281unsafe extern "C" fn trampoline_ui_event<F>(ud: *mut c_void, ui_id: yog_abi::YogStr, event_id: yog_abi::YogStr)
1282where F: Fn(&str, &str) + Send + Sync + 'static
1283{
1284    let f = &*(ud as *const F);
1285    let ui = unsafe { ui_id.as_str() };
1286    let ev = unsafe { event_id.as_str() };
1287    f(ui, ev);
1288}
1289
1290// ── Mod trait ─────────────────────────────────────────────────────────────────
1291
1292/// Implemented by every Yog mod. Called once at startup to register handlers.
1293pub trait Mod {
1294    fn register(registry: &mut Registry);
1295}