Paper Plugin Guide
File mode0

Paper essentials

Worlds, locations and movement

Coordinates, worlds, directions and moving things around.

Beginner38 min read

Every thing in Minecraft is somewhere. On this page you will learn how Paper describes a position, how to measure distances and directions, how to launch players through the air, how to read and change the time, the weather and the game rules of a world, and how to teleport without dropping anyone into lava. You will build launch pads and a /top command.

Coordinates: x, y, z, yaw and pitch

You already know the F3 screen. It shows three numbers for where you stand and two for where you look. Paper uses exactly the same system:

  • x goes east (bigger) and west (smaller).
  • y goes up (bigger) and down (smaller). Sea level is at 63 in a normal world.
  • z goes south (bigger) and north (smaller). Yes, north is the negative direction; this surprises everyone once.
  • yaw is the direction you look sideways, in degrees: 0 is south, 90 is west, 180 is north and 270 (or -90) is east.
  • pitch is the direction you look up or down: -90 is straight up, 0 is level and 90 is straight down.
Coordinates and directions Minecraft axes: x grows east, y grows up, z grows south. Yaw is the horizontal facing: 0 south, 90 west, 180 north, 270 east. Pitch is the vertical angle: minus 90 looks up, 0 is level, 90 looks down. Position: x, y, z y: up x: east z: south 0, 0, 0 North is -z, west is -x, down is -y. Facing, seen from above North (-z) South (+z) yaw 0 yaw 180 East (+x) yaw 270 West (-x) yaw 90 you Looking up and down: pitch pitch -90 straight up pitch 0: level pitch 90 straight down
The coordinate axes, the four compass directions with their yaw values, and the pitch scale.

Exact positions and block positions

A player can stand anywhere inside a block, so an exact position has decimals, like 100.73. A block position is a whole number: the number of the block you are inside. Paper gives you both, and you must pick the one that fits the job.

Looking down at a row of blocks along the x axis block 99 block 100 block 101 99.0 100.0 101.0 102.0 the player getX() = 100.73 exact spot inside the block getBlockX() = 100 the block the player is inside
The player stands at x 100.73, which is inside block 100. getX() returns the exact number, getBlockX() the block.

The block number is the exact number rounded down (floor), so a player at x -0.5 is inside block -1, not block 0. The middle of block 100 is 100.5, which is why teleport code often adds 0.5: it puts the player in the middle of the block instead of on its edge.

Location: a place in a world

A Location bundles a World, the three coordinates and the two look angles into one object. Players, entities and blocks all have one, and you hand a Location to anything that needs to know where:

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

13void coordinates(Player player) {14    Location where = player.getLocation();15    double exactX = where.getX();16    int blockX = where.getBlockX();17    float yaw = where.getYaw();18    float pitch = where.getPitch();19    World world = where.getWorld();20}
  1. The player's current position. You get a copy, so changing it does not move the player.
  2. The exact x as a double (a number with decimals). getY() and getZ() work the same way.
  3. The x of the block the player is inside, as a whole number (int).
  4. Yaw and pitch are floats, another kind of decimal number.

To make a location yourself, you need the world. Yaw and pitch are optional (they default to 0, which means looking south and level):

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

22void buildLocation(World world) {23    Location mid = new Location(world, 100.5, 64, -20.5);24    Location facingEast = new Location(world, 100.5, 64, -20.5, -90f, 0f);25}
  1. The middle of block (100, 64, -20) in world. The .5 values put you in the center of the block.
  2. Yaw -90 means facing east and pitch 0 means looking level. The f after a number marks it as a float.

Changing a Location, and why clone matters

location.add(x, y, z) moves a location by that much. It is a very common call, and it hides a trap: add changes the location you call it on, and also returns it. It does not make a new one.

This looks fine but is wrongHas a mistake
1Location base = player.getLocation();2Location up = base.add(0, 2, 0);3Location east = base.add(1, 0, 0);

All three variables end up pointing at one single Location, which was changed twice. You wanted three different places.

Without clone() With clone() base up east one Location y 66, x 101 changed twice base.add(0, 2, 0) and base.add(1, 0, 0) both edit the same object base up east x 100, y 64 x 100, y 66 x 101, y 64 base.clone().add(0, 2, 0) each variable has its own copy
add() edits the object it is called on. clone() first makes a separate copy, so every variable keeps its own position.

The fix is clone(), which makes an independent copy to change:

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

27void addAndClone(Player player) {28    Location here = player.getLocation();29    Location twoAbove = here.clone().add(0, 2, 0);30    Location hundredEast = here.clone().add(100, 0, 0);31}
  1. A fresh copy of here. Changing the copy leaves here alone.
  2. Two blocks up. Chaining the calls (clone().add(...)) is the usual way to write it.

subtract works like add in the other direction, and setX, setY, setZ, setYaw and setPitch change one value. location.getBlock() gives the block at that spot, and location.getWorld() the world.

Distance

a.distance(b) gives the straight-line distance in blocks. It has two catches:

  • If the two locations are in different worlds, it throws an IllegalArgumentException. Compare the worlds first.
  • It uses a square root, which is slow compared with ordinary math. For a simple "is he closer than 10 blocks?" check, use distanceSquared and compare with 10 times 10.
LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

33void distance(Player first, Player second) {34    Location a = first.getLocation();35    Location b = second.getLocation();36    if (!a.getWorld().equals(b.getWorld())) {37        return;38    }39    double blocks = a.distance(b);40    boolean withinTen = a.distanceSquared(b) <= 10 * 10;41}
  1. "If the worlds are not the same, stop." Comparing worlds with equals is the safe way to avoid the exception.
  2. The exact distance, such as 7.07. Good for showing a number to the player.
  3. The squared distance has no square root, so it is fast. Comparing it with the radius multiplied by itself gives the same answer as distance(b) <= 10.

Vectors: direction and speed

A Vector is three numbers (x, y, z) that describe a push or a direction, not a place. You use it to say "go this way, this fast". Two vectors matter most:

  • location.getDirection() is the way something is looking, as a vector of length exactly 1 (a "unit vector").
  • entity.setVelocity(vector) gives an entity a push, measured in blocks per tick. Walking is about 0.2, a strong launch about 1.5 to 3.
LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

43void vectors(Player player) {44    Vector direction = player.getLocation().getDirection();45    Vector push = direction.multiply(2);46    player.setVelocity(push);47}
  1. Where the player is looking, as an arrow of length 1. Looking straight south gives (0, 0, 1).
  2. Makes the arrow twice as long, so twice as fast. Like Location, a Vector changes itself when you call multiply, add or normalize.
  3. Throws the player: they now move with this velocity, so they fly in the direction they were looking.

Vectors have the usual math methods: add, subtract, multiply, length, normalize (turn into a length-1 arrow) and clone to copy, because they change themselves just like locations do:

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

49void vectorMath() {50    Vector start = new Vector(0, 64, 0);51    Vector step = new Vector(1, 0, 0);52    Vector end = start.clone().add(step.multiply(5));53    double length = end.length();54    Vector unit = end.clone().normalize();55}
  1. Copies start, then adds a step that is five times longer. step.multiply(5) also changes step itself, to (5, 0, 0).
  2. How long the arrow is, a number.
  3. Shrinks a copy of the vector to length 1 without changing its direction.

Worlds

A World is one dimension on your server: the overworld, the Nether, the End, or any extra world a plugin made. You get one in several ways, depending on what you have:

  • player.getWorld() or location.getWorld(): the world something is in.
  • Bukkit.getWorld("world_nether"): a world by its name. It returns null when no world has that name.
  • Bukkit.getWorlds(): a list of all loaded worlds. The first one is normally the main overworld.
LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

57void findWorlds() {58    World nether = Bukkit.getWorld("world_nether");59    World main = Bukkit.getWorlds().get(0);60    String name = main.getName();61    World.Environment kind = main.getEnvironment();62}
  1. The Nether of a default server is named world_nether. The result can be null if the name is wrong, so check it before use.
  2. The first world in the list. get(0) means "item number zero", because Java counts from 0.
  3. Which kind of dimension it is: NORMAL, NETHER, THE_END or CUSTOM.

The default names are world, world_nether and world_the_end. Every world also has a spawn point, world.getSpawnLocation(), and a list of the players inside it, world.getPlayers().

Time and weather

Time in a world is counted in ticks, 20 per second. A full Minecraft day is 24,000 ticks, which is 20 minutes of real time. world.getTime() and setTime() use the time of the current day:

Time valueWhat it looks like
0Sunrise (the start of the day)
6000Noon, the sun is highest
12000Sunset
13000Night begins
18000Midnight
LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

64void timeAndWeather(World world) {65    world.setTime(6000);66    boolean isDay = world.isDayTime();67    world.setStorm(true);68    world.setThundering(false);69    world.setWeatherDuration(6000);70}
  1. Jumps to noon.
  2. True while it is day. Handy for "only at night" features.
  3. Starts rain (or snow in cold biomes). false clears the sky.
  4. Thunder is its own switch, separate from rain.
  5. How long this weather lasts, in ticks: 6000 ticks is five minutes.

Game rules

A game rule is a world setting like keepInventory, which you may know from the /gamerule command. In code, each rule is a constant in the GameRules class, and you read or change it on the world:

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

72void gameRules(World world) {73    world.setGameRule(GameRules.KEEP_INVENTORY, true);74    Boolean keepsItems = world.getGameRuleValue(GameRules.KEEP_INVENTORY);75}
  1. The rule that keeps items when a player dies. The constants are named like the rules in the game, written in capital letters.
  2. Reading a rule gives a Boolean for true/false rules, or an Integer for number rules. Autocomplete shows which.

The highest block

world.getHighestBlockYAt(x, z) returns the y of the highest block you cannot walk through at that column. Put a player at that y plus 1 and they stand on the surface. world.getHighestBlockAt(x, z) returns the Block itself.

LocationSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-snippetssrcmainjavacomexampleworldslocationssnippetsLocationSnippets.java

The package com.example.worldslocationssnippets is the folder path com/example/worldslocationssnippets inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-snippets/
    • src/main/java/com/example/worldslocationssnippets/Package com.example.worldslocationssnippets
      • LocationSnippets.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

77void highestBlock(World world) {78    int topY = world.getHighestBlockYAt(100, 200);79    Block topBlock = world.getHighestBlockAt(100, 200);80    Location spawn = world.getSpawnLocation();81}
  1. The surface height at x 100, z 200 as a number.
  2. The same spot as a Block, which you can ask for its type (see Blocks and block data).

Teleporting safely

You met teleport and teleportAsync on the players page. Both move an entity to a Location. Two beginner problems come with teleporting:

  • Unsafe destinations. If the place is inside a wall, in lava or above a hole, the player suffocates, burns or falls. Check the destination first.
  • Pausing the server. If the target chunk is not loaded, teleport loads it on the spot and the server waits. teleportAsync loads it in the background and then moves the player.

A destination is safe when the player has free space to stand in (feet and head blocks are empty and not liquid) and something solid under the feet. This helper checks exactly that:

SafeSpot.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-demosrcmainjavacomexampleworldslocationsdemoSafeSpot.java

The package com.example.worldslocationsdemo is the folder path com/example/worldslocationsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-demo/
    • src/main/
      • java/com/example/worldslocationsdemo/Package com.example.worldslocationsdemo
        • LaunchCommand.javaCommand (BasicCommand)
        • LaunchPadListener.javaListener: reacts to events
        • Launcher.javaHelper class
        • SafeSpot.javayou are hereHelper class
        • TopCommand.javaCommand (BasicCommand)
        • WorldsLocationsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsdemo;2 3import org.bukkit.Location;4import org.bukkit.block.Block;5 6final class SafeSpot {7 8    private SafeSpot() {9    }10 11    static boolean isSafe(Location spot) {12        Block feet = spot.getBlock();13        Block head = spot.clone().add(0, 1, 0).getBlock();14        Block ground = spot.clone().subtract(0, 1, 0).getBlock();15 16        boolean roomToStand = feet.isPassable() && !feet.isLiquid() && head.isPassable() && !head.isLiquid();17        return roomToStand && ground.getType().isSolid();18    }19}
  1. The block where the feet will be.
  2. One block higher, where the head will be. We clone first so spot stays the same.
  3. One block lower, the ground the player will stand on.
  4. True for blocks you can walk through, like air, grass and torches.
  5. True for water and lava. Passable but not a good place to arrive.
  6. The ground must be a solid block, not air or a flower.

Reacting to movement

PlayerMoveEvent fires whenever a player moves or just turns their head. That is many times per second for every player, so a handler for it must be tiny. The most important trick is to leave early unless the player really moved to a new block:

  • event.hasChangedBlock() is true when the player is now in a different block than before.
  • event.hasChangedPosition() is true when any coordinate changed, even inside the same block.
  • event.getFrom() and event.getTo() are the old and the new location.

Demo plugin: launch pads and /top

This plugin has three features. Stepping onto an emerald block throws you forward, /launch does the same on command, and /top teleports you to the surface above you. The throwing code sits in one small class, so the command and the pad share it:

Launcher.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-demosrcmainjavacomexampleworldslocationsdemoLauncher.java

The package com.example.worldslocationsdemo is the folder path com/example/worldslocationsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-demo/
    • src/main/
      • java/com/example/worldslocationsdemo/Package com.example.worldslocationsdemo
        • LaunchCommand.javaCommand (BasicCommand)
        • LaunchPadListener.javaListener: reacts to events
        • Launcher.javayou are hereHelper class
        • SafeSpot.javaHelper class
        • TopCommand.javaCommand (BasicCommand)
        • WorldsLocationsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsdemo;2 3import org.bukkit.Sound;4import org.bukkit.entity.Player;5import org.bukkit.util.Vector;6 7final class Launcher {8 9    private static final double MINIMUM_UPWARD_SPEED = 0.8;10 11    private Launcher() {12    }13 14    static void launch(Player player, double strength) {15        Vector push = player.getLocation().getDirection().multiply(strength);16        push.setY(Math.max(push.getY(), MINIMUM_UPWARD_SPEED));17        player.setVelocity(push);18        player.playSound(player.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1.0f, 1.0f);19    }20}
  1. A named number instead of a bare 0.8 in the code. static final means it is one shared value that never changes.
  2. A private constructor stops anyone from creating a Launcher object. This class only holds a method, so there is nothing to create.
  3. Where the player looks, stretched by strength. getDirection() gives a fresh vector, so changing it is safe.
  4. If the player looks level, the push has no upward part, and they would only slide. This makes sure there is always at least a little lift.
  5. The launch itself.
  6. Plays a firework sound at the player's position. See Titles, action bars, boss bars and sounds.

The pad listener only wakes up when the player enters a new block, then looks at the block just under their feet:

LaunchPadListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-demosrcmainjavacomexampleworldslocationsdemoLaunchPadListener.java

The package com.example.worldslocationsdemo is the folder path com/example/worldslocationsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-demo/
    • src/main/
      • java/com/example/worldslocationsdemo/Package com.example.worldslocationsdemo
        • LaunchCommand.javaCommand (BasicCommand)
        • LaunchPadListener.javayou are hereListener: reacts to events
        • Launcher.javaHelper class
        • SafeSpot.javaHelper class
        • TopCommand.javaCommand (BasicCommand)
        • WorldsLocationsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsdemo;2 3import org.bukkit.Location;4import org.bukkit.Material;5import org.bukkit.event.EventHandler;6import org.bukkit.event.Listener;7import org.bukkit.event.player.PlayerMoveEvent;8 9public final class LaunchPadListener implements Listener {10 11    @EventHandler12    public void onMove(PlayerMoveEvent event) {13        if (!event.hasChangedBlock()) {14            return;15        }16 17        Location belowFeet = event.getTo().clone().subtract(0, 0.5, 0);18        if (belowFeet.getBlock().getType() == Material.EMERALD_BLOCK) {19            Launcher.launch(event.getPlayer(), 2.0);20        }21    }22}
  1. The cheap guard. Turning your head or shuffling inside one block returns here immediately.
  2. We clone because getTo() is the real destination object, and subtract would change where the player is going.
  3. Half a block below the feet. That finds the block the player stands on, and also works on slabs.
  4. The pad block. Material lists every block and item type.
  5. A stronger launch than the command, because pads are for big jumps.
TopCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-demosrcmainjavacomexampleworldslocationsdemoTopCommand.java

The package com.example.worldslocationsdemo is the folder path com/example/worldslocationsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-demo/
    • src/main/
      • java/com/example/worldslocationsdemo/Package com.example.worldslocationsdemo
        • LaunchCommand.javaCommand (BasicCommand)
        • LaunchPadListener.javaListener: reacts to events
        • Launcher.javaHelper class
        • SafeSpot.javaHelper class
        • TopCommand.javayou are hereCommand (BasicCommand)
        • WorldsLocationsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsdemo;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.Location;6import org.bukkit.World;7import org.bukkit.entity.Player;8 9public final class TopCommand implements BasicCommand {10 11    @Override12    public void execute(CommandSourceStack source, String[] args) {13        if (!(source.getSender() instanceof Player player)) {14            source.getSender().sendRichMessage("<red>Only players can use /top.");15            return;16        }17 18        Location here = player.getLocation();19        World world = here.getWorld();20        if (world.getEnvironment() == World.Environment.NETHER) {21            player.sendRichMessage("<red>The Nether has a roof, so /top is turned off here.");22            return;23        }24 25        int topY = world.getHighestBlockYAt(here);26        Location top = new Location(world, here.getBlockX() + 0.5, topY + 1, here.getBlockZ() + 0.5,27            here.getYaw(), here.getPitch());28        if (!SafeSpot.isSafe(top)) {29            player.sendRichMessage("<red>There is no safe place to stand up there.");30            return;31        }32 33        player.teleportAsync(top).thenAccept(success -> {34            if (success) {35                player.sendRichMessage("<green>Whoosh! You are on top of the world.");36            }37        });38    }39}
  1. Refuses in the Nether, where the "highest block" is the bedrock roof.
  2. The surface height above the player's column.
  3. The center of the surface block, one block up (topY + 1) so the player stands on it. The last two arguments keep the direction the player was looking.
  4. Skips unsafe landings, for example when the surface is water.
  5. Loads the destination without freezing the server, then teleports.
  6. Runs after the teleport finished. success tells you whether it worked.
Whoosh! You are on top of the world.

Finally, the main class registers everything:

WorldsLocationsDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-demosrcmainjavacomexampleworldslocationsdemoWorldsLocationsDemoPlugin.java

The package com.example.worldslocationsdemo is the folder path com/example/worldslocationsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-demo/
    • src/main/
      • java/com/example/worldslocationsdemo/Package com.example.worldslocationsdemo
        • LaunchCommand.javaCommand (BasicCommand)
        • LaunchPadListener.javaListener: reacts to events
        • Launcher.javaHelper class
        • SafeSpot.javaHelper class
        • TopCommand.javaCommand (BasicCommand)
        • WorldsLocationsDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsdemo;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class WorldsLocationsDemoPlugin extends JavaPlugin {6 7    @Override8    public void onEnable() {9        registerCommand("launch", "Throws you forward and up", new LaunchCommand());10        registerCommand("top", "Takes you to the highest block above you", new TopCommand());11        getServer().getPluginManager().registerEvents(new LaunchPadListener(), this);12    }13}
  1. Adds /launch. Commands are covered in Commands, part 1.
  2. Registers the pad listener, as in Events and listeners.
  • worlds-locations-demo/
    • src/
      • main/
        • java/
          • com/example/worldslocationsdemo/
            • WorldsLocationsDemoPlugin.javaThe main class: registers commands and the listener
            • Launcher.javaThe shared throwing code
            • LaunchCommand.javaThe /launch command
            • LaunchPadListener.javaEmerald blocks launch players who step on them
            • TopCommand.javaThe /top command
            • SafeSpot.javaChecks whether a place is safe to stand on
        • resources/
          • plugin.ymlPlugin name, version and main class

Download the demo as a Gradle project, place an emerald block on your test world, and walk onto it.

Mistakes to avoid

  • Changing a Location without cloning it. add, subtract and multiply change the object they are called on.
  • Measuring distance across worlds. distance throws an IllegalArgumentException if the worlds differ. Compare getWorld() first.
  • Mixing up block and exact coordinates. Use getBlockX() to find a block and getX() for exact math.
  • Forgetting that north is negative z. Walking north makes z smaller.
  • Doing heavy work in PlayerMoveEvent. Leave early with hasChangedBlock().
  • Trusting Bukkit.getWorld(name). It returns null for an unknown name.
  • Using the old GameRule names. Pick the constants from GameRules.
Try it

A /distance command

Write /distance (name). It tells the player how many blocks away another online player is. If the other player is in a different world, say so instead of crashing.

Hint 1

Find the other player with Bukkit.getPlayerExact and remember the null check from the players page.

Hint 2

Get both locations with getLocation(), compare their worlds with equals, and only then call distance.

Show the solution

The command leaves early four times (not a player, no argument, unknown player, different worlds). After those guards, distance is safe to call.

DistanceCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesworlds-locations-exercisesrcmainjavacomexampleworldslocationsexerciseDistanceCommand.java

The package com.example.worldslocationsexercise is the folder path com/example/worldslocationsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • worlds-locations-exercise/
    • src/main/
      • java/com/example/worldslocationsexercise/Package com.example.worldslocationsexercise
        • DistanceCommand.javayou are hereCommand (BasicCommand)
        • WorldsLocationsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.worldslocationsexercise;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;6import org.bukkit.Bukkit;7import org.bukkit.Location;8import org.bukkit.entity.Player;9 10public final class DistanceCommand implements BasicCommand {11 12    @Override13    public void execute(CommandSourceStack source, String[] args) {14        if (!(source.getSender() instanceof Player player)) {15            source.getSender().sendRichMessage("<red>Only players can measure distances.");16            return;17        }18        if (args.length == 0) {19            player.sendRichMessage("<red>Usage: /distance (name)");20            return;21        }22 23        Player other = Bukkit.getPlayerExact(args[0]);24        if (other == null) {25            player.sendRichMessage("<red>Nobody called <name> is online.",26                Placeholder.unparsed("name", args[0]));27            return;28        }29 30        Location mine = player.getLocation();31        Location theirs = other.getLocation();32        if (!mine.getWorld().equals(theirs.getWorld())) {33            player.sendRichMessage("<yellow><name> is in a different world.",34                Placeholder.unparsed("name", other.getName()));35            return;36        }37 38        long blocks = Math.round(mine.distance(theirs));39        player.sendRichMessage("<green><name> is <blocks> blocks away.",40            Placeholder.unparsed("name", other.getName()),41            Placeholder.unparsed("blocks", String.valueOf(blocks)));42    }43}
  1. The check that prevents the IllegalArgumentException.
  2. Rounds to a whole number, so the message says 42 and not 41.7384....
Try it

A /day command

Add a /day command that sets the time to morning (1000 ticks) and clears the rain and thunder in the world of the player who typed it.

Hint

Get the world with player.getWorld(), then use setTime, setStorm and setThundering.

Show the solution

Inside execute, after you made sure the sender is a player:

Inside execute
1World world = player.getWorld();2world.setTime(1000);3world.setStorm(false);4world.setThundering(false);5player.sendRichMessage("<yellow>A new day begins!");

Rain and thunder are separate switches, so the code turns both off.

Recap

  • x goes east, y goes up and z goes south. Yaw is the sideways look angle, pitch the up and down angle.
  • A Location holds a world, exact coordinates and look angles. getBlockX() gives the block number, getX() the exact number.
  • add, subtract and multiply change the object itself. Call clone() first when you need to keep the original.
  • distance throws across worlds; distanceSquared is faster when you only compare.
  • getDirection() plus multiply plus setVelocity launches things.
  • A World has time (0 to 24000), weather, game rules (GameRules), a spawn point and an environment.
  • Check the destination before teleporting, and use teleportAsync to avoid pauses.
  • In PlayerMoveEvent, return early unless hasChangedBlock() is true.

Quick quiz

  1. A player stands at x = 12.9. What does getBlockX() return?

  2. What is the bug in Location up = base.add(0, 2, 0); if you still need base afterwards?

  3. Which direction is the smaller z value?

  4. Why should a PlayerMoveEvent handler start with if (!event.hasChangedBlock()) return;?

  5. What does player.setVelocity(direction.multiply(2)) do when direction comes from getDirection()?

Next steps