A plugin framework for Spigot and Bukkit.
This branch (v1) is a complete overhaul of Frame. The whole library has been rewritten from scratch to provide greater flexibility and decouple the whole library from Bukkit, allowing it to be used anywhere.
If you're looking for support or just a place to talk about the plugin, join the official Scarlet Crystal Public Discord!
- Frame by itself no longer runs as a plugin - a secondary layer, such as FrameBukkit, is now required.
- This means that developers can now use Frame in non-bukkit projects.
If you own a Spigot/Bukkit server and a plugin you use depends on Frame, head over to the FrameBukkit repo for instructions.
A Frame mod for Forge may be available in future.
If you're using Bukkit, you'll want to grab a FrameBukkit release instead. (The release of this is pending! Just working on polishing it up.)
If you're using Frame in a standalone project, you can add it to your Gradle configuration (or any other build system, really) via a Maven dependency:
repositories {
maven {
url = 'https://maven.offbeatwit.ch/repository/releases'
}
}
dependencies {
// among your other dependencies
implementation "io.github.hedgehog1029:frame:1.4.1"
}
This is a guide for using Frame in standalone mode; however, many concepts apply to using Frame with an additional helper (such as FrameBukkit) as well.
To get started, you'll want to create a new Frame.Builder
instance:
// somewhere in your initialization code
Frame frame = new Frame.Builder()
.setCommandFactory(new MyCommandFactory()) // we'll revisit this
.build();
// our variable 'frame' now holds a brand new Frame instance
There's also the nicer Kotlin style, if you're using Kotlin:
val frame: Frame = Frame.Builder().apply {
commandFactory = MyCommandFactory()
}.build()
Frame.Builder
has all sorts of options you can set; in most cases,
you'll only want to use setCommandFactory
, setHookCallback
and
possibly setLogReceiver
; the rest of the setters have sane defaults
and don't need to be called in the majority of applications.
You are required to set a command factory, but all other properties are optional. We'll revisit command factories later in this guide.
Modules are a core part of Frame; they're where you register commands and use hooks. A "module" simply refers to a class which you instantiate and pass to Frame; there's no interface to implement, so the following is valid:
class MyModule {
// ...
}
To register this module, you simply need to call Frame#loadModule
,
like so:
frame.loadModule(new MyModule());
You've now successfully created and loaded a module! However, it doesn't do anything yet; let's learn how to register a command next!
Registering commands with Frame is super easy. Here's an example, for a command which adds two numbers together:
// our module class from before
class MyModule {
@Command(aliases = "add", description = "Add two numbers.")
private void echo(int first, int second) {
System.out.println(first + second);
}
}
Breaking this down, the @Command
annotation is the most important part;
we specify the aliases
(list of names this command goes by) and a
description
(which is just some helpful text explaining what the
command does). The method name itself is irrelevant; the next important
part is the parameters.
This is the most powerful part of Frame - its parametric command
binding. In this example, our two parameters are of the int
type -
they're numbers. This method signature (int first, int second)
signals
to Frame that we expect two arguments, and they should both be valid
integers. Even though we call into Frame with String arguments, we can
simply write our command handling function to accept two integers,
and Frame will attempt to map the string input into valid integers to
invoke your handling method with. If you provide an input that can't be
mapped, Frame will throw an exception.
The beauty of this system is that it's extensible; Frame ships with
built-in support for primitives and strings, but that's just the tip
of the iceberg. By calling Frame#loadBindings
with an instance
implementing IBindingProvider
, you can write and register your own
provider classes, which tell Frame how to transform string inputs into
your custom type; for example, FrameBukkit registers a provider for the
Player
class, which looks up a player by their username via the Bukkit
API and returns that if found.
This is all you need to do to register a command! Since we're in a standalone environment we don't have any context arguments by default; other environments such as FrameBukkit may make use of the @Sender annotation to mark implicitly passed arguments.
You may ask; where does this command actually get registered? The answer
is that your ICommandFactory
implementor that you registered during
setup will be invoked with a shiny new command mapping instance
implementing IPipeline
(after you call Frame#go
). The reason that
Frame doesn't have an internal command registry is that it's designed to
work alongside existing systems, such as Bukkit or Forge's command map.
If you're running Frame standalone, you should store received pipelines
against all of their aliases (such as in a HashMap), and look up commands
from there when invoking.
import io.github.hedgehog1029.frame.dispatcher.mapping.ICommandFactory;
import io.github.hedgehog1029.frame.dispatcher.pipeline.IPipeline;
class MyCommandFactory implements ICommandFactory {
@Override
public void registerCommand(IPipeline pipeline) {
for (String alias : pipeline.getAliases()) {
// do your registering here, for example:
// SomeLibrary.register(alias, pipeline)
}
}
}
The three steps to making Frame work are as follows:
- Set up a Frame instance using
Frame.Builder
. - Call
Frame#loadModule
for each of your modules. - Call
Frame#go
after loading all of your modules.
Once you call go()
, you'll receive all registered commands in your
command factory callback.