Pumpkin/crates/pumpkin-plugin-api/src/lib.rs
2026-08-06 12:56:40 +02:00

293 lines
9.5 KiB
Rust

//! Pumpkin plugin API.
#![warn(missing_docs)]
//!
//! This crate provides everything needed to write a Pumpkin server plugin compiled
//! to WebAssembly. A plugin consists of a type that implements [`Plugin`], registered
//! with the [`register_plugin!`] macro.
//!
//! # Quick start
//!
//! ```rust,ignore
//! use pumpkin_plugin_api::{Plugin, PluginMetadata, Context, register_plugin, permissions::permissions};
//!
//! struct MyPlugin;
//!
//! impl Plugin for MyPlugin {
//! fn new() -> Self { MyPlugin }
//! fn metadata(&self) -> PluginMetadata {
//! PluginMetadata {
//! name: "my-plugin".into(),
//! version: "0.1.0".into(),
//! authors: vec!["you".into()],
//! description: "An example plugin.".into(),
//! dependencies: vec![],
//! permissions: vec![permissions::NETWORK_DNS.into()],
//! }
//! }
//! }
//!
//! register_plugin!(MyPlugin);
//! ```
use crate::{
commands::COMMAND_HANDLERS, events::EVENT_HANDLERS, logging::WitSubscriber,
scheduler::TASK_HANDLERS, text::TextComponent,
};
/// Plugin command registration and handling utilities.
pub mod commands;
/// Event system and event handlers.
pub mod events;
mod ext;
/// Bedrock UI form builders.
pub mod forms;
/// Constants for plugin permissions.
///
/// Use these in your `PluginMetadata` to request access to specific host features.
pub mod permissions;
/// Scheduler utilities.
pub mod scheduler;
/// Command WIT API re-exports.
pub mod command {
pub use crate::wit::pumpkin::plugin::command::{
Arg, ArgumentType, Command, CommandError, CommandNode, CommandSender, ConsumedArgs,
StringType,
};
}
pub use wit::pumpkin::plugin::{
bedrock_packets, block_entity, boss_bar, command as command_wit, common,
context::{Context, Server},
data_components, entity,
entity_types::EntityType,
event::{self as events_wit, EventType},
gui, i18n, item_stack, java_dialogs, java_packets, particles, permission, player, scoreboard,
server, text, uuid, world,
};
// Convenience re-exports of commonly-used plugin types so plugin authors can
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
pub use events::{EventHandler, FromIntoEvent};
pub use wit::pumpkin::plugin::item_stack::ItemStack;
/// Java dialog WIT API re-exports.
pub mod java_dialog {
pub use crate::wit::pumpkin::plugin::java_dialogs::{ActionButton, DialogBody, DialogType};
}
/// WIT-based logging subscriber.
pub mod logging;
#[allow(clippy::too_many_arguments, missing_docs)]
mod wit {
wit_bindgen::generate!({
skip: ["init-plugin"],
path: "../pumpkin-plugin-wit/v0.1",
world: "plugin",
enable_method_chaining: true
});
use super::Component;
export!(Component);
}
struct Component;
/// Metadata that describes a plugin to the server.
pub struct PluginMetadata {
/// The human-readable name of the plugin.
pub name: String,
/// The plugin's version string (e.g. `"1.0.0"`).
pub version: String,
/// The list of plugin authors.
pub authors: Vec<String>,
/// A short description of what the plugin does.
pub description: String,
/// The list of plugin dependencies.
pub dependencies: Vec<String>,
/// The list of permissions requested by the plugin.
pub permissions: Vec<String>,
}
impl wit::exports::pumpkin::plugin::metadata::Guest for Component {
/// Returns the plugin metadata to the host.
fn get_metadata() -> wit::exports::pumpkin::plugin::metadata::PluginMetadata {
let metadata = plugin().metadata();
wit::exports::pumpkin::plugin::metadata::PluginMetadata {
name: metadata.name,
version: metadata.version,
authors: metadata.authors,
description: metadata.description,
dependencies: metadata.dependencies,
permissions: metadata.permissions,
}
}
}
impl wit::Guest for Component {
/// WIT entry point — delegates to [`Plugin::on_load`].
fn on_load(context: Context) -> Result<(), String> {
plugin().on_load(context)
}
/// WIT entry point — delegates to [`Plugin::on_unload`].
fn on_unload(context: Context) -> Result<(), String> {
plugin().on_unload(context)
}
/// WIT entry point — dispatches an incoming event to the registered handler for `event_id`.
///
/// Returns the event unchanged if no handler is registered for the given id.
fn handle_event(event_id: u32, server: Server, event: events::Event) -> events::Event {
let handlers = EVENT_HANDLERS.lock().unwrap();
if let Some(handler) = handlers.get(&event_id) {
handler.handle_erased(server, event)
} else {
event
}
}
/// WIT entry point — dispatches an incoming command invocation to the registered handler for `command_id`.
///
/// Returns a [`CommandError`](command::CommandError) if no handler is registered for the given id.
fn handle_command(
command_id: u32,
sender: command::CommandSender,
server: Server,
args: command::ConsumedArgs,
) -> Result<i32, command::CommandError> {
let handlers = COMMAND_HANDLERS.lock().unwrap();
handlers.get(&command_id).map_or_else(
|| {
Err(command::CommandError::CommandFailed(TextComponent::text(
&format!("no handler registered for command id {command_id}"),
)))
},
|handler| handler.handle(sender, server, args),
)
}
/// WIT entry point — dispatches a scheduled task invocation to the registered handler for `handler_id`.
fn handle_task(handler_id: u32, server: Server) {
let mut handlers = TASK_HANDLERS.lock().unwrap();
handlers.handle(handler_id, server);
}
fn handle_ai_goal_can_start(goal_id: u32, server: Server, entity: entity::Entity) -> bool {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.can_start(server, entity)
} else {
false
}
}
fn handle_ai_goal_should_continue(
goal_id: u32,
server: Server,
entity: entity::Entity,
) -> bool {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.should_continue(server, entity)
} else {
false
}
}
fn handle_ai_goal_start(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.start(server, entity);
}
}
fn handle_ai_goal_tick(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.tick(server, entity);
}
}
fn handle_ai_goal_stop(goal_id: u32, server: Server, entity: entity::Entity) {
let mut handlers = crate::ai::AI_GOAL_HANDLERS.lock().unwrap();
if let Some(goal) = handlers.handlers.get_mut(&goal_id) {
goal.stop(server, entity);
}
}
}
/// Convenience alias for `core::result::Result<T, String>` used throughout the plugin API.
pub type Result<T, E = String> = core::result::Result<T, E>;
/// The trait that every Pumpkin plugin must implement.
///
/// Use the [`register_plugin!`] macro to register your implementation with the runtime.
pub trait Plugin: Send + Sync {
/// Creates a new instance of the plugin.
///
/// Called once by the runtime before [`on_load`](Plugin::on_load).
fn new() -> Self
where
Self: Sized;
/// Returns the metadata for this plugin.
fn metadata(&self) -> PluginMetadata;
/// Called when the plugin is loaded by the server.
///
/// Use this to register event handlers, commands, and perform any setup work.
fn on_load(&mut self, _context: Context) -> Result<()> {
Ok(())
}
/// Called when the plugin is unloaded by the server.
///
/// Use this to clean up any resources acquired during [`on_load`](Plugin::on_load).
fn on_unload(&mut self, _context: Context) -> Result<()> {
Ok(())
}
}
#[doc(hidden)]
pub fn register_plugin(build_plugin: fn() -> Box<dyn Plugin>) {
let _ = tracing::subscriber::set_global_default(WitSubscriber::new());
unsafe { PLUGIN = Some(build_plugin()) }
}
/// Returns a mutable reference to the currently loaded plugin instance.
///
/// # Panics
/// If called before [`register_plugin`] has initialized `PLUGIN`.
fn plugin() -> &'static mut dyn Plugin {
#[expect(static_mut_refs)]
unsafe {
PLUGIN.as_deref_mut().unwrap()
}
}
/// The singleton plugin instance, initialised by [`register_plugin`].
static mut PLUGIN: Option<Box<dyn Plugin>> = None;
/// Registers the provided type as a Pumpkin plugin.
///
/// This macro generates the WebAssembly export entry point that the server uses to
/// instantiate the plugin. The type must implement the [`Plugin`] trait.
///
/// # Example
/// ```rust,ignore
/// register_plugin!(MyPlugin);
/// ```
#[macro_export]
macro_rules! register_plugin {
($plugin_type:ty) => {
#[unsafe(export_name = "init-plugin")]
pub extern "C" fn __init_plugin() {
$crate::register_plugin(|| Box::new(<$plugin_type as $crate::Plugin>::new()));
}
};
}
/// AI and mob goal utilities.
pub mod ai;