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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ of handling objects.

- **Java 17+**
- **Spigot 1.8.8+** (compiled against latest, runtime-compatible back to 1.8.8)
- **Paper** for Adventure-based APIs (`Text.parseMini`, `Text.tellComponent`, `mini:` message
prefix). These methods throw `UnsupportedOperationException` on vanilla Spigot. Use
`BasePlugin#isPaper()` to gate calls if you target both platforms.

## Documentation

Expand Down
8 changes: 6 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Spigot plugin framework with DI, commands, menus, schedulers, and cross-version support for Minecraft 1.8.8 - 1.21.11.
description: Spigot plugin framework with DI, commands, menus, schedulers, and cross-version support back to Minecraft 1.8.8.
---

# Introduction
Expand Down Expand Up @@ -30,7 +30,11 @@ PluginBase streamlines Spigot plugin development by providing ready-to-use compo

## Version Compatibility

Minecraft 1.8.8 - 1.21.11. Java 17+.
Minecraft 1.8.8+. Java 17+.

Adventure-based APIs (`Text.parseMini`, `Text.tellComponent`, `mini:` message prefix) require
Paper at runtime and throw `UnsupportedOperationException` on vanilla Spigot. All other features
work on both Paper and Spigot.

## Quick Start

Expand Down
12 changes: 9 additions & 3 deletions docs/core-features/text-and-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The `Text` utility class handles all message formatting and sending. It supports
|---|---|---|---|
| Legacy codes | `&` + code | `&a` (green), `&l` (bold) | Any version |
| HEX colors | `<#RRGGBB>` | `<#FF5733>` | Spigot 1.16+ |
| MiniMessage | `mini:` prefix | `mini:<gradient:red:blue>text</gradient>` | Adventure |
| MiniMessage | `mini:` prefix | `mini:<gradient:red:blue>text</gradient>` | Paper (Adventure) |
| Color scheme | `&p`, `&s`, `&t` | `&pPrimary color` | `ColorScheme` configured |

### Legacy and HEX Colors
Expand Down Expand Up @@ -49,7 +49,7 @@ Text.tell(player, "&pPrimary &sSecondary &tTertiary");
|---|---|---|---|
| `tell(CommandSender, String)` | Yes | Yes | Standard message |
| `tellRaw(CommandSender, String)` | No | Yes | Colors only, no prefix |
| `tellComponent(Player, Component)` | No | No | Sends an Adventure `Component` directly |
| `tellComponent(Player, Component)` | No | No | Sends an Adventure `Component` directly (Paper only) |
| `tellCentered(Player, String)` | No | Yes | Centered in chat; may not work with custom fonts or HEX colors |

```java
Expand Down Expand Up @@ -127,7 +127,13 @@ Component comp = Text.parseMini("<gradient:red:blue>Text</gradient>");
Text.tellComponent(player, comp);
```

The `MINI_MESSAGE` field on `Text` exposes the underlying `MiniMessage` instance if you need direct access.
Call `Text.miniMessage()` to access the underlying `MiniMessage` instance directly.

> **Paper required.** `parseMini`, `legacyParseMini`, `legacySerialize`, `tellComponent`,
> `miniMessage()`, and the `mini:` prefix in `tell`/`tellRaw`/`tellLocalized`/`tellLocalizedRaw`
> all require a Paper-derived server (Paper, Folia, Pufferfish, Purpur). They throw
> `UnsupportedOperationException` on vanilla Spigot, where Adventure is not provided. Use
> `BasePlugin#isPaper()` to gate calls if you target both platforms.

## Formatting Utilities

Expand Down
25 changes: 13 additions & 12 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
[versions]
shadow = "9.4.1"
spotless = "8.4.0"
spotless = "8.5.1"
errorprone-plugin = "5.1.0"
errorprone-core = "2.35.1"
nmcp = "1.4.4"
errorprone-core = "2.42.0"
nmcp = "1.5.0"

spigot-api = "1.21.11-R0.1-SNAPSHOT"
spigot-api = "26.1.2-R0.1-SNAPSHOT"
annotations = "26.1.0"
jsr305 = "3.0.2"
lombok = "1.18.44"
lombok = "1.18.46"

authlib = "6.0.57"
lamp = "4.0.0-rc.16"
xseries = "13.6.0"
xseries = "13.7.0"
adventure-api = "4.26.1"
adventure-platform = "4.4.1"
adventure-text-minimessage = "4.26.1"
adventure-text-serializer-legacy = "4.26.1"
expiringmap = "0.5.11"

hikaricp = "7.0.2"
sqlstreams = "1.0.0"

mongodb-driver = "5.6.4"
mongodb-driver = "5.7.0"

jedis = "7.4.1"
jedis = "7.5.0"
commons-pool2 = "2.13.1"

junit-jupiter = "6.0.3"
assertj = "3.27.7"
mockito = "5.23.0"
guava = "33.4.8-jre"
guava = "33.6.0-jre"

[libraries]
spigot-api = { module = "org.spigotmc:spigot-api", version.ref = "spigot-api" }
Expand All @@ -42,8 +43,8 @@ lamp-bukkit = { module = "io.github.revxrsal:lamp.bukkit", version.ref = "lamp"
lamp-brigadier = { module = "io.github.revxrsal:lamp.brigadier", version.ref = "lamp" }
xseries = { module = "com.github.cryptomorin:XSeries", version.ref = "xseries" }
adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure-api" }
adventure-platform-bukkit = { module = "net.kyori:adventure-platform-bukkit", version.ref = "adventure-platform" }
adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-api" }
adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure-text-minimessage" }
adventure-text-serializer-legacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure-text-serializer-legacy" }
expiringmap = { module = "net.jodah:expiringmap", version.ref = "expiringmap" }

hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" }
Expand Down
19 changes: 8 additions & 11 deletions pluginbase-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@ plugins {

dependencies {
compileOnly(libs.authlib)
compileOnly(libs.adventure.api)
compileOnly(libs.adventure.text.minimessage)
compileOnly(libs.adventure.text.serializer.legacy)

implementation(libs.bundles.lamp)
implementation(libs.xseries)
implementation(libs.adventure.api) {
exclude(group = "net.kyori", module = "adventure-bom")
}
implementation(libs.adventure.platform.bukkit) {
exclude(group = "net.kyori", module = "adventure-bom")
exclude(group = "net.kyori", module = "adventure-api")
}
implementation(libs.adventure.text.minimessage)
implementation(libs.expiringmap)

testImplementation(libs.spigot.api)
testImplementation(libs.adventure.api)
testImplementation(libs.adventure.text.minimessage)
testImplementation(libs.adventure.text.serializer.legacy)
}

tasks.shadowJar {
relocate("revxrsal.commands", "dev.demeng.pluginbase.lib.lamp")
relocate("com.cryptomorin.xseries", "dev.demeng.pluginbase.lib.xseries")
relocate("net.jodah.expiringmap", "dev.demeng.pluginbase.lib.expiringmap")
relocate("net.kyori.adventure", "dev.demeng.pluginbase.lib.adventure")
relocate("net.kyori.examination", "dev.demeng.pluginbase.lib.examination")
relocate("net.kyori.option", "dev.demeng.pluginbase.lib.option")
relocate("com.google.auto.service", "dev.demeng.pluginbase.lib.autoservice")
relocate("org.jspecify.annotations", "dev.demeng.pluginbase.lib.jspecify")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
Expand All @@ -49,9 +48,6 @@ public final class BaseManager {
/** The translator used for handling localized messages. */
@NotNull @Getter @Setter private static volatile Translator translator;

/** The BukkitAudiences instance to use for Adventure. Should be set when your plugin enables. */
@Getter @Setter private static volatile BukkitAudiences adventure;

/** The settings the library should use. */
@NotNull @Getter @Setter
private static volatile BaseSettings baseSettings = new BaseSettings() {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package dev.demeng.pluginbase.plugin;

import dev.demeng.pluginbase.BaseSettings;
import dev.demeng.pluginbase.Common;
import dev.demeng.pluginbase.Schedulers;
import dev.demeng.pluginbase.ServerProperties;
import dev.demeng.pluginbase.Services;
Expand All @@ -40,7 +41,7 @@
import dev.demeng.pluginbase.terminable.module.TerminableModule;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import java.util.function.Function;
import org.bukkit.event.Listener;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
Expand All @@ -63,6 +64,11 @@ public abstract class BasePlugin extends JavaPlugin implements TerminableConsume
/** The dependency injection container for this plugin. */
private DependencyContainer dependencyContainer;

/**
* Cached result of Paper-server detection; {@code null} until {@link #isPaper()} is first called.
*/
private static volatile Boolean paperCache;

@Override
public final void onLoad() {
BaseManager.setPlugin(this);
Expand All @@ -85,7 +91,6 @@ public final void onEnable() {
.bindWith(this.terminableRegistry);

BaseManager.setTranslator(Translator.create());
BaseManager.setAdventure(BukkitAudiences.create(this));

bindModule(new MenuManager());

Expand All @@ -97,11 +102,6 @@ public final void onDisable() {

disable();

if (getAdventure() != null) {
getAdventure().close();
BaseManager.setAdventure(null);
}

this.terminableRegistry.closeAndReportException();
BaseExecutors.shutdown();

Expand Down Expand Up @@ -292,12 +292,30 @@ public Translator getTranslator() {
}

/**
* Gets the BukkitAudiences instance to use for Adventure.
* Whether the server is running Paper (or a Paper fork like Pufferfish, Purpur, Folia). Returns
* {@code false} on vanilla Spigot or Bukkit. Cached after the first call. Detection looks for
* {@code io.papermc.paper.ServerBuildInfo}, which has shipped in Paper since 2024; older Paper
* builds will be reported as non-Paper.
*
* @return The BukkitAudiences instance to use for Adventure
* <p>This is exposed for downstream plugins that need to gate Adventure-using code (e.g. {@link
* dev.demeng.pluginbase.text.Text#parseMini(String)}, {@link
* dev.demeng.pluginbase.text.Text#tellComponent}). PluginBase itself does not consult this flag
* internally; {@code Text} relies on a {@link LinkageError} boundary instead so that detection
* cannot drift out of sync with actual class availability.
*
* @return true on Paper-derived servers, false otherwise
*/
public BukkitAudiences getAdventure() {
return BaseManager.getAdventure();
public boolean isPaper() {
Boolean cached = paperCache;
if (cached == null) {
cached = detectPaper(Common::checkClass);
paperCache = cached;
}
return cached;
}

static boolean detectPaper(@NotNull final Function<String, Class<?>> classLookup) {
return classLookup.apply("io.papermc.paper.ServerBuildInfo") != null;
}

/**
Expand Down
Loading