-
Notifications
You must be signed in to change notification settings - Fork 10
Example Addon
addons/LeafRTPCountdownAddon is the canonical reference addon. It is platform-agnostic: it implements the RTPAddon SPI, is discovered via ServiceLoader, and runs unchanged on Bukkit / Spigot / Paper / Folia, Fabric, and NeoForge - with zero org.bukkit.* imports and no plugin loader. It deliberately demonstrates the four interfaces most addons need without any real business logic, so it doubles as a working template.
Read it end-to-end before writing your own. This page summarises the four interfaces it exercises.
| File | Role |
|---|---|
build.gradle |
compileOnly for rtp-api (the RTPAddon SPI) and rtp-core (Configs / ConfigParser / TeleportPipelineTask). No spigot-api. |
META-INF/services/io.github.dailystruggle.rtp.api.addon.RTPAddon |
The ServiceLoader descriptor naming the RTPAddon implementation. This is how LeafRTP discovers the addon on every platform. |
countdown.yml |
Example YAML config the addon ships with. Keys match the enum constants in CountdownKeys. |
CountdownKeys.java |
Typed enum of the YAML keys. ConfigParser<E> is generic over this enum. |
RTPCountdownAddon.java |
The RTPAddon implementation. Wires everything up in onLoad(). |
ExampleCountdownHooks.java |
Two platform-agnostic teleport countdowns built on RTP.scheduler / RTP.serverAccessor - no org.bukkit.*. |
Create an enum (CountdownKeys) whose constants name your YAML keys, then register a ConfigParser against LeafRTP's Configs registry so that /rtp reload picks up your file and /rtp config tab-completes it:
RTP.configs.putParser(
new ConfigParser<>(
CountdownKeys.class,
"example", // YAML basename => countdown.yml
"1.0", // schema version
RTP.serverAccessor.getPluginDirectory(),
null,
RTP.configs.fileDatabase,
this.getClass().getClassLoader()));Contribute a predicate the pipeline evaluates asynchronously for every candidate location:
RTPAPI.hooks().verifiers().register(coords -> myCheckReturnsTrueIfSafe(coords));!!! warning "Rules of engagement"
Return quickly (the call happens on a worker thread). No synchronous chunk I/O on the main thread (S-005). Do not swallow failures silently (S-004) - log with RTP.log(Level.WARNING, msg, t). return true to accept the location, false to reject it (LeafRTP rerolls). For checks that await a database/network call, use registerAsync(...). See Hooks and verifiers.
Instead of Bukkit events, observe the teleport lifecycle through the platform-agnostic runnable lists on TeleportPipelineTask (and RTPTeleportCancel). These fire on every platform:
TeleportPipelineTask.teleportPostActions.add(task -> onPostTeleport(task));Available lists: setupPreActions / setupPostActions, loadPreActions / loadPostActions, teleportPreActions / teleportPostActions, cleanupPreActions / cleanupPostActions, and RTPTeleportCancel.postActions.
!!! note "These callbacks may run off the main thread"
Bounce any platform-facing work onto the appropriate thread via RTP.scheduler.
When operators run /rtp reload, LeafRTP drops its parsers and replays the hook list. Re-register your parser inside the callback so your config survives the reload:
Configs.onReload(() -> RTP.configs.putParser(buildParser()));LeafRTP's optional combat gate asks one question - "is this player in combat right now?" - through the single-binding PvPCombatStateRegistry. If your combat plugin is not one of the bundled integrations, bind your own authority from onLoad():
// Single-binding: the last bind wins and replaces the native tracker (and any bundled adapter).
RTPAPI.hooks().pvpCombatState().bind(uuid -> myCombatPlugin.isTagged(uuid));Provider is a functional interface - boolean isInCombat(UUID player). true = in combat (teleport refused/delayed per safety.yml), false = free to teleport. Offline/unknown UUIDs should return false. Call RTPAPI.hooks().pvpCombatState().clear() from onUnload() so your provider does not outlive your addon.
From the repository root:
.\gradlew :addons:LeafRTPCountdownAddon:buildThe jar lands in addons/LeafRTPCountdownAddon/build/libs/. For where to put it and how to confirm it loaded, see Addon loading.
- A
META-INF/services/io.github.dailystruggle.rtp.api.addon.RTPAddonentry names yourRTPAddon. -
onLoad()registers a parser and aConfigs.onReloadcallback for it. -
onUnload()releases anythingonLoad()allocated. - Long-running work is scheduled via
RTP.scheduler, never run on the main thread. - Any allocated chunk ticket or pipeline task is released on every exit path (see
MemoryTracker). - Failures go through
RTP.log(Level.WARNING, msg, t)- neverprintStackTrace().
- Addon development - landing page and happy path.
- Addon loading - discovery, classpath placement, lifecycle.
- Hooks and verifiers - the full behavior-modification catalog.