RTP API
Random teleportation - requests, warmups, world search, and results. Package net.godlycow.org.essc.api.rtp.
Overview
The RTP module runs the whole random-teleport pipeline: validate the request, check permissions and cooldowns, start a warmup, search a world for a safe spot, teleport - with 9 events along the way to veto or extend any stage. Two entry points matter most: requestRtp() for the full async flow, and the synchronous state/cooldown queries.
RtpWorldSettings describes each configured world (radii, blocked biomes, enabled flag), and per-world permissions like essentialsc.rtp.world.overworld control who may use it.
RtpManager
Obtain via api.getRtpManager().
System & config
| Method | Returns | Description |
|---|---|---|
| boolean isRtpSystemEnabled() | boolean | Whether the RTP system is enabled in config. |
| Collection<RtpWorldSettings> getConfiguredWorlds() | Collection<RtpWorldSettings> | Settings for every RTP-enabled world. |
| RtpWorldSettings getWorldSettings(String worldName) | RtpWorldSettings | Settings for a world, or null. |
| boolean isWorldEnabled(String worldName) | boolean | Whether a world is configured and enabled for RTP. |
| boolean isWorldBorderGloballyEnabled() | boolean | Global config flag for clamping to world borders. |
| long getGlobalCooldownSeconds() | long | Default cooldown applied to every world. |
| long getGlobalWarmupSeconds() | long | Default warmup applied to every world. |
| boolean isCancelOnMovementEnabled() | boolean | Whether moving during the warmup cancels the teleport. |
| boolean areParticlesEnabled() | boolean | Whether destination particles are shown. |
| int getMaxSearchAttempts() | int | Max safe-location search attempts before giving up. |
| int getGlobalMinY() | int | Global minimum Y for destination search. |
| int getGlobalMaxY() | int | Global maximum Y for destination search. |
Player state & permissions
| Method | Returns | Description |
|---|---|---|
| boolean isRtpInProgress(Player player) | boolean | Whether the player has an active RTP (warmup or search). |
| boolean isOnCooldown(Player player) | boolean | Whether the player is inside the RTP cooldown. |
| long getRemainingCooldownSeconds(Player player) | long | Seconds left on the cooldown, or 0. |
| boolean hasBypassPermission(Player player, String type) | boolean | Checks a bypass permission by type, e.g. cooldown, warmup, movement. |
| boolean hasWorldPermission(Player player, String worldName) | boolean | Checks essentialsc.rtp.world.<world> (and wildcard). |
| RtpPlayerState getPlayerState(Player player) | RtpPlayerState | Snapshot of a player's RTP state. |
| RtpRequest getActiveRequest(Player player) | RtpRequest | The player's current request, or null. |
| List<String> getAvailableWorldNamesFor(Player player) | List<String> | Worlds the player has permission to RTP in. |
| int getPlayerCountInWorld(String worldName) | int | Online players in a world. |
Actions
| Method | Returns | Description |
|---|---|---|
| CompletableFuture<RtpResult> requestRtp(Player player, World world) | RtpResult | Runs the full RTP flow for a player in a world. The result tells you if it worked and why it failed. |
| void cancelPendingRtp(Player player) | void | Aborts the player's pending warmup/search. |
RtpManager rtp = api.getRtpManager();
// Guard with the same checks EssentialsC uses.
if (!rtp.isRtpSystemEnabled()) return;
if (rtp.isRtpInProgress(player)) {
player.sendMessage("An RTP is already in progress.");
return;
}
if (rtp.isOnCooldown(player) && !rtp.hasBypassPermission(player, "cooldown")) {
player.sendMessage("Cooldown: " + rtp.getRemainingCooldownSeconds(player) + "s");
return;
}
World target = player.getWorld();
if (!rtp.isWorldEnabled(target.getName())
|| !rtp.hasWorldPermission(player, target.getName())) {
player.sendMessage("RTP is not available here.");
return;
}
rtp.requestRtp(player, target).thenAccept(result -> {
if (result.wasSuccessful()) {
player.sendMessage("RTP complete at " + result.getDestination().getBlockX() + ", "
+ result.getDestination().getBlockZ());
} else {
player.sendMessage("RTP failed: " + result.getFailureReason());
}
});
RtpWorldSettings
Configuration snapshot for one RTP world.
| Method | Returns | Description |
|---|---|---|
| String getWorldName() | String | Bukkit world name. |
| String getDisplayName() | String | Friendly name shown to players. |
| int getMinRadius() | int | Minimum distance from world origin. |
| int getMaxRadius() | int | Maximum distance from world origin. |
| List<String> getBlockedBiomes() | List<String> | Biome ids excluded from the search. |
| boolean isEnabled() | boolean | Whether this world accepts RTP requests. |
RtpWorldSettings settings = rtp.getWorldSettings("world");
if (settings == null) return;
player.sendMessage("RTP range for " + settings.getDisplayName() + ": "
+ settings.getMinRadius() + " - " + settings.getMaxRadius() + " blocks");
if (settings.getBlockedBiomes().contains("minecraft:deep_ocean")) {
player.sendMessage("Careful - deep oceans are off limits!");
}
RtpPlayerState
Snapshot of a player's RTP state at a given moment.
| Method | Returns | Description |
|---|---|---|
| UUID getPlayerId() | UUID | The player. |
| boolean isRtpInProgress() | boolean | Whether an RTP is currently running. |
| boolean isOnCooldown() | boolean | Whether the player is on cooldown. |
| long getRemainingCooldownSeconds() | long | Seconds left on the cooldown. |
| long getLastRtpTimestamp() | long | Millis of the last completed RTP. |
| int getTotalRtpCount() | int | Lifetime RTP count for the player. |
| boolean hasPendingWarmup() | boolean | Whether a warmup is currently counting down. |
RtpPlayerState state = rtp.getPlayerState(player);
String status = state.isRtpInProgress()
? "in progress" : "idle";
if (state.isOnCooldown()) {
status += " (cooldown " + state.getRemainingCooldownSeconds() + "s)";
}
player.sendMessage("RTP status: " + status
+ " | total: " + state.getTotalRtpCount());
RtpRequest
Describes an in-flight RTP request, fetched with getActiveRequest().
| Method | Returns | Description |
|---|---|---|
| UUID getRequestId() | UUID | Unique id for this request. |
| Player getPlayer() | Player | The requesting player. |
| World getTargetWorld() | World | World being searched. |
| long getRequestTimestamp() | long | Millis when the request was created. |
| boolean wasWarmupRequired() | boolean | Whether a warmup applies (false if bypassed). |
| long getWarmupSeconds() | long | Warmup length in seconds. |
RtpResult
The result of requestRtp(). Always check wasSuccessful() before trusting the destination.
| Method | Returns | Description |
|---|---|---|
| boolean wasSuccessful() | boolean | Whether the RTP completed. |
| Location getDestination() | Location | Final location (null on failure). |
| World getWorld() | World | World searched. |
| String getFailureReason() | String | Human-readable failure detail, empty on success. |
| long getRequestTimestamp() | long | Millis the request started. |
| long getCompletionTimestamp() | long | Millis the RTP finished (success or fail). |
| int getSearchAttempts() | int | How many safe-spot candidates were tried. |
rtp.requestRtp(player, world).thenAccept(result -> {
if (result.wasSuccessful()) {
// getDestination() is safe to use here.
long elapsed = result.getCompletionTimestamp() - result.getRequestTimestamp();
getLogger().info(player.getName() + " RTP'd in " + result.getWorld().getName()
+ " after " + result.getSearchAttempts() + " attempts (" + elapsed + "ms)");
} else {
getLogger().info(player.getName() + " RTP failed: " + result.getFailureReason());
}
});
The RTP lifecycle
What happens when you call requestRtp():
Request validation
RtpRequestEvent (cancellable). Permission, world-permission, cooldown, and in-progress checks run; you can veto the whole request.
Warmup
RtpWarmupStartEvent (cancellable, adjustable length). Movement/leave aborts it via RtpWarmupCancelEvent.
Search
RtpSearchStartEvent (cancellable) fires before the world scan; RtpSearchCompleteEvent fires after a spot is found (location may be null on total failure).
Teleport
RtpTeleportEvent (cancellable, setDestination() to redirect) then RtpPostTeleportEvent.
Outcome
Success returns an RtpResult. Any failure - no permission, world disabled, no safe location, cancelled warmup - fires RtpFailEvent with a FailureReason.
Events
All nine RTP events live in net.godlycow.org.essc.api.rtp.event.
RtpRequestEvent - cancellable
@EventHandler
public void onRtpRequest(RtpRequestEvent event) {
// Block RTP during events.
if (isServerEventRunning()) {
event.setCancelled(true);
event.setCancelReason("RTP is disabled during server events.");
}
}
RtpWarmupStartEvent / RtpWarmupCancelEvent
@EventHandler
public void onRtpWarmupStart(RtpWarmupStartEvent event) {
// 3x warmup in the nether, instant for donors.
if (event.getWorld().getEnvironment() == World.Environment.NETHER) {
event.setWarmupSeconds(event.getWarmupSeconds() * 3);
} else if (event.getPlayer().hasPermission("vip.instantrtp")) {
event.setWarmupSeconds(0);
}
}
@EventHandler
public void onRtpWarmupCancel(RtpWarmupCancelEvent event) {
switch (event.getReason()) {
case PLAYER_MOVED -> event.getPlayer().sendMessage("RTP cancelled - you moved!");
case PLAYER_OFFLINE -> getLogger().info("RTP warmup dropped for offline player.");
case EVENT_CANCELLED -> { /* vetoed elsewhere */ }
}
}
RtpSearchStartEvent / RtpSearchCompleteEvent
@EventHandler
public void onRtpSearchStart(RtpSearchStartEvent event) {
// Refuse to search worlds with no configured settings.
if (rtp.getWorldSettings(event.getWorld().getName()) == null) {
event.setCancelled(true);
event.setCancelReason("World has no RTP settings.");
}
}
@EventHandler
public void onRtpSearchComplete(RtpSearchCompleteEvent event) {
Location loc = event.getLocation();
if (loc == null) {
getLogger().info("No safe spot found in " + event.getWorld().getName()
+ " after " + event.getAttempts() + " attempts.");
}
}
RtpTeleportEvent - cancellable, re-targetable
@EventHandler
public void onRtpTeleport(RtpTeleportEvent event) {
// Snap the destination to a spawn platform if one is configured.
Location dest = event.getDestination();
Location platform = findPlatform(dest.getWorld());
if (platform != null) {
event.setDestination(platform);
}
// Veto teleports into a locked region.
if (isLocked(dest)) {
event.setCancelled(true);
event.setCancelReason("Destination is locked.");
}
}
RtpPostTeleportEvent
@EventHandler
public void onRtpPostTeleport(RtpPostTeleportEvent event) {
Location dest = event.getDestination();
event.getPlayer().getWorld().spawnParticle(
Particle.CLOUD, dest, 40, 0.5, 1, 0.5, 0.01
);
}
RtpFailEvent
The reason is one of NO_PERMISSION, NO_WORLD_PERMISSION, ALREADY_IN_PROGRESS, COOLDOWN_ACTIVE, WORLD_DISABLED, NO_SAFE_LOCATION, TELEPORT_FAILED, WARMUP_CANCELLED, or EVENT_CANCELLED.
@EventHandler
public void onRtpFail(RtpFailEvent event) {
switch (event.getReason()) {
case NO_PERMISSION -> event.getPlayer().sendMessage("You can't use /rtp.");
case NO_SAFE_LOCATION -> event.getPlayer().sendMessage("No safe spot found - try again.");
case COOLDOWN_ACTIVE -> event.getPlayer().sendMessage("You're on RTP cooldown.");
case WARMUP_CANCELLED -> { /* already messaged */ }
default -> getLogger().info(event.getPlayer().getName() + " RTP failed: "
+ event.getReason() + " " + event.getDetailMessage());
}
}
RtpCooldownExpireEvent
@EventHandler
public void onRtpCooldownExpire(RtpCooldownExpireEvent event) {
event.getPlayer().sendActionBar("You can /rtp again!");
}
Worked examples
RTP to a random enabled world
List<String> worlds = rtp.getAvailableWorldNamesFor(player);
if (worlds.isEmpty()) {
player.sendMessage("No worlds available for RTP.");
return;
}
String chosen = worlds.get(ThreadLocalRandom.current().nextInt(worlds.size()));
World world = Bukkit.getWorld(chosen);
if (world != null) {
rtp.requestRtp(player, world);
}
Show a live cooldown in a scoreboard
// Called from your scoreboard update task (sync or async).
public String rtpLine(Player player) {
RtpPlayerState state = rtp.getPlayerState(player);
if (state.isOnCooldown()) {
return "RTP: " + state.getRemainingCooldownSeconds() + "s";
}
return "RTP: ready";
}
Cancel a stuck warmup
if (rtp.isRtpInProgress(player)) {
rtp.cancelPendingRtp(player);
player.sendMessage("Your pending RTP was cancelled.");
}