docs/paper/teleportation/SKILL.md
# Teleportation Skill — Paper ## Purpose Reference this skill when teleporting players or entities in Paper 1.21. Covers async teleport (`teleportAsync`), safe destination detection, `EntityTeleportEvent`, and cross-world teleportation patterns. ## When to Use This Skill - Teleporting a player to a saved home location - Sending a player to a warp point safely (ensuring the destination is not inside a block) - Cross-world teleportation (e.g., hub → game world) - Preventing certain entity telepo
npx skillsauth add MrPippi/MPS docs/paper/teleportationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill when teleporting players or entities in Paper 1.21. Covers async teleport (teleportAsync), safe destination detection, EntityTeleportEvent, and cross-world teleportation patterns.
| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| Player#teleportAsync(Location) | Async teleport (returns CompletableFuture<Boolean>) | Preferred in Paper 1.21 |
| Player#teleportAsync(Location, PlayerTeleportEvent.TeleportCause) | Async with cause | TeleportCause.PLUGIN for programmatic |
| Entity#teleport(Location) | Sync teleport (main thread only) | Still works; blocks tick briefly for chunk load |
| Location#isSafe() | Paper extension — checks if standing here is safe | Not in vanilla Bukkit API |
| Block#isPassable() | Check if a block can be walked through | For manual safety checks |
| Block#isSolid() | Check if block is solid | |
| PlayerTeleportEvent | Fires before a player teleports | Cancellable |
| EntityTeleportEvent | Fires before any entity teleports | Cancellable; fires for non-player entities too |
| PlayerTeleportEvent.TeleportCause | Reason for teleport | PLUGIN, COMMAND, NETHER_PORTAL, etc. |
package com.yourorg.myplugin.teleportation;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class TeleportService {
private final JavaPlugin plugin;
public TeleportService(JavaPlugin plugin) {
this.plugin = plugin;
}
// Async teleport with feedback (Paper 1.21 preferred pattern)
public void teleportToHome(Player player, Location home) {
// Find a safe spot at the destination
Location safe = findSafeLocation(home);
if (safe == null) {
player.sendMessage(
Component.text("Home location is unsafe. Please reset it.")
.color(NamedTextColor.RED)
);
return;
}
player.sendActionBar(Component.text("Teleporting...").color(NamedTextColor.YELLOW));
player.teleportAsync(safe, PlayerTeleportEvent.TeleportCause.PLUGIN)
.thenAccept(success -> {
// This callback may run on an async thread
if (success) {
// Schedule message on main thread (player mutation)
org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> {
if (player.isOnline()) {
player.sendMessage(
Component.text("Teleported to your home!")
.color(NamedTextColor.GREEN)
);
}
});
} else {
org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> {
if (player.isOnline()) {
player.sendMessage(
Component.text("Teleport failed.").color(NamedTextColor.RED)
);
}
});
}
})
.exceptionally(ex -> {
plugin.getSLF4JLogger().error("Teleport failed for {}", player.getName(), ex);
return null;
});
}
// Find the highest safe location at x,z — useful for surface warps
public Location findSafeLocation(Location base) {
World world = base.getWorld();
if (world == null) return null;
// Check the exact location first (e.g., saved homes at exact coords)
if (isSafe(base)) return base.clone().add(0, 0, 0);
// Walk upward from base to find air above solid ground
Location check = base.clone();
for (int dy = 0; dy <= 5; dy++) {
check.setY(base.getY() + dy);
if (isSafe(check)) return check;
}
return null;
}
// A location is safe if feet and head blocks are passable and floor is solid
private boolean isSafe(Location loc) {
World world = loc.getWorld();
if (world == null) return false;
Block feet = world.getBlockAt(loc);
Block head = world.getBlockAt(loc.clone().add(0, 1, 0));
Block floor = world.getBlockAt(loc.clone().add(0, -1, 0));
return feet.isPassable()
&& head.isPassable()
&& floor.isSolid()
&& !floor.isPassable();
}
}
Using sync teleport() for cross-world or long-distance teleports in Paper: Synchronous teleport() loads the destination chunk on the main thread, causing a brief freeze. Prefer teleportAsync() in Paper 1.21 for all teleports except trivial short-distance moves within the same loaded chunk.
teleportAsync result callback on async thread: The CompletableFuture<Boolean> callback from teleportAsync may execute on an async thread. Do not call Bukkit methods (like player.sendMessage()) directly inside .thenAccept() — schedule them with runTask(plugin, ...).
Teleporting to a Location with a null World: Always check location.getWorld() != null before teleporting. Serialised locations from config may have a null world if the world name changed.
Not checking player.isOnline() in async callback: The player may disconnect between the teleport initiation and the callback. Guard with if (player.isOnline()).
teleportAsync() is the recommended approach and handles async chunk loading internally. The sync teleport() still works for cases where the destination chunk is guaranteed to be loaded.Location#isSafe() is a convenience method only available on Paper builds, not in vanilla Bukkit.development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag