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 entity_rotation(&self, uuid: &str) -> Option<(f32, f32)> {
270        let s = srv!(self);
271        let mut out = yog_abi::YogVec3 { x: 0.0, y: 0.0, z: 0.0 };
272        let ok = unsafe { (s.entity_rotation)(s.ctx, YogStr::from_str(uuid), &mut out) };
273        if ok { Some((out.x as f32, out.y as f32)) } else { None }
274    }
275
276    fn send_actionbar(&self, player: &str, message: &str) -> bool {
277        let s = srv!(self);
278        unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
279    }
280
281    fn kick_player(&self, player: &str, reason: &str) -> bool {
282        let s = srv!(self);
283        unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
284    }
285
286    fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
287        let s = srv!(self);
288        unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
289    }
290
291    fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
292        let s = srv!(self);
293        unsafe { (s.bossbar_create)(s.ctx, YogStr::from_str(id), YogStr::from_str(title), YogStr::from_str(color), YogStr::from_str(style)) }
294    }
295
296    fn bossbar_remove(&self, id: &str) -> bool {
297        let s = srv!(self);
298        unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
299    }
300
301    fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
302        let s = srv!(self);
303        unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
304    }
305
306    fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
307        let s = srv!(self);
308        unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
309    }
310
311    fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
312        let s = srv!(self);
313        unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
314    }
315
316    fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
317        let s = srv!(self);
318        unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
319    }
320
321    fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
322        let s = srv!(self);
323        unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
324    }
325
326    fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
327        let s = srv!(self);
328        unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
329    }
330
331    fn online_players(&self) -> Vec<String> {
332        let s = srv!(self);
333        let owned = unsafe { (s.online_players)(s.ctx) };
334        if owned.is_none() { return Vec::new(); }
335        let text = unsafe {
336            String::from_utf8(
337                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
338            ).unwrap_or_default()
339        };
340        unsafe { (s.free_str)(owned.ptr, owned.len) };
341        if text.is_empty() { Vec::new() } else { text.lines().map(str::to_owned).collect() }
342    }
343
344    fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
345        let s = srv!(self);
346        let owned = unsafe {
347            (s.get_block_nbt)(s.ctx, YogStr::from_str(dimension),
348                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z })
349        };
350        if owned.is_none() { return None; }
351        let result = unsafe {
352            String::from_utf8(
353                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
354            ).ok()
355        };
356        unsafe { (s.free_str)(owned.ptr, owned.len) };
357        result
358    }
359
360    fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
361        let s = srv!(self);
362        unsafe {
363            (s.set_block_nbt)(s.ctx, YogStr::from_str(dimension),
364                yog_abi::YogBlockPos { x: pos.x, y: pos.y, z: pos.z },
365                YogStr::from_str(snbt))
366        }
367    }
368
369    fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
370        let s = srv!(self);
371        let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
372        if owned.is_none() { return Vec::new(); }
373        let text = unsafe {
374            String::from_utf8(
375                std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
376            ).unwrap_or_default()
377        };
378        unsafe { (s.free_str)(owned.ptr, owned.len) };
379        text.lines().filter_map(|line| {
380            let mut it = line.split('\t');
381            let slot: u32 = it.next()?.parse().ok()?;
382            let item_id = it.next()?.to_owned();
383            let count: u32 = it.next()?.parse().ok()?;
384            Some((slot, item_id, count))
385        }).collect()
386    }
387
388    fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
389        let s = srv!(self);
390        unsafe {
391            (s.player_set_slot)(s.ctx, YogStr::from_str(player), slot, YogStr::from_str(item_id), count)
392        }
393    }
394
395    fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
396        let s = srv!(self);
397        unsafe {
398            (s.player_teleport_dim)(s.ctx, YogStr::from_str(player),
399                YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z })
400        }
401    }
402
403    fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
404        let s = srv!(self);
405        unsafe {
406            (s.entity_teleport_dim)(s.ctx, YogStr::from_str(uuid),
407                YogStr::from_str(dimension), yog_abi::YogVec3 { x, y, z })
408        }
409    }
410
411    fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
412        let s = srv!(self);
413        unsafe { (s.world_entity_count)(s.ctx, YogStr::from_str(dimension), YogStr::from_str(entity_type)) }
414    }
415
416    fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
417        let s = srv!(self);
418        let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
419        if owned.is_none() { return None; }
420        let result = unsafe {
421            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
422        };
423        unsafe { (s.free_str)(owned.ptr, owned.len) };
424        result
425    }
426
427    fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
428        let s = srv!(self);
429        unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
430    }
431
432    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 {
433        let s = srv!(self);
434        unsafe {
435            (s.spawn_particles)(s.ctx, YogStr::from_str(dimension),
436                yog_abi::YogVec3 { x, y, z }, YogStr::from_str(particle_type),
437                count, dx, dy, dz, speed)
438        }
439    }
440
441    fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
442        let s = srv!(self);
443        let v = unsafe { (s.entity_attribute_get)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(attribute_id)) };
444        if v.is_nan() { None } else { Some(v) }
445    }
446
447    fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
448        let s = srv!(self);
449        unsafe { (s.entity_attribute_set)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(attribute_id), value) }
450    }
451
452    fn get_held_item_nbt(&self, player: &str) -> Option<String> {
453        let s = srv!(self);
454        let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
455        if owned.is_none() { return None; }
456        let result = unsafe {
457            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
458        };
459        unsafe { (s.free_str)(owned.ptr, owned.len) };
460        result
461    }
462
463    fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
464        let s = srv!(self);
465        unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
466    }
467
468    fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
469        let s = srv!(self);
470        let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
471        if owned.is_none() { return None; }
472        let result = unsafe {
473            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()).ok()
474        };
475        unsafe { (s.free_str)(owned.ptr, owned.len) };
476        result
477    }
478
479    fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
480        let s = srv!(self);
481        unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
482    }
483
484    fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
485        let s = srv!(self);
486        let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
487        if owned.is_none() { return None; }
488        let text = unsafe {
489            String::from_utf8(std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec())
490                .unwrap_or_default()
491        };
492        unsafe { (s.free_str)(owned.ptr, owned.len) };
493        let mut it = text.splitn(3, '\t');
494        let item_id = it.next()?.to_owned();
495        let count: u32 = it.next()?.parse().ok()?;
496        let nbt = it.next().unwrap_or("{}").to_owned();
497        Some((item_id, count, nbt))
498    }
499
500    fn set_slot_item(&self, player: &str, slot: u32, item_id: &str, count: u32, snbt: &str) -> bool {
501        let s = srv!(self);
502        unsafe {
503            (s.set_slot_item)(s.ctx, YogStr::from_str(player), slot,
504                YogStr::from_str(item_id), count, YogStr::from_str(snbt))
505        }
506    }
507}
508
509// ── Trampoline helpers ────────────────────────────────────────────────────────
510//
511// Each trampoline is a `unsafe extern "C" fn` that:
512//   1. Casts `ud` back to the original boxed closure.
513//   2. Converts ABI C structs to Rust event types.
514//   3. Builds a `CServer` from the `*const YogServer`.
515//   4. Calls the closure.
516//
517// Closures are Box::into_raw'd in Registry methods — they are INTENTIONALLY
518// leaked and live for the process lifetime.
519
520// ── Trampoline helpers ────────────────────────────────────────────────────────
521//
522// All phased event trampolines share the same pattern:
523//   `phase: u8`  0 = EventPhase::Pre, 1 = EventPhase::Post
524//   Return value is meaningful only in Pre phase.
525
526macro_rules! trampoline_phased {
527    ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr) => {
528        unsafe extern "C" fn $name<F>(
529            ud: *mut c_void, srv: *const YogServer, ev: *const $abi_ev, phase: u8,
530        ) -> bool
531        where F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
532        {
533            let f = &*(ud as *const F);
534            let $ev = &*ev;
535            let rust_ev = $build;
536            let p = if phase == 0 { EventPhase::Pre } else { EventPhase::Post };
537            f(&rust_ev, p, &CServer(srv))
538        }
539    };
540}
541
542trampoline_phased!(trampoline_block_break, YogBlockBreakEvent, BlockBreakEvent, |ev| BlockBreakEvent {
543    player_name: ev.player.as_str().to_owned(),
544    block_id:    ev.block.as_str().to_owned(),
545    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
546});
547
548trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
549    player_name: ev.player.as_str().to_owned(),
550    message:     ev.message.as_str().to_owned(),
551});
552
553trampoline_phased!(trampoline_player_join, YogPlayerEvent, PlayerJoinEvent, |ev| PlayerJoinEvent {
554    player_name: ev.player.as_str().to_owned(),
555    uuid:        ev.uuid.as_str().to_owned(),
556});
557
558trampoline_phased!(trampoline_player_leave, YogPlayerEvent, PlayerLeaveEvent, |ev| PlayerLeaveEvent {
559    player_name: ev.player.as_str().to_owned(),
560    uuid:        ev.uuid.as_str().to_owned(),
561});
562
563trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| UseItemEvent {
564    player_name: ev.player.as_str().to_owned(),
565    item_id:     ev.item.as_str().to_owned(),
566    sneaking: ev.sneaking,
567});
568
569trampoline_phased!(trampoline_use_block, YogUseBlockEvent, UseBlockEvent, |ev| UseBlockEvent {
570    player_name: ev.player.as_str().to_owned(),
571    block_id:    ev.block.as_str().to_owned(),
572    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
573});
574
575trampoline_phased!(trampoline_attack_entity, YogAttackEntityEvent, AttackEntityEvent, |ev| AttackEntityEvent {
576    player_name: ev.player.as_str().to_owned(),
577    target_type: ev.target_type.as_str().to_owned(),
578    target_uuid: ev.target_uuid.as_str().to_owned(),
579});
580
581trampoline_phased!(trampoline_entity_damage, YogEntityDamageEvent, EntityDamageEvent, |ev| EntityDamageEvent {
582    entity_type: ev.entity_type.as_str().to_owned(),
583    uuid:        ev.uuid.as_str().to_owned(),
584    amount:      ev.amount,
585    source:      ev.source.as_str().to_owned(),
586});
587
588trampoline_phased!(trampoline_entity_death, YogEntityDeathEvent, EntityDeathEvent, |ev| EntityDeathEvent {
589    entity_type: ev.entity_type.as_str().to_owned(),
590    uuid:        ev.uuid.as_str().to_owned(),
591    source:      ev.source.as_str().to_owned(),
592});
593
594trampoline_phased!(trampoline_entity_spawn, YogEntitySpawnEvent, EntitySpawnEvent, |ev| EntitySpawnEvent {
595    entity_type: ev.entity_type.as_str().to_owned(),
596    uuid:        ev.uuid.as_str().to_owned(),
597    dimension:   ev.dimension.as_str().to_owned(),
598});
599
600trampoline_phased!(trampoline_place_block, YogPlaceBlockEvent, PlaceBlockEvent, |ev| PlaceBlockEvent {
601    player_name: ev.player.as_str().to_owned(),
602    block_id:    ev.block.as_str().to_owned(),
603    pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
604});
605
606trampoline_phased!(trampoline_player_death, YogPlayerDeathEvent, PlayerDeathEvent, |ev| PlayerDeathEvent {
607    player_name: ev.player.as_str().to_owned(),
608    uuid:        ev.uuid.as_str().to_owned(),
609    source:      ev.source.as_str().to_owned(),
610});
611
612trampoline_phased!(trampoline_player_respawn, YogPlayerRespawnEvent, PlayerRespawnEvent, |ev| PlayerRespawnEvent {
613    player_name: ev.player.as_str().to_owned(),
614    uuid:        ev.uuid.as_str().to_owned(),
615    at_anchor:   ev.at_anchor,
616});
617
618trampoline_phased!(trampoline_advancement, YogAdvancementEvent, AdvancementEvent, |ev| AdvancementEvent {
619    player_name:    ev.player.as_str().to_owned(),
620    uuid:           ev.uuid.as_str().to_owned(),
621    advancement_id: ev.advancement.as_str().to_owned(),
622});
623
624trampoline_phased!(trampoline_entity_interact, YogEntityInteractEvent, EntityInteractEvent, |ev| EntityInteractEvent {
625    player_name: ev.player.as_str().to_owned(),
626    player_uuid: ev.player_uuid.as_str().to_owned(),
627    entity_type: ev.entity_type.as_str().to_owned(),
628    entity_uuid: ev.entity_uuid.as_str().to_owned(),
629    hand:        ev.hand.as_str().to_owned(),
630});
631
632trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| CraftEvent {
633    player_name:  ev.player.as_str().to_owned(),
634    player_uuid:  ev.player_uuid.as_str().to_owned(),
635    result_item:  ev.result_item.as_str().to_owned(),
636    result_count: ev.result_count,
637});
638
639trampoline_phased!(trampoline_explosion, YogExplosionEvent, ExplosionEvent, |ev| ExplosionEvent {
640    dimension:  ev.dimension.as_str().to_owned(),
641    x:          ev.x,
642    y:          ev.y,
643    z:          ev.z,
644    power:      ev.power,
645    cause_uuid: ev.cause_uuid.as_str().to_owned(),
646});
647
648trampoline_phased!(trampoline_item_pickup, YogItemPickupEvent, ItemPickupEvent, |ev| ItemPickupEvent {
649    player_name: ev.player.as_str().to_owned(),
650    player_uuid: ev.player_uuid.as_str().to_owned(),
651    item_id:     ev.item_id.as_str().to_owned(),
652    item_count:  ev.item_count,
653    entity_uuid: ev.entity_uuid.as_str().to_owned(),
654});
655
656trampoline_phased!(trampoline_player_move, YogPlayerMoveEvent, PlayerMoveEvent, |ev| PlayerMoveEvent {
657    player_name: ev.player.as_str().to_owned(),
658    player_uuid: ev.player_uuid.as_str().to_owned(),
659    x:     ev.x,
660    y:     ev.y,
661    z:     ev.z,
662    yaw:   ev.yaw,
663    pitch: ev.pitch,
664});
665
666trampoline_phased!(trampoline_container_open, YogContainerOpenEvent, ContainerOpenEvent, |ev| ContainerOpenEvent {
667    player_name:    ev.player.as_str().to_owned(),
668    player_uuid:    ev.player_uuid.as_str().to_owned(),
669    container_type: ev.container_type.as_str().to_owned(),
670});
671
672trampoline_phased!(trampoline_container_close, YogContainerCloseEvent, ContainerCloseEvent, |ev| ContainerCloseEvent {
673    player_name: ev.player.as_str().to_owned(),
674    player_uuid: ev.player_uuid.as_str().to_owned(),
675});
676
677trampoline_phased!(trampoline_projectile_hit, YogProjectileHitEvent, ProjectileHitEvent, |ev| ProjectileHitEvent {
678    projectile_type: ev.projectile_type.as_str().to_owned(),
679    projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
680    shooter_uuid:    ev.shooter_uuid.as_str().to_owned(),
681    hit_type:        ev.hit_type.as_str().to_owned(),
682    hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
683    x:               ev.x,
684    y:               ev.y,
685    z:               ev.z,
686    dimension:       ev.dimension.as_str().to_owned(),
687});
688
689unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
690where F: Fn(&dyn Server) + Send + Sync,
691{
692    let f = &*(ud as *const F);
693    f(&CServer(srv));
694}
695
696unsafe extern "C" fn trampoline_packet<F>(ud: *mut c_void, srv: *const YogServer, ev: *const YogPacketEvent)
697where F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
698{
699    let f = &*(ud as *const F);
700    let ev = &*ev;
701    let rust_ev = PacketEvent {
702        channel: ev.channel.as_str().to_owned(),
703        player:  ev.player.as_str().to_owned(),
704        payload: std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec(),
705    };
706    f(&rust_ev, &CServer(srv));
707}
708
709unsafe extern "C" fn trampoline_command<F>(
710    ud: *mut c_void,
711    srv: *const YogServer,
712    ev: *const YogCommandEvent,
713    reply_buf: *mut u8,
714    reply_cap: u32,
715    reply_len: *mut u32,
716) where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
717{
718    let f = &*(ud as *const F);
719    let ev = &*ev;
720    let ctx = CommandContext {
721        name:   ev.name.as_str().to_owned(),
722        args:   ev.args.as_str().to_owned(),
723        source: ev.source.as_str().to_owned(),
724        uuid:   ev.uuid.as_str().to_owned(),
725    };
726    *reply_len = 0;
727    if let Some(reply) = f(&ctx, &CServer(srv)) {
728        let bytes = reply.as_bytes();
729        let n = bytes.len().min(reply_cap as usize);
730        std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
731        *reply_len = n as u32;
732    }
733}
734
735unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
736where F: Fn(&dyn Server) + Send + Sync,
737{
738    let f = &*(ud as *const F);
739    f(&CServer(srv));
740}
741
742// ── ABI minor 10 — client-side trampolines ────────────────────────────────────
743
744unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
745where F: Fn(&ClientTickEvent) + Send + Sync,
746{
747    let f = &*(ud as *const F);
748    f(&ClientTickEvent {});
749}
750
751unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
752where F: Fn(&GfxContext) + Send + Sync,
753{
754    let f = &*(ud as *const F);
755    f(&GfxContext::from_raw(gfx));
756}
757
758unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
759where F: Fn(&GfxContext) + Send + Sync,
760{
761    let f = &*(ud as *const F);
762    f(&GfxContext::from_raw(gfx));
763}
764
765unsafe extern "C" fn trampoline_key_press<F>(
766    ud: *mut c_void,
767    ev: *const YogKeyPressEvent,
768) -> bool
769where F: Fn(&KeyPressEvent) -> bool + Send + Sync,
770{
771    let f = &*(ud as *const F);
772    let e = &*ev;
773    f(&KeyPressEvent {
774        key_code:  e.key_code,
775        scan_code: e.scan_code,
776        action:    e.action,
777        modifiers: e.modifiers,
778    })
779}
780
781unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
782where F: Fn(&ScreenEvent) + Send + Sync,
783{
784    let f = &*(ud as *const F);
785    f(&ScreenEvent { screen_class: screen_class.as_str().to_owned() });
786    true
787}
788
789// ── Registry ─────────────────────────────────────────────────────────────────
790
791/// Wraps the [`YogApi`] table and provides an ergonomic registration API.
792///
793/// Obtained inside `yog_mod_register` via `export_mod!`.  Closures registered
794/// here are boxed and leaked — they live as long as the process (which is the
795/// correct lifetime for a server mod).
796/// Pointer to the runtime's `YogApi` table (a process-lifetime static in the
797/// runtime), captured when the mod registers. Lets free functions like
798/// [`installed_mods`] work outside of `register()` — e.g. in UI handlers.
799static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
800    std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
801
802/// Metadata of an installed mod, as reported by the loader.
803#[derive(Debug, Clone)]
804pub struct ModInfo {
805    /// `"yog"` for .yog mods, `"platform"` for loader (Java) mods.
806    pub source:      String,
807    pub id:          String,
808    pub name:        String,
809    pub version:     String,
810    /// Comma-separated author list (may be empty).
811    pub authors:     String,
812    pub description: String,
813}
814
815/// All installed mods known to the loader: .yog mods plus, where the host
816/// exposes them, platform (Java) mods. Callable at any time after this mod's
817/// `register()` ran — including client-side UI handlers. Note that during
818/// `register()` mods that load after this one are not in the list yet; query
819/// lazily (e.g. on first render) for a complete view.
820pub fn installed_mods() -> Vec<ModInfo> {
821    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
822    if api.is_null() { return Vec::new(); }
823    let owned = unsafe { ((*api).mods_list)((*api).ctx) };
824    if owned.is_none() { return Vec::new(); }
825    let text = unsafe {
826        String::from_utf8(
827            std::slice::from_raw_parts(owned.ptr, owned.len as usize).to_vec()
828        ).unwrap_or_default()
829    };
830    unsafe { ((*api).free_str)(owned.ptr, owned.len) };
831    text.lines()
832        .filter_map(|line| {
833            let mut f = line.split('\t');
834            Some(ModInfo {
835                source:      f.next()?.to_string(),
836                id:          f.next()?.to_string(),
837                name:        f.next().unwrap_or_default().to_string(),
838                version:     f.next().unwrap_or_default().to_string(),
839                authors:     f.next().unwrap_or_default().to_string(),
840                description: f.next().unwrap_or_default().to_string(),
841            })
842        })
843        .collect()
844}
845
846/// Open the Yog UI registered as `ui_id` (client side; no-op on dedicated
847/// servers). `modal` blocks game input, `pause` pauses singleplayer. Callable
848/// from any handler after registration — e.g. an `on_client_packet` handler.
849pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
850    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
851    if api.is_null() { return; }
852    unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
853}
854
855/// Handle to the runtime's server-action table, usable from any handler after
856/// registration — including client-side ones that don't receive `&dyn Server`
857/// (UI event handlers, client tick). Server-world actions are no-ops client
858/// side, but networking (`send_to_server`) and similar client-safe calls work.
859pub fn server() -> Option<CServer> {
860    let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
861    if api.is_null() { return None; }
862    let srv = unsafe { (*api).server };
863    if srv.is_null() { return None; }
864    Some(CServer(srv))
865}
866
867pub struct Registry {
868    api: *const YogApi,
869}
870
871// SAFETY: `api` is a static provided by the runtime, valid for process lifetime.
872unsafe impl Send for Registry {}
873unsafe impl Sync for Registry {}
874
875impl Registry {
876    /// Build from the pointer passed by the runtime. Only called by `export_mod!`.
877    pub unsafe fn from_raw(api: *const YogApi) -> Self {
878        GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
879        Self { api }
880    }
881
882    #[inline]
883    fn ctx(&self) -> *mut c_void { unsafe { (*self.api).ctx } }
884
885    // ── helpers ──────────────────────────────────────────────────────────────
886
887    fn leak<F: 'static>(f: F) -> *mut c_void {
888        Box::into_raw(Box::new(f)) as *mut c_void
889    }
890
891    // ── events ───────────────────────────────────────────────────────────────
892    //
893    // All handlers receive `(event, EventPhase, &dyn Server) -> bool`.
894    // In `Pre` phase, returning `false` cancels the action.
895    // In `Post` phase, the return value is ignored.
896    // A single registration fires for BOTH phases.
897
898    pub fn on_block_break<F>(&mut self, handler: F)
899    where F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
900        let ud = Self::leak(handler);
901        unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
902    }
903
904    pub fn on_chat<F>(&mut self, handler: F)
905    where F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
906        let ud = Self::leak(handler);
907        unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
908    }
909
910    pub fn on_player_join<F>(&mut self, handler: F)
911    where F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
912        let ud = Self::leak(handler);
913        unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
914    }
915
916    pub fn on_player_leave<F>(&mut self, handler: F)
917    where F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
918        let ud = Self::leak(handler);
919        unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
920    }
921
922    pub fn on_use_item<F>(&mut self, handler: F)
923    where F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
924        let ud = Self::leak(handler);
925        unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
926    }
927
928    pub fn on_use_block<F>(&mut self, handler: F)
929    where F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
930        let ud = Self::leak(handler);
931        unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
932    }
933
934    pub fn on_attack_entity<F>(&mut self, handler: F)
935    where F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
936        let ud = Self::leak(handler);
937        unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
938    }
939
940    pub fn on_entity_damage<F>(&mut self, handler: F)
941    where F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
942        let ud = Self::leak(handler);
943        unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
944    }
945
946    pub fn on_entity_death<F>(&mut self, handler: F)
947    where F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
948        let ud = Self::leak(handler);
949        unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
950    }
951
952    pub fn on_entity_spawn<F>(&mut self, handler: F)
953    where F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
954        let ud = Self::leak(handler);
955        unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
956    }
957
958    pub fn on_player_place_block<F>(&mut self, handler: F)
959    where F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
960        let ud = Self::leak(handler);
961        unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
962    }
963
964    pub fn on_player_death<F>(&mut self, handler: F)
965    where F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
966        let ud = Self::leak(handler);
967        unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
968    }
969
970    pub fn on_player_respawn<F>(&mut self, handler: F)
971    where F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
972        let ud = Self::leak(handler);
973        unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
974    }
975
976    pub fn on_advancement<F>(&mut self, handler: F)
977    where F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
978        let ud = Self::leak(handler);
979        unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
980    }
981
982    pub fn on_entity_interact<F>(&mut self, handler: F)
983    where F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
984        let ud = Self::leak(handler);
985        unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
986    }
987
988    pub fn on_item_craft<F>(&mut self, handler: F)
989    where F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
990        let ud = Self::leak(handler);
991        unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
992    }
993
994    pub fn on_explosion<F>(&mut self, handler: F)
995    where F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
996        let ud = Self::leak(handler);
997        unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
998    }
999
1000    pub fn on_item_pickup<F>(&mut self, handler: F)
1001    where F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
1002        let ud = Self::leak(handler);
1003        unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
1004    }
1005
1006    pub fn on_player_move<F>(&mut self, handler: F)
1007    where F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
1008        let ud = Self::leak(handler);
1009        unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
1010    }
1011
1012    pub fn on_container_open<F>(&mut self, handler: F)
1013    where F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
1014        let ud = Self::leak(handler);
1015        unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
1016    }
1017
1018    pub fn on_container_close<F>(&mut self, handler: F)
1019    where F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
1020        let ud = Self::leak(handler);
1021        unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
1022    }
1023
1024    pub fn on_projectile_hit<F>(&mut self, handler: F)
1025    where F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static {
1026        let ud = Self::leak(handler);
1027        unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
1028    }
1029
1030    pub fn on_tick<F>(&mut self, listener: F)
1031    where F: Fn(&dyn Server) + Send + Sync + 'static {
1032        let ud = Self::leak(listener);
1033        unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
1034    }
1035
1036    pub fn on_server_started<F>(&mut self, listener: F)
1037    where F: Fn(&dyn Server) + Send + Sync + 'static {
1038        let ud = Self::leak(listener);
1039        unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
1040    }
1041
1042    pub fn on_server_stopping<F>(&mut self, listener: F)
1043    where F: Fn(&dyn Server) + Send + Sync + 'static {
1044        let ud = Self::leak(listener);
1045        unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
1046    }
1047
1048    // ── commands ─────────────────────────────────────────────────────────────
1049
1050    pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
1051    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
1052        let name_ys = YogStr::from_str(name.as_ref());
1053        let ud = Self::leak(handler);
1054        unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
1055    }
1056
1057    /// Register a command with Brigadier-typed arguments.
1058    ///
1059    /// `schema` is a space-separated list of argument types:
1060    /// `int`, `float`, `word`, `string` (greedy, must be last), `player`, `blockpos`.
1061    ///
1062    /// In the handler use `ctx.arg_int(0)`, `ctx.arg_blockpos(1)`, etc.
1063    pub fn on_typed_command<F>(&mut self, name: impl AsRef<str>, schema: impl AsRef<str>, handler: F)
1064    where F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static {
1065        let name_ys   = YogStr::from_str(name.as_ref());
1066        let schema_ys = YogStr::from_str(schema.as_ref());
1067        let ud = Self::leak(handler);
1068        unsafe { ((*self.api).register_typed_command)(self.ctx(), name_ys, schema_ys, ud, trampoline_command::<F>) }
1069    }
1070
1071    // ── networking ───────────────────────────────────────────────────────────
1072
1073    pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1074    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
1075        let ch = YogStr::from_str(channel.as_ref());
1076        let ud = Self::leak(handler);
1077        unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1078    }
1079
1080    pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
1081    where F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static {
1082        let ch = YogStr::from_str(channel.as_ref());
1083        let ud = Self::leak(handler);
1084        unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
1085    }
1086
1087    /// Register a typed-packet handler.
1088    ///
1089    /// The payload is decoded from raw bytes using `P`'s [`Packet`] impl.
1090    /// Malformed payloads are silently dropped.
1091    pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
1092    where
1093        P: Packet + Send + Sync + 'static,
1094        F: Fn(&P, &dyn Server) + Send + Sync + 'static,
1095    {
1096        self.on_packet(channel, move |ev, srv| {
1097            if let Some(pkt) = P::decode(&ev.payload) {
1098                handler(&pkt, srv);
1099            }
1100        });
1101    }
1102
1103    // ── recipes ──────────────────────────────────────────────────────────────
1104
1105    fn recipe(&mut self, ns: &str, name: &str, json: &str) {
1106        unsafe {
1107            ((*self.api).register_recipe_json)(
1108                self.ctx(),
1109                YogStr::from_str(ns), YogStr::from_str(name), YogStr::from_str(json),
1110            )
1111        }
1112    }
1113
1114    /// Register a shaped crafting recipe.
1115    pub fn add_shaped_recipe(&mut self, recipe: ShapedRecipe) {
1116        let json = recipe.to_json();
1117        let (ns, name) = recipe.ns_name();
1118        self.recipe(ns, name, &json);
1119    }
1120
1121    /// Register a shapeless crafting recipe.
1122    pub fn add_shapeless_recipe(&mut self, recipe: ShapelessRecipe) {
1123        let json = recipe.to_json();
1124        let (ns, name) = recipe.ns_name();
1125        self.recipe(ns, name, &json);
1126    }
1127
1128    /// Register a furnace smelting recipe.
1129    pub fn add_furnace_recipe(&mut self, recipe: FurnaceRecipe) {
1130        let json = recipe.to_json();
1131        let (ns, name) = recipe.ns_name();
1132        self.recipe(ns, name, &json);
1133    }
1134
1135    // ── content ──────────────────────────────────────────────────────────────
1136
1137    pub fn register_item(&mut self, def: ItemDef) {
1138        // Build a C-compatible YogItemDef whose YogStr fields point into `def`'s
1139        // String storage.  We then call register_item which must copy the data
1140        // before returning (the runtime stores owned Strings).
1141        let food_nutrition  = def.food.as_ref().map_or(0, |f| f.nutrition);
1142        let food_saturation = def.food.as_ref().map_or(0.0, |f| f.saturation);
1143        let food_always_eat = def.food.as_ref().map_or(false, |f| f.can_always_eat);
1144        let c = YogItemDef {
1145            id:              YogStr::from_str(&def.id),
1146            max_stack:       def.max_stack as u32,
1147            name:            def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1148            tooltip:         def.tooltip.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1149            max_damage:      def.max_damage,
1150            fire_resistant:  def.fire_resistant,
1151            fuel_ticks:      def.fuel_ticks,
1152            food_nutrition,
1153            food_saturation,
1154            food_always_eat,
1155        };
1156        unsafe { ((*self.api).register_item)(self.ctx(), &c) }
1157    }
1158
1159    pub fn register_block(&mut self, def: BlockDef) {
1160        let shape = def.shape.unwrap_or([0.0; 6]);
1161        let c = YogBlockDef {
1162            id:            YogStr::from_str(&def.id),
1163            hardness:      def.hardness,
1164            resistance:    def.resistance,
1165            name:          def.name.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1166            light_level:   def.light_level,
1167            sound:         def.sound.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1168            requires_tool: def.requires_tool,
1169            no_collision:  def.no_collision,
1170            slipperiness:  def.slipperiness,
1171            shape,
1172        };
1173        unsafe { ((*self.api).register_block)(self.ctx(), &c) }
1174    }
1175
1176    // ── startup grants ───────────────────────────────────────────────────────
1177
1178    /// Register a startup grant: items/books to give once when a player first joins.
1179    pub fn register_startup_grant(&mut self, grant: StartupGrant) {
1180        let items_str = grant.items.join("|");
1181        let c = YogStartupGrantDef {
1182            id:      YogStr::from_str(&grant.id),
1183            items:   YogStr::from_str(&items_str),
1184            book:    grant.book.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1185            command: grant.command.as_deref().map(YogStr::from_str).unwrap_or(YogStr::EMPTY),
1186        };
1187        unsafe { ((*self.api).register_startup_grant)(self.ctx(), &c) }
1188    }
1189
1190    /// Register a book with its JSON-serialized content.
1191    pub fn register_book(&mut self, book: &Book) {
1192        let json = book.to_json();
1193        let id = YogStr::from_str(&book.id);
1194        let j = YogStr::from_str(&json);
1195        unsafe { ((*self.api).register_book)(self.ctx(), id, j) }
1196    }
1197
1198
1199    /// Register a menu entry — the host renders a button on vanilla screens
1200    /// (TitleScreen on Fabric, ModListScreen on Forge/NeoForge) that opens `ui_id`.
1201    /// `label` is the human-readable button text (e.g. "Yog Mods").
1202    /// `ui_id` is the UI to open when clicked (e.g. "yog:modlist").
1203    /// See [`installed_mods`]. During `register()` the list only contains
1204    /// mods loaded before this one.
1205    pub fn installed_mods(&self) -> Vec<ModInfo> {
1206        installed_mods()
1207    }
1208
1209    pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
1210        let l = YogStr::from_str(label);
1211        let u = YogStr::from_str(ui_id);
1212        unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
1213    }
1214
1215    /// Register a UI tree with an event callback.
1216    /// `ui_id` is the unique identifier (e.g. "mymod:menu").
1217    /// `handler` receives `(ui_id, event_id)` when a widget is clicked.
1218    pub fn register_ui<F>(&mut self, ui_id: &str, handler: F)
1219    where F: Fn(&str, &str) + Send + Sync + 'static {
1220        let ud = Self::leak(handler);
1221        let id = YogStr::from_str(ui_id);
1222        let empty = YogStr::from_str("{}");
1223        unsafe {
1224            ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F>)
1225        }
1226    }
1227
1228    // ── scheduler ────────────────────────────────────────────────────────────
1229
1230    pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
1231    where F: Fn(&dyn Server) + Send + Sync + 'static {
1232        let ud = Self::leak(handler);
1233        unsafe { ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>) }
1234    }
1235
1236    pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
1237    where F: Fn(&dyn Server) + Send + Sync + 'static {
1238        let ud = Self::leak(handler);
1239        unsafe { ((*self.api).schedule_repeating)(self.ctx(), period_ticks, ud, trampoline_scheduled::<F>) }
1240    }
1241
1242    // ── client-side hooks (ABI minor 10) ─────────────────────────────────────
1243
1244    /// Register a handler called every client tick (render thread, no server).
1245    pub fn on_client_tick<F>(&mut self, handler: F)
1246    where F: Fn(&ClientTickEvent) + Send + Sync + 'static {
1247        let ud = Self::leak(handler);
1248        unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
1249    }
1250
1251    /// Register a render callback for a specific UI screen (`ui_id`).
1252    ///
1253    /// Called from `YogUIScreen.render()` — i.e. AFTER `renderBackground()` darkens
1254    /// the screen — so your UI draws on top of the dimmed world view.
1255    /// Use this instead of `on_hud_render` for book/inventory screens.
1256    ///
1257    /// Clicks are forwarded as `"click:X:Y"` via the `register_ui` handler so you
1258    /// can do hit-testing on your own stored layout.
1259    pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
1260    where F: Fn(&GfxContext) + Send + Sync + 'static {
1261        let ud = Self::leak(handler);
1262        let id = YogStr::from_str(ui_id);
1263        unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
1264    }
1265
1266    /// Register a handler called every frame when the HUD is rendered.
1267    ///
1268    /// `gfx` provides full GPU pipeline access plus 2D convenience draw calls.
1269    /// `view_proj` and `camera_pos` are zero in HUD context; use `gfx.draw2d()` for HUD elements.
1270    pub fn on_hud_render<F>(&mut self, handler: F)
1271    where F: Fn(&GfxContext) + Send + Sync + 'static {
1272        let ud = Self::leak(handler);
1273        unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
1274    }
1275
1276    /// Register a handler called every frame at the end of world rendering.
1277    ///
1278    /// `gfx.view_proj()` is the combined projection × view matrix (camera-relative).
1279    /// `gfx.camera_pos()` is the camera world position.
1280    /// To render at world position P, translate by `P - camera_pos` before drawing.
1281    pub fn on_world_render<F>(&mut self, handler: F)
1282    where F: Fn(&GfxContext) + Send + Sync + 'static {
1283        let ud = Self::leak(handler);
1284        unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
1285    }
1286
1287    /// Register a handler for keyboard input (client-side).
1288    /// Return `false` to prevent Minecraft from processing the key.
1289    pub fn on_key_press<F>(&mut self, handler: F)
1290    where F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static {
1291        let ud = Self::leak(handler);
1292        unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
1293    }
1294
1295    /// Register a handler called when a GUI screen is opened.
1296    pub fn on_screen_open<F>(&mut self, handler: F)
1297    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1298        let ud = Self::leak(handler);
1299        unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
1300    }
1301
1302    /// Register a handler called when a GUI screen is closed.
1303    pub fn on_screen_close<F>(&mut self, handler: F)
1304    where F: Fn(&ScreenEvent) + Send + Sync + 'static {
1305        let ud = Self::leak(handler);
1306        unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
1307    }
1308}
1309
1310unsafe extern "C" fn trampoline_ui_event<F>(ud: *mut c_void, ui_id: yog_abi::YogStr, event_id: yog_abi::YogStr)
1311where F: Fn(&str, &str) + Send + Sync + 'static
1312{
1313    let f = &*(ud as *const F);
1314    let ui = unsafe { ui_id.as_str() };
1315    let ev = unsafe { event_id.as_str() };
1316    f(ui, ev);
1317}
1318
1319// ── Mod trait ─────────────────────────────────────────────────────────────────
1320
1321/// Implemented by every Yog mod. Called once at startup to register handlers.
1322pub trait Mod {
1323    fn register(registry: &mut Registry);
1324}