Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/.gradle/
/run/
/.kotlin/
/logs/
33 changes: 14 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

<!--suppress CheckImageSize -->
<img alt="logo.png" src="logo.png" width="20%"/>

# Backbone
Expand All @@ -7,7 +8,7 @@
![GitHub last commit](https://img.shields.io/github/last-commit/integr-dev/backbone)
[![GitHub license](https://img.shields.io/github/license/integr-dev/backbone)](https://github.com/integr-dev/backbone/blob/master/LICENSE)

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.
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 speeding up 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.

Expand All @@ -17,7 +18,7 @@ Whether you're a server administrator looking to add custom features with simple
- **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.
- **Storage Abstraction:** 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.
Expand All @@ -26,11 +27,11 @@ Whether you're a server administrator looking to add custom features with simple

## Getting Started

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.
Getting started with Backbone is straightforward. The primary way to use Backbone is by installing it as a plugin and then creating your own custom features through its scripting engine.

### Requirements
- Minecraft Java Edition Server version 1.21 or higher.
- [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.62/) (optional, for placeholder support).
- [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi) (optional, for placeholder support).

### Installation
1. **Download:** Download the latest release from the [official releases page](https://github.com/integr-dev/backbone/releases).
Expand Down Expand Up @@ -186,7 +187,7 @@ This will create directories at `storage/mystorage/` and `config/myconfig/` in y

#### Configuration

You can easily manage typed configuration files. Backbone handles the serialization and deserialization of your data classes automatically.
You can manage typed configuration files. Backbone handles the serialization and deserialization of your data classes automatically.

First, define a serializable data class for your configuration:

Expand Down Expand Up @@ -215,7 +216,6 @@ configHandler.writeState(currentConfig.copy(settingB = 20))
#### Databases

Backbone provides a simple and efficient way to work with SQLite databases from within your scripts.

```kotlin
// Get a connection to a database file named 'playerdata.db'
val dbConnection = myScriptStorage.database("playerdata.db")
Expand All @@ -239,7 +239,7 @@ dbConnection.useConnection {

### Custom Events

Backbone's event system allows you to create and listen for custom events, giving you more control over your script's behavior.
Backbone's event system allows you to create and listen to custom events, giving you more control over your script's behavior.

```kotlin
// Define a custom event
Expand All @@ -250,7 +250,7 @@ class MyCustomEvent(val message: String) : Event()
@BackboneEventHandler(EventPriority.THREE_BEFORE)
fun onMyCustomEvent(event: MyCustomEvent) {
println("Received custom event: ${event.message}")
event.setCallback("yay!")
event.callback = "yay!"
}

// Fire the custom event from anywhere in your code
Expand Down Expand Up @@ -279,14 +279,14 @@ object MyCommand : Command("mycommand", "My first command") {
}

override suspend fun exec(ctx: Execution) {
// Require a permission for this command
// Require 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.
// To affect the 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)
Expand Down Expand Up @@ -382,7 +382,7 @@ 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
// Set up, for example, armor
}

override fun setupGoals(mob: Zombie) {
Expand All @@ -398,7 +398,7 @@ override fun onLoad() {
Backbone.Handlers.ENTITY.register(GuardEntity)
}

// You can then spawn the entity for example using a command
// 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)
```
Expand Down Expand Up @@ -467,7 +467,7 @@ component {

#### Command Feedback Format

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.
You can create a custom `CommandFeedbackFormat` to change how command responses are displayed. Or inherit from it to unlock even more customization via the component system.

```kotlin
val myFormat = CommandFeedbackFormat("MyPlugin", Color.RED)
Expand All @@ -486,12 +486,7 @@ You can create your own custom alphabets by implementing the `Alphabet` interfac

```kotlin
object MyAlphabet : Alphabet {
const val ALPHABET = "..." // Your custom alphabet characters

override fun encode(str: String): String {
// Your encoding logic here
return "encoded_string"
}
override val alphabet = "..." // Your custom alphabet characters
}
```

Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = "net.integr"
version = "1.2.0"
version = "1.4.0"

repositories {
mavenCentral()
Expand Down Expand Up @@ -41,7 +41,7 @@ dependencies {

implementation("org.apache.ivy:ivy:2.5.2")

implementation("tools.jackson.core:jackson-databind:3.0.4")
implementation("tools.jackson.core:jackson-databind:3.1.0")
implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.0.4")
implementation("tools.jackson.module:jackson-module-kotlin:3.0.4")

Expand Down
18 changes: 10 additions & 8 deletions src/main/kotlin/net/integr/backbone/Backbone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import org.jetbrains.annotations.ApiStatus
* @since 1.0.0
*/
object Backbone {
//TODO: dialogues, command help builder
/**
* Backbones internal storage pool. **Important:** Do not use this.
* Create a new pool instead:
Expand Down Expand Up @@ -109,12 +108,15 @@ object Backbone {

/**
* The internally checked plugin instance.
* Null if we are in a testing environment.
* Null if we are not in a plugin environment.
*
* This is used to avoid null checks in the codebase.
* If you are not in a plugin environment, you can safely ignore this.
*
* @since 1.0.0
*/
private val pluginInternal: JavaPlugin? by lazy {
Utils.tryOrNull { JavaPlugin.getPlugin(BackboneServer::class.java) } // For testing purposes
Utils.tryOrNull { JavaPlugin.getPlugin(BackboneServer::class.java) } // For testing purposes we allow null here
}

/**
Expand Down Expand Up @@ -144,28 +146,28 @@ object Backbone {

/**
* Registers a listener to the server's plugin manager and the internal event bus.
* 1. Registers the listener with the internal event bus.
* 2. Registers the listener with the plugin manager.
*
* @param listener The listener to register.
*
* @since 1.0.0
*/
fun registerListener(listener: Listener) {
LOGGER.info("Registering listener: ${listener.javaClass.name}")
SERVER.pluginManager.registerEvents(listener, PLUGIN)
EventBus.register(listener)
SERVER.pluginManager.registerEvents(listener, PLUGIN)
}

/**
* Removes a listener from the server's plugin manager and the internal event bus.
*
* @param listener The listener to register.
* @param listener The listener to unregister.
*
* @since 1.0.0
*/
fun unregisterListener(listener: Listener) {
LOGGER.info("Unregistering listener: ${listener.javaClass.name}")
HandlerList.unregisterAll(listener)
EventBus.unregister(listener)
HandlerList.unregisterAll(listener)
}

/**
Expand Down
19 changes: 19 additions & 0 deletions src/main/kotlin/net/integr/backbone/BackboneLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,26 @@ class BackboneLogger(name: String) : Logger(name, null) {
if (record.level == Level.SEVERE) logFile.appendText(fileMessage + "\n")
}

/**
* Flushes any buffered output.
*
* This implementation is intentionally empty as the handler writes directly to
* the console (via `println`) and file (via `appendText`), both of which handle
* their own flushing automatically.
*
* @since 1.0.0
*/
override fun flush() {}

/**
* Closes the handler and releases any associated resources.
*
* This implementation is intentionally empty as the handler does not maintain
* any resources that require explicit cleanup. The log file is opened and closed
* on each write operation, and console output requires no cleanup.
*
* @since 1.0.0
*/
override fun close() {}
}

Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/net/integr/backbone/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

package net.integr.backbone

import net.kyori.adventure.builder.AbstractBuilder
import kotlin.reflect.full.declaredMemberFunctions

/**
* Utility functions for various tasks.
* @since 1.0.0
Expand Down Expand Up @@ -54,4 +57,41 @@ object Utils {
fun isUid(string: String): Boolean {
return string.matches("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$".toRegex())
}

/**
* Used to more easily get the result of a builder with applied block.
*
* Example:
* ```kotlin
* val builder = Something.builder()
* builder.block()
* val result = builder.build()
* ```
*
* is changed to
*
* ```kotlin
* val result = blockBuild(Something.builder(), block)
* ```
*
* Invokes a builders build method via reflection.
* Does not run any safety checks. It is your job to figure out
* if this will work or not.
*
* @param T the builder class
* @param U the builders result class
* @param builder the builder instance
* @param block the block to apply to the builder
* @since 1.4.0
*/
inline fun <reified T : Any, U> blockBuild(builder: T, block: T.() -> Unit): U {
builder.block()
// Assume a build method is there
val method = builder::class.java.getDeclaredMethod("build")

// It is the users duty to only call this on builders with this signature
@Suppress("UNCHECKED_CAST")
val result = method.invoke(builder) as U
return result
}
}
Loading