293 lines
9.5 KiB
Rust
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;
|