-
Notifications
You must be signed in to change notification settings - Fork 10
Addon Troubleshooting
When an addon "does nothing", the cause is almost always one of a handful of well-known mistakes: the loader never saw the jar, the ServiceLoader entry is missing, code ran before core was ready, or work was scheduled on the wrong thread. This page is the checklist. Work top to bottom; each symptom lists the fastest way to confirm and fix it.
!!! tip "First, confirm LeafRTP even tried to load your addon"
On startup LeafRTP logs an [ADDONS] line listing every addon it discovered. If your addon is not in that line, the problem is discovery (sections 1-3 below), not your code. If it is listed but does nothing, the problem is lifecycle or threading (sections 4-6).
Confirm: search the server log for [ADDONS].
Likely causes, in order:
| Cause | Fix |
|---|---|
| Jar is in neither scanned location | Drop the addon jar into the LeafRTP-owned plugins/RTP/addons/ folder or the server's plugins/ folder (Fabric/NeoForge: the platform's RTP config dir). Both are scanned. See Addon loading. |
addons/ folder missing/empty and jar not in plugins/
|
Create plugins/RTP/addons/ and drop the jar in (or place it in plugins/). A missing or empty addons/ folder is a silent no-op by design. |
Missing META-INF/services entry |
Ship META-INF/services/io.github.dailystruggle.rtp.api.addon.RTPAddon containing your implementation's fully-qualified class name. Without it ServiceLoader finds nothing. |
| Service file points at the wrong class | The single line must be the exact FQN of your RTPAddon implementation, no .class, no trailing junk. |
| Annotation processor stripped the service file | If you generate the file, confirm it survived the shade/jar step (`jar tf youraddon.jar |
Confirm: look for a ServiceConfigurationError or a stack trace next to the [ADDONS] line.
Likely causes:
- Your
RTPAddonhas no public no-arg constructor.ServiceLoaderneeds one. - The class is
abstract, non-public, or a non-static inner class. - A class it references at construction time is missing at runtime (you compiled against something you did not also ship or
compileOnly).
- The
ServiceLoaderSPI is platform-neutral and works everywhere - if it works on one platform and not another, the difference is placement, not code. Confirm the jar is in the correct per-platform location (see the Addon loading table). - If your addon touches
org.bukkit.*types directly it willNoClassDefFoundErroron Fabric/NeoForge. Keep platform code out of the addon, or guard it behind a platform check and route throughRTP.serverAccessor.
The number-one cause: you registered too early.
- Do all registration from
RTPAddon.onLoad(), not from a static initializer, a field initializer, or your ownJavaPluginconstructor. Contract-tier entry points throwIllegalStateException(S-006) when called before core is ready - they do not silently no-op, so check the log for that exception. - A config parser registered with
RTP.configs.putParser(...)must be re-registered on reload: hookConfigs.onReload(...), or your file will work once and vanish after/rtp reload. - A verifier registered via
RTPAPI.hooks().verifiers().register(...)only affects future selections. Locations already in the cache were verified before your hook existed; clear the cache (or/rtp scan reset) to re-verify with the new rule.
This is a threading-contract violation. The engine is strict about it on purpose.
| Symptom | Cause | Fix |
|---|---|---|
ThreadAccessException (Folia) |
You touched world/player state from the wrong region thread, or used a raw Thread/Executors. |
Schedule through RTP.scheduler (it maps to the right thread per platform). Never spawn your own threads in addon code. |
Server stalls during /rtp
|
Your verifier or callback blocked, or did main-thread chunk I/O. | Verifiers run async and must not block or load chunks on the main thread (S-005). Make them pure/cheap. |
| Teleports silently stop happening | Your verifier or callback swallowed an exception. | Never catch-and-ignore (S-004). Let it propagate or log via RTP.log(...). |
| State touched off-tick after a network call | You used a NetworkTransport future result directly. |
Those futures complete on a transport-owned executor; hop back via RTP.scheduler before touching players. See Cross-server RTP for addons. |
- Read the
RTPResultyou got back - it is never a silent no-op. It tells you whether the request succeeded, was queued (a coordinate is being generated; the player teleports when it is ready), or was rejected with a reason (cooldown, cost, permission, no valid destination). Surface that reason to the player instead of assuming failure. - If results are always "queued" and never complete, the region's cache is empty and generation is not keeping up - run
/rtp scanand check performance.yml.
-
getAllowedTargets(player)is permission-filtered. Empty list means the player lacks the region/world permissions, not that the API is broken. -
getTargetStatus(...)reflects live cooldown/cost; if it looks stale, you are caching it - re-query per open. - Remote (cross-server) rows only exist when a
NetworkTransportbinding is installed; with none, fall back to local targets. See Cross-server RTP for addons.
- Capture the full startup log (the
[ADDONS]line and any stack trace) and/rtp infooutput. - Confirm your build pins a release tag and uses
compileOnly/provided(notimplementation) - see API stability. - Compare against the working reference: Example addon.
See also: Addon development, Addon loading, Hooks and verifiers.