Skip to content

Writing the basic logic

Plugin base

There is a lot going on under the hood of even a basic plugin, so to greatly simplify plugin development we will use the pumpkin-api-macros crate to create a basic empty plugin.

Open the src/lib.rs file and replace its contents with this:

rs
use pumpkin_api_macros::plugin_impl;

#[plugin_impl]
pub struct MyPlugin {}

impl MyPlugin {
    pub fn new() -> Self {
        MyPlugin {}
    }
}

impl Default for MyPlugin {
    fn default() -> Self {
        Self::new()
    }
}

This will create a empty plugin and implement all the necessary methods for it to be loaded by pumpkin.

We can now try to compile our plugin for the first time, to do so, run this command in your project folder:

bash
cargo build --release

NOTICE

If you are using Windows, you must use the --release flag, or you will run into issues. If you are on another platform, you don't have to use it if you want to speed up compile time

The initial compilation will take a bit, but don't worry, later compilations will be faster.

If all went well, you should be left with a message like this:

log
╰─ cargo build --release
   Compiling hello-pumpkin v0.1.0 (/home/vypal/Dokumenty/GitHub/hello-pumpkin)
    Finished `release` profile [optimized] target(s) in 0.68s

Now you can go to the ./target/release folder (or ./target/debug if you didn't use --release) and locate your plugin binary

Depending on your operating system, the file will have one of three possible names:

  • For Windows: hello-pumpkin.dll
  • For MacOS: libhello-pumpkin.dylib
  • For Linux: libhello-pumpkin.so

NOTE

If you used a different project name in the Cargo.toml file, look for a file which contains your project name

You can rename this file to whatever you like, however you must keep the file extension (.dll, .dylib, or .so) the same.

Testing the plugin

Now that we have our plugin binary, we can go ahead and test it on the Pumpkin server. Installing a plugin is as simple as putting the plugin binary that we just built into the plugins/ folder of your Pumpkin server!

Thanks to the #[plugin_impl] macro, the plugin will have it's details (like the name, authors, version, and description) built into the binary so that the server can read them.

When you start up the server and run the /plugins command, you should see an output like this:

There is 1 plugin loaded:
hello-pumpkin

Basic methods

The Pumpkin server currently uses two "methods" to tell the plugin about it's state. These methods are on_load and on_unload.

These methods don't have to be implemented, but you will usually implement at least the on_load method. In this method you get access to a Context object which can give the plugin access to information about the server, but also allows the plugin to register command handlers or events.

To make implementing these methods easier, there is another macro provided by the pumpkin-api-macros crate. Add these lines to your src/lib.rs file:

rs
use pumpkin_api_macros::{plugin_impl, plugin_method}; 
use pumpkin::plugin::Context;
use pumpkin_api_macros::plugin_impl; 

#[plugin_method] 
async fn on_load(&mut self, server: &Context) -> Result<(), String> {
    Ok(())
}

#[plugin_impl]
pub struct MyPlugin {}

impl MyPlugin {
    pub fn new() -> Self {
        MyPlugin {}
    }
}

impl Default for MyPlugin {
    fn default() -> Self {
        Self::new()
    }
}

IMPORTANT

It is important that you define any plugin methods before the #[plugin_impl] block

This method gets a mutable reference to itself (in this case the MyPlugin struct) which it can use to initialize any data stored in the plugin's main struct, and a reference to a Context object. This object is specifically constructed for this plugin, based on the plugin's metadata.

Methods implemented on the Context object:

rs
fn get_data_folder() -> String

Returns the path to a folder dedicated to this plugin, which should be used for persistant data storage

rs
async fn get_player_by_name(player_name: String) -> Option<Arc<Player>>

If a player by the name player_name is found (has to be currently online), this function will return a reference to him.

rs
async fn register_command(tree: CommandTree, permission: PermissionLvl)

Registers a new command handler, with a minimum required permission level.

rs
async fn register_event(handler: H, priority: EventPriority, blocking: bool)

Registers a new event handler with a set priority and if it is blocking or not.

Basic on-load method

For now we will only implement a very basic on_load method to be able to see that the plugin is running.

Here we will setup the env_logger and setup a "Hello, Pumpkin!", so that we can see out plugin in action.

Add this to the on_load method:

rs
#[plugin_method]
async fn on_load(&mut self, server: &Context) -> Result<(), String> {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 

    log::info!("Hello, Pumpkin!");

    Ok(())
}

If we build the plugin again and start up the server, you should now see this somewhere in the log:

log
[2025-01-18T09:36:16Z INFO  hello_pumpkin] Hello, Pumpkin!

Released under the MIT License.