ADR-057 — Platform-agnostic addon SPI¶
Status: Accepted Date: 2026-05-30
Context¶
RTP's reference addons (RTP_ExampleAddon, LeafRTPGuiAddon) were authored as Bukkit
plugins: each extends org.bukkit.plugin.java.JavaPlugin, ships a plugin.yml with a
hard depend: RTP, and is discovered and enabled by the Bukkit plugin manager. The
Bukkit-family platforms (Bukkit/Paper/Folia) inherit this path for free, but Fabric and
the proxy JVMs (Velocity/BungeeCord) have no Bukkit plugin loader, so an addon jar never
loads there.
This is inconsistent with the project's architecture: rtp-core and rtp-api are
platform-agnostic, and almost everything the example addons actually do is already
platform-neutral: config registration via RTP.configs.putParser(...), safety
predicates via RTPAPI.hooks().verifiers().register(...), reload hooks via
Configs.onReload(...), and the teleport/query delegates on RTPAPI. Only two
touch-points tied the addons to Bukkit:
- Loading via
JavaPlugin+plugin.yml. - Events via
org.bukkit.event.Listenerconsuming the Bukkit-onlyio.github.dailystruggle.rtp.bukkit.events.PostTeleportEvent.
Decision¶
Introduce a platform-agnostic addon SPI and discovery mechanism:
io.github.dailystruggle.rtp.api.addon.RTPAddon(inrtp-api): a lifecycle interface withonLoad(),onUnload(), andname(). Implementations declare a public no-arg constructor and register themselves inMETA-INF/services/io.github.dailystruggle.rtp.api.addon.RTPAddon.io.github.dailystruggle.rtp.common.addon.AddonRegistry(inrtp-core, exposed asRTP.addons): discovers addons viajava.util.ServiceLoaderand/or accepts programmaticregister(RTPAddon)calls from a platform adapter.loadAll()invokesonLoad()once per addon after core initialisation has settled (scheduled as a startup task soRTPAPIdelegates installed by the platform adapter are visible);unloadAll()invokesonUnload()duringRTP.stop(). Per-addon failures are isolated so one bad addon cannot abort startup/shutdown of its peers.- Events: addons observe teleport lifecycle through the existing platform-agnostic
TeleportPipelineTask.teleportPostActions(and siblingsetup/load/cleanuplists, plusRTPTeleportCancel.postActions) rather than the BukkitPostTeleportEvent. These lists are already consumed cross-platform by the Bukkit and Fabric effects handlers, so the Bukkit event was a redundant, platform-locked mirror.
The reference RTP_ExampleAddon is ported to implement RTPAddon with zero org.bukkit.*
imports; its plugin.yml and Bukkit Listener are removed and its build trimmed to
rtp-api + rtp-core.
Alternatives Considered¶
| Alternative | Why Rejected |
|---|---|
Keep JavaPlugin loading, add per-platform loaders |
Duplicates loader logic per platform and keeps org.bukkit.* in addons; defeats the platform-agnostic goal. |
A bespoke RTP event bus in rtp-api |
Unnecessary: TeleportPipelineTask already exposes platform-agnostic post-action runnable lists fired on every platform. |
Wrap the runnable lists behind an RTPAPI.hooks().lifecycle() facade now |
Deferred as optional polish; not required to ship the platform-neutral loader and can be added without breaking the SPI. |
Consequences¶
- Positive: Addons run on every RTP platform (Bukkit/Paper/Folia, Fabric, and proxy JVMs
where the used API surface permits) with no Bukkit dependency. Discovery is pure JDK
(
ServiceLoader). The SPI mirrors the existingRTPAPIdelegate/facade and ADR-026 hook patterns. Lifecycle is uniform and failure-isolated. - Negative / Trade-offs: Existing third-party addons authored against
JavaPluginare not auto-migrated; a Bukkit back-compat shim (a thinJavaPluginthat forwards toRTP.addons.register(...)) can be provided per platform if needed. Addons touchingRTP.configs/world state remain backend-only; proxy JVMs only supportRTPAPIquery/teleport-level addons (documented on the SPI).
References¶
rtp-api/src/main/java/io/github/dailystruggle/rtp/api/addon/RTPAddon.javartp-core/src/main/java/io/github/dailystruggle/rtp/common/addon/AddonRegistry.javartp-core/src/main/java/io/github/dailystruggle/rtp/common/RTP.java(RTP.addons, startup load,stop()unload)addons/RTP_ExampleAddon/(ported reference addon +META-INF/servicesentry)rtp-core/src/test/java/io/github/dailystruggle/rtp/common/addon/AddonRegistryTest.java- ADR-026 (external hook API surface),
docs/dev/EXTERNAL_HOOKS.md