use std::os::raw::c_void;
use yog_abi::{
YogAdvancementEvent, YogApi, YogAttackEntityEvent, YogBlockBreakEvent,
YogChatEvent, YogCommandEvent, YogContainerCloseEvent, YogContainerOpenEvent, YogCraftEvent,
YogEntityDamageEvent, YogEntityDeathEvent, YogEntityInteractEvent, YogEntitySpawnEvent,
YogExplosionEvent, YogGfxApi, YogItemInsertEvent, YogItemPickupEvent, YogMachineCraftEvent,
YogKeyPressEvent, YogPacketEvent, YogPlaceBlockEvent, YogPlayerDeathEvent, YogPlayerEvent,
YogPlayerMoveEvent, YogPlayerRespawnEvent, YogProjectileHitEvent, YogServer,
YogStr, YogUseBlockEvent, YogUseItemEvent,
};
use yog_book::Book;
use yog_command::CommandContext;
use yog_core::Server;
use yog_dimensions::{ChunkWriter, YogDimensionDef};
use yog_event::{
AdvancementEvent, AttackEntityEvent, BlockBreakEvent, ChatEvent, ClientTickEvent,
ContainerCloseEvent, ContainerOpenEvent, CraftEvent, EntityDamageEvent, EntityDeathEvent,
EntityInteractEvent, EntitySpawnEvent, EventPhase, ExplosionEvent, ItemInsertEvent, ItemPickupEvent,
KeyPressEvent, PlaceBlockEvent, PlayerDeathEvent, PlayerJoinEvent, PlayerLeaveEvent,
PlayerMoveEvent, PlayerRespawnEvent, ProjectileHitEvent, ScreenEvent, UseBlockEvent,
UseItemEvent,
};
use yog_gfx::GfxContext;
use yog_network::{Packet, PacketEvent};
use yog_registry::{ArmorMaterial, BlockDef, ItemDef, Recipe, SoundGroup, StartupGrant, ToolMaterial};
pub struct CServer(pub *const YogServer);
unsafe impl Send for CServer {}
unsafe impl Sync for CServer {}
macro_rules! srv {
($self:ident) => {
unsafe { &*$self.0 }
};
}
impl Server for CServer {
fn broadcast(&self, message: &str) {
let s = srv!(self);
unsafe { (s.broadcast)(s.ctx, YogStr::from_str(message)) }
}
fn get_block(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
let s = srv!(self);
let owned = unsafe {
(s.get_block)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
)
};
take_owned(s.free_str, owned)
}
fn set_block(&self, dimension: &str, pos: yog_core::BlockPos, block_id: &str) -> bool {
let s = srv!(self);
unsafe {
(s.set_block)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
YogStr::from_str(block_id),
)
}
}
fn give_item(&self, player: &str, item_id: &str, count: u32) -> bool {
let s = srv!(self);
unsafe {
(s.give_item)(
s.ctx,
YogStr::from_str(player),
YogStr::from_str(item_id),
count,
)
}
}
fn teleport(&self, player: &str, x: f64, y: f64, z: f64) -> bool {
let s = srv!(self);
unsafe {
(s.player_teleport)(
s.ctx,
YogStr::from_str(player),
yog_abi::YogVec3 { x, y, z },
)
}
}
fn send_to_player(&self, player: &str, channel: &str, payload: &[u8]) -> bool {
let s = srv!(self);
unsafe {
(s.send_to_player)(
s.ctx,
YogStr::from_str(player),
YogStr::from_str(channel),
payload.as_ptr(),
payload.len() as u32,
)
}
}
fn send_to_server(&self, channel: &str, payload: &[u8]) -> bool {
let s = srv!(self);
unsafe {
(s.send_to_server)(
s.ctx,
YogStr::from_str(channel),
payload.as_ptr(),
payload.len() as u32,
)
}
}
fn entity_teleport(&self, uuid: &str, x: f64, y: f64, z: f64) -> bool {
let s = srv!(self);
unsafe { (s.entity_teleport)(s.ctx, YogStr::from_str(uuid), yog_abi::YogVec3 { x, y, z }) }
}
fn entity_position(&self, uuid: &str) -> Option<(f64, f64, f64)> {
let s = srv!(self);
let mut out = yog_abi::YogVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
if unsafe { (s.entity_position)(s.ctx, YogStr::from_str(uuid), &mut out) } {
Some((out.x, out.y, out.z))
} else {
None
}
}
fn entity_health(&self, uuid: &str) -> Option<f32> {
let s = srv!(self);
let mut hp = 0f32;
if unsafe { (s.entity_health)(s.ctx, YogStr::from_str(uuid), &mut hp) } {
Some(hp)
} else {
None
}
}
fn entity_set_health(&self, uuid: &str, health: f32) -> bool {
let s = srv!(self);
unsafe { (s.entity_set_health)(s.ctx, YogStr::from_str(uuid), health) }
}
fn entity_kill(&self, uuid: &str) -> bool {
let s = srv!(self);
unsafe { (s.entity_kill)(s.ctx, YogStr::from_str(uuid)) }
}
fn spawn_entity(
&self,
entity_type: &str,
dimension: &str,
x: f64,
y: f64,
z: f64,
) -> Option<String> {
let s = srv!(self);
let owned = unsafe {
(s.spawn_entity)(
s.ctx,
YogStr::from_str(entity_type),
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
)
};
take_owned(s.free_str, owned)
}
fn entity_add_effect(
&self,
uuid: &str,
effect_id: &str,
duration_ticks: i32,
amplifier: u8,
show_particles: bool,
) -> bool {
let s = srv!(self);
unsafe {
(s.entity_add_effect)(
s.ctx,
YogStr::from_str(uuid),
YogStr::from_str(effect_id),
duration_ticks,
amplifier,
show_particles,
)
}
}
fn entity_remove_effect(&self, uuid: &str, effect_id: &str) -> bool {
let s = srv!(self);
unsafe {
(s.entity_remove_effect)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(effect_id))
}
}
fn entity_clear_effects(&self, uuid: &str) -> bool {
let s = srv!(self);
unsafe { (s.entity_clear_effects)(s.ctx, YogStr::from_str(uuid)) }
}
fn drop_loot(&self, table_id: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
let s = srv!(self);
unsafe {
(s.drop_loot)(
s.ctx,
YogStr::from_str(table_id),
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
)
}
}
fn has_item_tag(&self, item_id: &str, tag_id: &str) -> bool {
let s = srv!(self);
unsafe { (s.has_item_tag)(s.ctx, YogStr::from_str(item_id), YogStr::from_str(tag_id)) }
}
fn has_block_tag(&self, block_id: &str, tag_id: &str) -> bool {
let s = srv!(self);
unsafe { (s.has_block_tag)(s.ctx, YogStr::from_str(block_id), YogStr::from_str(tag_id)) }
}
fn world_time(&self, dimension: &str) -> Option<i64> {
let s = srv!(self);
let mut t = 0i64;
if unsafe { (s.world_time)(s.ctx, YogStr::from_str(dimension), &mut t) } {
Some(t)
} else {
None
}
}
fn world_set_time(&self, dimension: &str, time: i64) -> bool {
let s = srv!(self);
unsafe { (s.set_time)(s.ctx, YogStr::from_str(dimension), time) }
}
fn world_is_raining(&self, dimension: &str) -> bool {
let s = srv!(self);
unsafe { (s.is_raining)(s.ctx, YogStr::from_str(dimension)) }
}
fn world_set_weather(&self, dimension: &str, raining: bool, duration_ticks: i32) -> bool {
let s = srv!(self);
unsafe { (s.set_weather)(s.ctx, YogStr::from_str(dimension), raining, duration_ticks) }
}
fn entity_velocity(&self, uuid: &str) -> Option<(f64, f64, f64)> {
let s = srv!(self);
let mut v = yog_abi::YogVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
if unsafe { (s.entity_velocity)(s.ctx, YogStr::from_str(uuid), &mut v) } {
Some((v.x, v.y, v.z))
} else {
None
}
}
fn entity_set_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
let s = srv!(self);
unsafe {
(s.entity_set_velocity)(
s.ctx,
YogStr::from_str(uuid),
yog_abi::YogVec3 {
x: vx,
y: vy,
z: vz,
},
)
}
}
fn entity_add_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool {
let s = srv!(self);
unsafe {
(s.entity_add_velocity)(
s.ctx,
YogStr::from_str(uuid),
yog_abi::YogVec3 {
x: vx,
y: vy,
z: vz,
},
)
}
}
fn scoreboard_get(&self, objective: &str, player: &str) -> Option<i32> {
let s = srv!(self);
let mut score = 0i32;
if unsafe {
(s.scoreboard_get)(
s.ctx,
YogStr::from_str(objective),
YogStr::from_str(player),
&mut score,
)
} {
Some(score)
} else {
None
}
}
fn scoreboard_set(&self, objective: &str, player: &str, score: i32) -> bool {
let s = srv!(self);
unsafe {
(s.scoreboard_set)(
s.ctx,
YogStr::from_str(objective),
YogStr::from_str(player),
score,
)
}
}
fn scoreboard_add(&self, objective: &str, player: &str, delta: i32) -> Option<i32> {
let s = srv!(self);
let mut new_score = 0i32;
if unsafe {
(s.scoreboard_add)(
s.ctx,
YogStr::from_str(objective),
YogStr::from_str(player),
delta,
&mut new_score,
)
} {
Some(new_score)
} else {
None
}
}
fn game_dir(&self) -> String {
let s = srv!(self);
let owned = unsafe { (s.game_dir)(s.ctx) };
take_owned(s.free_str, owned).unwrap_or_default()
}
fn play_sound(
&self,
dimension: &str,
x: f64,
y: f64,
z: f64,
sound_id: &str,
volume: f32,
pitch: f32,
) -> bool {
let s = srv!(self);
unsafe {
(s.play_sound)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
YogStr::from_str(sound_id),
volume,
pitch,
)
}
}
fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool {
let s = srv!(self);
unsafe {
(s.play_sound_player)(
s.ctx,
YogStr::from_str(player),
YogStr::from_str(sound_id),
volume,
pitch,
)
}
}
fn send_title(
&self,
player: &str,
title: &str,
subtitle: &str,
fadein: i32,
stay: i32,
fadeout: i32,
) -> bool {
let s = srv!(self);
unsafe {
(s.send_title)(
s.ctx,
YogStr::from_str(player),
YogStr::from_str(title),
YogStr::from_str(subtitle),
fadein,
stay,
fadeout,
)
}
}
fn entity_rotation(&self, uuid: &str) -> Option<(f32, f32)> {
let s = srv!(self);
let mut out = yog_abi::YogVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
let ok = unsafe { (s.entity_rotation)(s.ctx, YogStr::from_str(uuid), &mut out) };
if ok {
Some((out.x as f32, out.y as f32))
} else {
None
}
}
fn send_actionbar(&self, player: &str, message: &str) -> bool {
let s = srv!(self);
unsafe { (s.send_actionbar)(s.ctx, YogStr::from_str(player), YogStr::from_str(message)) }
}
fn kick_player(&self, player: &str, reason: &str) -> bool {
let s = srv!(self);
unsafe { (s.kick_player)(s.ctx, YogStr::from_str(player), YogStr::from_str(reason)) }
}
fn set_gamemode(&self, player: &str, gamemode: &str) -> bool {
let s = srv!(self);
unsafe { (s.set_gamemode)(s.ctx, YogStr::from_str(player), YogStr::from_str(gamemode)) }
}
fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool {
let s = srv!(self);
unsafe {
(s.bossbar_create)(
s.ctx,
YogStr::from_str(id),
YogStr::from_str(title),
YogStr::from_str(color),
YogStr::from_str(style),
)
}
}
fn bossbar_remove(&self, id: &str) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_remove)(s.ctx, YogStr::from_str(id)) }
}
fn bossbar_set_title(&self, id: &str, title: &str) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_set_title)(s.ctx, YogStr::from_str(id), YogStr::from_str(title)) }
}
fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_set_progress)(s.ctx, YogStr::from_str(id), progress) }
}
fn bossbar_set_color(&self, id: &str, color: &str) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_set_color)(s.ctx, YogStr::from_str(id), YogStr::from_str(color)) }
}
fn bossbar_add_player(&self, id: &str, player: &str) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_add_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
}
fn bossbar_remove_player(&self, id: &str, player: &str) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_remove_player)(s.ctx, YogStr::from_str(id), YogStr::from_str(player)) }
}
fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool {
let s = srv!(self);
unsafe { (s.bossbar_set_visible)(s.ctx, YogStr::from_str(id), visible) }
}
fn online_players(&self) -> Vec<String> {
let s = srv!(self);
let owned = unsafe { (s.online_players)(s.ctx) };
let Some(text) = take_owned(s.free_str, owned) else {
return Vec::new();
};
if text.is_empty() {
Vec::new()
} else {
text.lines().map(str::to_owned).collect()
}
}
fn get_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
let s = srv!(self);
let owned = unsafe {
(s.get_block_nbt)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
)
};
take_owned(s.free_str, owned)
}
fn set_block_nbt(&self, dimension: &str, pos: yog_core::BlockPos, snbt: &str) -> bool {
let s = srv!(self);
unsafe {
(s.set_block_nbt)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
YogStr::from_str(snbt),
)
}
}
fn get_inventory_slot(
&self,
dimension: &str,
pos: yog_core::BlockPos,
slot: u32,
) -> Option<(String, u32, String)> {
let s = srv!(self);
let owned = unsafe {
(s.get_inventory_slot)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
slot,
)
};
let text = take_owned(s.free_str, owned)?;
let mut it = text.splitn(3, '\t');
let item_id = it.next()?.to_owned();
let count = it.next()?.parse().ok()?;
let snbt = it.next().unwrap_or("{}").to_owned();
Some((item_id, count, snbt))
}
fn set_inventory_slot(
&self,
dimension: &str,
pos: yog_core::BlockPos,
slot: u32,
item_id: &str,
count: u32,
snbt: &str,
) -> bool {
let item = if snbt.is_empty() { item_id.to_string() } else { format!("{item_id}\t{snbt}") };
let s = srv!(self);
unsafe {
(s.set_inventory_slot)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
},
slot,
YogStr::from_str(&item),
count,
)
}
}
fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)> {
let s = srv!(self);
let owned = unsafe { (s.player_inventory)(s.ctx, YogStr::from_str(player)) };
let Some(text) = take_owned(s.free_str, owned) else {
return Vec::new();
};
text.lines()
.filter_map(|line| {
let mut it = line.split('\t');
let slot: u32 = it.next()?.parse().ok()?;
let item_id = it.next()?.to_owned();
let count: u32 = it.next()?.parse().ok()?;
Some((slot, item_id, count))
})
.collect()
}
fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool {
let s = srv!(self);
unsafe {
(s.player_set_slot)(
s.ctx,
YogStr::from_str(player),
slot,
YogStr::from_str(item_id),
count,
)
}
}
fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
let s = srv!(self);
unsafe {
(s.player_teleport_dim)(
s.ctx,
YogStr::from_str(player),
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
)
}
}
fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool {
let s = srv!(self);
unsafe {
(s.entity_teleport_dim)(
s.ctx,
YogStr::from_str(uuid),
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
)
}
}
fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32 {
let s = srv!(self);
unsafe {
(s.world_entity_count)(
s.ctx,
YogStr::from_str(dimension),
YogStr::from_str(entity_type),
)
}
}
fn entity_get_nbt(&self, uuid: &str) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.entity_get_nbt)(s.ctx, YogStr::from_str(uuid)) };
take_owned(s.free_str, owned)
}
fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool {
let s = srv!(self);
unsafe { (s.entity_set_nbt)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(snbt)) }
}
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 {
let s = srv!(self);
unsafe {
(s.spawn_particles)(
s.ctx,
YogStr::from_str(dimension),
yog_abi::YogVec3 { x, y, z },
YogStr::from_str(particle_type),
count,
dx,
dy,
dz,
speed,
)
}
}
fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64> {
let s = srv!(self);
let v = unsafe {
(s.entity_attribute_get)(
s.ctx,
YogStr::from_str(uuid),
YogStr::from_str(attribute_id),
)
};
if v.is_nan() { None } else { Some(v) }
}
fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool {
let s = srv!(self);
unsafe {
(s.entity_attribute_set)(
s.ctx,
YogStr::from_str(uuid),
YogStr::from_str(attribute_id),
value,
)
}
}
fn get_held_item_nbt(&self, player: &str) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.get_held_item_nbt)(s.ctx, YogStr::from_str(player)) };
take_owned(s.free_str, owned)
}
fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool {
let s = srv!(self);
unsafe { (s.set_held_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
}
fn get_offhand_item_nbt(&self, player: &str) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.get_offhand_item_nbt)(s.ctx, YogStr::from_str(player)) };
take_owned(s.free_str, owned)
}
fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool {
let s = srv!(self);
unsafe { (s.set_offhand_item_nbt)(s.ctx, YogStr::from_str(player), YogStr::from_str(snbt)) }
}
fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)> {
let s = srv!(self);
let owned = unsafe { (s.get_slot_item)(s.ctx, YogStr::from_str(player), slot) };
let text = take_owned(s.free_str, owned)?;
let mut it = text.splitn(3, '\t');
let item_id = it.next()?.to_owned();
let count: u32 = it.next()?.parse().ok()?;
let nbt = it.next().unwrap_or("{}").to_owned();
Some((item_id, count, nbt))
}
fn set_slot_item(
&self,
player: &str,
slot: u32,
item_id: &str,
count: u32,
snbt: &str,
) -> bool {
let s = srv!(self);
unsafe {
(s.set_slot_item)(
s.ctx,
YogStr::from_str(player),
slot,
YogStr::from_str(item_id),
count,
YogStr::from_str(snbt),
)
}
}
fn entity_info(&self, uuid: &str) -> Option<yog_core::EntityInfo> {
let s = srv!(self);
let owned = unsafe { (s.entity_info)(s.ctx, YogStr::from_str(uuid)) };
take_owned(s.free_str, owned).and_then(|l| yog_core::EntityInfo::decode(&l))
}
fn entities_in_box(
&self,
dimension: &str,
min: (f64, f64, f64),
max: (f64, f64, f64),
) -> Vec<String> {
let s = srv!(self);
let owned =
unsafe { (s.entities_in_box)(s.ctx, YogStr::from_str(dimension), v3(min), v3(max)) };
take_owned(s.free_str, owned)
.map(|t| {
t.lines()
.filter(|l| !l.is_empty())
.map(str::to_owned)
.collect()
})
.unwrap_or_default()
}
fn raycast_block(
&self,
dimension: &str,
from: (f64, f64, f64),
to: (f64, f64, f64),
) -> Option<yog_core::BlockHit> {
let s = srv!(self);
let owned =
unsafe { (s.raycast_block)(s.ctx, YogStr::from_str(dimension), v3(from), v3(to)) };
take_owned(s.free_str, owned).and_then(|l| yog_core::BlockHit::decode(&l))
}
fn raycast_entity(
&self,
dimension: &str,
from: (f64, f64, f64),
to: (f64, f64, f64),
exclude: &str,
) -> Option<String> {
let s = srv!(self);
let owned = unsafe {
(s.raycast_entity)(
s.ctx,
YogStr::from_str(dimension),
v3(from),
v3(to),
YogStr::from_str(exclude),
)
};
take_owned(s.free_str, owned).filter(|u| !u.is_empty())
}
fn block_info(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<yog_core::BlockInfo> {
let s = srv!(self);
let owned = unsafe { (s.block_info)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
take_owned(s.free_str, owned).and_then(|l| yog_core::BlockInfo::decode(&l))
}
fn break_block(
&self,
dimension: &str,
pos: yog_core::BlockPos,
drop: bool,
breaker: &str,
) -> bool {
let s = srv!(self);
unsafe {
(s.break_block)(
s.ctx,
YogStr::from_str(dimension),
bp(pos),
drop,
YogStr::from_str(breaker),
)
}
}
fn place_block_from_slot(
&self,
player: &str,
slot: u32,
dimension: &str,
pos: yog_core::BlockPos,
) -> bool {
let s = srv!(self);
unsafe {
(s.place_block_from_slot)(
s.ctx,
YogStr::from_str(player),
slot,
YogStr::from_str(dimension),
bp(pos),
)
}
}
fn bonemeal(&self, dimension: &str, pos: yog_core::BlockPos) -> bool {
let s = srv!(self);
unsafe { (s.bonemeal)(s.ctx, YogStr::from_str(dimension), bp(pos)) }
}
fn explode(
&self,
dimension: &str,
x: f64,
y: f64,
z: f64,
power: f32,
fire: bool,
source: &str,
) -> bool {
let s = srv!(self);
unsafe {
(s.explode)(
s.ctx,
YogStr::from_str(dimension),
v3((x, y, z)),
power,
fire,
YogStr::from_str(source),
)
}
}
fn tellraw(&self, player: &str, json: &str) -> bool {
let s = srv!(self);
unsafe { (s.tellraw)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
}
fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool {
let s = srv!(self);
unsafe {
(s.entity_damage)(
s.ctx,
YogStr::from_str(uuid),
amount,
YogStr::from_str(damage_type),
YogStr::from_str(attacker),
)
}
}
fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool {
let s = srv!(self);
unsafe { (s.entity_set_fire_ticks)(s.ctx, YogStr::from_str(uuid), ticks) }
}
fn player_add_xp(&self, player: &str, amount: i32) -> bool {
let s = srv!(self);
unsafe { (s.player_add_xp)(s.ctx, YogStr::from_str(player), amount) }
}
fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool {
let s = srv!(self);
unsafe { (s.player_set_may_fly)(s.ctx, YogStr::from_str(player), may_fly) }
}
fn player_info(&self, player: &str) -> Option<yog_core::PlayerInfo> {
let s = srv!(self);
let owned = unsafe { (s.player_info)(s.ctx, YogStr::from_str(player)) };
take_owned(s.free_str, owned).and_then(|l| yog_core::PlayerInfo::decode(&l))
}
fn spawn_item(
&self,
dimension: &str,
pos: (f64, f64, f64),
vel: (f64, f64, f64),
item_id: &str,
count: u32,
snbt: &str,
) -> Option<String> {
let s = srv!(self);
let owned = unsafe {
(s.spawn_item)(
s.ctx,
YogStr::from_str(dimension),
v3(pos),
v3(vel),
YogStr::from_str(item_id),
count,
YogStr::from_str(snbt),
)
};
take_owned(s.free_str, owned).filter(|u| !u.is_empty())
}
fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)> {
let s = srv!(self);
let owned = unsafe { (s.item_entity_stack)(s.ctx, YogStr::from_str(uuid)) };
let text = take_owned(s.free_str, owned)?;
let mut it = text.splitn(3, '\t');
let item_id = it.next()?.to_owned();
let count = it.next()?.parse().ok()?;
let nbt = it.next().unwrap_or("{}").to_owned();
Some((item_id, count, nbt))
}
fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool {
let s = srv!(self);
unsafe {
(s.item_entity_set_stack)(
s.ctx,
YogStr::from_str(uuid),
YogStr::from_str(item_id),
count,
YogStr::from_str(snbt),
)
}
}
fn actionbar_json(&self, player: &str, json: &str) -> bool {
let s = srv!(self);
unsafe { (s.actionbar_json)(s.ctx, YogStr::from_str(player), YogStr::from_str(json)) }
}
fn use_item_on_block(
&self,
dimension: &str,
pos: yog_core::BlockPos,
face: yog_core::Face,
item_id: &str,
player: &str,
) -> bool {
let s = srv!(self);
unsafe {
(s.use_item_on_block)(
s.ctx,
YogStr::from_str(dimension),
bp(pos),
face.index(),
YogStr::from_str(item_id),
YogStr::from_str(player),
)
}
}
fn pour_fluid(&self, dimension: &str, pos: yog_core::BlockPos, bucket_item_id: &str) -> bool {
let s = srv!(self);
unsafe {
(s.pour_fluid)(
s.ctx,
YogStr::from_str(dimension),
bp(pos),
YogStr::from_str(bucket_item_id),
)
}
}
fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool {
let s = srv!(self);
unsafe {
(s.has_entity_tag)(
s.ctx,
YogStr::from_str(entity_type),
YogStr::from_str(tag_id),
)
}
}
fn item_block(&self, item_id: &str) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.item_block)(s.ctx, YogStr::from_str(item_id)) };
take_owned(s.free_str, owned).filter(|b| !b.is_empty())
}
fn place_feature(&self, dimension: &str, pos: yog_core::BlockPos, feature_id: &str) -> bool {
let s = srv!(self);
unsafe {
(s.place_feature)(
s.ctx,
YogStr::from_str(dimension),
bp(pos),
YogStr::from_str(feature_id),
)
}
}
fn set_item_cooldown(&self, player: &str, item_id: &str, ticks: u32) -> bool {
let s = srv!(self);
unsafe { (s.set_item_cooldown)(s.ctx, YogStr::from_str(player), YogStr::from_str(item_id), ticks.min(i32::MAX as u32) as i32) }
}
fn entity_vehicle(&self, uuid: &str) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.entity_vehicle)(s.ctx, YogStr::from_str(uuid)) };
take_owned(s.free_str, owned).filter(|v| !v.is_empty())
}
fn entity_passengers(&self, uuid: &str) -> Vec<String> {
let s = srv!(self);
let owned = unsafe { (s.entity_passengers)(s.ctx, YogStr::from_str(uuid)) };
take_owned(s.free_str, owned)
.map(|t| t.lines().filter(|l| !l.is_empty()).map(str::to_string).collect())
.unwrap_or_default()
}
fn entity_mount(&self, rider: &str, vehicle: &str, force: bool) -> bool {
let s = srv!(self);
unsafe { (s.entity_mount)(s.ctx, YogStr::from_str(rider), YogStr::from_str(vehicle), force) }
}
fn entity_dismount(&self, uuid: &str) -> bool {
let s = srv!(self);
unsafe { (s.entity_dismount)(s.ctx, YogStr::from_str(uuid)) }
}
fn entity_data(&self, uuid: &str, key: &str) -> Option<Vec<u8>> {
let s = srv!(self);
let mut out = Vec::new();
let found = unsafe {
(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)
};
found.then_some(out)
}
fn set_entity_data(&self, uuid: &str, key: &str, value: Option<&[u8]>, persist: bool) -> bool {
let s = srv!(self);
let flags = value.map_or(0, |_| 1) | if persist { 2 } else { 0 };
let v = value.unwrap_or(&[]);
unsafe { (s.entity_data_set)(s.ctx, YogStr::from_str(uuid), YogStr::from_str(key), v.as_ptr(), v.len() as u32, flags) }
}
fn entity_set_dimensions(&self, uuid: &str, size: Option<(f32, f32, f32)>) -> bool {
let s = srv!(self);
let (w, h, e) = size.unwrap_or((-1.0, 0.0, 0.0));
unsafe { (s.entity_set_dimensions)(s.ctx, YogStr::from_str(uuid), w, h, e) }
}
fn entity_set_fall_distance(&self, uuid: &str, distance: f32) -> bool {
let s = srv!(self);
unsafe { (s.entity_set_fall_distance)(s.ctx, YogStr::from_str(uuid), distance) }
}
fn redstone_signal(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
let s = srv!(self);
let p = unsafe { (s.redstone_signal)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
(0..=15).contains(&p).then_some(p as u8)
}
fn redstone_power(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<u8> {
let s = srv!(self);
let p = unsafe { (s.redstone_power)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
(0..=15).contains(&p).then_some(p as u8)
}
fn set_redstone_output(&self, dimension: &str, pos: yog_core::BlockPos, level: u8) -> bool {
let s = srv!(self);
unsafe { (s.set_redstone_output)(s.ctx, YogStr::from_str(dimension), bp(pos), level.min(15) as i32) }
}
fn get_block_state(&self, dimension: &str, pos: yog_core::BlockPos) -> Option<String> {
let s = srv!(self);
let owned = unsafe { (s.get_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos)) };
take_owned(s.free_str, owned).filter(|b| !b.is_empty())
}
fn set_block_state(&self, dimension: &str, pos: yog_core::BlockPos, state: &str) -> bool {
let s = srv!(self);
unsafe { (s.set_block_state)(s.ctx, YogStr::from_str(dimension), bp(pos), YogStr::from_str(state)) }
}
fn world_is_thundering(&self, dimension: &str) -> bool {
let s = srv!(self);
unsafe { (s.world_is_thundering)(s.ctx, YogStr::from_str(dimension)) }
}
fn world_set_thunder(&self, dimension: &str, thundering: bool, duration_ticks: i32) -> bool {
let s = srv!(self);
unsafe { (s.world_set_thunder)(s.ctx, YogStr::from_str(dimension), thundering, duration_ticks) }
}
fn fill_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos, state: &str) -> Option<u32> {
let s = srv!(self);
let n = unsafe { (s.fill_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to), YogStr::from_str(state)) };
u32::try_from(n).ok()
}
fn scan_region(&self, dimension: &str, from: yog_core::BlockPos, to: yog_core::BlockPos) -> Vec<(yog_core::BlockPos, String)> {
let s = srv!(self);
let owned = unsafe { (s.scan_region)(s.ctx, YogStr::from_str(dimension), bp(from), bp(to)) };
let Some(text) = take_owned(s.free_str, owned) else {
return Vec::new();
};
text.lines()
.filter_map(|line| {
let mut it = line.splitn(4, ' ');
let (x, y, z) = (it.next()?.parse().ok()?, it.next()?.parse().ok()?, it.next()?.parse().ok()?);
Some((yog_core::BlockPos { x, y, z }, it.next()?.to_string()))
})
.collect()
}
}
pub(crate) fn take_owned(
free_str: unsafe extern "C" fn(ptr: *mut u8, len: u32),
owned: yog_abi::YogOwnedStr,
) -> Option<String> {
if owned.is_none() {
return None;
}
let text = unsafe {
String::from_utf8_lossy(std::slice::from_raw_parts(owned.ptr, owned.len as usize))
.into_owned()
};
unsafe { free_str(owned.ptr, owned.len) };
Some(text)
}
fn v3((x, y, z): (f64, f64, f64)) -> yog_abi::YogVec3 {
yog_abi::YogVec3 { x, y, z }
}
fn bp(pos: yog_core::BlockPos) -> yog_abi::YogBlockPos {
yog_abi::YogBlockPos {
x: pos.x,
y: pos.y,
z: pos.z,
}
}
fn guard<R>(fallback: R, call: impl FnOnce() -> R) -> R {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(call)).unwrap_or_else(|_| {
crate::error!("a mod's callback panicked (see above); carrying on without it");
fallback
})
}
macro_rules! trampoline_phased {
($name:ident, $abi_ev:ty, $rust_ev:ty, |$ev:ident| $build:expr_2021) => {
unsafe extern "C" fn $name<F>(
ud: *mut c_void,
srv: *const YogServer,
ev: *const $abi_ev,
phase: u8,
) -> bool
where
F: Fn(&$rust_ev, EventPhase, &dyn Server) -> bool + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let $ev = &*ev;
let rust_ev = $build;
let p = if phase == 0 {
EventPhase::Pre
} else {
EventPhase::Post
};
guard(true, || f(&rust_ev, p, &CServer(srv)))
}
}
};
}
trampoline_phased!(
trampoline_block_break,
YogBlockBreakEvent,
BlockBreakEvent,
|ev| BlockBreakEvent {
player_name: ev.player.as_str().to_owned(),
block_id: ev.block.as_str().to_owned(),
pos: yog_core::BlockPos {
x: ev.pos.x,
y: ev.pos.y,
z: ev.pos.z
},
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(trampoline_chat, YogChatEvent, ChatEvent, |ev| ChatEvent {
player_name: ev.player.as_str().to_owned(),
message: ev.message.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
});
trampoline_phased!(
trampoline_player_join,
YogPlayerEvent,
PlayerJoinEvent,
|ev| PlayerJoinEvent {
player_name: ev.player.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_player_leave,
YogPlayerEvent,
PlayerLeaveEvent,
|ev| PlayerLeaveEvent {
player_name: ev.player.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(trampoline_use_item, YogUseItemEvent, UseItemEvent, |ev| {
UseItemEvent {
player_name: ev.player.as_str().to_owned(),
item_id: ev.item.as_str().to_owned(),
sneaking: ev.sneaking,
dimension: ev.dimension.as_str().to_owned(),
}
});
trampoline_phased!(
trampoline_use_block,
YogUseBlockEvent,
UseBlockEvent,
|ev| UseBlockEvent {
player_name: ev.player.as_str().to_owned(),
block_id: ev.block.as_str().to_owned(),
pos: yog_core::BlockPos {
x: ev.pos.x,
y: ev.pos.y,
z: ev.pos.z
},
dimension: ev.dimension.as_str().to_owned(),
sneaking: ev.sneaking,
}
);
trampoline_phased!(
trampoline_attack_entity,
YogAttackEntityEvent,
AttackEntityEvent,
|ev| AttackEntityEvent {
player_name: ev.player.as_str().to_owned(),
target_type: ev.target_type.as_str().to_owned(),
target_uuid: ev.target_uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_entity_damage,
YogEntityDamageEvent,
EntityDamageEvent,
|ev| EntityDamageEvent {
entity_type: ev.entity_type.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
amount: ev.amount,
source: ev.source.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_entity_death,
YogEntityDeathEvent,
EntityDeathEvent,
|ev| EntityDeathEvent {
entity_type: ev.entity_type.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
source: ev.source.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_entity_spawn,
YogEntitySpawnEvent,
EntitySpawnEvent,
|ev| EntitySpawnEvent {
entity_type: ev.entity_type.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_place_block,
YogPlaceBlockEvent,
PlaceBlockEvent,
|ev| PlaceBlockEvent {
player_name: ev.player.as_str().to_owned(),
block_id: ev.block.as_str().to_owned(),
pos: yog_core::BlockPos {
x: ev.pos.x,
y: ev.pos.y,
z: ev.pos.z
},
dimension: ev.dimension.as_str().to_owned(),
sneaking: ev.sneaking,
}
);
trampoline_phased!(
trampoline_player_death,
YogPlayerDeathEvent,
PlayerDeathEvent,
|ev| PlayerDeathEvent {
player_name: ev.player.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
source: ev.source.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_player_respawn,
YogPlayerRespawnEvent,
PlayerRespawnEvent,
|ev| PlayerRespawnEvent {
player_name: ev.player.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
at_anchor: ev.at_anchor,
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_advancement,
YogAdvancementEvent,
AdvancementEvent,
|ev| AdvancementEvent {
player_name: ev.player.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
advancement_id: ev.advancement.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_entity_interact,
YogEntityInteractEvent,
EntityInteractEvent,
|ev| EntityInteractEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
entity_type: ev.entity_type.as_str().to_owned(),
entity_uuid: ev.entity_uuid.as_str().to_owned(),
hand: ev.hand.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(trampoline_craft, YogCraftEvent, CraftEvent, |ev| {
CraftEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
result_item: ev.result_item.as_str().to_owned(),
result_count: ev.result_count,
dimension: ev.dimension.as_str().to_owned(),
}
});
#[derive(Debug, Clone)]
pub struct MachineCraftEvent {
pub machine: String,
pub kind: String,
pub recipe: String,
pub result_item: String,
pub result_count: u32,
pub values: yog_data::Value,
pub pos: yog_core::BlockPos,
pub dimension: String,
pub player: String,
}
trampoline_phased!(trampoline_machine_craft, YogMachineCraftEvent, MachineCraftEvent, |ev| {
MachineCraftEvent {
machine: ev.machine.as_str().to_owned(),
kind: ev.kind.as_str().to_owned(),
recipe: ev.recipe.as_str().to_owned(),
result_item: ev.result_item.as_str().to_owned(),
result_count: ev.result_count,
values: yog_data::json::from_json_text(ev.values.as_str()).unwrap_or(yog_data::Value::Null),
pos: yog_core::BlockPos { x: ev.pos.x, y: ev.pos.y, z: ev.pos.z },
dimension: ev.dimension.as_str().to_owned(),
player: ev.player.as_str().to_owned(),
}
});
trampoline_phased!(
trampoline_explosion,
YogExplosionEvent,
ExplosionEvent,
|ev| ExplosionEvent {
dimension: ev.dimension.as_str().to_owned(),
x: ev.x,
y: ev.y,
z: ev.z,
power: ev.power,
cause_uuid: ev.cause_uuid.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_item_insert,
YogItemInsertEvent,
ItemInsertEvent,
|ev| ItemInsertEvent {
block_id: ev.block.as_str().to_owned(),
pos: yog_core::BlockPos {
x: ev.pos.x,
y: ev.pos.y,
z: ev.pos.z
},
dimension: ev.dimension.as_str().to_owned(),
item_id: ev.item_id.as_str().to_owned(),
count: ev.count,
nbt: ev.nbt.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_item_pickup,
YogItemPickupEvent,
ItemPickupEvent,
|ev| ItemPickupEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
item_id: ev.item_id.as_str().to_owned(),
item_count: ev.item_count,
entity_uuid: ev.entity_uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_player_move,
YogPlayerMoveEvent,
PlayerMoveEvent,
|ev| PlayerMoveEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
x: ev.x,
y: ev.y,
z: ev.z,
yaw: ev.yaw,
pitch: ev.pitch,
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_container_open,
YogContainerOpenEvent,
ContainerOpenEvent,
|ev| ContainerOpenEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
container_type: ev.container_type.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_container_close,
YogContainerCloseEvent,
ContainerCloseEvent,
|ev| ContainerCloseEvent {
player_name: ev.player.as_str().to_owned(),
player_uuid: ev.player_uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
}
);
trampoline_phased!(
trampoline_projectile_hit,
YogProjectileHitEvent,
ProjectileHitEvent,
|ev| ProjectileHitEvent {
projectile_type: ev.projectile_type.as_str().to_owned(),
projectile_uuid: ev.projectile_uuid.as_str().to_owned(),
shooter_uuid: ev.shooter_uuid.as_str().to_owned(),
hit_type: ev.hit_type.as_str().to_owned(),
hit_entity_uuid: ev.hit_entity_uuid.as_str().to_owned(),
x: ev.x,
y: ev.y,
z: ev.z,
dimension: ev.dimension.as_str().to_owned(),
}
);
unsafe extern "C" fn trampoline_server_fn<F>(ud: *mut c_void, srv: *const YogServer)
where
F: Fn(&dyn Server) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
guard((), || f(&CServer(srv)));
}
}
unsafe extern "C" fn trampoline_packet<F>(
ud: *mut c_void,
srv: *const YogServer,
ev: *const YogPacketEvent,
) where
F: Fn(&PacketEvent, &dyn Server) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let ev = &*ev;
let rust_ev = PacketEvent {
channel: ev.channel.as_str().to_owned(),
player: ev.player.as_str().to_owned(),
payload: if ev.payload.is_null() || ev.payload_len == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(ev.payload, ev.payload_len as usize).to_vec()
},
};
guard((), || f(&rust_ev, &CServer(srv)));
}
}
unsafe extern "C" fn trampoline_command<F>(
ud: *mut c_void,
srv: *const YogServer,
ev: *const YogCommandEvent,
reply_buf: *mut u8,
reply_cap: u32,
reply_len: *mut u32,
) where
F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let ev = &*ev;
let ctx = CommandContext {
name: ev.name.as_str().to_owned(),
args: ev.args.as_str().to_owned(),
source: ev.source.as_str().to_owned(),
uuid: ev.uuid.as_str().to_owned(),
dimension: ev.dimension.as_str().to_owned(),
};
*reply_len = 0;
if let Some(reply) = guard(None, || f(&ctx, &CServer(srv))) {
let bytes = reply.as_bytes();
let mut n = bytes.len().min(reply_cap as usize);
while !reply.is_char_boundary(n) {
n -= 1;
}
std::ptr::copy_nonoverlapping(bytes.as_ptr(), reply_buf, n);
*reply_len = n as u32;
}
}
}
unsafe extern "C" fn trampoline_scheduled<F>(ud: *mut c_void, srv: *const YogServer)
where
F: Fn(&dyn Server) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
guard((), || f(&CServer(srv)));
}
}
unsafe extern "C" fn trampoline_client_tick<F>(ud: *mut c_void)
where
F: Fn(&ClientTickEvent) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
guard((), || f(&ClientTickEvent {}));
}
}
unsafe extern "C" fn trampoline_hud_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
where
F: Fn(&GfxContext) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
guard((), || f(&GfxContext::from_raw(gfx)));
}
}
struct TooltipPicture<S, D> {
size: S,
draw: D,
}
unsafe extern "C" fn trampoline_tooltip_size<S, D>(ud: *mut c_void, item: YogStr, snbt: YogStr, w: *mut f32, h: *mut f32) -> bool
where
S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync,
{
unsafe {
let t = &*(ud as *const TooltipPicture<S, D>);
match guard(None, || (t.size)(item.as_str(), snbt.as_str())) {
Some((pw, ph)) => {
(*w, *h) = (pw, ph);
true
}
None => false,
}
}
}
unsafe extern "C" fn trampoline_item_text<F>(ud: *mut c_void, item: YogStr, snbt: YogStr, sink: *mut c_void, write: yog_abi::YogTextSink) -> bool
where
F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let Some((name, lines)) = guard(None, || f(item.as_str(), snbt.as_str())) else { return false };
let mut text = name.unwrap_or_default();
for l in lines {
text.push('\n');
text.push_str(&l.replace('\n', " "));
}
write(sink, YogStr::from_str(&text));
true
}
}
unsafe extern "C" fn trampoline_entity_tick<F>(ud: *mut c_void, srv: *const yog_abi::YogServer, uuid: YogStr)
where
F: Fn(&dyn Server, &str) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let s = CServer(srv);
guard((), || f(&s, uuid.as_str()));
}
}
unsafe extern "C" fn trampoline_body<F>(ud: *mut c_void, frame: *const u8, len: usize, sink: *mut c_void, write: yog_abi::YogBytesSink)
where
F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let Some(frame) = yog_body::Frame::decode(std::slice::from_raw_parts(frame, len)) else { return };
let mut ops = yog_body::Ops::default();
guard((), || f(&frame, &mut ops));
if !ops.is_empty() {
write(sink, ops.0.as_ptr(), ops.0.len());
}
}
}
unsafe extern "C" fn trampoline_camera<F>(ud: *mut c_void, frame: *const yog_abi::YogCameraFrame, out: *mut yog_abi::YogCameraOut)
where
F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let c = &*frame;
let frame = yog_body::CameraFrame {
uuid: c.uuid.as_str().to_owned(),
partial: c.partial,
pos: c.pos,
yaw: c.yaw,
pitch: c.pitch,
detached: c.detached,
flags: c.flags,
};
let o = &mut *out;
let mut cam = yog_body::CameraOut {
local_offset: yog_body::Vec3::from_array(o.local_offset),
world_offset: yog_body::Vec3::from_array(o.world_offset),
yaw: o.yaw,
pitch: o.pitch,
roll: o.roll,
fov: o.fov,
};
guard((), || f(&frame, &mut cam));
*o = yog_abi::YogCameraOut {
local_offset: cam.local_offset.to_array(),
world_offset: cam.world_offset.to_array(),
yaw: cam.yaw,
pitch: cam.pitch,
roll: cam.roll,
fov: cam.fov,
};
}
}
unsafe extern "C" fn trampoline_travel<F>(ud: *mut c_void, frame: *const yog_abi::YogTravelFrame, velocity: *mut [f32; 3]) -> bool
where
F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let t = &*frame;
let frame = yog_body::TravelFrame {
uuid: t.uuid.as_str().to_owned(),
velocity: yog_body::Vec3::from_array(t.velocity),
look: yog_body::Vec3::from_array(t.look),
yaw: t.yaw,
pitch: t.pitch,
forward: t.forward,
strafe: t.strafe,
jumping: t.jumping,
sneaking: t.sneaking,
gravity: t.gravity,
flags: t.flags,
};
match guard(None, || f(&frame)) {
Some(v) => {
*velocity = v.to_array();
true
}
None => false,
}
}
}
#[allow(clippy::too_many_arguments)]
unsafe 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)
where
D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync,
{
unsafe {
let t = &*(ud as *const TooltipPicture<S, D>);
guard((), || (t.draw)(&GfxContext::from_raw(gfx), item.as_str(), snbt.as_str(), [x, y, w, h]));
}
}
#[allow(clippy::too_many_arguments)]
unsafe 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)
where
F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let bytes = if data.is_null() { &[][..] } else { std::slice::from_raw_parts(data, len) };
let value = yog_data::binary::decode(bytes).unwrap_or(yog_data::Value::Null);
guard((), || f(&GfxContext::from_raw(gfx), &value, [x, y, w, h]));
}
}
unsafe extern "C" fn trampoline_world_render<F>(ud: *mut c_void, gfx: *const YogGfxApi)
where
F: Fn(&GfxContext) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
guard((), || f(&GfxContext::from_raw(gfx)));
}
}
unsafe extern "C" fn trampoline_key_press<F>(ud: *mut c_void, ev: *const YogKeyPressEvent) -> bool
where
F: Fn(&KeyPressEvent) -> bool + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let e = &*ev;
let ev = KeyPressEvent { key_code: e.key_code, scan_code: e.scan_code, action: e.action, modifiers: e.modifiers };
guard(false, || f(&ev))
}
}
unsafe extern "C" fn trampoline_screen<F>(ud: *mut c_void, screen_class: YogStr) -> bool
where
F: Fn(&ScreenEvent) + Send + Sync,
{
unsafe {
let f = &*(ud as *const F);
let ev = ScreenEvent { screen_class: screen_class.as_str().to_owned() };
guard((), || f(&ev));
true
}
}
static GLOBAL_API: std::sync::atomic::AtomicPtr<YogApi> =
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
pub(crate) fn global_api() -> *mut YogApi {
GLOBAL_API.load(std::sync::atomic::Ordering::Acquire)
}
#[derive(Debug, Clone)]
pub struct ModInfo {
pub source: String,
pub id: String,
pub name: String,
pub version: String,
pub authors: String,
pub description: String,
}
pub fn installed_mods() -> Vec<ModInfo> {
let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
if api.is_null() {
return Vec::new();
}
let owned = unsafe { ((*api).mods_list)((*api).ctx) };
let Some(text) = take_owned(unsafe { (*api).free_str }, owned) else {
return Vec::new();
};
text.lines()
.filter_map(|line| {
let mut f = line.split('\t');
Some(ModInfo {
source: f.next()?.to_string(),
id: f.next()?.to_string(),
name: f.next().unwrap_or_default().to_string(),
version: f.next().unwrap_or_default().to_string(),
authors: f.next().unwrap_or_default().to_string(),
description: f.next().unwrap_or_default().to_string(),
})
})
.collect()
}
pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
if api.is_null() {
return;
}
unsafe { ((*api).ui_open)((*api).ctx, YogStr::from_str(ui_id), modal, pause) }
}
pub fn close_ui(ui_id: &str) {
let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
if api.is_null() {
return;
}
unsafe { ((*api).ui_close)((*api).ctx, YogStr::from_str(ui_id)) }
}
pub trait UiHandled {
fn handled(self) -> bool;
}
impl UiHandled for () {
fn handled(self) -> bool {
false
}
}
impl UiHandled for bool {
fn handled(self) -> bool {
self
}
}
pub fn server() -> Option<CServer> {
let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
if api.is_null() {
return None;
}
let srv = unsafe { (*api).server };
if srv.is_null() {
return None;
}
Some(CServer(srv))
}
pub struct Registry {
api: *const YogApi,
}
unsafe impl Send for Registry {}
unsafe impl Sync for Registry {}
impl Registry {
pub unsafe fn from_raw(api: *const YogApi) -> Self {
GLOBAL_API.store(api as *mut YogApi, std::sync::atomic::Ordering::Release);
Self { api }
}
#[inline]
fn ctx(&self) -> *mut c_void {
unsafe { (*self.api).ctx }
}
fn leak<F: 'static>(f: F) -> *mut c_void {
Box::into_raw(Box::new(f)) as *mut c_void
}
pub fn on_block_break<F>(&mut self, handler: F)
where
F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_block_break)(self.ctx(), ud, trampoline_block_break::<F>) }
}
pub fn on_chat<F>(&mut self, handler: F)
where
F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_chat)(self.ctx(), ud, trampoline_chat::<F>) }
}
pub fn on_player_join<F>(&mut self, handler: F)
where
F: Fn(&PlayerJoinEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_join)(self.ctx(), ud, trampoline_player_join::<F>) }
}
pub fn on_player_leave<F>(&mut self, handler: F)
where
F: Fn(&PlayerLeaveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_leave)(self.ctx(), ud, trampoline_player_leave::<F>) }
}
pub fn on_use_item<F>(&mut self, handler: F)
where
F: Fn(&UseItemEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_use_item)(self.ctx(), ud, trampoline_use_item::<F>) }
}
pub fn on_use_block<F>(&mut self, handler: F)
where
F: Fn(&UseBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_use_block)(self.ctx(), ud, trampoline_use_block::<F>) }
}
pub fn on_attack_entity<F>(&mut self, handler: F)
where
F: Fn(&AttackEntityEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_attack_entity)(self.ctx(), ud, trampoline_attack_entity::<F>) }
}
pub fn on_entity_damage<F>(&mut self, handler: F)
where
F: Fn(&EntityDamageEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_entity_damage)(self.ctx(), ud, trampoline_entity_damage::<F>) }
}
pub fn on_entity_death<F>(&mut self, handler: F)
where
F: Fn(&EntityDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_entity_death)(self.ctx(), ud, trampoline_entity_death::<F>) }
}
pub fn on_entity_spawn<F>(&mut self, handler: F)
where
F: Fn(&EntitySpawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_entity_spawn)(self.ctx(), ud, trampoline_entity_spawn::<F>) }
}
pub fn on_player_place_block<F>(&mut self, handler: F)
where
F: Fn(&PlaceBlockEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_place_block)(self.ctx(), ud, trampoline_place_block::<F>) }
}
pub fn on_player_death<F>(&mut self, handler: F)
where
F: Fn(&PlayerDeathEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_death)(self.ctx(), ud, trampoline_player_death::<F>) }
}
pub fn on_player_respawn<F>(&mut self, handler: F)
where
F: Fn(&PlayerRespawnEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_respawn)(self.ctx(), ud, trampoline_player_respawn::<F>) }
}
pub fn on_advancement<F>(&mut self, handler: F)
where
F: Fn(&AdvancementEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_advancement)(self.ctx(), ud, trampoline_advancement::<F>) }
}
pub fn on_entity_interact<F>(&mut self, handler: F)
where
F: Fn(&EntityInteractEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_entity_interact)(self.ctx(), ud, trampoline_entity_interact::<F>) }
}
pub fn on_item_craft<F>(&mut self, handler: F)
where
F: Fn(&CraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_item_craft)(self.ctx(), ud, trampoline_craft::<F>) }
}
pub fn on_machine_craft<F>(&mut self, handler: F)
where
F: Fn(&MachineCraftEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_machine_craft)(self.ctx(), ud, trampoline_machine_craft::<F>) }
}
pub fn on_explosion<F>(&mut self, handler: F)
where
F: Fn(&ExplosionEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_explosion)(self.ctx(), ud, trampoline_explosion::<F>) }
}
pub fn on_item_insert<F>(&mut self, handler: F)
where
F: Fn(&ItemInsertEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_item_insert)(self.ctx(), ud, trampoline_item_insert::<F>) }
}
pub fn on_item_pickup<F>(&mut self, handler: F)
where
F: Fn(&ItemPickupEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_item_pickup)(self.ctx(), ud, trampoline_item_pickup::<F>) }
}
pub fn on_player_move<F>(&mut self, handler: F)
where
F: Fn(&PlayerMoveEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_player_move)(self.ctx(), ud, trampoline_player_move::<F>) }
}
pub fn on_container_open<F>(&mut self, handler: F)
where
F: Fn(&ContainerOpenEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_container_open)(self.ctx(), ud, trampoline_container_open::<F>) }
}
pub fn on_container_close<F>(&mut self, handler: F)
where
F: Fn(&ContainerCloseEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_container_close)(self.ctx(), ud, trampoline_container_close::<F>) }
}
pub fn on_projectile_hit<F>(&mut self, handler: F)
where
F: Fn(&ProjectileHitEvent, EventPhase, &dyn Server) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_projectile_hit)(self.ctx(), ud, trampoline_projectile_hit::<F>) }
}
pub fn on_tick<F>(&mut self, listener: F)
where
F: Fn(&dyn Server) + Send + Sync + 'static,
{
let ud = Self::leak(listener);
unsafe { ((*self.api).on_server_tick)(self.ctx(), ud, trampoline_server_fn::<F>) }
}
pub fn on_server_started<F>(&mut self, listener: F)
where
F: Fn(&dyn Server) + Send + Sync + 'static,
{
let ud = Self::leak(listener);
unsafe { ((*self.api).on_server_started)(self.ctx(), ud, trampoline_server_fn::<F>) }
}
pub fn on_server_stopping<F>(&mut self, listener: F)
where
F: Fn(&dyn Server) + Send + Sync + 'static,
{
let ud = Self::leak(listener);
unsafe { ((*self.api).on_server_stopping)(self.ctx(), ud, trampoline_server_fn::<F>) }
}
pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
where
F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
{
let name_ys = YogStr::from_str(name.as_ref());
let ud = Self::leak(handler);
unsafe { ((*self.api).register_command)(self.ctx(), name_ys, ud, trampoline_command::<F>) }
}
pub fn on_typed_command<F>(
&mut self,
name: impl AsRef<str>,
schema: impl AsRef<str>,
handler: F,
) where
F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync + 'static,
{
let name_ys = YogStr::from_str(name.as_ref());
let schema_ys = YogStr::from_str(schema.as_ref());
let ud = Self::leak(handler);
unsafe {
((*self.api).register_typed_command)(
self.ctx(),
name_ys,
schema_ys,
ud,
trampoline_command::<F>,
);
}
}
pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
where
F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
{
let ch = YogStr::from_str(channel.as_ref());
let ud = Self::leak(handler);
unsafe { ((*self.api).on_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
}
pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
where
F: Fn(&PacketEvent, &dyn Server) + Send + Sync + 'static,
{
let ch = YogStr::from_str(channel.as_ref());
let ud = Self::leak(handler);
unsafe { ((*self.api).on_client_packet)(self.ctx(), ch, ud, trampoline_packet::<F>) }
}
pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
where
P: Packet + Send + Sync + 'static,
F: Fn(&P, &dyn Server) + Send + Sync + 'static,
{
self.on_packet(channel, move |ev, srv| {
if let Some(pkt) = P::decode(&ev.payload) {
handler(&pkt, srv);
}
});
}
fn content(&mut self, kind: &str, id: &str, def: &yog_data::Value) {
let blob = yog_data::binary::encode(def);
unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str(kind), YogStr::from_str(id), blob.as_ptr(), blob.len()) }
}
fn json(text: &str) -> yog_data::Value {
yog_data::json::from_json_text(text).unwrap_or_default()
}
pub fn add_recipe(&mut self, recipe: Recipe) {
let (ns, name) = recipe.ns_name();
let id = format!("{ns}:{name}");
self.content("recipe", &id, &recipe.to_data());
}
pub fn add_recipe_json(&mut self, id: &str, json: &str) {
let id = if id.contains(':') { id.to_string() } else { format!("minecraft:{id}") };
self.content("recipe", &id, &Self::json(json));
}
pub fn register_item(&mut self, def: ItemDef) {
use yog_data::Schema;
self.content("item", &def.id.clone(), &def.write());
}
pub fn register_block(&mut self, def: BlockDef) {
use yog_data::Schema;
self.content("block", &def.id.clone(), &def.write());
}
pub fn register_entity(&mut self, def: yog_registry::EntityDef) {
use yog_data::Schema;
if crate::entities::drawn(&def) {
self.on_body(crate::entities::draw);
}
self.content("entity", &def.id.clone(), &def.write());
}
pub fn register_entity_data(&mut self, id: &str, data: &[u8]) {
match yog_data::read_bytes::<yog_registry::EntityDef>(data) {
Ok(def) => self.register_entity(def),
Err(e) => crate::error!("entity {id}: {e:?}"),
}
}
pub fn on_entity_tick<F>(&mut self, entity_type: &str, handler: F)
where
F: Fn(&dyn Server, &str) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_entity_tick)(self.ctx(), YogStr::from_str(entity_type), ud, trampoline_entity_tick::<F>) }
}
pub fn add_biome_feature(&mut self, feature: &str, biomes: &str, step: &str) {
use yog_data::Schema;
let f = yog_registry::BiomeFeature { feature: feature.into(), biomes: biomes.into(), step: step.into() };
self.content("biome_feature", feature, &f.write());
}
pub fn register_tool_material(&mut self, material: ToolMaterial) {
use yog_data::Schema;
self.content("tool_material", &material.id.clone(), &material.write());
}
pub fn register_armor_material(&mut self, material: ArmorMaterial) {
use yog_data::Schema;
self.content("armor_material", &material.id.clone(), &material.write());
}
pub fn register_sound_group(&mut self, group: SoundGroup) {
use yog_data::Schema;
self.content("sound_group", &group.id.clone(), &group.write());
}
pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
use yog_data::Schema;
self.content("inventory", &def.id.clone(), &def.write());
}
pub fn register_startup_grant(&mut self, grant: StartupGrant) {
use yog_data::Schema;
self.content("startup_grant", &grant.id.clone(), &grant.write());
}
pub fn register_book(&mut self, book: &Book) {
use yog_data::Schema;
self.content("book", &book.id, &book.write());
}
pub fn register_book_data(&mut self, id: &str, data: &[u8]) {
unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("book"), YogStr::from_str(id), data.as_ptr(), data.len()) }
}
pub fn register_dimension(&mut self, def: &YogDimensionDef) {
let value = Self::json(&def.to_json());
self.content("dimension", &def.id.clone(), &value);
}
pub fn register_dimension_data(&mut self, id: &str, data: &[u8]) {
unsafe { ((*self.api).register_content)(self.ctx(), YogStr::from_str("dimension"), YogStr::from_str(id), data.as_ptr(), data.len()) }
}
pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
where
F: Fn(&ChunkWriter) + Send + Sync + 'static,
{
let ud = Self::leak(generator);
let id = YogStr::from_str(generator_type_id);
unsafe {
((*self.api).register_chunk_generator)(
self.ctx(),
id,
ud,
trampoline_chunk_generator::<F>,
);
}
}
pub fn installed_mods(&self) -> Vec<ModInfo> {
installed_mods()
}
pub fn interop(&self) -> crate::Interop {
crate::Interop::new(self.api)
}
pub fn raw_api(&self) -> *const yog_abi::YogApi {
self.api
}
pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
let l = YogStr::from_str(label);
let u = YogStr::from_str(ui_id);
unsafe { ((*self.api).register_menu_entry)(self.ctx(), l, u) }
}
pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
where
F: Fn(&str, &str) -> R + Send + Sync + 'static,
R: UiHandled,
{
let ud = Self::leak(handler);
let id = YogStr::from_str(ui_id);
let empty = YogStr::from_str("{}");
unsafe { ((*self.api).register_ui)(self.ctx(), id, empty, ud, trampoline_ui_event::<F, R>) }
}
pub fn schedule_once<F>(&self, delay_ticks: u64, handler: F)
where
F: Fn(&dyn Server) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe {
((*self.api).schedule_once)(self.ctx(), delay_ticks, ud, trampoline_scheduled::<F>);
}
}
pub fn schedule_repeating<F>(&self, period_ticks: u64, handler: F)
where
F: Fn(&dyn Server) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe {
((*self.api).schedule_repeating)(
self.ctx(),
period_ticks,
ud,
trampoline_scheduled::<F>,
);
}
}
pub fn on_client_tick<F>(&mut self, handler: F)
where
F: Fn(&ClientTickEvent) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_client_tick)(self.ctx(), ud, trampoline_client_tick::<F>) }
}
pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
let id = YogStr::from_str(ui_id);
unsafe { ((*self.api).on_ui_render)(self.ctx(), id, ud, trampoline_hud_render::<F>) }
}
pub fn on_book_page<F>(&mut self, drawer: &str, handler: F)
where
F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
let id = YogStr::from_str(drawer);
unsafe { ((*self.api).on_book_page)(self.ctx(), id, ud, trampoline_book_page::<F>) }
}
pub fn on_item_tooltip<S, D>(&mut self, item: &str, size: S, draw: D)
where
S: Fn(&str, &str) -> Option<(f32, f32)> + Send + Sync + 'static,
D: Fn(&GfxContext, &str, &str, [f32; 4]) + Send + Sync + 'static,
{
let ud = Self::leak(TooltipPicture { size, draw });
let id = YogStr::from_str(item);
unsafe {
((*self.api).on_item_tooltip)(self.ctx(), id, ud, trampoline_tooltip_size::<S, D>, trampoline_tooltip_draw::<S, D>)
}
}
pub fn on_item_text<F>(&mut self, item: &str, text: F)
where
F: Fn(&str, &str) -> Option<(Option<String>, Vec<String>)> + Send + Sync + 'static,
{
let ud = Self::leak(text);
let id = YogStr::from_str(item);
unsafe { ((*self.api).on_item_text)(self.ctx(), id, ud, trampoline_item_text::<F>) }
}
pub fn register_body_model(&mut self, id: &str, data: &[u8]) {
if let Err(e) = yog_body::register_model(id, data) {
crate::error!("{e}");
}
}
pub fn register_body_anims(&mut self, id: &str, data: &[u8]) {
if let Err(e) = yog_body::register_anims(id, data) {
crate::error!("{e}");
}
}
pub fn on_body<F>(&mut self, handler: F)
where
F: Fn(&yog_body::Frame, &mut yog_body::Ops) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_body)(self.ctx(), ud, trampoline_body::<F>) }
}
pub fn on_camera<F>(&mut self, handler: F)
where
F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_camera)(self.ctx(), ud, trampoline_camera::<F>) }
}
pub fn on_travel<F>(&mut self, handler: F)
where
F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_travel)(self.ctx(), ud, trampoline_travel::<F>) }
}
pub fn on_hud_render<F>(&mut self, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_hud_render)(self.ctx(), ud, trampoline_hud_render::<F>) }
}
pub fn on_world_render<F>(&mut self, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_world_render)(self.ctx(), ud, trampoline_world_render::<F>) }
}
pub fn on_key_press<F>(&mut self, handler: F)
where
F: Fn(&KeyPressEvent) -> bool + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_key_press)(self.ctx(), ud, trampoline_key_press::<F>) }
}
pub fn on_screen_open<F>(&mut self, handler: F)
where
F: Fn(&ScreenEvent) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_screen_open)(self.ctx(), ud, trampoline_screen::<F>) }
}
pub fn on_screen_close<F>(&mut self, handler: F)
where
F: Fn(&ScreenEvent) + Send + Sync + 'static,
{
let ud = Self::leak(handler);
unsafe { ((*self.api).on_screen_close)(self.ctx(), ud, trampoline_screen::<F>) }
}
}
unsafe extern "C" fn trampoline_ui_event<F, R>(
ud: *mut c_void,
ui_id: yog_abi::YogStr,
event_id: yog_abi::YogStr,
) where
F: Fn(&str, &str) -> R + Send + Sync + 'static,
R: UiHandled,
{
let f = unsafe { &*(ud as *const F) };
let ui = unsafe { ui_id.as_str() };
let ev = unsafe { event_id.as_str() };
if guard(false, || f(ui, ev).handled()) {
let api = GLOBAL_API.load(std::sync::atomic::Ordering::Acquire);
if !api.is_null() {
unsafe { ((*api).ui_consume_event)((*api).ctx) }
}
}
}
unsafe extern "C" fn trampoline_chunk_generator<F>(
ud: *mut c_void,
writer: *const yog_abi::YogChunkWriterApi,
) where
F: Fn(&ChunkWriter) + Send + Sync + 'static,
{
let f = unsafe { &*(ud as *const F) };
let writer = unsafe { ChunkWriter::from_raw(writer) };
guard((), || f(&writer));
}
pub trait Mod {
fn register(registry: &mut Registry);
}
#[cfg(test)]
mod trampoline_tests {
use super::*;
use yog_abi::{YogBlockPos, YogChatEvent, YogCommandEvent, YogPacketEvent};
fn ud<F>(f: &F) -> *mut c_void {
f as *const F as *mut c_void
}
fn chat<F: Fn(&ChatEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogChatEvent, phase: u8) -> bool {
unsafe { trampoline_chat::<F>(ud(f), std::ptr::null(), ev, phase) }
}
fn block_break<F: Fn(&BlockBreakEvent, EventPhase, &dyn Server) -> bool + Send + Sync>(f: &F, ev: &YogBlockBreakEvent) -> bool {
unsafe { trampoline_block_break::<F>(ud(f), std::ptr::null(), ev, 0) }
}
fn command<F: Fn(&CommandContext, &dyn Server) -> Option<String> + Send + Sync>(f: &F, ev: &YogCommandEvent, buf: &mut [u8]) -> u32 {
let mut len = 99u32;
unsafe { trampoline_command::<F>(ud(f), std::ptr::null(), ev, buf.as_mut_ptr(), buf.len() as u32, &mut len) };
len
}
fn packet<F: Fn(&PacketEvent, &dyn Server) + Send + Sync>(f: &F, ev: &YogPacketEvent) {
unsafe { trampoline_packet::<F>(ud(f), std::ptr::null(), ev) }
}
#[test]
fn events_arrive_converted_with_their_phase() {
let seen = std::sync::Mutex::new(Vec::new());
let handler = |ev: &ChatEvent, p: EventPhase, _: &dyn Server| {
seen.lock().unwrap().push((ev.player_name.clone(), ev.message.clone(), p));
ev.message != "cancel me"
};
let ev = |msg: &'static str| YogChatEvent {
player: YogStr::from_str("Dev"),
message: YogStr::from_str(msg),
dimension: YogStr::from_str("minecraft:overworld"),
};
assert!(chat(&handler, &ev("hi"), 0));
assert!(!chat(&handler, &ev("cancel me"), 0));
chat(&handler, &ev("after"), 1);
let seen = seen.into_inner().unwrap();
assert_eq!(seen[0], ("Dev".into(), "hi".into(), EventPhase::Pre));
assert_eq!(seen[2].2, EventPhase::Post);
}
#[test]
fn a_panicking_handler_allows_the_action() {
let handler = |_: &BlockBreakEvent, _: EventPhase, _: &dyn Server| -> bool { panic!("mod bug") };
let ev = YogBlockBreakEvent {
player: YogStr::from_str("Dev"),
block: YogStr::from_str("minecraft:stone"),
pos: YogBlockPos { x: 1, y: 2, z: 3 },
dimension: YogStr::from_str("minecraft:overworld"),
};
assert!(block_break(&handler, &ev));
}
#[test]
fn command_replies_fit_the_buffer_whole_characters_only() {
let handler = |c: &CommandContext, _: &dyn Server| Some(format!("{}: привет", c.args));
let ev = YogCommandEvent {
name: YogStr::from_str("hex"),
args: YogStr::from_str("x"),
source: YogStr::from_str("Dev"),
uuid: YogStr::EMPTY,
dimension: YogStr::EMPTY,
};
let mut buf = [0u8; 6];
let len = command(&handler, &ev, &mut buf);
assert_eq!(std::str::from_utf8(&buf[..len as usize]), Ok("x: п"));
}
#[test]
fn empty_packets_need_no_payload_pointer() {
let got = std::sync::Mutex::new(None);
let handler = |p: &PacketEvent, _: &dyn Server| *got.lock().unwrap() = Some(p.payload.len());
let ev = YogPacketEvent {
channel: YogStr::from_str("mymod:ping"),
player: YogStr::from_str("Dev"),
payload: std::ptr::null(),
payload_len: 0,
};
packet(&handler, &ev);
assert_eq!(*got.lock().unwrap(), Some(0));
}
}