Skip to content
BlazeZDocs

CrossTPA

Teleport requests with a confirm menu and anti-trap checks, on one server or across a network.

Version
2.4.2
Runs on
Paper, Purpur, Folia, Spigot
Minecraft
1.21+
Java
21+
Works with
PlaceholderAPI, WorldGuard
On this page

CrossTPA runs /tpa and /tpahere requests. Before a request goes out or gets accepted, a small menu shows the other player's world and whether they're flying. When the countdown ends, CrossTPA checks the landing spot for common trap setups and cancels the teleport if one fails.

Accepting pins the destination to where the accepter stands at that moment, so walking off during the countdown doesn't drag the other player along. On a network, requests, player lists and teleports work across every backend server through Redis.

Permissions#

PermissionLets a playerDefault
donuttpa.tpaSend /tpa requests.OP
donuttpa.tpahereSend /tpahere requests.OP
donuttpa.acceptAccept requests.OP
donuttpa.denyDecline requests.OP
donuttpa.cancelWithdraw their own requests.OP
donuttpa.tpaallUse /tpaall.OP
donuttpa.tphereallUse /tphereall.OP
donuttpa.autoUse /tpauto.OP
donuttpa.tpatoggleUse /tpatoggle.OP
donuttpa.guiUse /tpaguitoggle.OP
donuttpa.settingOpen /tpasetting.OP
donuttpa.adminOpen /tpaadmin.OP
donuttpa.cooldown.bypassSkip the request cooldown.OP
tpa.adminRun /donuttpa reload.OP

plugin.yml also lists donuttpa.tphere and donuttpa.toggle. Nothing checks those two, so grant donuttpa.tpahere and donuttpa.tpatoggle instead.

Commands#

CommandWhat it doesPermission
/tpa <player>Asks to teleport to a player.donuttpa.tpa
/tpahere <player>Asks a player to teleport to you.donuttpa.tpahere
/tpaccept [player]Accepts a request, or your latest one without a name. Alias /tpyes.donuttpa.accept
/tpdeny [player]Declines a request, or your latest one without a name. Alias /tpno.donuttpa.deny
/tpacancelWithdraws every request you've sent.donuttpa.cancel
/tpaallSends a /tpa request to every online player. The first to accept gets you.donuttpa.tpaall
/tphereallTeleports every player on your server to you, no request.donuttpa.tphereall
/tpautoToggles auto-accepting /tpa requests.donuttpa.auto
/tpatoggleToggles receiving /tpa requests.donuttpa.tpatoggle
/tpaheretoggleToggles receiving /tpahere requests.None
/tpaguitoggleToggles the confirm menu for yourself.donuttpa.gui
/tpasettingOpens your settings menu.donuttpa.setting
/tpaadminOpens the safe-teleport admin menu.donuttpa.admin
/donuttpa reloadReloads config.yml, messages.yml and the menu files. Works from console.tpa.admin

Configuration#

FileWhat it controls
config.ymlCountdown, safety checks, cooldown, blacklists, world names and icons, sounds, MySQL and Redis.
messages.ymlEvery chat, action bar and title message, each with its own optional sound.
license.ymlYour license key.
inventories/tpa-inventory.ymlThe confirm menu.
inventories/tpa-settings.ymlThe /tpasetting menu.
inventories/tpa-admin.ymlThe /tpaadmin menu.
inventories/tpa-safe-teleport-settings.ymlThe per-check menu opened from /tpaadmin.
playerdata/<uuid>.ymlEach player's toggles, when MySQL is off.

On startup, keys missing from your config.yml are added from the defaults and your existing values are kept.

config.yml#

KeyDefaultWhat it does
tpa-countdown-seconds5Countdown before the teleport, once accepted.
smooth-countdown-decimals2Decimals on the action bar timer, 0 to 2.
allowed-walk-range1Blocks the moving player may walk during the countdown.
request-cooldown30000Milliseconds before the same sender can ask the same target again.
data.clear-time120Seconds before an unanswered request expires.
safe-teleporttrueMaster switch for the safety checks.
safe-teleport-checks.*trueThe five checks in Safe teleport.
disable-tpa-guifalsetrue turns the confirm menu off for everyone.
disable-tphereallfalsetrue removes /tphereall for everyone, including from tab completion.
blacklisted-worldsothersideWorlds where players can't send requests.
world-guard.blacklisted-regionsemptyWorldGuard regions where players can't send requests.
world-nicknamesOverworld, Nether, EndNames shown for %world% in the confirm menu.
world-iconsGRASS_BLOCK, NETHERRACK, END_STONEMaterial of the location icon per world. Unknown materials fall back to GRASS_BLOCK.
placeholders-valuesYes / NoText the placeholders return for on and off.
teleport-limitations-1Has no effect. Leave it at -1.

Sounds#

The sounds section takes Minecraft sound keys such as entity.experience_orb.pickup.

KeyPlays when
tpa-receiveA request reaches the receiver.
count-downEach second of the countdown.
accept-teleportConfirm is clicked in the confirm menu.
information-clickAn info icon or Cancel is clicked in the confirm menu.
errorCancel finds no request to drop.
tpaallOnce per player requested by /tpaall. Falls back to tpa-receive if removed.

Messages#

Every key in messages.yml is a block like this one:

messages.yml
messages:
  request-cooldown:
    enabled: true
    type: chat
    message: '&cYou''re on cooldown for sending requests to this player.'
    sound:
      enabled: true
      key: entity.villager.no
      volume: 1.0
      pitch: 1.0

type can be chat, actionbar (text under actionbar.message), title (title.main, title.sub and title.duration.fade_in, stay, fade_out in milliseconds) or list (lines under messages). enabled: false silences a message. Text takes & color codes and &#rrggbb hex colors; write hex in lowercase.

In receive-request and receive-here-request, | splits the text into lines, and a line containing <clickable> runs /tpaccept <player> when clicked.

Keys missing from your messages.yml fall back to the built-in text.

Each menu file sets a name (title), rows, and icons with slot, material, display-name and lore. Confirm menu icons take %player%, %world% and %is_flying%, and PlaceholderAPI placeholders when it's installed.

Every confirm menu (sending, sending here and accepting) uses the request-inventory section of tpa-inventory.yml. Its accept-inventory section and tpa-here-inventory.yml are generated but not used.

Install#

  1. Stop the server and put the CrossTPA jar in plugins/. On a network, do this on every backend server.
  2. Start the server once. CrossTPA creates plugins/DonutTPA/ with config.yml, messages.yml, license.yml and an inventories/ folder.
  3. Paste your license key into license-key in license.yml. Your key is under My Licenses on the BlazeZ portal; licenses linked to your Discord account show up there automatically. If yours is missing, open a License Key ticket in the BlazeZ Discord.
  4. Restart the server. If the key is missing or wrong, the console prints the reason at startup and no commands load.
  5. Give your players the permissions they need (see Permissions).
  6. After editing a file, run /donuttpa reload. Turning the database or Redis on or off needs a restart.

Warning

plugin.yml sets no defaults, so every permission is OP-only until you grant it. With LuckPerms, for example: /lp group default permission set donuttpa.tpa true, and the same for each player node you want.

Sending and accepting requests#

/tpa <player> asks to go to a player, and /tpahere <player> asks a player to come to you. Names match by prefix, so /tpa ste finds Steve. Tab completion lists every online player, network-wide in cross-server mode.

With the confirm menu on (the default), /tpa, /tpahere and /tpaccept open a menu first. It shows the other player's name, their world and whether they're flying. Confirm sends or accepts the request, Cancel drops it.

The receiver gets a chat message with a clickable line that runs /tpaccept <player>, an action bar and the tpa-receive sound. /tpaccept and /tpdeny without a name act on the most recent request you received.

A request expires after data.clear-time seconds (default 120). /tpacancel withdraws every request you've sent, and logging out does the same. One player can't send another request to the same target for request-cooldown milliseconds (default 30000, 30 seconds). donuttpa.cooldown.bypass skips that cooldown.

Countdown#

Once a request is accepted, the player who is moving gets a countdown of tpa-countdown-seconds (default 5). A chat line and the count-down sound come once per second, and the action bar timer shows smooth-countdown-decimals decimals (0, 1 or 2, default 2).

If the moving player walks more than allowed-walk-range blocks (default 1) from where they stood at accept time, the teleport cancels. Changing worlds cancels it too.

The destination is where the other player stood when the request was accepted: the accepter for /tpa, the sender for /tpahere.

/tpaall and /tphereall#

/tpaall sends a /tpa request to every other online player, network-wide in cross-server mode. The first player to accept gets you, and the rest of the requests are dropped. You hear the tpaall sound once per player requested.

/tphereall moves every other player on your server to you instantly, with no request and no safety checks. It only covers the server you're on. Set disable-tphereall: true to remove it for everyone, OPs included.

Personal toggles#

CommandEffect
/tpatoggleStop or resume receiving /tpa requests. Senders are told you aren't accepting.
/tpaheretoggleThe same for /tpahere requests.
/tpaguitoggleTurn the confirm menu off or on for yourself. /tpasetting has the same switch as a button.
/tpautoAccept incoming /tpa requests automatically, checked once per second.

/tpauto only accepts /tpa requests (never /tpahere) from players on the same server. It switches itself off when you die, so a respawn can't be used to trap you. Set disable-tpa-gui: true to turn the confirm menu off for everyone.

Toggles are saved per player in playerdata/<uuid>.yml, or in MySQL when database.enabled is true.

CrossTPA settings menu
/tpasetting opens each player's personal toggles.

Safe teleport#

When the countdown ends, CrossTPA checks the destination. If an enabled check fails, both players get the teleport-fail-safe message, the request is dropped and nobody moves.

CheckBlocks the teleport when
check-vehicleThe player at the destination is riding something (boat, minecart, horse).
check-nearby-entityAn armor stand or item frame is within 3 blocks of the destination.
check-fallingBoth blocks under the destination are air.
check-waterEither of the 2 blocks under the destination is water.
check-signA sign is at the destination block or within 2 blocks above or below it.

All five sit under safe-teleport-checks and are on by default. safe-teleport: false turns them all off.

Staff can change them in game with /tpaadmin. Left-click the Safe Teleport icon to flip the master switch, right-click it to open the per-check menu. Every click is saved to config.yml. Per-check changes apply straight away; the master switch applies after /donuttpa reload.

Note

Cross-server teleports run the checks on the destination server and don't read safe-teleport there. To disable checks on a network, turn off the single checks under safe-teleport-checks.

Blacklisted worlds and regions#

Players standing in a world from blacklisted-worlds (default otherside) or a WorldGuard region from world-guard.blacklisted-regions can't send /tpa, /tpahere or /tpaall. The receiver is never checked, so anyone can accept a request, including to leave a blacklisted area. /tphereall ignores both lists.

Cross-server setup#

CrossTPA links backend servers through Redis. Each server publishes its online players every 7 seconds, requests and messages travel through Redis, and when a countdown ends the destination server pulls the player over through your proxy.

  1. Run a Redis server that every backend server can reach.
  2. Install CrossTPA on every backend server, each with its license key in license.yml.
  3. Check the proxy. BungeeCord needs nothing. On Velocity, keep bungee-plugin-message-channel = true in velocity.toml (the default). CrossTPA uses that channel to learn each server's name and to move players between servers.
  4. On every backend server, set database.redis.enabled to true in config.yml and fill in host, port and password for your Redis server.
  5. Optional, and worth doing: set database.enabled to true and fill in database.sql on every server, all pointing at the same MySQL database. Player toggles then follow players between servers, and the request cooldown counts across the network instead of per server.
  6. Restart every backend server.
  7. Test with two players on different servers. Within about 7 seconds, /tpa should tab-complete the other player's name.
config.yml
database:
  enabled: false
  thread-pool-size: 4
  sql:
    hostname: "hostname"
    database: "database"
    username: "username"
    password: "password"
  redis:
    enabled: false
    host: "localhost"
    port: 6379
    password: "password"

hostname can include a port (db.example.com:3307); without one, CrossTPA uses 3306. thread-pool-size is the size of the MySQL connection pool. The tables are created on first start.

There is no server name to set. CrossTPA reads each backend's name from the proxy once a player is online on it, so the names in your proxy config are the ones it uses.

Each server reads its own config.yml and messages.yml. Copy your changes to every server.

Placeholders#

PlaceholderReturns
%donuttpa_tpatoggle_status%Whether the player accepts /tpa requests.
%donuttpa_tpaheretoggle_status%Whether the player accepts /tpahere requests.
%donuttpa_tpaguitoggle_status%Whether the confirm menu is on for the player. Always off while disable-tpa-gui is true.
%donuttpa_tpauto_status%Whether /tpauto is on.

Values come from placeholders-values (default Yes and No). A player who isn't loaded returns N/A.

Developer API#

net.luxcube.minecraft.api.TeleportAPI reads and changes the same toggles the commands use: isAcceptingTpa / setAcceptingTpa, isAcceptingTpHere / setAcceptingTpHere and isTeleportAuto / setTeleportAuto. Each takes a Player or a UUID and is safe from any thread. Setters save through CrossTPA's storage. For a player who isn't online on that server, setters do nothing and every method returns false.

import net.luxcube.minecraft.api.TeleportAPI;

if (!TeleportAPI.isAcceptingTpa(player)) {
    TeleportAPI.setAcceptingTpa(player, true);
}
TeleportAPI.setTeleportAuto(player.getUniqueId(), false);

Events#

CrossTPA fires Bukkit events from net.luxcube.minecraft.api.event.impl. Cancelling one stops the action silently, so your plugin sends its own message.

EventCancellableFires when
TeleportRequestSendEventYesA /tpa, /tpahere or /tpaall request is about to be sent, once per target. getSender(), getTargetUniqueId(), getTargetName(), getRequestType(), isTpaAll().
TeleportRequestAcceptEventYesA request is accepted by command, menu or /tpauto. getAccepter(), getSenderUniqueId(), getSenderName(), getRequestType(), isTpaAll().
TeleportRequestDenyEventYesA request is denied by /tpdeny or the menu. getDenier(), getSenderUniqueId(), getSenderName(), getRequestType().
TeleportCompleteEventNoThe teleport succeeded, on the server the player landed on. getPlayer(), getFrom() (null across servers), getTo(), getRequestType(), isCrossServer().

getRequestType() returns TPA or TPAHERE. Events sent from the async command thread are marked async, so don't touch the world from them without scheduling.

@EventHandler
public void onRequest(TeleportRequestSendEvent event) {
    if (isInCombat(event.getSender())) {
        event.setCancelled(true);
        event.getSender().sendMessage("You can't send requests in combat.");
    }
}

Add softdepend: [DonutTPA] to your plugin.yml. DonutTPA is the plugin's internal name.

Troubleshooting#

  • Players get a no-permission message on /tpa: every node is OP-only by default. Grant the nodes from Permissions.
  • "The teleport was not safe!": a check in Safe teleport failed at the destination. The accepter should stand on solid ground, away from signs, armor stands and item frames, and off any mount.
  • Edits to tpa-here-inventory.yml or to accept-inventory don't show: edit request-inventory in tpa-inventory.yml.
  • Players on other servers don't show up: check database.redis.enabled: true on every server, all using the same Redis, then wait 7 seconds. On Velocity, check bungee-plugin-message-channel.