-
Notifications
You must be signed in to change notification settings - Fork 1
Getting started
Spigot is an event based API. When using it via Java, plugin development is done by creating callback functions, and overloading select Java classes.
The BlackboxMC plugin replicates this behavior by searching for functions that a shared library exports, according to a certain format:
-
__on__EventName
- for Event Functions. -
__extends__[EXTENDED_CLASS_NAME]__[CLASS_NAME]__[METHOD_NAME]
- for Class Extensions.
An event function is a function that is fired when Spigot fires a certain event, such as BlockBreakEvent.
We can start to make such a function via the following syntax:
use jni::{objects::JObject, JNIEnv};
#[no_mangle]
pub extern "system" fn __on__BlockBreakEvent(env: JNIEnv<'_>, obj: JObject<'_>) {
# ...
}
A list of Spigot's events can be found here
The first argument is a reference to the JNI pointer, and is used as you might expect: to call back to the Java VM. In the JNI crate, this pointer is bound to a struct that is mutably borrowed for every single method. We need to be able to pass this reference to multiple function calls, so the blackboxmc_rs_general
crate contains a wrapper struct that uses interior mutability, allowing us to pass a non-mutable reference to methods.
use blackboxmc_general::SharedJNIEnv;
pub extern "system" fn __on__BlockBreakEvent(env: JNIEnv<'_>, obj: JObject<'_>) {
let e = SharedJNIEnv::new(env);
# ...
The second object is the event that the game would normally pass into your overloaded function. In this case, obj
is BlockBreakEvent. Each bound function in blackboxmc-rs has an error-checked "from_raw" function that lets you create structs via a shared JNI reference and a JObject.
let mut event = blackboxmc_bukkit::event::block::BlockBreakEvent::from_raw(&e, obj).unwrap();
The event functions do not return anything, meaning that you have to handle errors yourself. For this example, we'll use .unwrap()
, but it's recommended that you handle errors properly Java doesn't properly catch crashes from libraries, meaning that unwrap()
will ungracefully stop the entire server.
Since we don't have to worry about return functions, the rest of the function is up to you. Here's the full example code:
use blackboxmc_general::SharedJNIEnv;
use jni::{objects::JObject, JNIEnv};
#[no_mangle]
pub extern "system" fn __on__BlockBreakEvent(env: JNIEnv<'_>, obj: JObject<'_>) {
let e = SharedJNIEnv::new(env);
let mut event = blackboxmc_bukkit::event::block::BlockBreakEvent::from_raw(&e, obj).unwrap();
// Cancel the event.
event.set_cancelled(true).expect("Couldn't cancel event");
let mut player = event.player().expect("Couldn't get player");
println!(
"{}",
*player
.inventory()
.expect("Couldn't get inventory")
.item_in_hand()
.expect("Couldn't get item in hand")
.get_type()
.expect("Couldn't get type")
);
}
This is horrifically unsafe to do and chances are you should wait until the appropriate macros are setup to handle this for you.
The BlackboxMC plugin has a select few "extendable" versions of Spigot classes that you can overload using specially crafted functions.
Said functions uses the format __extends__[EXTENDED_CLASS_NAME]__[CLASS_NAME]__[METHOD_NAME]
.
- EXTENDED_CLASS_NAME = The class being extended.
- CLASS_NAME = User-given "class name" that is used when telling the plugin to instantiate a new extended class.
- METHOD_NAME = The method being overridden.
Each extended class has their own set of required methods that you MUST override. If you do not, the plugin will throw an error when it tries to instantiate the relevant class. The required methods to override are listed below.
When you create the actual method, it's expected to take, in order,
- JNIEnv
- Memory Address (explained below)
- Plugin
- List of objects that corresponds to the arguments given.
The function should look something like this:
#[no_mangle]
pub extern "system" fn __extends__BukkitRunnable__HungerThread__run<'mc>(
mut env: JNIEnv<'mc>,
address: jint,
plugin: JObject<'mc>,
objs: JObjectArray<'mc>,
) {
let e = SharedJNIEnv::new(env);
let mut plug = Plugin::from_raw(&e, plugin).unwrap();
# ...
}
For alot of classes, it's not unreasonable to want to instantiate the extended versions of them multiple times. This is where the address integer comes into play. This is actually something you give to the plugin below, and you can use this to do your own memory management and work with/return different values.
The extendable classes, and their methods, are below. Note that the function syntax is based on what the plugin gives (in the object array) and expects back, but when creating the actual functions, anything that isn't a primitive is given to you as a JObject or JObjectArray, and you must return a JObject if applicable.
Extendable Class | Required Methods |
---|---|
BiomeProvider |
|
CommandExecutor |
|
ConversationCanceller |
|
ConversationPrefix |
|
HelpTopic |
|
MapRenderer |
|
MetadataValue |
|
NoiseGenerator |
|
Plugin |
|
Prompt |
|
BukkitRunnable |
|
TabCompleter |
|
TabExecutor | |
TabCompleter | |
BlockPopulator | |
ChunkGenerator | |
PersistentDataType | |
PluginBase |
Lastly, each of the appropriate structs has a from_extendable
method through which you can pass the user given class name.
BukkitRunnable::from_extendable(
env, // &SharedJNIEnv
plugin, // Plugin
0, // Memory address
format!("lib{}", std::env!("CARGO_CRATE_NAME")), // The name of your library. Must match the filename.
"HungerThread".to_string(), // The name of your instantiated class.
)
If you have not decided to wait for the macro to be made then god bless your soul.