1use std::os::raw::c_void;
11
12use yog_abi::{
13 YogAdvancementEvent, YogApi, YogAttackEntityEvent, YogBlockBreakEvent,
14 YogChatEvent, YogCommandEvent, YogContainerCloseEvent, YogContainerOpenEvent, YogCraftEvent,
15 YogEntityDamageEvent, YogEntityDeathEvent, YogEntityInteractEvent, YogEntitySpawnEvent,
16 YogExplosionEvent, YogGfxApi, YogItemInsertEvent, YogItemPickupEvent, YogMachineCraftEvent,
17 YogKeyPressEvent, YogPacketEvent, YogPlaceBlockEvent, YogPlayerDeathEvent, YogPlayerEvent,
18 YogPlayerMoveEvent, YogPlayerRespawnEvent, YogProjectileHitEvent, YogServer,
19 YogStr, YogUseBlockEvent, YogUseItemEvent,
20};
21use yog_book::Book;
22use yog_command::CommandContext;
23use yog_core::Server;
24use yog_dimensions::{ChunkWriter, YogDimensionDef};
25use yog_event::{
26 AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
27 ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
28 EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemInsertEvent, ItemPickupEvent,
29 KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
30 PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
31 UseItemEvent,
32};
33use yog_gfx::GfxContext;
34use yog_network::{Packet, PacketEvent};
35use yog_registry::{ArmorMaterial, BlockDef, ItemDef, Recipe, SoundGroup, StartupGrant, ToolMaterial};
36
37pub struct CServer(pub *const YogServer);
42
43unsafe impl Send for CServer {}
44unsafe impl Sync for CServer {}
45
46macro_rules! srv {
47 ($self:ident) => {
48 unsafe { &*$self.0 }
49 };
50}
51
52impl Server for CServer {
53 fn broadcast(&self, message: &str) {
54 let s = srv!(self);
55 unsafe { (s.broadcast)(s.ctx, YogStr::from_str(message)) }
56 }
57
58 fn get_block(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
59 let s = srv!(self);
60 let owned = unsafe {
61 (s.get_block)(
62 s.ctx,
63 YogStr::from_str(dimension),
64 yog_abi::YogBlockPos {
65 x: pos.x,
66 y: pos.y,
67 z: pos.z,
68 },
69 )
70 };
71
72 take_owned(s.free_str, owned)
73 }
74
75 fn set_block(&self, dimension: &str, pos: yog_core::BlockPos, block_id: &str) -> bool {
76 let s = srv!(self);
77 unsafe {
78 (s.set_block)(
79 s.ctx,
80 YogStr::from_str(dimension),
81 yog_abi::YogBlockPos {
82 x: pos.x,
83 y: pos.y,
84 z: pos.z,
85 },
86 YogStr::from_str(block_id),
87 )
88 }
89 }
90
91 fn give_item(&self, player: &str, item_id: &str, count: u32) -> bool {
92 let s = srv!(self);
93 unsafe {
94 (s.give_item)(
95 s.ctx,
96 YogStr::from_str(player),
97 YogStr::from_str(item_id),
98 count,
99 )
100 }
101 }
102
103 fn teleport(&self, player: &str, x: f64, y: f64, z: f64) -> bool {
104 let s = srv!(self);
105 unsafe {
106 (s.player_teleport)(
107 s.ctx,
108 YogStr::from_str(player),
109 yog_abi::YogVec3 { x, y, z },
110 )
111 }
112 }
113
114 fn send_to_player(&self, player: &str, channel: &str, payload: &[u8]) -> bool {
115 let s = srv!(self);
116 unsafe {
117 (s.send_to_player)(
118 s.ctx,
119 YogStr::from_str(player),
120 YogStr::from_str(channel),
121 payload.as_ptr(),
122 payload.len() as u32,
123 )
124 }
125 }
126
127 fn send_to_server(&self, channel: &str, payload: &[u8]) -> bool {
128 let s = srv!(self);
129 unsafe {
130 (s.send_to_server)(
131 s.ctx,
132 YogStr::from_str(channel),
133 payload.as_ptr(),
134 payload.len() as u32,
135 )
136 }
137 }
138
139 fn entity_teleport(&self, uuid: &str, x: f64, y: f64, z: f64) -> bool {
140 let s = srv!(self);
141 unsafe { (s.entity_teleport)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x, y, z }) }
142 }
143
144 fn entity_position(&self, uuid: &str) -> Option<(f64, f64, f64)> {
145 let s = srv!(self);
146 let mut out = yog_abi::YogVec3 {
147 x: 0.0,
148 y: 0.0,
149 z: 0.0,
150 };
151 if unsafe { (s.entity_position)(s.ctx, YogStr::from_str(uuid), &mut out) } {
152 Some((out.x, out.y, out.z))
153 } else {
154 None
155 }
156 }
157
158 fn entity_health(&self, uuid: &str) -> Option<f32> {
159 let s = srv!(self);
160 let mut hp = 0f32;
161 if unsafe { (s.entity_health)(s.ctx, YogStr::from_str(uuid), &mut hp) } {
162 Some(hp)
163 } else {
164 None
165 }
166 }
167
168 fn entity_set_health(&self, uuid: &str, health: f32) -> bool {
169 let s = srv!(self);
170 unsafe { (s.entity_set_health)(s.ctx, YogStr::from_str(uuid), health) }
171 }
172
173 fn entity_kill(&self, uuid: &str) -> bool {
174 let s = srv!(self);
175 unsafe { (s.entity_kill)(s.ctx, YogStr::from_str(uuid)) }
176 }
177
178 fn spawn_entity(
179 &self,
180 entity_type: &str,
181 dimension: &str,
182 x: f64,
183 y: f64,
184 z: f64,
185 ) -> Option<String> {
186 let s = srv!(self);
187 let owned = unsafe {
188 (s.spawn_entity)(
189 s.ctx,
190 YogStr::from_str(entity_type),
191 YogStr::from_str(dimension),
192 yog_abi::YogVec3 { x, y, z },
193 )
194 };
195
196 take_owned(s.free_str, owned)
197 }
198
199 fn entity_add_effect(
200 &self,
201 uuid: &str,
202 effect_id: &str,
203 duration_ticks: i32,
204 amplifier: u8,
205 show_particles: bool,
206 ) -> bool {
207 let s = srv!(self);
208 unsafe {
209 (s.entity_add_effect)(
210 s.ctx,
211 YogStr::from_str(uuid),
212 YogStr::from_str(effect_id),
213 duration_ticks,
214 amplifier,
215 show_particles,
216 )
217 }
218 }
219
220 fn entity_remove_effect(&self, uuid: &str, effect_id: &str) -> bool {
221 let s = srv!(self);
222 unsafe {
223 (s.entity_remove_effect)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(effect_id))
224 }
225 }
226
227 fn entity_clear_effects(&self, uuid: &str) -> bool {
228 let s = srv!(self);
229 unsafe { (s.entity_clear_effects)(s.ctx, YogStr::from_str(uuid)) }
230 }
231
232 fn drop_loot(&self, table_id: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
233 let s = srv!(self);
234 unsafe {
235 (s.drop_loot)(
236 s.ctx,
237 YogStr::from_str(table_id),
238 YogStr::from_str(dimension),
239 yog_abi::YogVec3 { x, y, z },
240 )
241 }
242 }
243
244 fn has_item_tag(&self, item_id: &str, tag_id: &str) -> bool {
245 let s = srv!(self);
246 unsafe { (s.has_item_tag)(s.ctx, YogStr::from_str(item_id), YogStr::from_str(tag_id)) }
247 }
248
249 fn has_block_tag(&self, block_id: &str, tag_id: &str) -> bool {
250 let s = srv!(self);
251 unsafe { (s.has_block_tag)(s.ctx, YogStr::from_str(block_id), YogStr::from_str(tag_id)) }
252 }
253
254 fn world_time(&self, dimension: &str) -> Option<i64> {
255 let s = srv!(self);
256 let mut t = 0i64;
257 if unsafe { (s.world_time)(s.ctx, YogStr::from_str(dimension), &mut t) } {
258 Some(t)
259 } else {
260 None
261 }
262 }
263
264 fn world_set_time(&self, dimension: &str, time: i64) -> bool {
265 let s = srv!(self);
266 unsafe { (s.set_time)(s.ctx, YogStr::from_str(dimension), time) }
267 }
268
269 fn world_is_raining(&self, dimension: &str) -> bool {
270 let s = srv!(self);
271 unsafe { (s.is_raining)(s.ctx, YogStr::from_str(dimension)) }
272 }
273
274 fn world_set_weather(&self, dimension: &str, raining: bool, duration_ticks: i32) -> bool {
275 let s = srv!(self);
276 unsafe { (s.set_weather)(s.ctx, YogStr::from_str(dimension), raining, duration_ticks) }
277 }
278
279 fn entity_velocity(&self, uuid: &str) -> Option<(f64, f64, f64)> {
280 let s = srv!(self);
281 let mut v = yog_abi::YogVec3 {
282 x: 0.0,
283 y: 0.0,
284 z: 0.0,
285 };
286 if unsafe { (s.entity_velocity)(s.ctx, YogStr::from_str(uuid), &mut v) } {
287 Some((v.x, v.y, v.z))
288 } else {
289 None
290 }
291 }
292
293 fn entity_set_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
294 let s = srv!(self);
295 unsafe {
296 (s.entity_set_velocity)(
297 s.ctx,
298 YogStr::from_str(uuid),
299 yog_abi::YogVec3 {
300 x: vx,
301 y: vy,
302 z: vz,
303 },
304 )
305 }
306 }
307
308 fn entity_add_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
309 let s = srv!(self);
310 unsafe {
311 (s.entity_add_velocity)(
312 s.ctx,
313 YogStr::from_str(uuid),
314 yog_abi::YogVec3 {
315 x: vx,
316 y: vy,
317 z: vz,
318 },
319 )
320 }
321 }
322
323 fn scoreboard_get(&self, objective: &str, player: &str) -> Option<i32> {
324 let s = srv!(self);
325 let mut score = 0i32;
326 if unsafe {
327 (s.scoreboard_get)(
328 s.ctx,
329 YogStr::from_str(objective),
330 YogStr::from_str(player),
331 &mut score,
332 )
333 } {
334 Some(score)
335 } else {
336 None
337 }
338 }
339
340 fn scoreboard_set(&self, objective: &str, player: &str, score: i32) -> bool {
341 let s = srv!(self);
342 unsafe {
343 (s.scoreboard_set)(
344 s.ctx,
345 YogStr::from_str(objective),
346 YogStr::from_str(player),
347 score,
348 )
349 }
350 }
351
352 fn scoreboard_add(&self, objective: &str, player: &str, delta: i32) -> Option<i32> {
353 let s = srv!(self);
354 let mut new_score = 0i32;
355 if unsafe {
356 (s.scoreboard_add)(
357 s.ctx,
358 YogStr::from_str(objective),
359 YogStr::from_str(player),
360 delta,
361 &mut new_score,
362 )
363 } {
364 Some(new_score)
365 } else {
366 None
367 }
368 }
369
370 fn game_dir(&self) -> String {
371 let s = srv!(self);
372 let owned = unsafe { (s.game_dir)(s.ctx) };
373 take_owned(s.free_str, owned).unwrap_or_default()
374 }
375
376 fn play_sound(
377 &self,
378 dimension: &str,
379 x: f64,
380 y: f64,
381 z: f64,
382 sound_id: &str,
383 volume: f32,
384 pitch: f32,
385 ) -> bool {
386 let s = srv!(self);
387 unsafe {
388 (s.play_sound)(
389 s.ctx,
390 YogStr::from_str(dimension),
391 yog_abi::YogVec3 { x, y, z },
392 YogStr::from_str(sound_id),
393 volume,
394 pitch,
395 )
396 }
397 }
398
399 fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool {
400 let s = srv!(self);
401 unsafe {
402 (s.play_sound_player)(
403 s.ctx,
404 YogStr::from_str(player),
405 YogStr::from_str(sound_id),
406 volume,
407 pitch,
408 )
409 }
410 }
411
412 fn send_title(
413 &self,
414 player: &str,
415 title: &str,
416 subtitle: &str,
417 fadein: i32,
418 stay: i32,
419 fadeout: i32,
420 ) -> bool {
421 let s = srv!(self);
422 unsafe {
423 (s.send_title)(
424 s.ctx,
425 YogStr::from_str(player),
426 YogStr::from_str(title),
427 YogStr::from_str(subtitle),
428 fadein,
429 stay,
430 fadeout,
431 )
432 }
433 }
434
435 fn entity_rotation(&self, uuid: &str) -> Option<(f32, f32)> {
436 let s = srv!(self);
437 let mut out = yog_abi::YogVec3 {
438 x: 0.0,
439 y: 0.0,
440 z: 0.0,
441 };
442 let ok = unsafe { (s.entity_rotation)(s.ctx, YogStr::from_str(uuid), &mut out) };
443 if ok {
444 Some((out.x as f32, out.y as f32))
445 } else {
446 None
447 }
448 }
449
450 fn send_actionbar(&self, player: &str, message: &str) -> bool {
451 let s = srv!(self);
452 unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
453 }
454
455 fn kick_player(&self, player: &str, reason: &str) -> bool {
456 let s = srv!(self);
457 unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
458 }
459
460 fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
461 let s = srv!(self);
462 unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
463 }
464
465 fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
466 let s = srv!(self);
467 unsafe {
468 (s.bossbar_create)(
469 s.ctx,
470 YogStr::from_str(id),
471 YogStr::from_str(title),
472 YogStr::from_str(color),
473 YogStr::from_str(style),
474 )
475 }
476 }
477
478 fn bossbar_remove(&self, id: &str) -> bool {
479 let s = srv!(self);
480 unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
481 }
482
483 fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
484 let s = srv!(self);
485 unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
486 }
487
488 fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
489 let s = srv!(self);
490 unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
491 }
492
493 fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
494 let s = srv!(self);
495 unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
496 }
497
498 fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
499 let s = srv!(self);
500 unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
501 }
502
503 fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
504 let s = srv!(self);
505 unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
506 }
507
508 fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
509 let s = srv!(self);
510 unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
511 }
512
513 fn online_players(&self) -> Vec<String> {
514 let s = srv!(self);
515 let owned = unsafe { (s.online_players)(s.ctx) };
516 let Some(text) = take_owned(s.free_str, owned) else {
517 return Vec::new();
518 };
519 if text.is_empty() {
520 Vec::new()
521 } else {
522 text.lines().map(str::to_owned).collect()
523 }
524 }
525
526 fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
527 let s = srv!(self);
528 let owned = unsafe {
529 (s.get_block_nbt)(
530 s.ctx,
531 YogStr::from_str(dimension),
532 yog_abi::YogBlockPos {
533 x: pos.x,
534 y: pos.y,
535 z: pos.z,
536 },
537 )
538 };
539
540 take_owned(s.free_str, owned)
541 }
542
543 fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
544 let s = srv!(self);
545 unsafe {
546 (s.set_block_nbt)(
547 s.ctx,
548 YogStr::from_str(dimension),
549 yog_abi::YogBlockPos {
550 x: pos.x,
551 y: pos.y,
552 z: pos.z,
553 },
554 YogStr::from_str(snbt),
555 )
556 }
557 }
558
559 fn get_inventory_slot(
560 &self,
561 dimension: &str,
562 pos: yog_core::BlockPos,
563 slot: u32,
564 ) -> Option<(String, u32, String)> {
565 let s = srv!(self);
566 let owned = unsafe {
567 (s.get_inventory_slot)(
568 s.ctx,
569 YogStr::from_str(dimension),
570 yog_abi::YogBlockPos {
571 x: pos.x,
572 y: pos.y,
573 z: pos.z,
574 },
575 slot,
576 )
577 };
578 let text = take_owned(s.free_str, owned)?;
579 let mut it = text.splitn(3, '\t');
580 let item_id = it.next()?.to_owned();
581 let count = it.next()?.parse().ok()?;
582 let snbt = it.next().unwrap_or("{}").to_owned();
583 Some((item_id, count, snbt))
584 }
585
586 fn set_inventory_slot(
587 &self,
588 dimension: &str,
589 pos: yog_core::BlockPos,
590 slot: u32,
591 item_id: &str,
592 count: u32,
593 snbt: &str,
594 ) -> bool {
595 let item = if snbt.is_empty() { item_id.to_string() } else { format!("{item_id}\t{snbt}") };
597 let s = srv!(self);
598 unsafe {
599 (s.set_inventory_slot)(
600 s.ctx,
601 YogStr::from_str(dimension),
602 yog_abi::YogBlockPos {
603 x: pos.x,
604 y: pos.y,
605 z: pos.z,
606 },
607 slot,
608 YogStr::from_str(&item),
609 count,
610 )
611 }
612 }
613
614 fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
615 let s = srv!(self);
616 let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
617 let Some(text) = take_owned(s.free_str, owned) else {
618 return Vec::new();
619 };
620 text.lines()
621 .filter_map(|line| {
622 let mut it = line.split('\t');
623 let slot: u32 = it.next()?.parse().ok()?;
624 let item_id = it.next()?.to_owned();
625 let count: u32 = it.next()?.parse().ok()?;
626 Some((slot, item_id, count))
627 })
628 .collect()
629 }
630
631 fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
632 let s = srv!(self);
633 unsafe {
634 (s.player_set_slot)(
635 s.ctx,
636 YogStr::from_str(player),
637 slot,
638 YogStr::from_str(item_id),
639 count,
640 )
641 }
642 }
643
644 fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
645 let s = srv!(self);
646 unsafe {
647 (s.player_teleport_dim)(
648 s.ctx,
649 YogStr::from_str(player),
650 YogStr::from_str(dimension),
651 yog_abi::YogVec3 { x, y, z },
652 )
653 }
654 }
655
656 fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
657 let s = srv!(self);
658 unsafe {
659 (s.entity_teleport_dim)(
660 s.ctx,
661 YogStr::from_str(uuid),
662 YogStr::from_str(dimension),
663 yog_abi::YogVec3 { x, y, z },
664 )
665 }
666 }
667
668 fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
669 let s = srv!(self);
670 unsafe {
671 (s.world_entity_count)(
672 s.ctx,
673 YogStr::from_str(dimension),
674 YogStr::from_str(entity_type),
675 )
676 }
677 }
678
679 fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
680 let s = srv!(self);
681 let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
682
683 take_owned(s.free_str, owned)
684 }
685
686 fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
687 let s = srv!(self);
688 unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
689 }
690
691 fn spawn_particles(
692 &self,
693 dimension: &str,
694 x: f64,
695 y: f64,
696 z: f64,
697 particle_type: &str,
698 count: i32,
699 dx: f64,
700 dy: f64,
701 dz: f64,
702 speed: f64,
703 ) -> bool {
704 let s = srv!(self);
705 unsafe {
706 (s.spawn_particles)(
707 s.ctx,
708 YogStr::from_str(dimension),
709 yog_abi::YogVec3 { x, y, z },
710 YogStr::from_str(particle_type),
711 count,
712 dx,
713 dy,
714 dz,
715 speed,
716 )
717 }
718 }
719
720 fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
721 let s = srv!(self);
722 let v = unsafe {
723 (s.entity_attribute_get)(
724 s.ctx,
725 YogStr::from_str(uuid),
726 YogStr::from_str(attribute_id),
727 )
728 };
729 if v.is_nan() { None } else { Some(v) }
730 }
731
732 fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
733 let s = srv!(self);
734 unsafe {
735 (s.entity_attribute_set)(
736 s.ctx,
737 YogStr::from_str(uuid),
738 YogStr::from_str(attribute_id),
739 value,
740 )
741 }
742 }
743
744 fn get_held_item_nbt(&self, player: &str) -> Option<String> {
745 let s = srv!(self);
746 let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
747
748 take_owned(s.free_str, owned)
749 }
750
751 fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
752 let s = srv!(self);
753 unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
754 }
755
756 fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
757 let s = srv!(self);
758 let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
759
760 take_owned(s.free_str, owned)
761 }
762
763 fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
764 let s = srv!(self);
765 unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
766 }
767
768 fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
769 let s = srv!(self);
770 let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
771 let text = take_owned(s.free_str, owned)?;
772 let mut it = text.splitn(3, '\t');
773 let item_id = it.next()?.to_owned();
774 let count: u32 = it.next()?.parse().ok()?;
775 let nbt = it.next().unwrap_or("{}").to_owned();
776 Some((item_id, count, nbt))
777 }
778
779 fn set_slot_item(
780 &self,
781 player: &str,
782 slot: u32,
783 item_id: &str,
784 count: u32,
785 snbt: &str,
786 ) -> bool {
787 let s = srv!(self);
788 unsafe {
789 (s.set_slot_item)(
790 s.ctx,
791 YogStr::from_str(player),
792 slot,
793 YogStr::from_str(item_id),
794 count,
795 YogStr::from_str(snbt),
796 )
797 }
798 }
799
800 fn entity_info(&self, uuid: &str) -> Option<yog_core::EntityInfo> {
803 let s = srv!(self);
804 let owned = unsafe { (s.entity_info)(s.ctx, YogStr::from_str(uuid)) };
805 take_owned(s.free_str, owned).and_then(|l| yog_core::EntityInfo::decode(&l))
806 }
807
808 fn entities_in_box(
809 &self,
810 dimension: &str,
811 min: (f64, f64, f64),
812 max: (f64, f64, f64),
813 ) -> Vec<String> {
814 let s = srv!(self);
815 let owned =
816 unsafe { (s.entities_in_box)(s.ctx, YogStr::from_str(dimension), v3(min), v3(max)) };
817 take_owned(s.free_str, owned)
818 .map(|t| {
819 t.lines()
820 .filter(|l| !l.is_empty())
821 .map(str::to_owned)
822 .collect()
823 })
824 .unwrap_or_default()
825 }
826
827 fn raycast_block(
828 &self,
829 dimension: &str,
830 from: (f64, f64, f64),
831 to: (f64, f64, f64),
832 ) -> Option<yog_core::BlockHit> {
833 let s = srv!(self);
834 let owned =
835 unsafe { (s.raycast_block)(s.ctx, YogStr::from_str(dimension), v3(from), v3(to)) };
836 take_owned(s.free_str, owned).and_then(|l| yog_core::BlockHit::decode(&l))
837 }
838
839 fn raycast_entity(
840 &self,
841 dimension: &str,
842 from: (f64, f64, f64),
843 to: (f64, f64, f64),
844 exclude: &str,
845 ) -> Option<String> {
846 let s = srv!(self);
847 let owned = unsafe {
848 (s.raycast_entity)(
849 s.ctx,
850 YogStr::from_str(dimension),
851 v3(from),
852 v3(to),
853 YogStr::from_str(exclude),
854 )
855 };
856 take_owned(s.free_str, owned).filter(|u| !u.is_empty())
857 }
858
859 fn block_info(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<yog_core::BlockInfo> {
860 let s = srv!(self);
861 let owned = unsafe { (s.block_info)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
862 take_owned(s.free_str, owned).and_then(|l| yog_core::BlockInfo::decode(&l))
863 }
864
865 fn break_block(
866 &self,
867 dimension: &str,
868 pos: yog_core::BlockPos,
869 drop: bool,
870 breaker: &str,
871 ) -> bool {
872 let s = srv!(self);
873 unsafe {
874 (s.break_block)(
875 s.ctx,
876 YogStr::from_str(dimension),
877 bp(pos),
878 drop,
879 YogStr::from_str(breaker),
880 )
881 }
882 }
883
884 fn place_block_from_slot(
885 &self,
886 player: &str,
887 slot: u32,
888 dimension: &str,
889 pos: yog_core::BlockPos,
890 ) -> bool {
891 let s = srv!(self);
892 unsafe {
893 (s.place_block_from_slot)(
894 s.ctx,
895 YogStr::from_str(player),
896 slot,
897 YogStr::from_str(dimension),
898 bp(pos),
899 )
900 }
901 }
902
903 fn bonemeal(&self, dimension: &str, pos: yog_core::BlockPos) -> bool {
904 let s = srv!(self);
905 unsafe { (s.bonemeal)(s.ctx, YogStr::from_str(dimension), bp(pos)) }
906 }
907
908 fn explode(
909 &self,
910 dimension: &str,
911 x: f64,
912 y: f64,
913 z: f64,
914 power: f32,
915 fire: bool,
916 source: &str,
917 ) -> bool {
918 let s = srv!(self);
919 unsafe {
920 (s.explode)(
921 s.ctx,
922 YogStr::from_str(dimension),
923 v3((x, y, z)),
924 power,
925 fire,
926 YogStr::from_str(source),
927 )
928 }
929 }
930
931 fn tellraw(&self, player: &str, json: &str) -> bool {
932 let s = srv!(self);
933 unsafe { (s.tellraw)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
934 }
935
936 fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool {
937 let s = srv!(self);
938 unsafe {
939 (s.entity_damage)(
940 s.ctx,
941 YogStr::from_str(uuid),
942 amount,
943 YogStr::from_str(damage_type),
944 YogStr::from_str(attacker),
945 )
946 }
947 }
948
949 fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool {
950 let s = srv!(self);
951 unsafe { (s.entity_set_fire_ticks)(s.ctx, YogStr::from_str(uuid), ticks) }
952 }
953
954 fn player_add_xp(&self, player: &str, amount: i32) -> bool {
955 let s = srv!(self);
956 unsafe { (s.player_add_xp)(s.ctx, YogStr::from_str(player), amount) }
957 }
958
959 fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool {
960 let s = srv!(self);
961 unsafe { (s.player_set_may_fly)(s.ctx, YogStr::from_str(player), may_fly) }
962 }
963
964 fn player_info(&self, player: &str) -> Option<yog_core::PlayerInfo> {
965 let s = srv!(self);
966 let owned = unsafe { (s.player_info)(s.ctx, YogStr::from_str(player)) };
967 take_owned(s.free_str, owned).and_then(|l| yog_core::PlayerInfo::decode(&l))
968 }
969
970 fn spawn_item(
971 &self,
972 dimension: &str,
973 pos: (f64, f64, f64),
974 vel: (f64, f64, f64),
975 item_id: &str,
976 count: u32,
977 snbt: &str,
978 ) -> Option<String> {
979 let s = srv!(self);
980 let owned = unsafe {
981 (s.spawn_item)(
982 s.ctx,
983 YogStr::from_str(dimension),
984 v3(pos),
985 v3(vel),
986 YogStr::from_str(item_id),
987 count,
988 YogStr::from_str(snbt),
989 )
990 };
991 take_owned(s.free_str, owned).filter(|u| !u.is_empty())
992 }
993
994 fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)> {
995 let s = srv!(self);
996 let owned = unsafe { (s.item_entity_stack)(s.ctx, YogStr::from_str(uuid)) };
997 let text = take_owned(s.free_str, owned)?;
998 let mut it = text.splitn(3, '\t');
999 let item_id = it.next()?.to_owned();
1000 let count = it.next()?.parse().ok()?;
1001 let nbt = it.next().unwrap_or("{}").to_owned();
1002 Some((item_id, count, nbt))
1003 }
1004
1005 fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool {
1006 let s = srv!(self);
1007 unsafe {
1008 (s.item_entity_set_stack)(
1009 s.ctx,
1010 YogStr::from_str(uuid),
1011 YogStr::from_str(item_id),
1012 count,
1013 YogStr::from_str(snbt),
1014 )
1015 }
1016 }
1017
1018 fn actionbar_json(&self, player: &str, json: &str) -> bool {
1019 let s = srv!(self);
1020 unsafe { (s.actionbar_json)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
1021 }
1022
1023 fn use_item_on_block(
1024 &self,
1025 dimension: &str,
1026 pos: yog_core::BlockPos,
1027 face: yog_core::Face,
1028 item_id: &str,
1029 player: &str,
1030 ) -> bool {
1031 let s = srv!(self);
1032 unsafe {
1033 (s.use_item_on_block)(
1034 s.ctx,
1035 YogStr::from_str(dimension),
1036 bp(pos),
1037 face.index(),
1038 YogStr::from_str(item_id),
1039 YogStr::from_str(player),
1040 )
1041 }
1042 }
1043
1044 fn pour_fluid(&self, dimension: &str, pos: yog_core::BlockPos, bucket_item_id: &str) -> bool {
1045 let s = srv!(self);
1046 unsafe {
1047 (s.pour_fluid)(
1048 s.ctx,
1049 YogStr::from_str(dimension),
1050 bp(pos),
1051 YogStr::from_str(bucket_item_id),
1052 )
1053 }
1054 }
1055
1056 fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool {
1057 let s = srv!(self);
1058 unsafe {
1059 (s.has_entity_tag)(
1060 s.ctx,
1061 YogStr::from_str(entity_type),
1062 YogStr::from_str(tag_id),
1063 )
1064 }
1065 }
1066
1067 fn item_block(&self, item_id: &str) -> Option<String> {
1068 let s = srv!(self);
1069 let owned = unsafe { (s.item_block)(s.ctx, YogStr::from_str(item_id)) };
1070 take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1071 }
1072
1073 fn place_feature(&self, dimension: &str, pos: yog_core::BlockPos, feature_id: &str) -> bool {
1074 let s = srv!(self);
1075 unsafe {
1076 (s.place_feature)(
1077 s.ctx,
1078 YogStr::from_str(dimension),
1079 bp(pos),
1080 YogStr::from_str(feature_id),
1081 )
1082 }
1083 }
1084
1085 fn set_item_cooldown(&self, player: &str, item_id: &str, ticks: u32) -> bool {
1086 let s = srv!(self);
1087 unsafe { (s.set_item_cooldown)(s.ctx, YogStr::from_str(player), YogStr::from_str(item_id), ticks.min(i32::MAX as u32) as i32) }
1088 }
1089
1090 fn entity_vehicle(&self, uuid: &str) -> Option<String> {
1091 let s = srv!(self);
1092 let owned = unsafe { (s.entity_vehicle)(s.ctx, YogStr::from_str(uuid)) };
1093 take_owned(s.free_str, owned).filter(|v| !v.is_empty())
1094 }
1095
1096 fn entity_passengers(&self, uuid: &str) -> Vec<String> {
1097 let s = srv!(self);
1098 let owned = unsafe { (s.entity_passengers)(s.ctx, YogStr::from_str(uuid)) };
1099 take_owned(s.free_str, owned)
1100 .map(|t| t.lines().filter(|l| !l.is_empty()).map(str::to_string).collect())
1101 .unwrap_or_default()
1102 }
1103
1104 fn entity_mount(&self, rider: &str, vehicle: &str, force: bool) -> bool {
1105 let s = srv!(self);
1106 unsafe { (s.entity_mount)(s.ctx, YogStr::from_str(rider), YogStr::from_str(vehicle), force) }
1107 }
1108
1109 fn entity_dismount(&self, uuid: &str) -> bool {
1110 let s = srv!(self);
1111 unsafe { (s.entity_dismount)(s.ctx, YogStr::from_str(uuid)) }
1112 }
1113
1114 fn entity_data(&self, uuid: &str, key: &str) -> Option<Vec<u8>> {
1115 let s = srv!(self);
1116 let mut out = Vec::new();
1117 let found = unsafe {
1118 (s.entity_data_get)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(key), &mut out as *mut Vec<u8> as *mut c_void, crate::body::collect)
1119 };
1120 found.then_some(out)
1121 }
1122
1123 fn set_entity_data(&self, uuid: &str, key: &str, value: Option<&[u8]>, persist: bool) -> bool {
1124 let s = srv!(self);
1125 let flags = value.map_or(0, |_| 1) | if persist { 2 } else { 0 };
1126 let v = value.unwrap_or(&[]);
1127 unsafe { (s.entity_data_set)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(key), v.as_ptr(), v.len() as u32, flags) }
1128 }
1129
1130 fn entity_set_dimensions(&self, uuid: &str, size: Option<(f32, f32, f32)>) -> bool {
1131 let s = srv!(self);
1132 let (w, h, e) = size.unwrap_or((-1.0, 0.0, 0.0));
1133 unsafe { (s.entity_set_dimensions)(s.ctx, YogStr::from_str(uuid), w, h, e) }
1134 }
1135
1136 fn entity_set_fall_distance(&self, uuid: &str, distance: f32) -> bool {
1137 let s = srv!(self);
1138 unsafe { (s.entity_set_fall_distance)(s.ctx, YogStr::from_str(uuid), distance) }
1139 }
1140
1141 fn redstone_signal(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
1142 let s = srv!(self);
1143 let p = unsafe { (s.redstone_signal)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1144 (0..=15).contains(&p).then_some(p as u8)
1145 }
1146
1147 fn redstone_power(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
1148 let s = srv!(self);
1149 let p = unsafe { (s.redstone_power)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1150 (0..=15).contains(&p).then_some(p as u8)
1151 }
1152
1153 fn set_redstone_output(&self, dimension: &str, pos: yog_core::BlockPos, level: u8) -> bool {
1154 let s = srv!(self);
1155 unsafe { (s.set_redstone_output)(s.ctx, YogStr::from_str(dimension), bp(pos), level.min(15) as i32) }
1156 }
1157
1158 fn get_block_state(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
1159 let s = srv!(self);
1160 let owned = unsafe { (s.get_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
1161 take_owned(s.free_str, owned).filter(|b| !b.is_empty())
1162 }
1163
1164 fn set_block_state(&self, dimension: &str, pos: yog_core::BlockPos, state: &str) -> bool {
1165 let s = srv!(self);
1166 unsafe { (s.set_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos), YogStr::from_str(state)) }
1167 }
1168
1169 fn world_is_thundering(&self, dimension: &str) -> bool {
1170 let s = srv!(self);
1171 unsafe { (s.world_is_thundering)(s.ctx, YogStr::from_str(dimension)) }
1172 }
1173
1174 fn world_set_thunder(&self, dimension: &str, thundering: bool, duration_ticks: i32) -> bool {
1175 let s = srv!(self);
1176 unsafe { (s.world_set_thunder)(s.ctx, YogStr::from_str(dimension), thundering, duration_ticks) }
1177 }
1178
1179 fn fill_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos, state: &str) -> Option<u32> {
1180 let s = srv!(self);
1181 let n = unsafe { (s.fill_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to), YogStr::from_str(state)) };
1182 u32::try_from(n).ok()
1183 }
1184
1185 fn scan_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos) -> Vec<(yog_core::BlockPos, String)> {
1186 let s = srv!(self);
1187 let owned = unsafe { (s.scan_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to)) };
1188 let Some(text) = take_owned(s.free_str, owned) else {
1189 return Vec::new();
1190 };
1191 text.lines()
1192 .filter_map(|line| {
1193 let mut it = line.splitn(4, ' ');
1194 let (x, y, z) = (it.next()?.parse().ok()?, it.next()?.parse().ok()?, it.next()?.parse().ok()?);
1195 Some((yog_core::BlockPos { x, y, z }, it.next()?.to_string()))
1196 })
1197 .collect()
1198 }
1199}
1200
1201pub(crate) fn take_owned(
1203 free_str: unsafe extern "C" fn(ptr: *mut u8, len: u32),
1204 owned: yog_abi::YogOwnedStr,
1205) -> Option<String> {
1206 if owned.is_none() {
1207 return None;
1208 }
1209 let text = unsafe {
1211 String::from_utf8_lossy(std::slice::from_raw_parts(owned.ptr, owned.len as usize))
1212 .into_owned()
1213 };
1214 unsafe { free_str(owned.ptr, owned.len) };
1215 Some(text)
1216}
1217
1218fn v3((x, y, z): (f64, f64, f64)) -> yog_abi::YogVec3 {
1219 yog_abi::YogVec3 { x, y, z }
1220}
1221
1222fn bp(pos: yog_core::BlockPos) -> yog_abi::YogBlockPos {
1223 yog_abi::YogBlockPos {
1224 x: pos.x,
1225 y: pos.y,
1226 z: pos.z,
1227 }
1228}
1229
1230fn guard<R>(fallback: R, call: impl FnOnce() -> R) -> R {
1238 std::panic::catch_unwind(std::panic::AssertUnwindSafe(call)).unwrap_or_else(|_| {
1239 crate::error!("a mod's callback panicked (see above); carrying on without it");
1240 fallback
1241 })
1242}
1243macro_rules! trampoline_phased {
1260 ($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr_2021) => {
1261 unsafe extern "C" fn $name<F>(
1262 ud: *mut c_void,
1263 srv: *const YogServer,
1264 ev: *const $abi_ev,
1265 phase: u8,
1266 ) -> bool
1267 where
1268 F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
1269 {
1270 unsafe {
1271 let f = &*(ud as *const F);
1272 let $ev = &*ev;
1273 let rust_ev = $build;
1274 let p = if phase == 0 {
1275 EventPhase::Pre
1276 } else {
1277 EventPhase::Post
1278 };
1279 guard(true, || f(&rust_ev, p, &CServer(srv)))
1280 }
1281 }
1282 };
1283}
1284
1285trampoline_phased!(
1286 trampoline_block_break,
1287 YogBlockBreakEvent,
1288 BlockBreakEvent,
1289 |ev| BlockBreakEvent {
1290 player_name: ev.player.as_str().to_owned(),
1291 block_id: ev.block.as_str().to_owned(),
1292 pos: yog_core::BlockPos {
1293 x: ev.pos.x,
1294 y: ev.pos.y,
1295 z: ev.pos.z
1296 },
1297 dimension: ev.dimension.as_str().to_owned(),
1298 }
1299);
1300
1301trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
1302 player_name: ev.player.as_str().to_owned(),
1303 message: ev.message.as_str().to_owned(),
1304 dimension: ev.dimension.as_str().to_owned(),
1305});
1306
1307trampoline_phased!(
1308 trampoline_player_join,
1309 YogPlayerEvent,
1310 PlayerJoinEvent,
1311 |ev| PlayerJoinEvent {
1312 player_name: ev.player.as_str().to_owned(),
1313 uuid: ev.uuid.as_str().to_owned(),
1314 dimension: ev.dimension.as_str().to_owned(),
1315 }
1316);
1317
1318trampoline_phased!(
1319 trampoline_player_leave,
1320 YogPlayerEvent,
1321 PlayerLeaveEvent,
1322 |ev| PlayerLeaveEvent {
1323 player_name: ev.player.as_str().to_owned(),
1324 uuid: ev.uuid.as_str().to_owned(),
1325 dimension: ev.dimension.as_str().to_owned(),
1326 }
1327);
1328
1329trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| {
1330 UseItemEvent {
1331 player_name: ev.player.as_str().to_owned(),
1332 item_id: ev.item.as_str().to_owned(),
1333 sneaking: ev.sneaking,
1334 dimension: ev.dimension.as_str().to_owned(),
1335 }
1336});
1337
1338trampoline_phased!(
1339 trampoline_use_block,
1340 YogUseBlockEvent,
1341 UseBlockEvent,
1342 |ev| UseBlockEvent {
1343 player_name: ev.player.as_str().to_owned(),
1344 block_id: ev.block.as_str().to_owned(),
1345 pos: yog_core::BlockPos {
1346 x: ev.pos.x,
1347 y: ev.pos.y,
1348 z: ev.pos.z
1349 },
1350 dimension: ev.dimension.as_str().to_owned(),
1351 sneaking: ev.sneaking,
1352 }
1353);
1354
1355trampoline_phased!(
1356 trampoline_attack_entity,
1357 YogAttackEntityEvent,
1358 AttackEntityEvent,
1359 |ev| AttackEntityEvent {
1360 player_name: ev.player.as_str().to_owned(),
1361 target_type: ev.target_type.as_str().to_owned(),
1362 target_uuid: ev.target_uuid.as_str().to_owned(),
1363 dimension: ev.dimension.as_str().to_owned(),
1364 }
1365);
1366
1367trampoline_phased!(
1368 trampoline_entity_damage,
1369 YogEntityDamageEvent,
1370 EntityDamageEvent,
1371 |ev| EntityDamageEvent {
1372 entity_type: ev.entity_type.as_str().to_owned(),
1373 uuid: ev.uuid.as_str().to_owned(),
1374 amount: ev.amount,
1375 source: ev.source.as_str().to_owned(),
1376 dimension: ev.dimension.as_str().to_owned(),
1377 }
1378);
1379
1380trampoline_phased!(
1381 trampoline_entity_death,
1382 YogEntityDeathEvent,
1383 EntityDeathEvent,
1384 |ev| EntityDeathEvent {
1385 entity_type: ev.entity_type.as_str().to_owned(),
1386 uuid: ev.uuid.as_str().to_owned(),
1387 source: ev.source.as_str().to_owned(),
1388 dimension: ev.dimension.as_str().to_owned(),
1389 }
1390);
1391
1392trampoline_phased!(
1393 trampoline_entity_spawn,
1394 YogEntitySpawnEvent,
1395 EntitySpawnEvent,
1396 |ev| EntitySpawnEvent {
1397 entity_type: ev.entity_type.as_str().to_owned(),
1398 uuid: ev.uuid.as_str().to_owned(),
1399 dimension: ev.dimension.as_str().to_owned(),
1400 }
1401);
1402
1403trampoline_phased!(
1404 trampoline_place_block,
1405 YogPlaceBlockEvent,
1406 PlaceBlockEvent,
1407 |ev| PlaceBlockEvent {
1408 player_name: ev.player.as_str().to_owned(),
1409 block_id: ev.block.as_str().to_owned(),
1410 pos: yog_core::BlockPos {
1411 x: ev.pos.x,
1412 y: ev.pos.y,
1413 z: ev.pos.z
1414 },
1415 dimension: ev.dimension.as_str().to_owned(),
1416 sneaking: ev.sneaking,
1417 }
1418);
1419
1420trampoline_phased!(
1421 trampoline_player_death,
1422 YogPlayerDeathEvent,
1423 PlayerDeathEvent,
1424 |ev| PlayerDeathEvent {
1425 player_name: ev.player.as_str().to_owned(),
1426 uuid: ev.uuid.as_str().to_owned(),
1427 source: ev.source.as_str().to_owned(),
1428 dimension: ev.dimension.as_str().to_owned(),
1429 }
1430);
1431
1432trampoline_phased!(
1433 trampoline_player_respawn,
1434 YogPlayerRespawnEvent,
1435 PlayerRespawnEvent,
1436 |ev| PlayerRespawnEvent {
1437 player_name: ev.player.as_str().to_owned(),
1438 uuid: ev.uuid.as_str().to_owned(),
1439 at_anchor: ev.at_anchor,
1440 dimension: ev.dimension.as_str().to_owned(),
1441 }
1442);
1443
1444trampoline_phased!(
1445 trampoline_advancement,
1446 YogAdvancementEvent,
1447 AdvancementEvent,
1448 |ev| AdvancementEvent {
1449 player_name: ev.player.as_str().to_owned(),
1450 uuid: ev.uuid.as_str().to_owned(),
1451 advancement_id: ev.advancement.as_str().to_owned(),
1452 dimension: ev.dimension.as_str().to_owned(),
1453 }
1454);
1455
1456trampoline_phased!(
1457 trampoline_entity_interact,
1458 YogEntityInteractEvent,
1459 EntityInteractEvent,
1460 |ev| EntityInteractEvent {
1461 player_name: ev.player.as_str().to_owned(),
1462 player_uuid: ev.player_uuid.as_str().to_owned(),
1463 entity_type: ev.entity_type.as_str().to_owned(),
1464 entity_uuid: ev.entity_uuid.as_str().to_owned(),
1465 hand: ev.hand.as_str().to_owned(),
1466 dimension: ev.dimension.as_str().to_owned(),
1467 }
1468);
1469
1470trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| {
1471 CraftEvent {
1472 player_name: ev.player.as_str().to_owned(),
1473 player_uuid: ev.player_uuid.as_str().to_owned(),
1474 result_item: ev.result_item.as_str().to_owned(),
1475 result_count: ev.result_count,
1476 dimension: ev.dimension.as_str().to_owned(),
1477 }
1478});
1479
1480#[derive(Debug, Clone)]
1489pub struct MachineCraftEvent {
1490 pub machine: String,
1492 pub kind: String,
1494 pub recipe: String,
1495 pub result_item: String,
1496 pub result_count: u32,
1497 pub values: yog_data::Value,
1499 pub pos: yog_core::BlockPos,
1500 pub dimension: String,
1501 pub player: String,
1502}
1503
1504trampoline_phased!(trampoline_machine_craft, YogMachineCraftEvent, MachineCraftEvent, |ev| {
1505 MachineCraftEvent {
1506 machine: ev.machine.as_str().to_owned(),
1507 kind: ev.kind.as_str().to_owned(),
1508 recipe: ev.recipe.as_str().to_owned(),
1509 result_item: ev.result_item.as_str().to_owned(),
1510 result_count: ev.result_count,
1511 values: yog_data::json::from_json_text(ev.values.as_str()).unwrap_or(yog_data::Value::Null),
1512 pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
1513 dimension: ev.dimension.as_str().to_owned(),
1514 player: ev.player.as_str().to_owned(),
1515 }
1516});
1517
1518trampoline_phased!(
1519 trampoline_explosion,
1520 YogExplosionEvent,
1521 ExplosionEvent,
1522 |ev| ExplosionEvent {
1523 dimension: ev.dimension.as_str().to_owned(),
1524 x: ev.x,
1525 y: ev.y,
1526 z: ev.z,
1527 power: ev.power,
1528 cause_uuid: ev.cause_uuid.as_str().to_owned(),
1529 }
1530);
1531
1532trampoline_phased!(
1533 trampoline_item_insert,
1534 YogItemInsertEvent,
1535 ItemInsertEvent,
1536 |ev| ItemInsertEvent {
1537 block_id: ev.block.as_str().to_owned(),
1538 pos: yog_core::BlockPos {
1539 x: ev.pos.x,
1540 y: ev.pos.y,
1541 z: ev.pos.z
1542 },
1543 dimension: ev.dimension.as_str().to_owned(),
1544 item_id: ev.item_id.as_str().to_owned(),
1545 count: ev.count,
1546 nbt: ev.nbt.as_str().to_owned(),
1547 }
1548);
1549
1550trampoline_phased!(
1551 trampoline_item_pickup,
1552 YogItemPickupEvent,
1553 ItemPickupEvent,
1554 |ev| ItemPickupEvent {
1555 player_name: ev.player.as_str().to_owned(),
1556 player_uuid: ev.player_uuid.as_str().to_owned(),
1557 item_id: ev.item_id.as_str().to_owned(),
1558 item_count: ev.item_count,
1559 entity_uuid: ev.entity_uuid.as_str().to_owned(),
1560 dimension: ev.dimension.as_str().to_owned(),
1561 }
1562);
1563
1564trampoline_phased!(
1565 trampoline_player_move,
1566 YogPlayerMoveEvent,
1567 PlayerMoveEvent,
1568 |ev| PlayerMoveEvent {
1569 player_name: ev.player.as_str().to_owned(),
1570 player_uuid: ev.player_uuid.as_str().to_owned(),
1571 x: ev.x,
1572 y: ev.y,
1573 z: ev.z,
1574 yaw: ev.yaw,
1575 pitch: ev.pitch,
1576 dimension: ev.dimension.as_str().to_owned(),
1577 }
1578);
1579
1580trampoline_phased!(
1581 trampoline_container_open,
1582 YogContainerOpenEvent,
1583 ContainerOpenEvent,
1584 |ev| ContainerOpenEvent {
1585 player_name: ev.player.as_str().to_owned(),
1586 player_uuid: ev.player_uuid.as_str().to_owned(),
1587 container_type: ev.container_type.as_str().to_owned(),
1588 dimension: ev.dimension.as_str().to_owned(),
1589 }
1590);
1591
1592trampoline_phased!(
1593 trampoline_container_close,
1594 YogContainerCloseEvent,
1595 ContainerCloseEvent,
1596 |ev| ContainerCloseEvent {
1597 player_name: ev.player.as_str().to_owned(),
1598 player_uuid: ev.player_uuid.as_str().to_owned(),
1599 dimension: ev.dimension.as_str().to_owned(),
1600 }
1601);
1602
1603trampoline_phased!(
1604 trampoline_projectile_hit,
1605 YogProjectileHitEvent,
1606 ProjectileHitEvent,
1607 |ev| ProjectileHitEvent {
1608 projectile_type: ev.projectile_type.as_str().to_owned(),
1609 projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
1610 shooter_uuid: ev.shooter_uuid.as_str().to_owned(),
1611 hit_type: ev.hit_type.as_str().to_owned(),
1612 hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
1613 x: ev.x,
1614 y: ev.y,
1615 z: ev.z,
1616 dimension: ev.dimension.as_str().to_owned(),
1617 }
1618);
1619
1620unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
1621where
1622 F: Fn(&dyn Server) + Send + Sync,
1623{
1624 unsafe {
1625 let f = &*(ud as *const F);
1626 guard((), || f(&CServer(srv)));
1627 }
1628}
1629
1630unsafe extern "C" fn trampoline_packet<F>(
1631 ud: *mut c_void,
1632 srv: *const YogServer,
1633 ev: *const YogPacketEvent,
1634) where
1635 F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
1636{
1637 unsafe {
1638 let f = &*(ud as *const F);
1639 let ev = &*ev;
1640 let rust_ev = PacketEvent {
1641 channel: ev.channel.as_str().to_owned(),
1642 player: ev.player.as_str().to_owned(),
1643 payload: if ev.payload.is_null() || ev.payload_len == 0 {
1645 Vec::new()
1646 } else {
1647 std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec()
1648 },
1649 };
1650 guard((), || f(&rust_ev, &CServer(srv)));
1651 }
1652}
1653
1654unsafe extern "C" fn trampoline_command<F>(
1655 ud: *mut c_void,
1656 srv: *const YogServer,
1657 ev: *const YogCommandEvent,
1658 reply_buf: *mut u8,
1659 reply_cap: u32,
1660 reply_len: *mut u32,
1661) where
1662 F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
1663{
1664 unsafe {
1665 let f = &*(ud as *const F);
1666 let ev = &*ev;
1667 let ctx = CommandContext {
1668 name: ev.name.as_str().to_owned(),
1669 args: ev.args.as_str().to_owned(),
1670 source: ev.source.as_str().to_owned(),
1671 uuid: ev.uuid.as_str().to_owned(),
1672 dimension: ev.dimension.as_str().to_owned(),
1673 };
1674 *reply_len = 0;
1675 if let Some(reply) = guard(None, || f(&ctx, &CServer(srv))) {
1676 let bytes = reply.as_bytes();
1677 let mut n = bytes.len().min(reply_cap as usize);
1679 while !reply.is_char_boundary(n) {
1680 n -= 1;
1681 }
1682 std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
1683 *reply_len = n as u32;
1684 }
1685 }
1686}
1687
1688unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
1689where
1690 F: Fn(&dyn Server) + Send + Sync,
1691{
1692 unsafe {
1693 let f = &*(ud as *const F);
1694 guard((), || f(&CServer(srv)));
1695 }
1696}
1697
1698unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
1701where
1702 F: Fn(&ClientTickEvent) + Send + Sync,
1703{
1704 unsafe {
1705 let f = &*(ud as *const F);
1706 guard((), || f(&ClientTickEvent {}));
1707 }
1708}
1709
1710unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1711where
1712 F: Fn(&GfxContext) + Send + Sync,
1713{
1714 unsafe {
1715 let f = &*(ud as *const F);
1716 guard((), || f(&GfxContext::from_raw(gfx)));
1717 }
1718}
1719
1720struct TooltipPicture<S, D> {
1722 size: S,
1723 draw: D,
1724}
1725
1726unsafe extern "C" fn trampoline_tooltip_size<S, D>(ud: *mut c_void, item: YogStr, snbt: YogStr, w: *mut f32, h: *mut f32) -> bool
1727where
1728 S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync,
1729{
1730 unsafe {
1731 let t = &*(ud as *const TooltipPicture<S, D>);
1732 match guard(None, || (t.size)(item.as_str(), snbt.as_str())) {
1733 Some((pw, ph)) => {
1734 (*w, *h) = (pw, ph);
1735 true
1736 }
1737 None => false,
1738 }
1739 }
1740}
1741
1742unsafe extern "C" fn trampoline_item_text<F>(ud: *mut c_void, item: YogStr, snbt: YogStr, sink: *mut c_void, write: yog_abi::YogTextSink) -> bool
1743where
1744 F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync,
1745{
1746 unsafe {
1747 let f = &*(ud as *const F);
1748 let Some((name, lines)) = guard(None, || f(item.as_str(), snbt.as_str())) else { return false };
1749 let mut text = name.unwrap_or_default();
1750 for l in lines {
1751 text.push('\n');
1752 text.push_str(&l.replace('\n', " "));
1753 }
1754 write(sink, YogStr::from_str(&text));
1755 true
1756 }
1757}
1758
1759unsafe extern "C" fn trampoline_entity_tick<F>(ud: *mut c_void, srv: *const yog_abi::YogServer, uuid: YogStr)
1760where
1761 F: Fn(&dyn Server, &str) + Send + Sync,
1762{
1763 unsafe {
1764 let f = &*(ud as *const F);
1765 let s = CServer(srv);
1766 guard((), || f(&s, uuid.as_str()));
1767 }
1768}
1769
1770unsafe extern "C" fn trampoline_body<F>(ud: *mut c_void, frame: *const u8, len: usize, sink: *mut c_void, write: yog_abi::YogBytesSink)
1771where
1772 F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync,
1773{
1774 unsafe {
1775 let f = &*(ud as *const F);
1776 let Some(frame) = yog_body::Frame::decode(std::slice::from_raw_parts(frame, len)) else { return };
1777 let mut ops = yog_body::Ops::default();
1778 guard((), || f(&frame, &mut ops));
1779 if !ops.is_empty() {
1780 write(sink, ops.0.as_ptr(), ops.0.len());
1781 }
1782 }
1783}
1784
1785unsafe extern "C" fn trampoline_camera<F>(ud: *mut c_void, frame: *const yog_abi::YogCameraFrame, out: *mut yog_abi::YogCameraOut)
1786where
1787 F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync,
1788{
1789 unsafe {
1790 let f = &*(ud as *const F);
1791 let c = &*frame;
1792 let frame = yog_body::CameraFrame {
1793 uuid: c.uuid.as_str().to_owned(),
1794 partial: c.partial,
1795 pos: c.pos,
1796 yaw: c.yaw,
1797 pitch: c.pitch,
1798 detached: c.detached,
1799 flags: c.flags,
1800 };
1801 let o = &mut *out;
1802 let mut cam = yog_body::CameraOut {
1803 local_offset: yog_body::Vec3::from_array(o.local_offset),
1804 world_offset: yog_body::Vec3::from_array(o.world_offset),
1805 yaw: o.yaw,
1806 pitch: o.pitch,
1807 roll: o.roll,
1808 fov: o.fov,
1809 };
1810 guard((), || f(&frame, &mut cam));
1811 *o = yog_abi::YogCameraOut {
1812 local_offset: cam.local_offset.to_array(),
1813 world_offset: cam.world_offset.to_array(),
1814 yaw: cam.yaw,
1815 pitch: cam.pitch,
1816 roll: cam.roll,
1817 fov: cam.fov,
1818 };
1819 }
1820}
1821
1822unsafe extern "C" fn trampoline_travel<F>(ud: *mut c_void, frame: *const yog_abi::YogTravelFrame, velocity: *mut [f32; 3]) -> bool
1823where
1824 F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync,
1825{
1826 unsafe {
1827 let f = &*(ud as *const F);
1828 let t = &*frame;
1829 let frame = yog_body::TravelFrame {
1830 uuid: t.uuid.as_str().to_owned(),
1831 velocity: yog_body::Vec3::from_array(t.velocity),
1832 look: yog_body::Vec3::from_array(t.look),
1833 yaw: t.yaw,
1834 pitch: t.pitch,
1835 forward: t.forward,
1836 strafe: t.strafe,
1837 jumping: t.jumping,
1838 sneaking: t.sneaking,
1839 gravity: t.gravity,
1840 flags: t.flags,
1841 };
1842 match guard(None, || f(&frame)) {
1843 Some(v) => {
1844 *velocity = v.to_array();
1845 true
1846 }
1847 None => false,
1848 }
1849 }
1850}
1851
1852#[allow(clippy::too_many_arguments)]
1853unsafe extern "C" fn trampoline_tooltip_draw<S, D>(ud: *mut c_void, gfx: *const YogGfxApi, item: YogStr, snbt: YogStr, x: f32, y: f32, w: f32, h: f32)
1854where
1855 D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync,
1856{
1857 unsafe {
1858 let t = &*(ud as *const TooltipPicture<S, D>);
1859 guard((), || (t.draw)(&GfxContext::from_raw(gfx), item.as_str(), snbt.as_str(), [x, y, w, h]));
1860 }
1861}
1862
1863#[allow(clippy::too_many_arguments)]
1864unsafe extern "C" fn trampoline_book_page<F>(ud: *mut c_void, gfx: *const YogGfxApi, data: *const u8, len: usize, x: f32, y: f32, w: f32, h: f32)
1865where
1866 F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync,
1867{
1868 unsafe {
1869 let f = &*(ud as *const F);
1870 let bytes = if data.is_null() { &[][..] } else { std::slice::from_raw_parts(data, len) };
1871 let value = yog_data::binary::decode(bytes).unwrap_or(yog_data::Value::Null);
1872 guard((), || f(&GfxContext::from_raw(gfx), &value, [x, y, w, h]));
1873 }
1874}
1875
1876unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
1877where
1878 F: Fn(&GfxContext) + Send + Sync,
1879{
1880 unsafe {
1881 let f = &*(ud as *const F);
1882 guard((), || f(&GfxContext::from_raw(gfx)));
1883 }
1884}
1885
1886unsafe extern "C" fn trampoline_key_press<F>(ud: *mut c_void, ev: *const YogKeyPressEvent) -> bool
1887where
1888 F: Fn(&KeyPressEvent) -> bool + Send + Sync,
1889{
1890 unsafe {
1891 let f = &*(ud as *const F);
1892 let e = &*ev;
1893 let ev = KeyPressEvent { key_code: e.key_code, scan_code: e.scan_code, action: e.action, modifiers: e.modifiers };
1894 guard(false, || f(&ev))
1895 }
1896}
1897
1898unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
1899where
1900 F: Fn(&ScreenEvent) + Send + Sync,
1901{
1902 unsafe {
1903 let f = &*(ud as *const F);
1904 let ev = ScreenEvent { screen_class: screen_class.as_str().to_owned() };
1905 guard((), || f(&ev));
1906 true
1907 }
1908}
1909
1910static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
1921 std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
1922
1923pub(crate) fn global_api() -> *mut YogApi {
1925 GLOBAL_API.load(std::sync::atomic::Ordering::Acquire)
1926}
1927
1928#[derive(Debug, Clone)]
1930pub struct ModInfo {
1931 pub source: String,
1933 pub id: String,
1934 pub name: String,
1935 pub version: String,
1936 pub authors: String,
1938 pub description: String,
1939}
1940
1941pub fn installed_mods() -> Vec<ModInfo> {
1947 let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1948 if api.is_null() {
1949 return Vec::new();
1950 }
1951 let owned = unsafe { ((*api).mods_list)((*api).ctx) };
1952 let Some(text) = take_owned(unsafe { (*api).free_str }, owned) else {
1953 return Vec::new();
1954 };
1955 text.lines()
1956 .filter_map(|line| {
1957 let mut f = line.split('\t');
1958 Some(ModInfo {
1959 source: f.next()?.to_string(),
1960 id: f.next()?.to_string(),
1961 name: f.next().unwrap_or_default().to_string(),
1962 version: f.next().unwrap_or_default().to_string(),
1963 authors: f.next().unwrap_or_default().to_string(),
1964 description: f.next().unwrap_or_default().to_string(),
1965 })
1966 })
1967 .collect()
1968}
1969
1970pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
1974 let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1975 if api.is_null() {
1976 return;
1977 }
1978 unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
1979}
1980
1981pub fn close_ui(ui_id: &str) {
1986 let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
1987 if api.is_null() {
1988 return;
1989 }
1990 unsafe { ((*api).ui_close)((*api).ctx, YogStr::from_str(ui_id)) }
1991}
1992
1993pub trait UiHandled {
1997 fn handled(self) -> bool;
1998}
1999
2000impl UiHandled for () {
2001 fn handled(self) -> bool {
2002 false
2003 }
2004}
2005
2006impl UiHandled for bool {
2007 fn handled(self) -> bool {
2008 self
2009 }
2010}
2011
2012pub fn server() -> Option<CServer> {
2017 let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2018 if api.is_null() {
2019 return None;
2020 }
2021 let srv = unsafe { (*api).server };
2022 if srv.is_null() {
2023 return None;
2024 }
2025 Some(CServer(srv))
2026}
2027
2028pub struct Registry {
2029 api: *const YogApi,
2030}
2031
2032unsafe impl Send for Registry {}
2034unsafe impl Sync for Registry {}
2035
2036impl Registry {
2037 pub unsafe fn from_raw(api: *const YogApi) -> Self {
2044 GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
2045 Self { api }
2046 }
2047
2048 #[inline]
2049 fn ctx(&self) -> *mut c_void {
2050 unsafe { (*self.api).ctx }
2051 }
2052
2053 fn leak<F: 'static>(f: F) -> *mut c_void {
2056 Box::into_raw(Box::new(f)) as *mut c_void
2057 }
2058
2059 pub fn on_block_break<F>(&mut self, handler: F)
2067 where
2068 F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2069 {
2070 let ud = Self::leak(handler);
2071 unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
2072 }
2073
2074 pub fn on_chat<F>(&mut self, handler: F)
2075 where
2076 F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2077 {
2078 let ud = Self::leak(handler);
2079 unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
2080 }
2081
2082 pub fn on_player_join<F>(&mut self, handler: F)
2083 where
2084 F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2085 {
2086 let ud = Self::leak(handler);
2087 unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
2088 }
2089
2090 pub fn on_player_leave<F>(&mut self, handler: F)
2091 where
2092 F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2093 {
2094 let ud = Self::leak(handler);
2095 unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
2096 }
2097
2098 pub fn on_use_item<F>(&mut self, handler: F)
2099 where
2100 F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2101 {
2102 let ud = Self::leak(handler);
2103 unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
2104 }
2105
2106 pub fn on_use_block<F>(&mut self, handler: F)
2107 where
2108 F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2109 {
2110 let ud = Self::leak(handler);
2111 unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
2112 }
2113
2114 pub fn on_attack_entity<F>(&mut self, handler: F)
2115 where
2116 F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2117 {
2118 let ud = Self::leak(handler);
2119 unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
2120 }
2121
2122 pub fn on_entity_damage<F>(&mut self, handler: F)
2123 where
2124 F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2125 {
2126 let ud = Self::leak(handler);
2127 unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
2128 }
2129
2130 pub fn on_entity_death<F>(&mut self, handler: F)
2131 where
2132 F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2133 {
2134 let ud = Self::leak(handler);
2135 unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
2136 }
2137
2138 pub fn on_entity_spawn<F>(&mut self, handler: F)
2139 where
2140 F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2141 {
2142 let ud = Self::leak(handler);
2143 unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
2144 }
2145
2146 pub fn on_player_place_block<F>(&mut self, handler: F)
2147 where
2148 F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2149 {
2150 let ud = Self::leak(handler);
2151 unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
2152 }
2153
2154 pub fn on_player_death<F>(&mut self, handler: F)
2155 where
2156 F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2157 {
2158 let ud = Self::leak(handler);
2159 unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
2160 }
2161
2162 pub fn on_player_respawn<F>(&mut self, handler: F)
2163 where
2164 F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2165 {
2166 let ud = Self::leak(handler);
2167 unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
2168 }
2169
2170 pub fn on_advancement<F>(&mut self, handler: F)
2171 where
2172 F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2173 {
2174 let ud = Self::leak(handler);
2175 unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
2176 }
2177
2178 pub fn on_entity_interact<F>(&mut self, handler: F)
2179 where
2180 F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2181 {
2182 let ud = Self::leak(handler);
2183 unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
2184 }
2185
2186 pub fn on_item_craft<F>(&mut self, handler: F)
2187 where
2188 F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2189 {
2190 let ud = Self::leak(handler);
2191 unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
2192 }
2193
2194 pub fn on_machine_craft<F>(&mut self, handler: F)
2196 where
2197 F: Fn(&MachineCraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2198 {
2199 let ud = Self::leak(handler);
2200 unsafe { ((*self.api).on_machine_craft)(self.ctx(), ud, trampoline_machine_craft::<F>) }
2201 }
2202
2203 pub fn on_explosion<F>(&mut self, handler: F)
2204 where
2205 F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2206 {
2207 let ud = Self::leak(handler);
2208 unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
2209 }
2210
2211 pub fn on_item_insert<F>(&mut self, handler: F)
2215 where
2216 F: Fn(&ItemInsertEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2217 {
2218 let ud = Self::leak(handler);
2219 unsafe { ((*self.api).on_item_insert)(self.ctx(), ud, trampoline_item_insert::<F>) }
2220 }
2221
2222 pub fn on_item_pickup<F>(&mut self, handler: F)
2223 where
2224 F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2225 {
2226 let ud = Self::leak(handler);
2227 unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
2228 }
2229
2230 pub fn on_player_move<F>(&mut self, handler: F)
2231 where
2232 F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2233 {
2234 let ud = Self::leak(handler);
2235 unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
2236 }
2237
2238 pub fn on_container_open<F>(&mut self, handler: F)
2239 where
2240 F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2241 {
2242 let ud = Self::leak(handler);
2243 unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
2244 }
2245
2246 pub fn on_container_close<F>(&mut self, handler: F)
2247 where
2248 F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2249 {
2250 let ud = Self::leak(handler);
2251 unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
2252 }
2253
2254 pub fn on_projectile_hit<F>(&mut self, handler: F)
2255 where
2256 F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
2257 {
2258 let ud = Self::leak(handler);
2259 unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
2260 }
2261
2262 pub fn on_tick<F>(&mut self, listener: F)
2263 where
2264 F: Fn(&dyn Server) + Send + Sync + 'static,
2265 {
2266 let ud = Self::leak(listener);
2267 unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
2268 }
2269
2270 pub fn on_server_started<F>(&mut self, listener: F)
2271 where
2272 F: Fn(&dyn Server) + Send + Sync + 'static,
2273 {
2274 let ud = Self::leak(listener);
2275 unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
2276 }
2277
2278 pub fn on_server_stopping<F>(&mut self, listener: F)
2279 where
2280 F: Fn(&dyn Server) + Send + Sync + 'static,
2281 {
2282 let ud = Self::leak(listener);
2283 unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
2284 }
2285
2286 pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
2289 where
2290 F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
2291 {
2292 let name_ys = YogStr::from_str(name.as_ref());
2293 let ud = Self::leak(handler);
2294 unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
2295 }
2296
2297 pub fn on_typed_command<F>(
2304 &mut self,
2305 name: impl AsRef<str>,
2306 schema: impl AsRef<str>,
2307 handler: F,
2308 ) where
2309 F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
2310 {
2311 let name_ys = YogStr::from_str(name.as_ref());
2312 let schema_ys = YogStr::from_str(schema.as_ref());
2313 let ud = Self::leak(handler);
2314 unsafe {
2315 ((*self.api).register_typed_command)(
2316 self.ctx(),
2317 name_ys,
2318 schema_ys,
2319 ud,
2320 trampoline_command::<F>,
2321 );
2322 }
2323 }
2324
2325 pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2328 where
2329 F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2330 {
2331 let ch = YogStr::from_str(channel.as_ref());
2332 let ud = Self::leak(handler);
2333 unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2334 }
2335
2336 pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
2337 where
2338 F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
2339 {
2340 let ch = YogStr::from_str(channel.as_ref());
2341 let ud = Self::leak(handler);
2342 unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
2343 }
2344
2345 pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
2350 where
2351 P: Packet + Send + Sync + 'static,
2352 F: Fn(&P, &dyn Server) + Send + Sync + 'static,
2353 {
2354 self.on_packet(channel, move |ev, srv| {
2355 if let Some(pkt) = P::decode(&ev.payload) {
2356 handler(&pkt, srv);
2357 }
2358 });
2359 }
2360
2361 fn content(&mut self, kind: &str, id: &str, def: &yog_data::Value) {
2366 let blob = yog_data::binary::encode(def);
2367 unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str(kind), YogStr::from_str(id), blob.as_ptr(), blob.len()) }
2368 }
2369
2370 fn json(text: &str) -> yog_data::Value {
2372 yog_data::json::from_json_text(text).unwrap_or_default()
2373 }
2374
2375 pub fn add_recipe(&mut self, recipe: Recipe) {
2378 let (ns, name) = recipe.ns_name();
2379 let id = format!("{ns}:{name}");
2380 self.content("recipe", &id, &recipe.to_data());
2381 }
2382
2383 pub fn add_recipe_json(&mut self, id: &str, json: &str) {
2387 let id = if id.contains(':') { id.to_string() } else { format!("minecraft:{id}") };
2388 self.content("recipe", &id, &Self::json(json));
2389 }
2390
2391 pub fn register_item(&mut self, def: ItemDef) {
2392 use yog_data::Schema;
2393 self.content("item", &def.id.clone(), &def.write());
2394 }
2395
2396 pub fn register_block(&mut self, def: BlockDef) {
2397 use yog_data::Schema;
2398 self.content("block", &def.id.clone(), &def.write());
2399 }
2400
2401 pub fn register_entity(&mut self, def: yog_registry::EntityDef) {
2405 use yog_data::Schema;
2406 if crate::entities::drawn(&def) {
2407 self.on_body(crate::entities::draw);
2408 }
2409 self.content("entity", &def.id.clone(), &def.write());
2410 }
2411
2412 pub fn register_entity_data(&mut self, id: &str, data: &[u8]) {
2414 match yog_data::read_bytes::<yog_registry::EntityDef>(data) {
2415 Ok(def) => self.register_entity(def),
2416 Err(e) => crate::error!("entity {id}: {e:?}"),
2417 }
2418 }
2419
2420 pub fn on_entity_tick<F>(&mut self, entity_type: &str, handler: F)
2423 where
2424 F: Fn(&dyn Server, &str) + Send + Sync + 'static,
2425 {
2426 let ud = Self::leak(handler);
2427 unsafe { ((*self.api).on_entity_tick)(self.ctx(), YogStr::from_str(entity_type), ud, trampoline_entity_tick::<F>) }
2428 }
2429
2430 pub fn add_biome_feature(&mut self, feature: &str, biomes: &str, step: &str) {
2434 use yog_data::Schema;
2435 let f = yog_registry::BiomeFeature { feature: feature.into(), biomes: biomes.into(), step: step.into() };
2436 self.content("biome_feature", feature, &f.write());
2437 }
2438
2439 pub fn register_tool_material(&mut self, material: ToolMaterial) {
2442 use yog_data::Schema;
2443 self.content("tool_material", &material.id.clone(), &material.write());
2444 }
2445
2446 pub fn register_armor_material(&mut self, material: ArmorMaterial) {
2449 use yog_data::Schema;
2450 self.content("armor_material", &material.id.clone(), &material.write());
2451 }
2452
2453 pub fn register_sound_group(&mut self, group: SoundGroup) {
2456 use yog_data::Schema;
2457 self.content("sound_group", &group.id.clone(), &group.write());
2458 }
2459
2460 pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
2464 use yog_data::Schema;
2465 self.content("inventory", &def.id.clone(), &def.write());
2466 }
2467
2468 pub fn register_startup_grant(&mut self, grant: StartupGrant) {
2472 use yog_data::Schema;
2473 self.content("startup_grant", &grant.id.clone(), &grant.write());
2474 }
2475
2476 pub fn register_book(&mut self, book: &Book) {
2478 use yog_data::Schema;
2479 self.content("book", &book.id, &book.write());
2480 }
2481
2482 pub fn register_book_data(&mut self, id: &str, data: &[u8]) {
2485 unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("book"), YogStr::from_str(id), data.as_ptr(), data.len()) }
2486 }
2487
2488 pub fn register_dimension(&mut self, def: &YogDimensionDef) {
2503 let value = Self::json(&def.to_json());
2504 self.content("dimension", &def.id.clone(), &value);
2505 }
2506
2507 pub fn register_dimension_data(&mut self, id: &str, data: &[u8]) {
2511 unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("dimension"), YogStr::from_str(id), data.as_ptr(), data.len()) }
2512 }
2513
2514 pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
2526 where
2527 F: Fn(&ChunkWriter) + Send + Sync + 'static,
2528 {
2529 let ud = Self::leak(generator);
2530 let id = YogStr::from_str(generator_type_id);
2531 unsafe {
2532 ((*self.api).register_chunk_generator)(
2533 self.ctx(),
2534 id,
2535 ud,
2536 trampoline_chunk_generator::<F>,
2537 );
2538 }
2539 }
2540
2541 pub fn installed_mods(&self) -> Vec<ModInfo> {
2548 installed_mods()
2549 }
2550
2551 pub fn interop(&self) -> crate::Interop {
2556 crate::Interop::new(self.api)
2557 }
2558
2559 pub fn raw_api(&self) -> *const yog_abi::YogApi {
2562 self.api
2563 }
2564
2565 pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
2566 let l = YogStr::from_str(label);
2567 let u = YogStr::from_str(ui_id);
2568 unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
2569 }
2570
2571 pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
2580 where
2581 F: Fn(&str, &str) -> R + Send + Sync + 'static,
2582 R: UiHandled,
2583 {
2584 let ud = Self::leak(handler);
2585 let id = YogStr::from_str(ui_id);
2586 let empty = YogStr::from_str("{}");
2587 unsafe { ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F, R>) }
2588 }
2589
2590 pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
2593 where
2594 F: Fn(&dyn Server) + Send + Sync + 'static,
2595 {
2596 let ud = Self::leak(handler);
2597 unsafe {
2598 ((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>);
2599 }
2600 }
2601
2602 pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
2603 where
2604 F: Fn(&dyn Server) + Send + Sync + 'static,
2605 {
2606 let ud = Self::leak(handler);
2607 unsafe {
2608 ((*self.api).schedule_repeating)(
2609 self.ctx(),
2610 period_ticks,
2611 ud,
2612 trampoline_scheduled::<F>,
2613 );
2614 }
2615 }
2616
2617 pub fn on_client_tick<F>(&mut self, handler: F)
2621 where
2622 F: Fn(&ClientTickEvent) + Send + Sync + 'static,
2623 {
2624 let ud = Self::leak(handler);
2625 unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
2626 }
2627
2628 pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
2637 where
2638 F: Fn(&GfxContext) + Send + Sync + 'static,
2639 {
2640 let ud = Self::leak(handler);
2641 let id = YogStr::from_str(ui_id);
2642 unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
2643 }
2644
2645 pub fn on_book_page<F>(&mut self, drawer: &str, handler: F)
2655 where
2656 F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync + 'static,
2657 {
2658 let ud = Self::leak(handler);
2659 let id = YogStr::from_str(drawer);
2660 unsafe { ((*self.api).on_book_page)(self.ctx(), id, ud, trampoline_book_page::<F>) }
2661 }
2662
2663 pub fn on_item_tooltip<S, D>(&mut self, item: &str, size: S, draw: D)
2668 where
2669 S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync + 'static,
2670 D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync + 'static,
2671 {
2672 let ud = Self::leak(TooltipPicture { size, draw });
2673 let id = YogStr::from_str(item);
2674 unsafe {
2675 ((*self.api).on_item_tooltip)(self.ctx(), id, ud, trampoline_tooltip_size::<S, D>, trampoline_tooltip_draw::<S, D>)
2676 }
2677 }
2678
2679 pub fn on_item_text<F>(&mut self, item: &str, text: F)
2691 where
2692 F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync + 'static,
2693 {
2694 let ud = Self::leak(text);
2695 let id = YogStr::from_str(item);
2696 unsafe { ((*self.api).on_item_text)(self.ctx(), id, ud, trampoline_item_text::<F>) }
2697 }
2698
2699 pub fn register_body_model(&mut self, id: &str, data: &[u8]) {
2702 if let Err(e) = yog_body::register_model(id, data) {
2703 crate::error!("{e}");
2704 }
2705 }
2706
2707 pub fn register_body_anims(&mut self, id: &str, data: &[u8]) {
2710 if let Err(e) = yog_body::register_anims(id, data) {
2711 crate::error!("{e}");
2712 }
2713 }
2714
2715 pub fn on_body<F>(&mut self, handler: F)
2720 where
2721 F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync + 'static,
2722 {
2723 let ud = Self::leak(handler);
2724 unsafe { ((*self.api).on_body)(self.ctx(), ud, trampoline_body::<F>) }
2725 }
2726
2727 pub fn on_camera<F>(&mut self, handler: F)
2730 where
2731 F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync + 'static,
2732 {
2733 let ud = Self::leak(handler);
2734 unsafe { ((*self.api).on_camera)(self.ctx(), ud, trampoline_camera::<F>) }
2735 }
2736
2737 pub fn on_travel<F>(&mut self, handler: F)
2741 where
2742 F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync + 'static,
2743 {
2744 let ud = Self::leak(handler);
2745 unsafe { ((*self.api).on_travel)(self.ctx(), ud, trampoline_travel::<F>) }
2746 }
2747
2748 pub fn on_hud_render<F>(&mut self, handler: F)
2753 where
2754 F: Fn(&GfxContext) + Send + Sync + 'static,
2755 {
2756 let ud = Self::leak(handler);
2757 unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
2758 }
2759
2760 pub fn on_world_render<F>(&mut self, handler: F)
2766 where
2767 F: Fn(&GfxContext) + Send + Sync + 'static,
2768 {
2769 let ud = Self::leak(handler);
2770 unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
2771 }
2772
2773 pub fn on_key_press<F>(&mut self, handler: F)
2776 where
2777 F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static,
2778 {
2779 let ud = Self::leak(handler);
2780 unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
2781 }
2782
2783 pub fn on_screen_open<F>(&mut self, handler: F)
2785 where
2786 F: Fn(&ScreenEvent) + Send + Sync + 'static,
2787 {
2788 let ud = Self::leak(handler);
2789 unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
2790 }
2791
2792 pub fn on_screen_close<F>(&mut self, handler: F)
2794 where
2795 F: Fn(&ScreenEvent) + Send + Sync + 'static,
2796 {
2797 let ud = Self::leak(handler);
2798 unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
2799 }
2800}
2801
2802unsafe extern "C" fn trampoline_ui_event<F, R>(
2803 ud: *mut c_void,
2804 ui_id: yog_abi::YogStr,
2805 event_id: yog_abi::YogStr,
2806) where
2807 F: Fn(&str, &str) -> R + Send + Sync + 'static,
2808 R: UiHandled,
2809{
2810 let f = unsafe { &*(ud as *const F) };
2811 let ui = unsafe { ui_id.as_str() };
2812 let ev = unsafe { event_id.as_str() };
2813 if guard(false, || f(ui, ev).handled()) {
2814 let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
2815 if !api.is_null() {
2816 unsafe { ((*api).ui_consume_event)((*api).ctx) }
2817 }
2818 }
2819}
2820
2821unsafe extern "C" fn trampoline_chunk_generator<F>(
2822 ud: *mut c_void,
2823 writer: *const yog_abi::YogChunkWriterApi,
2824) where
2825 F: Fn(&ChunkWriter) + Send + Sync + 'static,
2826{
2827 let f = unsafe { &*(ud as *const F) };
2828 let writer = unsafe { ChunkWriter::from_raw(writer) };
2829 guard((), || f(&writer));
2830}
2831
2832pub trait Mod {
2836 fn register(registry: &mut Registry);
2837}
2838
2839#[cfg(test)]
2840mod trampoline_tests {
2841 use super::*;
2842 use yog_abi::{YogBlockPos, YogChatEvent, YogCommandEvent, YogPacketEvent};
2843
2844 fn ud<F>(f: &F) -> *mut c_void {
2845 f as *const F as *mut c_void
2846 }
2847
2848 fn chat<F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogChatEvent, phase: u8) -> bool {
2849 unsafe { trampoline_chat::<F>(ud(f), std::ptr::null(), ev, phase) }
2850 }
2851
2852 fn block_break<F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogBlockBreakEvent) -> bool {
2853 unsafe { trampoline_block_break::<F>(ud(f), std::ptr::null(), ev, 0) }
2854 }
2855
2856 fn command<F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync>(f: &F, ev: &YogCommandEvent, buf: &mut [u8]) -> u32 {
2857 let mut len = 99u32;
2858 unsafe { trampoline_command::<F>(ud(f), std::ptr::null(), ev, buf.as_mut_ptr(), buf.len() as u32, &mut len) };
2859 len
2860 }
2861
2862 fn packet<F: Fn(&PacketEvent, &dyn Server) + Send + Sync>(f: &F, ev: &YogPacketEvent) {
2863 unsafe { trampoline_packet::<F>(ud(f), std::ptr::null(), ev) }
2864 }
2865
2866 #[test]
2867 fn events_arrive_converted_with_their_phase() {
2868 let seen = std::sync::Mutex::new(Vec::new());
2869 let handler = |ev: &ChatEvent, p: EventPhase, _: &dyn Server| {
2870 seen.lock().unwrap().push((ev.player_name.clone(), ev.message.clone(), p));
2871 ev.message != "cancel me"
2872 };
2873 let ev = |msg: &'static str| YogChatEvent {
2874 player: YogStr::from_str("Dev"),
2875 message: YogStr::from_str(msg),
2876 dimension: YogStr::from_str("minecraft:overworld"),
2877 };
2878 assert!(chat(&handler, &ev("hi"), 0));
2879 assert!(!chat(&handler, &ev("cancel me"), 0));
2880 chat(&handler, &ev("after"), 1);
2881 let seen = seen.into_inner().unwrap();
2882 assert_eq!(seen[0], ("Dev".into(), "hi".into(), EventPhase::Pre));
2883 assert_eq!(seen[2].2, EventPhase::Post);
2884 }
2885
2886 #[test]
2887 fn a_panicking_handler_allows_the_action() {
2888 let handler = |_: &BlockBreakEvent, _: EventPhase, _: &dyn Server| -> bool { panic!("mod bug") };
2889 let ev = YogBlockBreakEvent {
2890 player: YogStr::from_str("Dev"),
2891 block: YogStr::from_str("minecraft:stone"),
2892 pos: YogBlockPos { x: 1, y: 2, z: 3 },
2893 dimension: YogStr::from_str("minecraft:overworld"),
2894 };
2895 assert!(block_break(&handler, &ev));
2896 }
2897
2898 #[test]
2899 fn command_replies_fit_the_buffer_whole_characters_only() {
2900 let handler = |c: &CommandContext, _: &dyn Server| Some(format!("{}: привет", c.args));
2901 let ev = YogCommandEvent {
2902 name: YogStr::from_str("hex"),
2903 args: YogStr::from_str("x"),
2904 source: YogStr::from_str("Dev"),
2905 uuid: YogStr::EMPTY,
2906 dimension: YogStr::EMPTY,
2907 };
2908 let mut buf = [0u8; 6];
2909 let len = command(&handler, &ev, &mut buf);
2910 assert_eq!(std::str::from_utf8(&buf[..len as usize]), Ok("x: п"));
2912 }
2913
2914 #[test]
2915 fn empty_packets_need_no_payload_pointer() {
2916 let got = std::sync::Mutex::new(None);
2917 let handler = |p: &PacketEvent, _: &dyn Server| *got.lock().unwrap() = Some(p.payload.len());
2918 let ev = YogPacketEvent {
2919 channel: YogStr::from_str("mymod:ping"),
2920 player: YogStr::from_str("Dev"),
2921 payload: std::ptr::null(),
2922 payload_len: 0,
2923 };
2924 packet(&handler, &ev);
2925 assert_eq!(*got.lock().unwrap(), Some(0));
2926 }
2927}