use yog_abi::YogApi;
use yog_command::CommandContext;
use yog_dimensions::{GeneratedChunk, YogDimensionDef};
use yog_event::client::net::Packet as ClientPacketEvent;
use yog_event::server::net::Packet as PacketEvent;
use yog_formats::book::Book;
use yog_gfx::GfxContext;
use yog_network::Packet;
use yog_registry::{
ArmorMaterial, BlockDef, ItemDef, Recipe, SoundGroup, StartupGrant, ToolMaterial,
};
pub(crate) 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
})
}
static GLOBAL_API: yog_sync::atomic::AtomicPtr<YogApi> =
yog_sync::atomic::AtomicPtr::new(std::ptr::null_mut());
pub(crate) fn global_api() -> *mut YogApi {
GLOBAL_API.load(yog_sync::atomic::Ordering::Acquire)
}
pub use yog_event::game::ModInfo;
pub fn installed_mods() -> Vec<ModInfo> {
crate::call(&yog_event::game::Mods {}).unwrap_or_default()
}
pub fn open_ui(ui_id: &str, modal: bool, pause: bool) {
crate::call(&yog_event::ui::Open {
ui_id: ui_id.to_owned(),
modal,
pause,
});
}
pub fn close_ui(ui_id: &str) {
crate::call(&yog_event::ui::Close {
ui_id: ui_id.to_owned(),
});
}
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 struct Registry {
api: *const YogApi,
}
unsafe impl Send for Registry {}
unsafe impl Sync for Registry {}
fn log_to_host(level: yog_logging::Level, message: &str) {
crate::emit(&yog_event::log::Record {
level: level as u32,
message: message.to_owned(),
});
}
impl Registry {
pub unsafe fn from_raw(api: *const YogApi) -> Self {
GLOBAL_API.store(api as *mut YogApi, yog_sync::atomic::Ordering::Release);
yog_logging::set_sink(log_to_host);
yog_event::set_caller(crate::service::call_at);
if !yog_data::atom::install(crate::service::ATOMS) {
yog_logging::warn!(
"atoms were made before the mod was registered: their numbers are this mod's own"
);
}
Self { api }
}
pub(crate) fn api(&self) -> &YogApi {
unsafe { &*self.api }
}
fn register_blob(&mut self, kind: &str, id: &str, data: &[u8]) {
let register = yog_event::content::Register {
kind: kind.to_owned(),
id: id.to_owned(),
data: data.to_vec(),
};
if self.call(®ister) != Some(true) {
crate::error!("{kind} {id}: the runtime didn't take it");
}
}
pub fn on_command<F>(&mut self, name: impl AsRef<str>, handler: F)
where
F: Fn(&CommandContext) -> Option<String> + Send + Sync + 'static,
{
self.command(name.as_ref(), None, handler);
}
pub fn on_typed_command<F>(
&mut self,
name: impl AsRef<str>,
schema: impl AsRef<str>,
handler: F,
) where
F: Fn(&CommandContext) -> Option<String> + Send + Sync + 'static,
{
self.command(name.as_ref(), Some(schema.as_ref()), handler);
}
fn command<F>(&mut self, name: &str, schema: Option<&str>, handler: F)
where
F: Fn(&CommandContext) -> Option<String> + Send + Sync + 'static,
{
let declare = yog_event::command::CommandSpec {
name: name.to_owned(),
schema: schema.map(str::to_owned),
};
if self.call(&declare) != Some(true) {
crate::error!("command {name}: the host wasn't told of it");
}
let handler = yog_sync::Arc::new(handler);
for &fit in yog_command::Fit::ways(schema) {
let (handler, schema) = (yog_sync::Arc::clone(&handler), declare.schema.clone());
self.change_key_at::<yog_event::server::command::Run>(
name,
fit.rank() as i32,
move |run, _| {
if run.reply.is_some() {
return;
}
let Some(args) = fit.take(schema.as_deref(), &run.args) else {
return;
};
let ctx = CommandContext {
name: run.name.clone(),
args,
source: run.source.clone(),
uuid: run.uuid.clone(),
dimension: run.dimension.clone(),
};
run.reply = handler(&ctx);
},
);
}
}
pub fn on_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
where
F: Fn(&PacketEvent) + Send + Sync + 'static,
{
self.on_key::<PacketEvent, _>(channel.as_ref(), move |p, _| handler(p));
}
pub fn on_client_packet<F>(&mut self, channel: impl AsRef<str>, handler: F)
where
F: Fn(&ClientPacketEvent) + Send + Sync + 'static,
{
self.on_key::<ClientPacketEvent, _>(channel.as_ref(), move |p, _| handler(p));
}
pub fn on_typed_packet<P, F>(&mut self, channel: impl AsRef<str>, handler: F)
where
P: Packet + Send + Sync + 'static,
F: Fn(&P) + Send + Sync + 'static,
{
self.on_packet(channel, move |ev| {
if let Some(pkt) = P::from_payload(&ev.payload) {
handler(&pkt);
}
});
}
fn content(&mut self, kind: &str, id: &str, def: &yog_data::Value) {
self.register_blob(kind, id, &yog_data::binary::encode(def))
}
pub fn add_recipe(&mut self, recipe: Recipe) {
let (ns, name) = recipe.ns_name();
let id = format!("{ns}:{name}");
self.content(yog_ids::kind::RECIPE, &id, &recipe.to_data());
}
pub fn add_recipe_data(&mut self, id: &str, data: &[u8]) {
self.register_blob(yog_ids::kind::RECIPE, &yog_ids::qualify(id), data)
}
pub fn register_item(&mut self, def: ItemDef) {
use yog_data::Schema;
self.content(yog_ids::kind::ITEM, &def.id.clone(), &def.write());
}
pub fn register_block(&mut self, def: BlockDef) {
use yog_data::Schema;
self.content(yog_ids::kind::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(yog_ids::kind::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 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(yog_ids::kind::BIOME_FEATURE, feature, &f.write());
}
pub fn register_tool_material(&mut self, material: ToolMaterial) {
use yog_data::Schema;
self.content(
yog_ids::kind::TOOL_MATERIAL,
&material.id.clone(),
&material.write(),
);
}
pub fn register_armor_material(&mut self, material: ArmorMaterial) {
use yog_data::Schema;
self.content(
yog_ids::kind::ARMOR_MATERIAL,
&material.id.clone(),
&material.write(),
);
}
pub fn register_sound_group(&mut self, group: SoundGroup) {
use yog_data::Schema;
self.content(
yog_ids::kind::SOUND_GROUP,
&group.id.clone(),
&group.write(),
);
}
pub fn register_inventory(&mut self, def: yog_inventory::InventoryDef) {
use yog_data::Schema;
self.content(yog_ids::kind::INVENTORY, &def.id.clone(), &def.write());
}
pub fn register_startup_grant(&mut self, grant: StartupGrant) {
use yog_data::Schema;
self.content(
yog_ids::kind::STARTUP_GRANT,
&grant.id.clone(),
&grant.write(),
);
}
pub fn register_book(&mut self, book: &Book) {
use yog_data::Schema;
self.content(yog_ids::kind::BOOK, &book.id, &book.write());
}
pub fn register_book_data(&mut self, id: &str, data: &[u8]) {
self.register_blob(yog_ids::kind::BOOK, id, data)
}
pub fn register_font_data(&mut self, id: &str, data: &[u8]) {
self.register_blob(yog_ids::kind::FONT, id, data)
}
pub fn register_dimension(&mut self, def: &YogDimensionDef) {
self.register_blob(yog_ids::kind::DIMENSION, &def.id, &yog_data::to_bytes(def));
}
pub fn register_dimension_data(&mut self, id: &str, data: &[u8]) {
self.register_blob(yog_ids::kind::DIMENSION, id, data)
}
pub fn register_chunk_generator<F>(&mut self, generator_type_id: &str, generator: F)
where
F: Fn(&mut GeneratedChunk) + Send + Sync + 'static,
{
self.change_key_at::<GeneratedChunk>(
generator_type_id,
yog_abi::priority::DEFAULT,
move |chunk, _| generator(chunk),
);
}
pub fn installed_mods(&self) -> Vec<ModInfo> {
installed_mods()
}
pub fn raw_api(&self) -> *const yog_abi::YogApi {
self.api
}
pub fn register_menu_entry(&mut self, label: &str, ui_id: &str) {
if self.call(&yog_event::menu::MenuEntry {
label: label.to_owned(),
ui_id: ui_id.to_owned(),
}) != Some(true)
{
crate::error!("menu entry {ui_id}: the host wasn't told of it");
}
}
pub fn register_ui<F, R>(&mut self, ui_id: &str, handler: F)
where
F: Fn(&str, &str) -> R + Send + Sync + 'static,
R: UiHandled,
{
self.before_key::<yog_event::client::ui::Input, _>(ui_id, move |input, _| {
!handler(&input.ui_id, &input.event).handled()
});
}
pub fn on_ui_render<F>(&mut self, ui_id: &str, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
self.on_draw::<yog_event::client::render::Ui>(Some(ui_id), handler);
}
pub fn on_book_page<F>(&mut self, drawer: &str, handler: F)
where
F: Fn(&GfxContext, &yog_data::Value, [f32; 4]) + Send + Sync + 'static,
{
self.on_key::<yog_event::client::render::Page, _>(drawer, move |page, _| {
if let Some(gfx) = crate::draw::frame_gfx() {
handler(&gfx, &page.data, page.rect);
}
});
}
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,
{
self.provide_key::<yog_event::item::Picture>(item, move |p, _| {
size(&p.item, &p.snbt).map(|(w, h)| [w, h])
});
self.on_key::<yog_event::client::render::Tooltip, _>(item, move |t, _| {
if let Some(gfx) = crate::draw::frame_gfx() {
draw(&gfx, &t.item, &t.snbt, t.rect);
}
});
}
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,
{
self.provide_key::<yog_event::item::Text>(item, move |t, _| {
text(&t.item, &t.snbt).map(|(name, lines)| yog_event::item::Shown { name, lines })
});
}
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,
{
self.change::<yog_event::client::render::Body>(move |e, _| {
let mut ops = yog_body::Ops::default();
handler(&e.frame, &mut ops);
e.ops.add(ops);
});
}
pub fn add_loot_modifier(&mut self, modifier: crate::loot::LootModifier) {
self.change::<yog_event::world::loot::Roll>(move |loot, _| {
crate::loot::apply(&modifier, loot)
});
}
pub fn register_attribute(&mut self, def: yog_registry::AttributeDef) {
use yog_data::Schema;
self.content(yog_ids::kind::ATTRIBUTE, &def.id.clone(), &def.write());
}
pub fn register_effect(&mut self, def: yog_registry::EffectDef) {
use yog_data::Schema;
self.content(yog_ids::kind::EFFECT, &def.id.clone(), &def.write());
}
pub fn register_potion(&mut self, def: yog_registry::PotionDef) {
use yog_data::Schema;
self.content(yog_ids::kind::POTION, &def.id.clone(), &def.write());
}
pub fn register_definition_data(&mut self, kind: &str, id: &str, data: &[u8]) {
self.register_blob(kind, id, data)
}
pub fn add_loot_modifier_data(&mut self, id: &str, data: &[u8]) {
match yog_data::read_bytes::<crate::loot::LootModifier>(data) {
Ok(m) => self.add_loot_modifier(m),
Err(e) => crate::error!("loot modifier {id}: {e:?}"),
}
}
pub fn loot_function<F>(&mut self, name: &str, function: F)
where
F: Fn(&mut crate::loot::LootStack, &crate::loot::Loot) + Send + Sync + 'static,
{
let mut f = crate::loot::functions().write();
f.insert(name.to_string(), yog_sync::Arc::new(function));
}
pub fn on_camera<F>(&mut self, handler: F)
where
F: Fn(&yog_body::CameraFrame, &mut yog_body::CameraOut) + Send + Sync + 'static,
{
self.change::<yog_event::client::render::Camera>(move |e, _| handler(&e.frame, &mut e.out));
}
pub fn on_travel<F>(&mut self, handler: F)
where
F: Fn(&yog_body::TravelFrame) -> Option<yog_body::Vec3> + Send + Sync + 'static,
{
self.change::<yog_event::client::game::Travel>(move |e, _| {
if e.velocity.is_none() {
e.velocity = handler(&e.frame);
}
});
}
pub fn on_hud_render<F>(&mut self, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
self.on_draw::<yog_event::client::render::Hud>(None, handler);
}
pub fn on_world_render<F>(&mut self, handler: F)
where
F: Fn(&GfxContext) + Send + Sync + 'static,
{
self.on_draw::<yog_event::client::render::World>(None, handler);
}
pub fn key_binding<F>(&mut self, id: &str, name: &str, default_key: Option<i32>, handler: F)
where
F: Fn() + Send + Sync + 'static,
{
let declare = yog_event::key_binding::KeyBindingSpec {
id: id.to_owned(),
name: name.to_owned(),
default_key: default_key.unwrap_or(crate::client::keys::UNBOUND),
};
if self.call(&declare) != Some(true) {
crate::error!("key binding {id}: the host wasn't told of it");
}
self.on_key::<yog_event::client::input::Binding, _>(id, move |_, _| handler());
}
}
pub trait Mod {
fn register(registry: &mut Registry);
}