Backbone is a powerful and flexible plugin for Spigot-based Minecraft servers, designed to supercharge server customization. Its core philosophy is to enable server administrators and developers to write, test, and update server logic on a live server without requiring restarts, dramatically accelerating the development lifecycle.
Whether you're a server administrator looking to add custom features with simple scripts or a developer prototyping new ideas, Backbone provides the tools you need to be more productive and creative.
- Hot-Loadable Scripts: Write and reload Kotlin scripts without restarting the server, enabling rapid development and iteration.
- Advanced Scripting: Go beyond simple scripts with support for inter-script imports, Maven dependencies, and custom compiler options.
- Event System: A custom event bus that complements Bukkit's event system, offering more control and flexibility within your scripts.
- Command Framework: A simple yet powerful command system to create custom commands directly from your scripts.
- Storage Abstraction: Easily manage data with a flexible storage system that supports SQLite databases and typed configuration files.
- GUI Framework: A declarative GUI framework for creating complex and interactive inventories from your scripts.
- Text Formatting: A flexible text formatting system with support for custom alphabets and color codes.
- Entity Framework: Custom entity utility for adding custom entities via the goals api.
- Display Entity Rendering System: A powerful display entity rendering system for creating custom visuals
- Custom Item Framework: A stateful custom item framework for creating items with unique abilities.
Getting started with Backbone is simple. The primary way to use Backbone is by installing it as a plugin and then creating your own custom features through its scripting engine.
- Minecraft Java Edition Server version 1.21 or higher.
- PlaceholderAPI (optional, for placeholder support).
- Download: Download the latest release from the official releases page.
- Install: Place the downloaded
.jarfile into your server'spluginsdirectory. - Start: Launch your server. Backbone will generate its necessary folders in your server's root directory.
- Scripting: You can now begin writing custom logic in
.bb.ktsscript files inside thescripts/directory. See the examples below to get started!
For advanced users who wish to build a plugin that depends on Backbone, you can add it as a dependency. However, for most use cases, the scripting engine provides all the power you need.
Backbone comes with a set of powerful management commands to control its various systems. The base command is /backbone, which can also be accessed using the alias /bb.
The /bb scripting command provides tools to manage your hot-loadable scripts.
/bb scripting: Lists all discovered scripts and their current status (enabled/disabled)./bb scripting reload: Reloads all scripts, applying any changes you've made./bb scripting enable <script_name>: Enables a disabled script./bb scripting disable <script_name>: Disables an enabled script./bb scripting wipe <script_name> <confirmation>: Wipes the persistent state of a script. Requires the script name to be entered twice for confirmation.
The /bb item command allows you to interact with the custom item system.
/bb item: Lists all registered custom items./bb item give <item_name>: Gives you the specified custom item./bb item replicate: Creates a new instance of the custom item you are currently holding./bb item read: Reads and displays the NBT tags of the item you are holding.
The /bb entity command provides control over custom entities.
/bb entity: Lists all registered custom entities./bb entity spawn <entity_name>: Spawns the specified custom entity at your location.
All examples are designed to be placed in their own .bb.kts files inside the scripts/ directory.
Backbone's most powerful feature is its hot-loadable script engine. This allows you to write Kotlin code in script files (.bb.kts) and load, unload, and reload them on the fly without needing to restart your server. This is incredibly useful for rapid development, prototyping, and live server updates.
All script files should be placed in the scripts/ directory in your server's root. The script engine automatically discovers and compiles any .bb.kts files in this location when scripts are loaded.
Every script file must evaluate to an object that extends ManagedLifecycle. This class provides the necessary hooks for the script engine to manage the script's lifecycle.
import net.integr.backbone.Backbone
import net.integr.backbone.events.TickEvent
import net.integr.backbone.systems.event.BackboneEventHandler
import net.integr.backbone.systems.hotloader.lifecycle.ManagedLifecycle
import net.integr.backbone.systems.hotloader.lifecycle.sustained
// Each script must return an object that extends ManagedLifecycle.
object : ManagedLifecycle() {
// 'sustained' properties persist their values across script reloads.
var counter by sustained(0)
// Standard variables are reset every time the script is reloaded.
var otherCounter = 0
// Called when the script is loaded or enabled
override fun onLoad() {
Backbone.registerListener(this)
}
// Called when the script is unloaded or disabled
override fun onUnload() {
Backbone.unregisterListener(this)
}
// This event fires every server tick while the script is enabled.
@BackboneEventHandler
fun onTick(event: TickEvent) {
counter++
otherCounter++
if (counter % 20 == 0) {
Backbone.PLUGIN.server.onlinePlayers
.forEach { it.sendMessage("Sustained Count: $counter | Volatile Count: $otherCounter") }
}
}
}You can make your scripts even more powerful by using file-level annotations to manage dependencies and compiler settings.
You can define utility scripts with the .bbu.kts file extension. These scripts function as shared libraries. Backbone automatically compiles them and injects their classes and functions into the classpath and default imports of all main .bb.kts scripts.
utils.bbu.kts
class MyUtilities {
fun getGreeting(): String = "Hello from a utility script!"
}This means you can use the defined classes and methods in your main scripts as if they were in the same file.
main.bb.kts
// ... inside your ManagedLifecycle
val utils = MyUtilities()
println(utils.getGreeting()) // Prints "Hello from a utility script!"You can pull in external libraries directly from Maven repositories using the @DependsOn and @Repository annotations. This lets you use powerful third-party libraries without having to manually bundle them with your server.
// Add a custom Maven repository
@file:Repository("https://jitpack.io")
// Depend on a library from that repository
@file:DependsOn("com.github.javafaker:javafaker:1.0.2")
import com.github.javafaker.Faker
import kotlin.script.experimental.dependencies.DependsOn
import kotlin.script.experimental.dependencies.Repository
// ... inside your script
val faker = Faker()
val randomName = faker.name().fullName()
println("A random name: $randomName")The @CompilerOptions annotation gives you fine-grained control over the Kotlin compiler, allowing you to enable specific language features or pass other flags.
// Enable a specific language feature, like context receivers, or just pass any plain old compiler option
@file:CompilerOptions("-Xcontext-receivers")
// Your script code can now use context receivers
context(String)
fun greet() {
println("Hello, $this")
}Backbone provides a simple and powerful way to manage your plugin's data and configuration through ResourcePools. This system allows you to easily handle databases and configuration files in a structured manner.
A ResourcePool is a namespaced container for your resources. It's recommended to create a separate pool for each script or feature set to avoid conflicts.
// Create a resource pool for your script's storage
val myScriptStorage = ResourcePool.fromStorage("mystorage")
// Create a resource pool for your script's configuration
val myScriptConfig = ResourcePool.fromConfig("myconfig")This will create directories at storage/mystorage/ and config/myconfig/ in your server's root directory.
You can easily manage typed configuration files. Backbone handles the serialization and deserialization of your data classes automatically.
First, define a serializable data class for your configuration:
@Serializable // Requires the kotlinx.serialization plugin
data class MyConfig(val settingA: String = "default", val settingB: Int = 10)Then, use the config() function on your resource pool to get a handler for it:
// Get a handler for a config file named 'settings.yml'
val configHandler = myScriptConfig.config<MyConfig>("settings.yml")
// Load the config file synchronously
configHandler.updateSync()
// Get the current config from the cache
val currentConfig = configHandler.getState()
println("Setting A: ${currentConfig?.settingA}")
// Modify and save the config asynchronously
configHandler.writeState(currentConfig.copy(settingB = 20))Backbone provides a simple and efficient way to work with SQLite databases from within your scripts.
// Get a connection to a database file named 'playerdata.db'
val dbConnection = myScriptStorage.database("playerdata.db")
// The useConnection block handles connection setup and teardown, preventing resource leaks.
dbConnection.useConnection {
// The 'this' context is a StatementCreator instance.
execute("CREATE TABLE IF NOT EXISTS players (uuid TEXT PRIMARY KEY, name TEXT NOT NULL);")
// Use a prepared statement to safely insert data.
preparedStatement("INSERT INTO players (uuid, name) VALUES (?, ?)") {
setString(1, "some-uuid")
setString(2, "PlayerName")
executeUpdate()
}
val playerName = query("SELECT name FROM players WHERE uuid = 'some-uuid'") { it.getString("name") }
println("Found player: $playerName")
}Backbone's event system allows you to create and listen for custom events, giving you more control over your script's behavior.
// Define a custom event
class MyCustomEvent(val message: String) : Event()
// Register a listener for the custom event.
// Priority ranges from -3 to 3, with 0 being normal. Lower values execute first.
@BackboneEventHandler(EventPriority.THREE_BEFORE)
fun onMyCustomEvent(event: MyCustomEvent) {
println("Received custom event: ${event.message}")
}
// Fire the custom event from anywhere in your code
EventBus.post(MyCustomEvent("Hello, world!"))Backbone's command framework makes it easy to create and manage commands with arguments, subcommands, and permission checks.
Commands are executed asynchronously by default. For this reason, any API calls that modify server state must be wrapped in a Backbone.dispatchMain {} block to ensure they run on the main server thread.
// Define a command
object MyCommand : Command("mycommand", "My first command") {
val perm = PermissionNode("myplugin")
override fun onBuild() {
// Register subcommands
subCommands(MySubCommand)
// Define arguments for the command
arguments(
scriptArgument("text", "My string")
)
}
override suspend fun exec(ctx: Execution) {
// Require a permission for this command
ctx.requirePermission(perm.derive("mycommand")) // "myplugin.mycommand"
val text = ctx.get<String>("text")
ctx.respond("Hello ${ctx.sender.name}: $text")
// To affect server state, dispatch to the main thread for the next tick.
Backbone.dispatchMain {
val player = ctx.getPlayer() // Get the sender as a player (and require it to be one)
player.world.spawnEntity(player.location, EntityType.BEE)
}
// Halt execution and mark the command as failed.
ctx.fail("Something is not right!")
}
}
// In your ManagedLifecycle's onLoad:
override fun onLoad() {
Backbone.Handlers.COMMAND.register(MyCommand)
}
// In your ManagedLifecycle's onUnload:
override fun onUnload() {
Backbone.Handlers.COMMAND.unregister(MyCommand)
}You can also create your own custom argument types by extending the Argument class. This allows you to define custom parsing and tab-completion logic.
Here is an example of a custom DoubleArgument:
class DoubleArgument(name: String, description: String) : Argument<Double>(name, description) {
override fun getCompletions(current: ArgumentInput): CompletionResult {
val arg = current.getNextSingle()
val completions = if (arg.text.isBlank()) mutableListOf("<$name:double>") else mutableListOf()
return CompletionResult(completions, arg.end)
}
override fun parse(current: ArgumentInput): ParseResult<Double> {
val arg = current.getNextSingle()
val value = arg.text.toDoubleOrNull() ?: throw CommandArgumentException("Argument '$name' must be a valid double.")
return ParseResult(value, arg.end)
}
}You can then use this custom argument in your command definitions:
arguments(
DoubleArgument("amount", "A decimal number")
)Backbone includes a powerful framework for creating custom items with unique behaviors and state.
To create a custom item, you extend the CustomItem class. This class allows you to define the item's ID, its default state, and its behavior when interacted with.
// Define a custom item
object MyItem : CustomItem("my_item", MyItemState) {
// This method is called when a player interacts with the item
override fun onInteract(event: PlayerInteractEvent) {
event.player.sendMessage("You used My Item!")
}
}
// Define the state for the custom item
object MyItemState : CustomItemState("default") {
override fun generate(): ItemStack {
return ItemStack(Material.DIAMOND_SWORD).apply {
itemMeta = itemMeta.apply {
setDisplayName("My Custom Sword")
}
}
}
}
// In your ManagedLifecycle's onLoad:
override fun onLoad() {
Backbone.Handlers.ITEM.register(MyItem)
}You can then give the item to a player using a command:
// In a command's exec method
val item = MyItem.generate()
ctx.getPlayer().inventory.addItem(item)Backbone allows you to create custom entities with unique AI goals.
// Define a custom entity that is a non-moving zombie
object GuardEntity : CustomEntity<Zombie>("guard", EntityType.ZOMBIE) {
override fun prepare(mob: Zombie) {
// Set up for example armor
}
override fun setupGoals(mob: Zombie) {
// Clear existing goals and add a simple look goal
val goals = Backbone.SERVER.mobGoals
goals.removeAllGoals(mob)
goals.addGoal(mob, 1, LookAtPlayerGoal(mob))
}
}
// In your ManagedLifecycle's onLoad:
override fun onLoad() {
Backbone.Handlers.ENTITY.register(GuardEntity)
}
// You can then spawn the entity for example using a command
// In a command's exec method:
GuardEntity.spawn(ctx.getPlayer().location, ctx.getPlayer().world)Backbone provides a powerful rendering system using display entities. This allows you to create custom visuals in the world.
Here is an example of how to create a glowing box around a player:
// Create a renderable object
val playerBox = BoxRenderable()
// In your ManagedLifecycle's onLoad:
override fun onLoad() {
// Spawn the box when the script loads
val player = Backbone.SERVER.onlinePlayers.firstOrNull()
if (player != null) {
playerBox.spawn(player.world, player.location)
}
}
// In your ManagedLifecycle's onUnload:
override fun onUnload() {
// Despawn the box when the script unloads
playerBox.despawn()
}
// In a tick event, update the box's position and appearance
@BackboneEventHandler
fun onTick(event: TickEvent) {
val player = Backbone.SERVER.onlinePlayers.firstOrNull()
if (player != null) {
playerBox.update(player.location, player.location.clone().add(0.0, 1.0, 0.0), Material.GLASS.createBlockData())
}
}Backbone includes a flexible text component system that allows you to customize the look and feel of your script's output.
Backbone has a simple component builder abstraction that is based on the papermc net.kyori.adventure.text.Component
component {
append("Hello") {
color(Color.RED)
}
append("World") {
color(Color.GREEN)
onHover(HoverEvent.showText(component {
append("Hover")
}))
}
append("!") {
color(Color.YELLOW)
}
}You can create a custom CommandFeedbackFormat to change how command responses are displayed. Or simply inherit from it to unlock even more customisation via the component system.
val myFormat = CommandFeedbackFormat("MyPlugin", Color.RED)
// You can then pass this format to your command.
object MyCommand : Command("mycommand", "My first command", format = myFormat) {
// ...
}This will format command responses with a custom prefix and color. The CommandFeedbackFormat uses a custom Alphabet to encode the handler name, giving it a unique look.
You can create your own custom alphabets by implementing the Alphabet interface. This allows you to encode text in unique ways, such as the BoldSmallAlphabet used by the CommandFeedbackFormat.
object MyAlphabet : Alphabet {
const val ALPHABET = "..." // Your custom alphabet characters
override fun encode(str: String): String {
// Your encoding logic here
return "encoded_string"
}
}Backbone's GUI framework provides a declarative way to create and manage inventories. The GUI handler automatically manages state on a per-player basis, differentiating each player's unique inventory view.
// Create a Test Inventory Gui with 27 slots
object TestGui : Gui(component { append("Test Gui") }, 27) {
// 'prepare' is run once during construction.
// Use this to define the static layout of your GUI, such as placing buttons.
override fun prepare(inventory: Inventory) {
inventory.setItem(0, ItemStack(Material.GOLDEN_APPLE))
}
// 'onOpen' is run whenever the inventory is first loaded for a player.
// Use this to dynamically load player-specific data.
override fun onOpen(player: Player, inventory: Inventory) {
// GUI has been opened for the player
}
// 'onClose' is called when the inventory is closed.
// Note: To open another GUI from this event, schedule it for the next tick
// by wrapping the .open() call in Backbone.dispatchMain {}.
override fun onClose(inventory: InventoryCloseEvent) {
// GUI has been closed
}
// 'onTick' runs every game tick for open GUIs.
// Useful for animations and other dynamic logic.
override fun onTick(inventory: Inventory) {
val randomSlot = (0 until inventory.size).random()
inventory.setItem(randomSlot, ItemStack(Material.APPLE))
}
// 'onClick' runs when a slot is clicked in this GUI.
override fun onClick(inventory: InventoryClickEvent) {
// A slot was clicked
}
// 'onInteract' runs on any interaction (including clicks).
override fun onInteract(inventory: InventoryInteractEvent) {
// An interaction occurred
}
}
// In a command or event within your script:
TestGui.open(player)Backbone provides a set of placeholders through its soft dependency on PlaceholderAPI. If you have PlaceholderAPI installed, you can use the following placeholders in any plugin that supports them:
%backbone_version%: Displays the current version of the Backbone plugin.
More placeholders are planned for future releases.
