Skip to content

Working with Players

One of the most used objects when developing for Paper is the Player object. It represents a player who is playing on your server. Through it you can send the player messages, set their health, teleport them, and many other fun stuff!

The Adventure library’s Audience object declares a lot of methods for sending messages or other UI elements to the player. You can see an overview with those on the adventure/audiences page!

There is three main methods with which you can send messages to a player:

The first one is declared in the Audience interface. You can send styled and colored messages with it! The second method is used for sending a MiniMessage-formatted string to the player. Internally, this converts the string to a component and then uses Player#sendMessage(Component). You can optionally include TagResolvers for custom tags. The last method is for sending a non-parsed message to the client. This one will make no attempt to parse the string’s contents — what you put in is what gets send!

In our event handler in BlockBreakListener.java, we can replace the logger call with a player#sendRichMessage. Now you don’t have to check the console!

Our new message
BlockBreakListener.java
@EventHandler
void onBlockBreak(BlockBreakEvent event) {
counter++;
// Every second block break
if (counter % 2 == 0) {
event.setCancelled(true);
}
final Player player = event.getPlayer();
player.sendRichMessage("Was block breaking of <gold><block></gold> cancelled: <color><b><cancelled>",
Placeholder.component("block", Component.translatable(event.getBlock())),
Placeholder.component("cancelled", Component.text(event.isCancelled())),
Placeholder.styling("color", event.isCancelled() ? NamedTextColor.GREEN : NamedTextColor.RED)
);
}

Another set of often used methods are the positional methods. These include, but are not limited to:

All of these are accessible on a Player, even if they originate from a different interface. A Location object describes a x, y, and z position in a World. It can also include yaw and pitch values. They are generally mutable. That means that any operation, which modifies the mentioned values, will apply that change to itself as well. If you want to keep a location unmodified, you should store a copy of that location by running Location#clone().

You can modify a Location with multiple methods and method overloads. Some of these include:

When getting the location from Player#getEyeLocation(), you can get they player’s current facing direction with Location#getDirection(). You can then work with that direction. One example can be to check if the player is looking at something, which would be done with a ray trace. Since that is too complicated for us for now, I instead chose the following, rather fun example:

Whenever the player sneaks (pressed the sneak button), they get a velocity applied to them! For this, we first retrieve the player’s eye location with Player#getEyeLocation, whose direction (Location#getDirection) is the same as the direction the player is currently facing.

We then set the player’s velocity to the player’s directional vector (Entity#setVelocity), and off we go flying!

(Tip: to avoid getting kicked, set allow-flight=true in your server.properties).

Preview Video
PlayerSneakListener.java
8 collapsed lines
package com.learnpaperdev.beginner;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.util.Vector;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class PlayerSneakListener implements Listener {
@EventHandler
void onPlayerSneak(PlayerToggleSneakEvent event) {
// Only run if the player started sneaking to avoid double-calls
if (!event.isSneaking()) {
return;
}
final Player player = event.getPlayer();
final Vector lookDirection = player.getEyeLocation().getDirection();
player.setVelocity(lookDirection);
}
}

Don’t forget to register this listener in your main class!

BeginnerPlugin.java
@Override
public void onEnable() {
5 collapsed lines
// We use a PluginManager in order to register events
final PluginManager pluginManager = getServer().getPluginManager();
// Create our listener instance
final BlockBreakListener listener = new BlockBreakListener(getComponentLogger());
// #registerEvents requires the listener we want to register, and our plugin instance
pluginManager.registerEvents(listener, this);
pluginManager.registerEvents(new PlayerSneakListener(), this);
getComponentLogger().warn("Our plugin has been enabled!");
}

One great use for locations is the ability to teleport entities to that location. For this, you can use the Entity#teleport(Location) and Entity#teleportAsync(Location) methods respectively.

Location location = ...;
Player player = ...;
// Synchronous way
player.teleport(location);
player.sendMessage("Success!");
// Asynchronous way
player.teleportAsync(location).thenRun(
() -> player.sendMessage("Success!")
);

But what is the difference?

The difference is that teleport runs on the current thread that you are executing it from. This also means that if you are teleporting an entity into unloaded chunks, it first has to load those chunks, which is all but ideal for performance. teleportAsync first loads the chunk asynchronously and then teleports the player.

So when to use which one?

If you are not sure if the chunk of a location is currently loaded, you should always use teleportAsync. If you know for sure that a location will be loaded (e.g. because another player is nearby), it is safe to use teleport directly.

The player list (tab list) can be modified using the follow methods:

For modifying both at once, you can use:

There is also the possibility of setting a player’s tab list display name: Player#playerListName().

Setting the player’s list on player join

Section titled “Setting the player’s list on player join”

We can write another listener which modifies the player’s player list whenever they join the server. We can even style the player’s name! In this case, we will apply a random gradient whenever a player joins. Sounds fun, doesn’t it? For this we will add a new event handler for the PlayerJoinEvent. The code that we have to write is pretty straight-forward. The one part that you may be unfamiliar with is the usage of the pre-process Tag. We use it in order to be able to put a tag into the parameters of the <gradient> tag, as otherwise, it would just use <first_color> and <second_color> as literal parameters, which is not what we want!

Player List Preview
PlayerJoinListener.java
14 collapsed lines
package com.learnpaperdev.beginner;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.jspecify.annotations.NullMarked;
import java.util.Random;
import static net.kyori.adventure.text.minimessage.MiniMessage.miniMessage;
@NullMarked
public class PlayerJoinListener implements Listener {
private final Random random = new Random();
@EventHandler
void onPlayerJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
final Component header = miniMessage().deserialize("""
<b><gradient:red:white>A Very Cool Title</b>
""",
Placeholder.component("player", player.name())
);
final Component footer = miniMessage().deserialize("""
<gray>Enjoy your time here!</gray>"""
);
player.sendPlayerListHeaderAndFooter(header, footer);
final TextColor firstColor = TextColor.color(random.nextInt(0xFFFFFF));
final TextColor secondColor = TextColor.color(random.nextInt(0xFFFFFF));
final Component playerName = miniMessage().deserialize("<b><gradient:<first_color>:<second_color>><playername>",
TagResolver.resolver("first_color", Tag.preProcessParsed(firstColor.asHexString())),
TagResolver.resolver("second_color", Tag.preProcessParsed(secondColor.asHexString())),
Placeholder.component("playername", player.name())
);
player.playerListName(playerName);
}
}

And again, don’t forget to register this new listener.

BeginnerPlugin.java
@Override
public void onEnable() {
5 collapsed lines
// We use a PluginManager in order to register events
final PluginManager pluginManager = getServer().getPluginManager();
// Create our listener instance
final BlockBreakListener listener = new BlockBreakListener(getComponentLogger());
// #registerEvents requires the listener we want to register, and our plugin instance
pluginManager.registerEvents(listener, this);
pluginManager.registerEvents(new PlayerSneakListener(), this);
pluginManager.registerEvents(new PlayerJoinListener(), this);
getComponentLogger().warn("Our plugin has been enabled!");
}

Learn Paper Dev is licensed under CC BY-NC-SA 4.0