10547 lines
344 KiB
Markdown
10547 lines
344 KiB
Markdown
```markdown
|
||
# PaperMC Knowledge Base
|
||
|
||
## General
|
||
|
||
### Contributing
|
||
* [Contributing](Contributing) - Information on how to contribute to the PaperMC project.
|
||
|
||
### Miscellaneous
|
||
|
||
* [Using databases](Using databases) - Recommended way to store a large amount of data.
|
||
* [Debugging your plugin](Debugging your plugin) - Common ways to debug your plugin.
|
||
* [Minecraft internals](Minecraft internals) - Overview of how to use internals in your plugin.
|
||
* [Reading stacktraces](Reading stacktraces) - Basics of how to read stacktraces produced by the JVM when an exception occurs.
|
||
|
||
## API
|
||
|
||
### Command API
|
||
* [Command API](Command API) - Guide to Paper's Brigadier command API.
|
||
|
||
* **Basics**
|
||
* [Introduction](Command%20API) - A guide to Paper's Brigadier command API.
|
||
* [Command trees](Command%20trees) - An extensive guide to building up a command tree.
|
||
* [Arguments and literals](Arguments%20and%20literals) - An extensive guide to command arguments and literals.
|
||
* [Executors](Executors) - A guide to execution logic for Brigadier commands.
|
||
* [Registration](Registration) - A guide to registering Brigadier commands.
|
||
* [Requirements](Requirements) - A guide to setting requirements for commands.
|
||
* [Suggestions](Suggestions) - Documentation about defining custom argument suggestions.
|
||
* [Custom arguments](Custom%20arguments) - Guide on custom arguments.
|
||
* **Arguments**
|
||
* [Minecraft-specific](Minecraft-specific) - Everything regarding the essential Brigadier arguments.
|
||
* [Location](Location) - BlockPosition, FinePosition and World argument documentation.
|
||
* [Entities and players](Entities%20and%20players) - Player and Entity arguments documentation.
|
||
* [Registry](Registry) - Documentation for arguments retrieving registry values.
|
||
* [Paper-specific](Paper-specific) - Documentation for arguments handling miscellaneous Paper API values.
|
||
* [Enums](Enums) - Documentation for EntityAnchor, GameMode and similar enum value arguments.
|
||
* [Predicates](Predicates) - Documentation for arguments that allow value validation.
|
||
* [Adventure](Adventure) - Documentation for all arguments returning Adventure API objects.
|
||
* **Miscellaneous**
|
||
* [Basic commands](Basic%20commands) - An overview of a Bukkit-style command declaration using Brigadier.
|
||
* [Comparison](Comparison) - A comparison between Brigadier and Bukkit commands.
|
||
|
||
### Component API
|
||
* [Component API](Component%20API) - Guide to Adventure components.
|
||
* [Introduction](Component%20API) - An introduction to how components work.
|
||
* [Internationalization](Internationalization) - How to use Adventure's internationalization.
|
||
* [Audiences](Audiences) - How to use Adventure's Audiences.
|
||
* [Signed messages](Signed%20messages) - A guide to working with SignedMessage objects.
|
||
|
||
### Event API
|
||
* [Event API](Event%20API) - Guide to Paper's event system.
|
||
* [Listeners](Listeners) - Developer guide for how to listen to the broadcasted events.
|
||
* [Custom events](Custom%20events) - A guide to show you how to add custom events to your plugin.
|
||
* [Handler lists](Handler%20lists) - An explanation to what an event's HandlerList is.
|
||
* [Chat events](Chat%20events) - An outline on AsyncChatEvent and how to handle it.
|
||
|
||
### Entity API
|
||
* [Entity API](Entity%20API) - Documentation for working with entities.
|
||
* [Teleportation](Teleportation) - Guide on entity teleportation.
|
||
* [Display entities](Display%20entities) - Guide on display entities.
|
||
|
||
### Inventories
|
||
* [Inventories](Inventories) - Documentation for working with inventories.
|
||
* [Menu Type API](Menu%20Type%20API) - A guide to the Menu Type API.
|
||
* [Custom InventoryHolders](Custom%20InventoryHolders) - How to use a custom InventoryHolder to identify custom inventories.
|
||
|
||
### Lifecycle API
|
||
* [Lifecycle API](Lifecycle%20API) - Documentation for Paper's Lifecycle API.
|
||
* [Introduction](Lifecycle%20API) - An introduction to Paper's Lifecycle API.
|
||
* [Datapack discovery](Datapack%20discovery) - A guide to including datapacks in your plugin and registering them with the lifecycle API.
|
||
|
||
### Experimental
|
||
* [Data components](Data%20components) - A guide to the ItemStack data component API.
|
||
* [Persistent data container (PDC)](Persistent%20data%20container%20(PDC)) - A guide to the PDC API for storing data.
|
||
* [Scheduling](Scheduling) - A guide on how to use BukkitScheduler to run code at specific times.
|
||
* [Plugin messaging](Plugin%20messaging) - How to communicate with clients or proxies.
|
||
* [Plugin configuration](Plugin%20configuration) - How to create configuration files for your plugins to customize behavior.
|
||
* [Registries](Registries) - A guide to registries and their modification on Paper.
|
||
* [Dialog API](Dialog%20API) - A guide to the dialog API introduced in 1.21.7.
|
||
* [Recipes](Recipes) - How to create and manage recipes.
|
||
* [Particles](Particles) - A comprehensive guide to particle spawning.
|
||
|
||
## Development
|
||
|
||
* [Development](Development) - Welcome to the Paper development guide!
|
||
* [Getting started](Development#getting-started)
|
||
* [Paper plugins](Paper%20plugins) - A development guide for how to write Paper-specific plugins.
|
||
* [Project setup](Project%20setup) - Step-by-step instructions on how to set up a plugin development environment.
|
||
* [How plugins work](How%20plugins%20work) - How plugins work in Paper.
|
||
* [plugin.yml](plugin.yml) - A guide to Bukkit's plugin.yml file.
|
||
* [paperweight-userdev](paperweight-userdev) - A guide on how to use the paperweight-userdev Gradle plugin to access internal code.
|
||
|
||
## Command API - Arguments
|
||
|
||
### Minecraft-specific Arguments
|
||
|
||
#### Location Argument
|
||
|
||
* **Block position argument**
|
||
* Used for retrieving the position of a block. Works the same way as the first argument of the `/setblock <position> <block>` Vanilla command.
|
||
* Returns a `BlockPosition` variable from the `BlockPositionResolver`.
|
||
* Example Usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> blockPositionArgument() {
|
||
return Commands.literal("blockpositionargument")
|
||
.then(Commands.argument("arg", ArgumentTypes.blockPosition())
|
||
.executes(ctx -> {
|
||
final BlockPositionResolver blockPositionResolver = ctx.getArgument("arg", BlockPositionResolver.class);
|
||
final BlockPosition blockPosition = blockPositionResolver.resolve(ctx.getSource());
|
||
ctx.getSource().getSender().sendPlainMessage(
|
||
"Put in " + blockPosition.x() + " " + blockPosition.y() + " " + blockPosition.z());
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Fine position argument**
|
||
* Works similarly to the block position argument, with the difference being that it can accept decimal (precise) location input.
|
||
* The optional overload (`ArgumentTypes.finePosition(boolean centerIntegers)`), which defaults to false if not set, will center whole input, meaning 5 becomes 5.5 (5.0 would stay as 5.0 though), as that is the “middle” of a block. This only applies to X/Z. The y coordinate is untouched by this operation.
|
||
* Returns a `FinePositionResolver`. You can resolve that by running `FinePositionResolver#resolve(CommandSourceStack)` to get the resulting `FinePosition`.
|
||
* Example Usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> finePositionArgument() {
|
||
return Commands.literal("fineposition")
|
||
.then(Commands.argument("arg", ArgumentTypes.finePosition(true))
|
||
.executes(ctx -> {
|
||
final FinePositionResolver resolver = ctx.getArgument("arg", FinePositionResolver.class);
|
||
final FinePosition finePosition = resolver.resolve(ctx.getSource());
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Position: <red><x></red> <green><y></green> <blue><z></blue>",
|
||
Placeholder.unparsed("x", Double.toString(finePosition.x())),
|
||
Placeholder.unparsed("y", Double.toString(finePosition.y())),
|
||
Placeholder.unparsed("z", Double.toString(finePosition.z()))
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **World argument**
|
||
* Allows the user to select one of the currently loaded worlds.
|
||
* Returns a generic Bukkit `World` object.
|
||
* Example Usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> worldArgument() {
|
||
return Commands.literal("teleport-to-world")
|
||
.then(Commands.argument("world", ArgumentTypes.world())
|
||
.executes(ctx -> {
|
||
final World world = ctx.getArgument("world", World.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Successfully teleported <player> to <aqua><world></aqua>",
|
||
Placeholder.component("player", player.name()),
|
||
Placeholder.unparsed("world", world.getName())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### Entities and players Argument
|
||
|
||
* The arguments described in this section relate to arguments which you can use to retrieve entities. Their main usage is the selection of command targets.
|
||
* All of these have entity selectors (`@a`, `@e`, `@n`, etc.) as valid inputs, though they require the `minecraft.command.selector` permission in order to be able to be used. The specific arguments may allow or disallow certain selectors.
|
||
* Due to the permission requirement for selectors it is advised to add a `requires` statement to your command:
|
||
`.requires(ctx -> ctx.getSender().hasPermission("minecraft.command.selector"))`
|
||
|
||
* **Entity argument**
|
||
* Returns a list of exactly one entity.
|
||
* Returns an `EntitySelectorArgumentResolver`.
|
||
* Safe to call `List#getFirst()` to retrieve that entity.
|
||
* You can resolve it using `ArgumentResolver#resolve(CommandSourceStack)`
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> entityArgument() {
|
||
return Commands.literal("entityarg")
|
||
.then(Commands.argument("arg", ArgumentTypes.entity())
|
||
.executes(ctx -> {
|
||
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
|
||
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Found <green><entityname>",
|
||
Placeholder.component("entityname", entities.getFirst().name())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Entities argument**
|
||
* Accepts any amount of entities, with the minimum amount of entities being 1.
|
||
* Returns an `EntitySelectorArgumentResolver`.
|
||
* Can be resolved using `ArgumentResolver#resolve(CommandSourceStack)`, which returns a `List<Entity>`.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> entitiesArgument() {
|
||
return Commands.literal("entitiesarg")
|
||
.then(Commands.argument("arg", ArgumentTypes.entities())
|
||
.executes(ctx -> {
|
||
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
|
||
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
|
||
final Component foundEntities = Component.join(JoinConfiguration.commas(true),
|
||
entities.stream().map(Entity::name).toList());
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Found <green><entitynames>",
|
||
Placeholder.component("entitynames", foundEntities)
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Player argument**
|
||
* Allows to retrieve a `PlayerSelectorArgumentResolver` for player arguments.
|
||
* For this “single player” argument, you can safely get the target player by running `PlayerSelectorArgumentResolver.resolve(ctx.getSource()).getFirst()`, which returns a `Player` object.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playerArgument() {
|
||
return Commands.literal("player")
|
||
.then(Commands.argument("target", ArgumentTypes.player())
|
||
.executes(ctx -> {
|
||
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
|
||
final Player target = targetResolver.resolve(ctx.getSource()).getFirst();
|
||
target.setVelocity(new Vector(0, 100, 0));
|
||
target.sendRichMessage("<rainbow>Yeeeeeeeeeet");
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Yeeted <target>!",
|
||
Placeholder.component("target", target.name())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Players argument**
|
||
* The “multiple players” argument works similarly to the “single player” argument, also returning a `PlayerSelectorArgumentResolver`.
|
||
* Instead of just resolving to exactly one `Player`, this one can resolve to more than just one player - which you should account for in case of using this argument.
|
||
* `PlayerSelectorArgumentResolver.resolve(ctx.getSource())` returns a `List<Player>`, which you can just iterate through.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playersArgument() {
|
||
return Commands.literal("players")
|
||
.then(Commands.argument("targets", ArgumentTypes.players())
|
||
.executes(ctx -> {
|
||
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("targets", PlayerSelectorArgumentResolver.class);
|
||
final List<Player> targets = targetResolver.resolve(ctx.getSource());
|
||
final CommandSender sender = ctx.getSource().getSender();
|
||
for (final Player target : targets) {
|
||
target.setVelocity(new Vector(0, 100, 0));
|
||
target.sendRichMessage("<rainbow>Yeeeeeeeeeet");
|
||
sender.sendRichMessage(
|
||
"Yeeted <target>!",
|
||
Placeholder.component("target", target.name())
|
||
);
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Player profiles argument**
|
||
* Can retrieve both offline and online players.
|
||
* Returns the result of the argument as a `PlayerProfileListResolver`, which resolves to a `Collection<PlayerProfile>`.
|
||
* This collection can be iterated to get the resulting profile(s). Usually, it only returns a single `PlayerProfile` if retrieving a player by name, but it can return multiple if using the entity selectors (like `@a` on online players). Thus it always makes sense to run whatever operation you want to run on all entries in the collection instead of just the first one.
|
||
* This argument will run API calls to Mojang servers in order to retrieve player information for players which have never joined the server before.
|
||
* It is suggested to resolve this argument in an asynchronous context in order to not cause any server lag.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playerProfilesArgument() {
|
||
return Commands.literal("lookup")
|
||
.then(Commands.argument("profile", ArgumentTypes.playerProfiles())
|
||
.executes(ctx -> {
|
||
final PlayerProfileListResolver profilesResolver = ctx.getArgument("profile", PlayerProfileListResolver.class);
|
||
final Collection<PlayerProfile> foundProfiles = profilesResolver.resolve(ctx.getSource());
|
||
for (final PlayerProfile profile : foundProfiles) {
|
||
ctx.getSource().getSender().sendPlainMessage("Found " + profile.getName());
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### Enum Argument
|
||
|
||
* **Entity anchor argument**
|
||
* Has two valid inputs: `feet` and `eyes`.
|
||
* The resulting `LookAnchor` is mainly used for methods like `Entity#lookAt(Position, LookAnchor)` or `Player#lookAt(Entity, LookAnchor, LookAnchor)`.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> entityAnchorArgument() {
|
||
return Commands.literal("entityanchor")
|
||
.then(Commands.argument("arg", ArgumentTypes.entityAnchor())
|
||
.executes(ctx -> {
|
||
final LookAnchor lookAnchor = ctx.getArgument("arg", LookAnchor.class);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"You chose <aqua><anchor></aqua>!",
|
||
Placeholder.unparsed("anchor", lookAnchor.name())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **GameMode argument**
|
||
* Works the same way as the first argument of the Vanilla `/gamemode <gamemode>` command.
|
||
* Accepts any of the 4 valid game modes, returning a `GameMode` enum to use in code.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> gameModeArgument() {
|
||
return Commands.literal("gamemodearg")
|
||
.then(Commands.argument("arg", ArgumentTypes.gameMode())
|
||
.executes(ctx -> {
|
||
final GameMode gamemode = ctx.getArgument("arg", GameMode.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.setGameMode(gamemode);
|
||
player.sendRichMessage(
|
||
"Your gamemode has been set to <red><gamemode></red>!",
|
||
Placeholder.component("gamemode", Component.translatable(gamemode))
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendPlainMessage("This command requires a player!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **HeightMap argument**
|
||
* Consists of the following, valid inputs: `motion_blocking`, `motion_blocking_no_leaves`, `ocean_floor`, and `world_surface`.
|
||
* It is often used for declaring relative positioning for data packs or the `/execute positioned over <height_map>` command.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> heightMapArgument() {
|
||
return Commands.literal("heightmap")
|
||
.then(Commands.argument("arg", ArgumentTypes.heightMap())
|
||
.executes(ctx -> {
|
||
final HeightMap heightMap = ctx.getArgument("arg", HeightMap.class);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"You selected <gold><selection></gold>",
|
||
Placeholder.unparsed("selection", heightMap.name())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Scoreboard display slot argument**
|
||
* Allows you to retrieve a `DisplaySlot` enum value from the user.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> scoreboardDisplaySlotArgument() {
|
||
return Commands.literal("scoreboarddisplayslot")
|
||
.then(Commands.argument("slot", ArgumentTypes.scoreboardDisplaySlot())
|
||
.executes(ctx -> {
|
||
final DisplaySlot slot = ctx.getArgument("slot", DisplaySlot.class);
|
||
ctx.getSource().getSender().sendPlainMessage("You selected: " + slot.getId());
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Template mirror argument**
|
||
* The user has 3 valid input possibilities: `front_back`, `left_right`, and `none`.
|
||
* You can retrieve the result of the argument as a `Mirror` enum value.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> templateMirrorArgument() {
|
||
return Commands.literal("templatemirror")
|
||
.then(Commands.argument("mirror", ArgumentTypes.templateMirror())
|
||
.executes(ctx -> {
|
||
final Mirror mirror = ctx.getArgument("mirror", Mirror.class);
|
||
ctx.getSource().getSender().sendPlainMessage("You selected: " + mirror.name());
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Template rotation argument**
|
||
* The user has 4 valid input possibilities: `180`, `clockwise_90`, `counterclockwise_90`, and `none`.
|
||
* You can retrieve the result of the argument as a `StructureRotation` enum value.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> templateRotationArgument() {
|
||
return Commands.literal("templaterotation")
|
||
.then(Commands.argument("rotation", ArgumentTypes.templateRotation())
|
||
.executes(ctx -> {
|
||
final StructureRotation rotation = ctx.getArgument("rotation", StructureRotation.class);
|
||
ctx.getSource().getSender().sendPlainMessage("You selected: " + rotation.name());
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### Adventure Argument
|
||
|
||
* **Component argument**
|
||
* Expects the JSON representation of a text component, making it inappropriate for general user input.
|
||
* The result is returned as an Adventure component to work with.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> componentArgument() {
|
||
return Commands.literal("componentargument")
|
||
.then(Commands.argument("arg", ArgumentTypes.component())
|
||
.executes(ctx -> {
|
||
final Component component = ctx.getArgument("arg", Component.class);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Your message: <input>",
|
||
Placeholder.component("input", component)
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Key argument**
|
||
* Allows a user to put in any artificial (namespaced) key, ensuring its validity.
|
||
* Returns a `Key`, which can be used at various other places in the Paper API.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> keyArgument() {
|
||
return Commands.literal("key")
|
||
.then(Commands.argument("key_input", ArgumentTypes.key())
|
||
.executes(ctx -> {
|
||
final Key key = ctx.getArgument("key_input", Key.class);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"You put in <aqua><key></aqua>!",
|
||
Placeholder.unparsed("key", key.asString())
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Named color argument**
|
||
* Provides the user with the ability to select between the 16 built-in “named” text colors.
|
||
* Returns a `NamedTextColor`, which you can use for applying a color to components.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> namedColorArgument() {
|
||
return Commands.literal("namedcolor")
|
||
.then(Commands.argument("color", ArgumentTypes.namedColor())
|
||
.then(Commands.argument("message", StringArgumentType.greedyString())
|
||
.executes(ctx -> {
|
||
final NamedTextColor color = ctx.getArgument("color", NamedTextColor.class);
|
||
final String msg = StringArgumentType.getString(ctx, "message");
|
||
ctx.getSource().getSender().sendMessage(Component.text(msg).color(color));
|
||
return Command.SINGLE_SUCCESS;
|
||
})))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Adventure style argument**
|
||
* Similar to the component argument, this argument is not really appropriate for general user input, as it also follows the JSON format for displaying components.
|
||
* Returns its value in the form of a `Style` object.
|
||
* This can be applied to any component using `Component#style(Style)`.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> styleArgument() {
|
||
return Commands.literal("style")
|
||
.then(Commands.argument("style", ArgumentTypes.style())
|
||
.then(Commands.argument("message", StringArgumentType.greedyString())
|
||
.executes(ctx -> {
|
||
final Style style = ctx.getArgument("style", Style.class);
|
||
final String msg = StringArgumentType.getString(ctx, "message");
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Your input: <input>",
|
||
Placeholder.component("input", Component.text(message).style(style))
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
})))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* **Signed message argument**
|
||
* Allows a player to send an argument in the form of a signed message to the server.
|
||
* Returns a `SignedMessageResolver`.
|
||
* In order to call its `#resolve` method, you have to pass in two parameters:
|
||
* The argument name
|
||
* The `CommandContext<CommandSourceStack>` object
|
||
* The resolved value is a `CompletableFuture<SignedMessage>`, whose `SignedMessage` value you can handle using `thenAccept(Consumer<T>)`.
|
||
* Example usage:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> signedMessageArgument() {
|
||
return Commands.literal("signedmessage")
|
||
.then(Commands.argument("target", ArgumentTypes.player())
|
||
.then(Commands.argument("message", ArgumentTypes.signedMessage())
|
||
.executes(MinecraftArguments::executeSignedMessageCommand)))
|
||
.build();
|
||
}
|
||
|
||
private static int executeSignedMessageCommand(final CommandContext<CommandSourceStack> ctx)
|
||
throws CommandSyntaxException {
|
||
final Player target = ctx.getArgument("target", PlayerSelectorArgumentResolver.class)
|
||
.resolve(ctx.getSource())
|
||
.getFirst();
|
||
final SignedMessageResolver messageResolver = ctx.getArgument("message", SignedMessageResolver.class);
|
||
messageResolver.resolveSignedMessage("message", ctx)
|
||
.thenAccept(msg -> {
|
||
target.sendMessage(msg, ChatType.CHAT.bind(ctx.getSource().getSender().name()));
|
||
});
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
```
|
||
|
||
## Event API - Chat Events
|
||
|
||
### Chat Events
|
||
* The chat event has evolved a few times over the years.
|
||
* This guide will explain how to properly use the new `AsyncChatEvent` and its `ChatRenderer`.
|
||
* The `AsyncChatEvent` is an improved version of the old `AsyncPlayerChatEvent` that allows you to render chat messages individually for each player.
|
||
|
||
#### AsyncChatEvent vs ChatEvent
|
||
* The key difference between `AsyncChatEvent` and `ChatEvent` is that `AsyncChatEvent` is fired asynchronously.
|
||
* This means that it does not block the main thread and sends the chat message when the listener has completed.
|
||
* Be aware that using the Bukkit API in an asynchronous context (i.e. the event handler) is unsafe and exceptions may be thrown.
|
||
* If you need to use the Bukkit API, you can use `ChatEvent`.
|
||
* However, we recommend using `BukkitScheduler`.
|
||
|
||
#### Understanding the renderer
|
||
* The renderer is Paper’s way of allowing plugins to modify the chat message before it is sent to the player.
|
||
* This is done by using the `ChatRenderer` interface with its `ChatRenderer#render(Player, Component, Component, Audience)` method.
|
||
* Previously, this was done by using the `AsyncPlayerChatEvent` with its `AsyncPlayerChatEvent#setFormat(String)` method.
|
||
|
||
```java
|
||
ChatRenderer#render
|
||
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
* The `render` method is called when a chat message is sent to the player.
|
||
* `source`: The player that sent the message.
|
||
* `sourceDisplayName`: The display name of the player that sent the message.
|
||
* `message`: The message that was sent.
|
||
* `viewer`: The player that is receiving the message.
|
||
|
||
* **ChatRenderer.ViewerUnaware**
|
||
* If your renderer does not need to know about the viewer, you can use the `ChatRenderer.ViewerUnaware` interface instead of the `ChatRenderer` interface.
|
||
* This will benefit performance as the message will only be rendered once instead of each individual player.
|
||
|
||
#### Using the renderer
|
||
* There are two ways to use the renderer:
|
||
1. Implementing the `ChatRenderer` interface in a class.
|
||
2. Using a lambda expression.
|
||
* Depending on the complexity of your renderer, you may want to use one or the other.
|
||
|
||
#### Implementing the ChatRenderer interface
|
||
* Tell the event to use the renderer by using the `AbstractChatEvent#renderer()` method.
|
||
|
||
```java
|
||
ChatListener.java
|
||
public class ChatListener implements Listener, ChatRenderer {
|
||
// Implement the ChatRenderer and Listener interface
|
||
// Listen for the AsyncChatEvent
|
||
@EventHandler
|
||
public void onChat(AsyncChatEvent event) {
|
||
event.renderer(this);
|
||
// Tell the event to use our renderer
|
||
}
|
||
|
||
// Override the render method
|
||
@Override
|
||
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
* **Note:** If you decide to create a separate class for your renderer, it is important to know that you don’t need to instantiate the class every time the event is called. In this case, you can use the singleton pattern to create a single instance of the class.
|
||
|
||
#### Using a lambda expression
|
||
|
||
```java
|
||
ChatListener.java
|
||
public class ChatListener implements Listener {
|
||
@EventHandler
|
||
public void onChat(AsyncChatEvent event) {
|
||
event.renderer((source, sourceDisplayName, message, viewer) -> {
|
||
// ...
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Rendering the message
|
||
* Return a new `Component` that contains the message you want to send.
|
||
|
||
```java
|
||
ChatListener.java
|
||
public class ChatListener implements Listener, ChatRenderer {
|
||
// Listener logic
|
||
@Override
|
||
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
||
return sourceDisplayName.append(Component.text(": ")).append(message);
|
||
}
|
||
}
|
||
```
|
||
|
||
# Command API Arguments
|
||
|
||
This knowledge base covers the various arguments available in the PaperMC Command API.
|
||
|
||
## Minecraft-specific Arguments
|
||
|
||
The `ArgumentTypes` class provides access to Minecraft-specific arguments.
|
||
|
||
### Quick Overview
|
||
|
||
| Method Name | Return Value | Quick Link |
|
||
| -------------------------- | ------------------------------ | --------------------------------------------- |
|
||
| `blockPosition()` | `BlockPositionResolver` | [Block Position Argument](#block-position-argument) |
|
||
| `blockState()` | `BlockState` | [Block State Argument](#block-state-argument) |
|
||
| `component()` | `Component (Kyori)` | [Component Argument](#component-argument) |
|
||
| `doubleRange()` | `DoubleRangeProvider` | [Double Range argument](#double-range-argument) |
|
||
| `entity()` | `EntitySelectorArgumentResolver`| [Entity Argument](#entity-argument) |
|
||
| `entities()` | `EntitySelectorArgumentResolver`| [Entities Argument](#entities-argument) |
|
||
| `entityAnchor()` | `LookAnchor` | Entity Anchor Argument |
|
||
| `finePosition(boolean centerIntegers)` | `FinePositionResolver` | [Fine Position Argument](#fine-position-argument) |
|
||
| `gameMode()` | `GameMode` | GameMode Argument |
|
||
| `heightMap()` | `HeightMap` | HeightMap Argument |
|
||
| `integerRange()` | `IntegerRangeProvider` | [Integer Range Argument](#integer-range-argument) |
|
||
| `itemPredicate()` | `ItemStackPredicate` | [Item Predicate Argument](#item-predicate-argument) |
|
||
| `itemStack()` | `ItemStack` | [ItemStack Argument](#itemstack-argument) |
|
||
| `key()` | `Key (Kyori)` | [Key Argument](#key-argument) |
|
||
| `namedColor()` | `NamedTextColor (Kyori)` | [Named Color Argument](#named-color-argument) |
|
||
| `namespacedKey()` | `NamespacedKey` | [Bukkit NamespacedKey Argument](#namespacedkey-argument) |
|
||
| `objectiveCriteria()` | `Criteria` | [Objective Criteria Argument](#objective-criteria-argument) |
|
||
| `player()` | `PlayerSelectorArgumentResolver`| [Player Argument](#player-argument) |
|
||
| `players()` | `PlayerSelectorArgumentResolver`| [Players Argument](#players-argument) |
|
||
| `playerProfiles()` | `PlayerProfileListResolver` | [Player Profiles Argument](#player-profiles-argument) |
|
||
| `resource(RegistryKey)` | `(Depends on RegistryKey)` | [Resource Argument](#resource-argument) |
|
||
| `resourceKey(RegistryKey)` | `(Depends on RegistryKey)` | [Resource Key Argument](#resource-key-argument) |
|
||
| `style()` | `Style (Kyori)` | [Style Argument](#adventure-style-argument) |
|
||
| `signedMessage()` | `SignedMessageResolver` | [Signed Message Argument](#signed-message-argument) |
|
||
| `scoreboardDisplaySlot()` | `DisplaySlot` | Scoreboard Display Slot Argument |
|
||
| `time(int mintime)` | `Integer` | [Time Argument](#time-argument) |
|
||
| `templateMirror()` | `Mirror` | Template Mirror Argument |
|
||
| `templateRotation()` | `StructureRotation` | Template Rotation Argument |
|
||
| `uuid()` | `UUID` | [UUID Argument](#uuid-argument) |
|
||
| `world()` | `World` | [World Argument](#world-argument) |
|
||
|
||
## Location Arguments
|
||
|
||
These arguments are used for retrieving location-based data.
|
||
|
||
### Block position argument
|
||
|
||
The block position argument is used for retrieving the position of a block.
|
||
It works the same way as the first argument of the `/setblock <position> <block>` Vanilla command.
|
||
|
||
* To retrieve the `BlockPosition` variable from the `BlockPositionResolver`, it must be resolved using the command source.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> blockPositionArgument() {
|
||
return Commands
|
||
.literal("blockpositionargument")
|
||
.then(Commands.argument("arg", ArgumentTypes.blockPosition())
|
||
.executes(ctx -> {
|
||
final BlockPositionResolver blockPositionResolver = ctx.getArgument("arg", BlockPositionResolver.class);
|
||
final BlockPosition blockPosition = blockPositionResolver.resolve(ctx.getSource());
|
||
ctx.getSource().getSender().sendPlainMessage(
|
||
"Put in " + blockPosition.x() + " " + blockPosition.y() + " " + blockPosition.z());
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Fine position argument
|
||
|
||
The fine position argument works similarly to the block position argument, with the only difference being that it can accept decimal (precise) location input.
|
||
The optional overload `ArgumentTypes.finePosition(boolean centerIntegers)`, which defaults to false if not set, will center whole input, meaning 5 becomes 5.5 (5.0 would stay as 5.0 though), as that is the “middle” of a block. This only applies to X/Z. The y coordinate is untouched by this operation.
|
||
|
||
* This argument returns a `FinePositionResolver`.
|
||
* Resolve by running `FinePositionResolver#resolve(CommandSourceStack)` to get the resulting `FinePosition`.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> finePositionArgument() {
|
||
return Commands
|
||
.literal("fineposition")
|
||
.then(Commands.argument("arg", ArgumentTypes.finePosition(true))
|
||
.executes(ctx -> {
|
||
final FinePositionResolver resolver = ctx.getArgument("arg", FinePositionResolver.class);
|
||
final FinePosition finePosition = resolver.resolve(ctx.getSource());
|
||
ctx.getSource().getSender().sendRichMessage("Position: <red><x></red> <green><y></green> <blue><z></blue>",
|
||
Placeholder.unparsed("x", Double.toString(finePosition.x())),
|
||
Placeholder.unparsed("y", Double.toString(finePosition.y())),
|
||
Placeholder.unparsed("z", Double.toString(finePosition.z())));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### World argument
|
||
|
||
This argument allows the user to select one of the currently loaded world.
|
||
|
||
* You can retrieve the result as a generic Bukkit `World` object.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> worldArgument() {
|
||
return Commands
|
||
.literal("teleport-to-world")
|
||
.then(Commands.argument("world", ArgumentTypes.world())
|
||
.executes(ctx -> {
|
||
final World world = ctx.getArgument("world", World.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND);
|
||
ctx.getSource().getSender().sendRichMessage("Successfully teleported <player> to <aqua><world></aqua>",
|
||
Placeholder.component("player", player.name()),
|
||
Placeholder.unparsed("world", world.getName()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!</red>");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
## Entities and Players Arguments
|
||
|
||
These arguments are used for retrieving entities and players.
|
||
|
||
### Entity argument
|
||
|
||
* This argument returns an `EntitySelectorArgumentResolver`.
|
||
* Use `ArgumentResolver#resolve(CommandSourceStack)` to resolve the entity.
|
||
* The `getFirst()` method returns a single `Entity` object.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> entityArgument() {
|
||
return Commands
|
||
.literal("entity")
|
||
.then(Commands.argument("target", ArgumentTypes.entity())
|
||
.executes(ctx -> {
|
||
final EntitySelectorArgumentResolver targetResolver = ctx.getArgument("target", EntitySelectorArgumentResolver.class);
|
||
final Entity entity = targetResolver.resolve(ctx.getSource()).getFirst();
|
||
ctx.getSource().getSender().sendRichMessage("Found <green><entityname></green>",
|
||
Placeholder.component("entityname", entity.name()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
If the executing player doesn’t have the `minecraft.command.selector` permission:
|
||
|
||
Your device does not support video playback.
|
||
|
||
If the executing player has the `minecraft.command.selector` permission:
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Entities argument
|
||
|
||
In contrast to the single entity argument, this multiple-entities argument accepts any amount of entities, with the minimum amount of entities being 1.
|
||
|
||
* They can be resolved using `ArgumentResolver#resolve(CommandSourceStack)`, which returns a `List<Entity>`.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> entitiesArgument() {
|
||
return Commands
|
||
.literal("entitiesarg")
|
||
.then(Commands.argument("arg", ArgumentTypes.entities())
|
||
.executes(ctx -> {
|
||
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
|
||
final List<Entity> entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
|
||
final Component foundEntities = Component.join(JoinConfiguration.commas(true), entities.stream().map(Entity::name).toList());
|
||
ctx.getSource().getSender().sendRichMessage("Found <green><entitynames></green>",
|
||
Placeholder.component("entitynames", foundEntities));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Player argument
|
||
|
||
The player argument allows to retrieve a `PlayerSelectorArgumentResolver` for player arguments.
|
||
|
||
* For this “single player” argument, you can safely get the target player by running `PlayerSelectorArgumentResolver.resolve(ctx.getSource()).getFirst()`, which returns a `Player` object.
|
||
|
||
#### Example usage
|
||
|
||
This command yeets the targeted player into the air!
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playerArgument() {
|
||
return Commands
|
||
.literal("player")
|
||
.then(Commands.argument("target", ArgumentTypes.player())
|
||
.executes(ctx -> {
|
||
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
|
||
final Player target = targetResolver.resolve(ctx.getSource()).getFirst();
|
||
target.setVelocity(new Vector(0, 100, 0));
|
||
target.sendRichMessage("<rainbow>Yeeeeeeeeeet</rainbow>");
|
||
ctx.getSource().getSender().sendRichMessage("Yeeted <target>!",
|
||
Placeholder.component("target", target.name()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Players argument
|
||
|
||
The “multiple players” argument works similarly to the “single player” argument, also returning a `PlayerSelectorArgumentResolver`.
|
||
|
||
* Instead of just resolving to exactly one `Player`, this one can resolve to more than just one player - which you should account for in case of using this argument.
|
||
* `PlayerSelectorArgumentResolver.resolve(ctx.getSource())` returns a `List<Player>`, which you can just iterate through.
|
||
|
||
#### Example usage
|
||
|
||
Extending the “single player” yeet command to support multiple targets can look like this:
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playersArgument() {
|
||
return Commands
|
||
.literal("players")
|
||
.then(Commands.argument("targets", ArgumentTypes.players())
|
||
.executes(ctx -> {
|
||
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("targets", PlayerSelectorArgumentResolver.class);
|
||
final List<Player> targets = targetResolver.resolve(ctx.getSource());
|
||
final CommandSender sender = ctx.getSource().getSender();
|
||
for (final Player target : targets) {
|
||
target.setVelocity(new Vector(0, 100, 0));
|
||
target.sendRichMessage("<rainbow>Yeeeeeeeeeet</rainbow>");
|
||
sender.sendRichMessage("Yeeted <target>!",
|
||
Placeholder.component("target", target.name()));
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Player profiles argument
|
||
|
||
The player profiles argument is a very powerful argument which can retrieve both offline and online players.
|
||
|
||
* It returns the result of the argument as a `PlayerProfileListResolver`, which resolves to a `Collection<PlayerProfile>`.
|
||
* This collection can be iterated to get the resulting profile(s).
|
||
* Usually, it only returns a single `PlayerProfile` if retrieving a player by name, but it can return multiple if using the entity selectors (like `@a` on online players).
|
||
* It always makes sense to run whatever operation you want to run on all entries in the collection instead of just the first one.
|
||
* This argument will run API calls to Mojang servers in order to retrieve player information for players which have never joined the server before.
|
||
* It is suggested to resolve this argument in an asynchronous context in order to not cause any server lag.
|
||
* Sometimes, these API calls may fail.
|
||
|
||
#### Example usage - player lookup command
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> playerProfilesArgument() {
|
||
return Commands
|
||
.literal("lookup")
|
||
.then(Commands.argument("profile", ArgumentTypes.playerProfiles())
|
||
.executes(ctx -> {
|
||
final PlayerProfileListResolver profilesResolver = ctx.getArgument("profile", PlayerProfileListResolver.class);
|
||
final Collection<PlayerProfile> foundProfiles = profilesResolver.resolve(ctx.getSource());
|
||
for (final PlayerProfile profile : foundProfiles) {
|
||
ctx.getSource().getSender().sendPlainMessage("Found " + profile.getName());
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
## Registry Arguments
|
||
|
||
Registries in Minecraft hold information about item or block types, enchantments, potion effects, and more.
|
||
|
||
There are two types of registry arguments: `resource` and `resourceKey`.
|
||
|
||
* `resource` argument returns the parsed value.
|
||
* `resourceKey` only returns a `TypedKey`, which you can use to retrieve the value yourself.
|
||
|
||
### Resource argument
|
||
|
||
* You can get a `ArgumentType<T>` reference to it using `ArgumentTypes.resource(RegistryKey<T>)`.
|
||
* A selection of possible registry keys can be found below.
|
||
* They are accessed in a static context using the `RegistryKey` interface.
|
||
* Each entry in `RegistryKey` returns a `RegistryKey<T>`.
|
||
* The `<T>` generic parameter describes the return type.
|
||
|
||
#### Example
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> enchantmentRegistry() {
|
||
return Commands
|
||
.literal("enchants-registry")
|
||
.then(Commands.argument("enchantment", ArgumentTypes.resource(RegistryKey.ENCHANTMENT))
|
||
.executes(ctx -> {
|
||
final Enchantment enchantment = ctx.getArgument("enchantment", Enchantment.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
final ItemStack stack = player.getInventory().getItemInMainHand();
|
||
stack.addUnsafeEnchantment(enchantment, 10);
|
||
ctx.getSource().getSender().sendRichMessage(
|
||
"Enchanted <player>'s <item> with <enchantment>!",
|
||
Placeholder.component("player", player.name()),
|
||
Placeholder.component("item", Component.translatable(stack)),
|
||
Placeholder.component("enchantment", enchantment.displayName(10))
|
||
);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendRichMessage("<red>This command requires a player!</red>");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Caution
|
||
|
||
There are certain edge-cases, where this argument, due to missing registries on the client, will cause a `Network Protocol Error`.
|
||
|
||
Basically, the only argument where this is the case right now is with the `STRUCTURE` registry key.
|
||
|
||
```java
|
||
// Registering this command will cause clients to not be able to connect to the server.
|
||
final LiteralCommandNode<CommandSourceStack> invalidRegistryArgument = Commands
|
||
.literal("registry-structure")
|
||
.then(Commands.argument("value", ArgumentTypes.resource(RegistryKey.STRUCTURE)))
|
||
.build();
|
||
```
|
||
|
||
Due to this fact, it is advised to only use the `STRUCTURE` registry key argument with a `resourceKey(...)` argument type and parse the values yourself.
|
||
|
||
### Resource key argument
|
||
|
||
For the client, there is barely any difference between the using `ArgumentTypes.resource` or `ArgumentTypes.resourceKey`. The only difference is that using `ArgumentTypes.resourceKey` does not provide error checking.
|
||
|
||
The resource argument provides a much cleaner user experience, whilst the `resourceKey` argument has one very important use case: You get the raw `TypedKey<T>` returned as an argument result.
|
||
This object is particularly useful, as it provides all information required to be able to retrieve a value from a registry yourself.
|
||
|
||
Unless you have a specific reason for using the `resourceKey` argument over the `resource` one, the `resource` argument is preferred due to the client-side error checking and simple usability.
|
||
|
||
#### Direct code comparison
|
||
|
||
Here is a simple code snipped on how one could use the `RegistryKey.ITEM` registry with a `resource` argument type:
|
||
|
||
```java
|
||
Commands
|
||
.argument("item", ArgumentTypes.resource(RegistryKey.ITEM))
|
||
.executes(ctx -> {
|
||
final ItemType item = ctx.getArgument("item", ItemType.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.getInventory().addItem(item.createItemStack());
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
|
||
Here is the same code, using a `resourceKey` argument type. Instead of directly retrieving the argument using `ctx.getArgument("item", TypedKey.class)`, we instead use the `RegistryArgumentExtractor` to retrieve our `TypedKey<ItemType>`.
|
||
|
||
```java
|
||
Commands
|
||
.argument("item", ArgumentTypes.resourceKey(RegistryKey.ITEM))
|
||
.executes(ctx -> {
|
||
final TypedKey<ItemType> itemKey = RegistryArgumentExtractor.getTypedKey(ctx, RegistryKey.ITEM, "item");
|
||
ItemType item = RegistryAccess.registryAccess().getRegistry(itemKey.registryKey()).get(itemKey.key());
|
||
if (item == null) {
|
||
ctx.getSource().getSender().sendRichMessage("<red>Please provide a valid item!</red>");
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.getInventory().addItem(item.createItemStack());
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
|
||
#### Using a TypedKey
|
||
|
||
1. In order to get the correct registry, you can run `RegistryAccess#getRegistry(RegistryKey)`.
|
||
2. In order to get a `RegistryAccess`, you can just use the static `RegistryAccess.registryAccess()` method.
|
||
3. The `RegistryKey` is retrieved using `TypedKey#registryKey()`.
|
||
4. Now, in order to get the final value `T`, you can run `Registry#get(Key)`, where the key can be retrieved using `TypedKey#key()`.
|
||
5. This will return the backing instance from that resource key or null, if no value has been found.
|
||
|
||
#### Use case over resource argument
|
||
|
||
The main use case for this argument type is the ability to store the key (the value returned to you by `TypedKey#key`).
|
||
If you want to be able to store the exact user input and be able to retrieve the backed instance without much trouble, that is the way to do it.
|
||
|
||
### Registry key previews
|
||
|
||
The following `RegistryKeys` exist:
|
||
|
||
| RegistryKeys Field | Return Value | Preview Video |
|
||
| ---------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `ATTRIBUTE` | `Attribute` | [Attribute](#attribute) |
|
||
| `BANNER_PATTERN` | `PatternType` | [Banner Pattern](#banner-pattern) |
|
||
| `BIOME` | `Biome` | [Biome](#biome) |
|
||
| `BLOCK` | `BlockType` | [Block](#block) |
|
||
| `CAT_VARIANT` | `Cat.Type` | [Cat Variant](#cat-variant) |
|
||
| `CHICKEN_VARIANT` | `Chicken.Variant` | [Chicken Variant](#chicken-variant) |
|
||
| `COW_VARIANT` | `Cow.Variant` | [Cow Variant](#cow-variant) |
|
||
| `DAMAGE_TYPE` | `DamageType` | [Damage Type](#damage-type) |
|
||
| `DATA_COMPONENT_TYPE` | `DataComponentType` | [Data Component Type](#data-component-type) |
|
||
| `DIALOG` | `Dialog` | [Dialog](#dialog) |
|
||
| `ENCHANTMENT` | `Enchantment` | [Enchantment](#enchantment) |
|
||
| `ENTITY_TYPE` | `EntityType` | [Entity Type](#entity-type) |
|
||
| `FLUID` | `Fluid` | [Fluid](#fluid) |
|
||
| `FROG_VARIANT` | `Frog.Variant` | [Frog Variant](#frog-variant) |
|
||
| `GAME_EVENT` | `GameEvent` | [Game Event](#game-event) |
|
||
| `INSTRUMENT` | `MusicInstrument` | [Instrument](#instrument) |
|
||
| `ITEM` | `ItemType` | [Item](#item) |
|
||
| `JUKEBOX_SONG` | `JukeboxSong` | [Jukebox Song](#jukebox-song) |
|
||
| `MAP_DECORATION_TYPE` | `MapCursor.Type` | [Map Decoration Type](#map-decoration-type) |
|
||
| `MEMORY_MODULE_TYPE` | `MemoryKey<?>` | [Memory Module Type](#memory-module-type) |
|
||
| `MENU` | `MenuType` | [Menu](#menu) |
|
||
| `MOB_EFFECT` | `PotionEffectType` | [Mob effect](#mob-effect) |
|
||
| `PAINTING_VARIANT` | `Art` | [Painting variant](#painting-variant) |
|
||
| `PARTICLE_TYPE` | `Particle` | [Particle](#particle) |
|
||
| `PIG_VARIANT` | `Pig.Variant` | [Pig Variant](#pig-variant) |
|
||
| `POTION` | `PotionType` | [Potion](#potion) |
|
||
| `SOUND_EVENT` | `Sound` | [Sound](#sound) |
|
||
| `STRUCTURE` | `Structure` | [Structure](#structure) |
|
||
| `STRUCTURE_TYPE` | `StructureType` | [Structure type](#structure-type) |
|
||
| `TRIM_MATERIAL` | `TrimMaterial` | [Trim material](#trim-material) |
|
||
| `TRIM_PATTERN` | `TrimPattern` | [Trim pattern](#trim-pattern) |
|
||
| `VILLAGER_PROFESSION` | `Villager.Profession` | [Villager Profession](#villager-profession) |
|
||
| `VILLAGER_TYPE` | `Villager.Type` | [Villager Type](#villager-type) |
|
||
| `WOLF_SOUND_VARIANT` | `Wolf.SoundVariant` | [Wolf Sound Variant](#wolf-sound-variant) |
|
||
| `WOLF_VARIANT` | `Wolf.Variant` | [Wolf Variant](#wolf-variant) |
|
||
|
||
#### Attribute
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Banner pattern
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Biome
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Block
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Cat variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Chicken variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Cow variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Damage type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Data component type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Dialog
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Enchantment
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Entity type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Fluid
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Frog variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Game event
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Instrument
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Item
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Jukebox Song
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Map decoration type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Memory module type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Menu
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Mob effect
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Painting variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Particle
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Pig variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Potion
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Sound
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Structure
|
||
|
||
This argument kicks the client, so no preview for this one ¯\_(ツ)_/¯
|
||
|
||
#### Structure type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Trim material
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Trim pattern
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Villager profession
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Villager type
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Wolf sound variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
#### Wolf variant
|
||
|
||
Your device does not support video playback.
|
||
|
||
## Paper-specific Arguments
|
||
|
||
These arguments return objects frequently used in Paper API.
|
||
|
||
### Block state argument
|
||
|
||
The block state argument can be used for getting a block type and explicit, associated data.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> blockStateArgument() {
|
||
return Commands
|
||
.literal("blockstateargument")
|
||
.then(Commands.argument("arg", ArgumentTypes.blockState())
|
||
.executes(ctx -> {
|
||
final BlockState blockState = ctx.getArgument("arg", BlockState.class);
|
||
ctx.getSource().getSender().sendPlainMessage("You specified a " + blockState.getType() + "!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### ItemStack argument
|
||
|
||
The item stack argument is the way to retrieve an `ItemStack` following the same argument format as the Vanilla `/give <player> <item> [<amount>]` command as its second argument.
|
||
|
||
* The user may also define components to further customize the `ItemStack`.
|
||
* If you only require a `Material`, you should instead check out the [registry arguments](#registry-arguments).
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> itemStackArgument() {
|
||
return Commands
|
||
.literal("itemstack")
|
||
.then(Commands.argument("stack", ArgumentTypes.itemStack())
|
||
.executes(ctx -> {
|
||
final ItemStack itemStack = ctx.getArgument("stack", ItemStack.class);
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.getInventory().addItem(itemStack);
|
||
ctx.getSource().getSender().sendRichMessage("<green>Successfully gave <player> a <item></green>",
|
||
Placeholder.component("player", player.name()),
|
||
Placeholder.component("item", Component.translatable(itemStack)));
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendRichMessage("<red>This argument requires a player!</red>");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### NamespacedKey argument
|
||
|
||
This argument allows the user to provide any artificial (namespaced) key.
|
||
|
||
* The return value of this argument is a `NamespacedKey`, which makes it useful when dealing with Bukkit API.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> namespacedKeyArgument() {
|
||
return Commands
|
||
.literal("namespacedkey")
|
||
.then(Commands.argument("key", ArgumentTypes.namespacedKey())
|
||
.executes(ctx -> {
|
||
final NamespacedKey key = ctx.getArgument("key", NamespacedKey.class);
|
||
ctx.getSource().getSender().sendRichMessage("You put in <aqua><key></aqua>!",
|
||
Placeholder.unparsed("key", key.toString()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Time argument
|
||
|
||
The time argument allows the user to define a time frame, similar to the Vanilla `/time <set|time> <time>` time argument. The user has 4 possible ways of inputting time:
|
||
|
||
* Just as a number: This resolves to as usual ticks (`/timearg 1`—> 1 tick)
|
||
* With a `t` suffix: This also resolves to ticks (`/timearg 1t`—> 1 tick)
|
||
* With a `s` suffix: This resolves to seconds, meaning multiplying the first number by 20. (`/timearg 1s`—> 20 ticks)
|
||
* With a `d` suffix. This resolves as in-game days, meaning multiplying the first number by 24000. (`/timearg 1d`—> 24000 ticks)
|
||
|
||
If you choose to use this argument, it is advised to explain to the users what these suffixes mean, as real time (`s` suffix) is mixed with in-game time (`t` and `d` suffix).
|
||
|
||
The `ArgumentTypes.time()` method has one additional overload: `ArgumentTypes.time(int mintime)`. This allows to set the minimum required amount of ticks this argument has to resolve to. By default this value is set to 0.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> timeArgument() {
|
||
return Commands
|
||
.literal("timearg")
|
||
.then(Commands.argument("time", ArgumentTypes.time())
|
||
.executes(ctx -> {
|
||
final int timeInTicks = IntegerArgumentType.getInteger(ctx, "time");
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.getWorld().setFullTime(player.getWorld().getFullTime() + timeInTicks);
|
||
player.sendRichMessage("Moved time forward by " + timeInTicks + " ticks!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
ctx.getSource().getSender().sendPlainMessage("This argument requires a player!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### UUID argument
|
||
|
||
The UUID argument allows the user to input a valid UUID.
|
||
|
||
* You can retrieve that value as a `UUID` object, which is used in various places, like `Bukkit.getOfflinePlayer(UUID)`.
|
||
* This argument is not very user-friendly, which is why it is suggested to only use this as a moderation or debug argument.
|
||
* For user input regarding offline player retrieval, the [player profiles argument](#player-profiles-argument) is preferred, as it allows by-name lookup.
|
||
|
||
#### Example usage - Lookup command
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> uuidArgument() {
|
||
return Commands
|
||
.literal("uuid-lookup")
|
||
.then(Commands.argument("uuid", ArgumentTypes.uuid())
|
||
.executes(ctx -> {
|
||
final UUID uuid = ctx.getArgument("uuid", UUID.class);
|
||
final OfflinePlayer result = Bukkit.getOfflinePlayer(uuid);
|
||
ctx.getSource().getSender().sendRichMessage("Has <aqua><uuid></aqua> played before: <result>",
|
||
Placeholder.unparsed("uuid", uuid.toString()),
|
||
Placeholder.parsed("result", result.hasPlayedBefore() ? "<green>true</green>" : "<red>false</red>"));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
### Objective criteria argument
|
||
|
||
You can retrieve the argument value as a `Criteria` enum value, which can be used with `Scoreboard` objects.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> objectiveCriteriaArgument() {
|
||
return Commands
|
||
.literal("objectivecriteria")
|
||
.then(Commands.argument("criteria", ArgumentTypes.objectiveCriteria())
|
||
.executes(ctx -> {
|
||
final Criteria criteria = ctx.getArgument("criteria", Criteria.class);
|
||
ctx.getSource().getSender().sendRichMessage("Default render type for <criteria>: <rendertype>",
|
||
Placeholder.unparsed("criteria", criteria.getName()),
|
||
Placeholder.unparsed("rendertype", criteria.getDefaultRenderType().name()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
#### In-game preview
|
||
|
||
Your device does not support video playback.
|
||
|
||
## Predicates Arguments
|
||
|
||
A predicate allows for checking for valid values. These arguments are dedicated to checking whether some sort of value is valid according to user input.
|
||
|
||
### Double range argument
|
||
|
||
This argument can be used as a predicate for numbers, which require precise input.
|
||
|
||
#### Example usage
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> doubleRangeArgument() {
|
||
return Commands
|
||
.literal("doublerange")
|
||
.then(Commands.argument("arg", ArgumentTypes.doubleRange())
|
||
.executes(ctx -> {
|
||
final DoubleRangeProvider doubleRangeProvider = ctx.getArgument("arg", DoubleRangeProvider.class);
|
||
final CommandSender sender = ctx.getSource().getSender();
|
||
for (int i = 0; i < 5; i++) {
|
||
sender.sendRich
|
||
|
||
```markdown
|
||
# Command API Knowledge Base
|
||
|
||
## Table of Contents
|
||
|
||
* [Minecraft Arguments](#minecraft-arguments)
|
||
* [Suggestions](#suggestions)
|
||
* [Command Trees](#command-trees)
|
||
* [Arguments and Literals](#arguments-and-literals)
|
||
* [Registry](#registry)
|
||
* [Executors](#executors)
|
||
|
||
## Minecraft Arguments
|
||
|
||
### Integer Range Argument
|
||
|
||
This argument only accepts integers.
|
||
|
||
#### Example Usage
|
||
|
||
```java
|
||
public
|
||
static
|
||
LiteralCommandNode<
|
||
CommandSourceStack
|
||
>
|
||
integerRangeArgument
|
||
()
|
||
{
|
||
return
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
integerrange
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
range
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
integerRange
|
||
())
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
tested_integer
|
||
"
|
||
,
|
||
IntegerArgumentType
|
||
.
|
||
integer
|
||
())
|
||
.
|
||
executes
|
||
(
|
||
MinecraftArguments
|
||
::
|
||
runIntegerRangeCommand
|
||
)))
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
private
|
||
static
|
||
int
|
||
runIntegerRangeCommand
|
||
(
|
||
final
|
||
CommandContext<
|
||
CommandSourceStack
|
||
>
|
||
ctx
|
||
)
|
||
{
|
||
final
|
||
IntegerRangeProvider
|
||
integerRangeProvider
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
range
|
||
"
|
||
,
|
||
IntegerRangeProvider
|
||
.
|
||
class
|
||
)
|
||
;
|
||
final
|
||
int
|
||
integerToTest
|
||
=
|
||
IntegerArgumentType
|
||
.
|
||
getInteger
|
||
(
|
||
ctx,
|
||
"
|
||
tested_integer
|
||
"
|
||
)
|
||
;
|
||
if
|
||
(
|
||
integerRangeProvider
|
||
.
|
||
range
|
||
()
|
||
.
|
||
contains
|
||
(
|
||
integerToTest
|
||
)
|
||
) {
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<aqua><input></aqua> <green>is</green> inside the specified range!
|
||
"
|
||
,
|
||
Placeholder
|
||
.
|
||
unparsed
|
||
(
|
||
"
|
||
input
|
||
"
|
||
,
|
||
Integer
|
||
.
|
||
toString
|
||
(
|
||
integerToTest
|
||
))
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<aqua><input></aqua> <red>is not</red> inside the specified range!
|
||
"
|
||
,
|
||
Placeholder
|
||
.
|
||
unparsed
|
||
(
|
||
"
|
||
input
|
||
"
|
||
,
|
||
Integer
|
||
.
|
||
toString
|
||
(
|
||
integerToTest
|
||
))
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
```
|
||
|
||
#### In-Game Preview
|
||
|
||
(Video playback not supported)
|
||
|
||
### Item Predicate Argument
|
||
|
||
This argument allows for checking whether an item fits some predicate. It is useful for filtering out certain items based on some criteria.
|
||
|
||
#### Example Usage
|
||
|
||
```java
|
||
public
|
||
static
|
||
LiteralCommandNode<
|
||
CommandSourceStack
|
||
>
|
||
itemPredicateArgument
|
||
()
|
||
{
|
||
return
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
itempredicate
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
predicate
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
itemPredicate
|
||
())
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
final
|
||
ItemStackPredicate
|
||
predicate
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
predicate
|
||
"
|
||
,
|
||
ItemStackPredicate
|
||
.
|
||
class
|
||
)
|
||
;
|
||
final
|
||
ItemStack
|
||
defaultWoodenSword
|
||
=
|
||
ItemType
|
||
.
|
||
WOODEN_SWORD
|
||
.
|
||
createItemStack
|
||
()
|
||
;
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
Does predicate include a default wooden sword? <result>
|
||
"
|
||
,
|
||
Placeholder
|
||
.
|
||
parsed
|
||
(
|
||
"
|
||
result
|
||
"
|
||
,
|
||
predicate
|
||
.
|
||
test
|
||
(
|
||
defaultWoodenSword
|
||
)
|
||
?
|
||
"
|
||
<green>true</green>
|
||
"
|
||
:
|
||
"
|
||
<red>false</red>
|
||
"
|
||
)
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
))
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
```
|
||
|
||
#### In-Game Preview
|
||
|
||
(Video playback not supported)
|
||
|
||
## Suggestions
|
||
|
||
Sometimes, you want to send your own suggestions to users. For this, you can use the `suggests(SuggestionProvider<CommandSourceStack>)` method when declaring arguments.
|
||
|
||
### Examining `SuggestionProvider<S>`
|
||
|
||
The `SuggestionProvider<S>` interface is defined as follows:
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public
|
||
interface
|
||
SuggestionProvider
|
||
<
|
||
S
|
||
> {
|
||
CompletableFuture
|
||
<
|
||
Suggestions
|
||
>
|
||
getSuggestions
|
||
(
|
||
final
|
||
CommandContext
|
||
<
|
||
S
|
||
>
|
||
context
|
||
,
|
||
final
|
||
SuggestionsBuilder
|
||
builder
|
||
)
|
||
throws
|
||
CommandSyntaxException
|
||
;
|
||
}
|
||
```
|
||
|
||
* For Paper, `S` is usually `CommandSourceStack`.
|
||
* It's a functional interface, so a lambda or method reference can be used.
|
||
* The lambda takes a `CommandContext<S>` and `SuggestionsBuilder` and returns a `CompletableFuture<Suggestions>`.
|
||
|
||
A very simple lambda for our suggests method might look like this:
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
name
|
||
"
|
||
,
|
||
StringArgumentType
|
||
.
|
||
word
|
||
())
|
||
.
|
||
suggests
|
||
(
|
||
(ctx, builder)
|
||
->
|
||
builder
|
||
.
|
||
buildFuture
|
||
())
|
||
;
|
||
```
|
||
|
||
This example obviously does not suggest anything, as we haven’t added any suggestions yet.
|
||
|
||
### The `SuggestionsBuilder`
|
||
|
||
The `SuggestionsBuilder` has a few methods we can use to construct our suggestions:
|
||
|
||
### Input Retrieval
|
||
|
||
The input retrieval methods are: `getInput()`, `getStart()`, `getRemaining()`, and `getRemainingLowerCase()`.
|
||
|
||
| Method | Return Value | Description |
|
||
| ---------------------- | ------------------------------- | ----------------------------------------------------------- |
|
||
| `getInput()` | `/customsuggestions Asumm13Text` | The full chat input |
|
||
| `getStart()` | `19` | The index of the first character of the argument’s input |
|
||
| `getRemaining()` | `Asumm13Text` | The input for the current argument |
|
||
| `getRemainingLowerCase()` | `asumm13text` | The input for the current argument, lowercased |
|
||
|
||
Example Input: `/customsuggestions Asumm13Text`
|
||
|
||
### Suggestions
|
||
|
||
The following overloads of the `SuggestionsBuilder#suggest` method add values that will be send to the client as argument suggestions:
|
||
|
||
| Overload | Description |
|
||
| ----------------------- | -------------------------------------------------- |
|
||
| `suggest(String)` | Adds a String to the suggestions |
|
||
| `suggest(String, Message)` | Adds a String with a tooltip to the suggestions |
|
||
| `suggest(int)` | Adds an int to the suggestions |
|
||
| `suggest(int, Message)` | Adds an int with a tooltip to the suggestions |
|
||
|
||
There are two ways of retrieving a `Message` instance:
|
||
|
||
* Using `LiteralMessage`, which can be used for basic, non-formatted text.
|
||
* Using the `MessageComponentSerializer`, which can be used to serialize `Component` objects into `Message` objects.
|
||
|
||
For example, if you add a suggestion like this:
|
||
|
||
```java
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
"
|
||
suggestion
|
||
"
|
||
,
|
||
MessageComponentSerializer
|
||
.
|
||
message
|
||
()
|
||
.
|
||
serialize
|
||
(
|
||
MiniMessage
|
||
.
|
||
miniMessage
|
||
()
|
||
.
|
||
deserialize
|
||
(
|
||
"
|
||
<green>Suggestion tooltip
|
||
"
|
||
)
|
||
))
|
||
;
|
||
```
|
||
|
||
It will look like this on the client:
|
||
|
||
### Building
|
||
|
||
There are two methods we can use to build our `Suggestions` object. The only difference between those is that one directly returns the finished `Suggestions` object, whilst the other one returns a `CompletableFuture<Suggestions>`.
|
||
|
||
The reason for these two methods is that `SuggestionProvider` expects the return value to be `CompletableFuture<Suggestions>`. This for once allows for constructing your suggestions asynchronously inside a `CompletableFuture.supplyAsync(Supplier<Suggestions>)` statement, or synchronously directly inside our lambda and returning the final `Suggestions` object asynchronously.
|
||
|
||
Here are the same suggestions declared in the two different ways mentioned above:
|
||
|
||
```java
|
||
// Here, you are safe to use all Paper API
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
name
|
||
"
|
||
,
|
||
StringArgumentType
|
||
.
|
||
word
|
||
())
|
||
.
|
||
suggests
|
||
(
|
||
(ctx, builder)
|
||
->
|
||
{
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
"
|
||
first
|
||
"
|
||
)
|
||
;
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
"
|
||
second
|
||
"
|
||
)
|
||
;
|
||
return
|
||
builder
|
||
.
|
||
buildFuture
|
||
()
|
||
;
|
||
}
|
||
)
|
||
;
|
||
// Here, most Paper API is not usable
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
name
|
||
"
|
||
,
|
||
StringArgumentType
|
||
.
|
||
word
|
||
())
|
||
.
|
||
suggests
|
||
(
|
||
(ctx, builder)
|
||
->
|
||
CompletableFuture
|
||
.
|
||
supplyAsync
|
||
(
|
||
()
|
||
->
|
||
{
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
"
|
||
first
|
||
"
|
||
)
|
||
;
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
"
|
||
second
|
||
"
|
||
)
|
||
;
|
||
return
|
||
builder
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
))
|
||
;
|
||
```
|
||
|
||
### Example: Suggesting Amounts in a Give Item Command
|
||
|
||
In commands, where you give players items, you oftentimes include an amount argument. We could suggest `1`, `16`, `32`, and `64` as common amounts for items given. The command implementation could look like this:
|
||
|
||
```java
|
||
@NullMarked
|
||
public
|
||
class
|
||
SuggestionsTest
|
||
{
|
||
public
|
||
static
|
||
LiteralCommandNode
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
constructGiveItemCommand
|
||
()
|
||
{
|
||
// Create new command: /giveitem
|
||
return
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
giveitem
|
||
"
|
||
)
|
||
// Require a player to execute the command
|
||
.
|
||
requires
|
||
(
|
||
ctx
|
||
->
|
||
ctx
|
||
.
|
||
getExecutor
|
||
()
|
||
instanceof
|
||
Player
|
||
)
|
||
// Declare a new ItemStack argument
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
itemStack
|
||
())
|
||
// Declare a new integer argument with the bounds of 1 to 99
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
amount
|
||
"
|
||
,
|
||
IntegerArgumentType
|
||
.
|
||
integer
|
||
(
|
||
1
|
||
,
|
||
99
|
||
))
|
||
// Here, we use method references, since otherwise, our command definition would grow too big
|
||
.
|
||
suggests
|
||
(
|
||
SuggestionsTest
|
||
::
|
||
getAmountSuggestions
|
||
)
|
||
.
|
||
executes
|
||
(
|
||
SuggestionsTest
|
||
::
|
||
executeCommandLogic
|
||
)
|
||
)
|
||
)
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
private
|
||
static
|
||
CompletableFuture
|
||
<
|
||
Suggestions
|
||
>
|
||
getAmountSuggestions
|
||
(
|
||
final
|
||
CommandContext
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
ctx
|
||
,
|
||
final
|
||
SuggestionsBuilder
|
||
builder
|
||
)
|
||
{
|
||
// Suggest 1, 16, 32, and 64 to the user when they reach the 'amount' argument
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
1
|
||
)
|
||
;
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
16
|
||
)
|
||
;
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
32
|
||
)
|
||
;
|
||
builder
|
||
.
|
||
suggest
|
||
(
|
||
64
|
||
)
|
||
;
|
||
return
|
||
builder
|
||
.
|
||
buildFuture
|
||
()
|
||
;
|
||
}
|
||
private
|
||
static
|
||
int
|
||
executeCommandLogic
|
||
(
|
||
final
|
||
CommandContext
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
ctx
|
||
)
|
||
{
|
||
// We know that the executor will be a player, so we can just silently return
|
||
if
|
||
(
|
||
!
|
||
(
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getExecutor
|
||
()
|
||
instanceof
|
||
Player
|
||
player)) {
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
// If the player has no empty slot, we tell the player that they have no free inventory space
|
||
final
|
||
int
|
||
firstEmptySlot
|
||
=
|
||
player
|
||
.
|
||
getInventory
|
||
()
|
||
.
|
||
firstEmpty
|
||
()
|
||
;
|
||
if
|
||
(firstEmptySlot
|
||
==
|
||
-
|
||
1
|
||
) {
|
||
player
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<light_purple>You do not have enough space in your inventory!
|
||
"
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
// Retrieve our argument values
|
||
final
|
||
ItemStack
|
||
item
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
ItemStack
|
||
.
|
||
class
|
||
)
|
||
;
|
||
final
|
||
int
|
||
amount
|
||
=
|
||
IntegerArgumentType
|
||
.
|
||
getInteger
|
||
(
|
||
ctx,
|
||
"
|
||
amount
|
||
"
|
||
)
|
||
;
|
||
// Set the item's amount and give it to the player
|
||
item
|
||
.
|
||
setAmount
|
||
(
|
||
amount
|
||
)
|
||
;
|
||
player
|
||
.
|
||
getInventory
|
||
()
|
||
.
|
||
setItem
|
||
(
|
||
firstEmptySlot, item
|
||
)
|
||
;
|
||
// Send a confirmation message
|
||
player
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<light_purple>You have been given <white><amount>x</white> <aqua><item></aqua>!
|
||
"
|
||
,
|
||
Placeholder
|
||
.
|
||
component
|
||
(
|
||
"
|
||
amount
|
||
"
|
||
,
|
||
Component
|
||
.
|
||
text
|
||
(
|
||
amount
|
||
))
|
||
,
|
||
Placeholder
|
||
.
|
||
component
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
Component
|
||
.
|
||
translatable
|
||
(
|
||
item
|
||
)
|
||
.
|
||
hoverEvent
|
||
(
|
||
item
|
||
))
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
}
|
||
```
|
||
|
||
And here is how the command looks in-game:
|
||
|
||
(Video playback not supported)
|
||
|
||
### Example: Filtering by User Input
|
||
|
||
If you have multiple values, it is suggested that you filter your suggestions by what the user has already put in. For this, we can declare the following, simple command as a test:
|
||
|
||
```java
|
||
public
|
||
static
|
||
LiteralCommandNode<
|
||
CommandSourceStack
|
||
>
|
||
constructStringSuggestionsCommand
|
||
()
|
||
{
|
||
final
|
||
List
|
||
<
|
||
String
|
||
>
|
||
names
|
||
=
|
||
List
|
||
.
|
||
of
|
||
(
|
||
"
|
||
Alex
|
||
"
|
||
,
|
||
"
|
||
Andreas
|
||
"
|
||
,
|
||
"
|
||
Stephanie
|
||
"
|
||
,
|
||
"
|
||
Sophie
|
||
"
|
||
,
|
||
"
|
||
Emily
|
||
"
|
||
)
|
||
;
|
||
return
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
selectname
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
name
|
||
"
|
||
,
|
||
StringArgumentType
|
||
.
|
||
word
|
||
())
|
||
.
|
||
suggests
|
||
(
|
||
(ctx, builder)
|
||
->
|
||
{
|
||
names
|
||
.
|
||
stream
|
||
()
|
||
.
|
||
filter
|
||
(
|
||
entry
|
||
->
|
||
entry
|
||
.
|
||
toLowerCase
|
||
()
|
||
.
|
||
startsWith
|
||
(
|
||
builder
|
||
.
|
||
getRemainingLowerCase
|
||
()))
|
||
.
|
||
forEach
|
||
(
|
||
builder
|
||
::
|
||
suggest
|
||
)
|
||
;
|
||
return
|
||
builder
|
||
.
|
||
buildFuture
|
||
()
|
||
;
|
||
}
|
||
)
|
||
)
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
```
|
||
|
||
This simple setup filters suggestions by user input, providing a smooth user experience when running the command:
|
||
|
||
(Video playback not supported)
|
||
|
||
## Command Trees
|
||
|
||
Command trees are the structure of Brigadier commands. This section explains how to understand and build them.
|
||
|
||
### What is a Tree?
|
||
|
||
A command tree is a hierarchical structure where the base command is the "root" and subsequent arguments or subcommands are "branches."
|
||
|
||
* **Root:** The base command (e.g., `/customplugin`).
|
||
* **Branch:** Arguments or subcommands following the root (e.g., `reload`, `tphere`).
|
||
|
||
Example:
|
||
|
||
```
|
||
/customplugin reload
|
||
/customplugin tphere
|
||
/customplugin killall
|
||
```
|
||
|
||
The root is `/customplugin`, and `reload`, `tphere`, and `killall` are branches.
|
||
|
||
### How Can We Visualize A Tree In Code?
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
root
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
customplugin
|
||
"
|
||
)
|
||
;
|
||
```
|
||
|
||
This defines the root of the command tree. Branches can be added using the `.then(...)` method.
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
root
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
customplugin
|
||
"
|
||
)
|
||
;
|
||
root
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
reload
|
||
"
|
||
))
|
||
;
|
||
root
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
tphere
|
||
"
|
||
))
|
||
;
|
||
root
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
killall
|
||
"
|
||
))
|
||
;
|
||
```
|
||
|
||
Here, `reload`, `tphere`, and `killall` are added as subcommands (child literals) of the `customplugin` root.
|
||
|
||
### Creating a More Advanced Command
|
||
|
||
Consider the following command structure:
|
||
|
||
```
|
||
/advanced
|
||
┣━┳ killall
|
||
┃ ┣━━ entities
|
||
┃ ┣━━ players
|
||
┃ ┗━━ zombies
|
||
┗━┳ eat
|
||
┣━━ ice-cream
|
||
┗━━ main-dish
|
||
```
|
||
|
||
This allows for:
|
||
|
||
* `/advanced killall entities`
|
||
* `/advanced killall players`
|
||
* `/advanced killall zombies`
|
||
* `/advanced eat ice-cream`
|
||
* `/advanced eat main-dish`
|
||
|
||
To create this, define the literals furthest from the root first:
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
entities
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
entities
|
||
"
|
||
)
|
||
;
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
players
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
players
|
||
"
|
||
)
|
||
;
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
zombies
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
zombies
|
||
"
|
||
)
|
||
;
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
iceCream
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
ice-cream
|
||
"
|
||
)
|
||
;
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
mainDish
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
main-dish
|
||
"
|
||
)
|
||
;
|
||
```
|
||
|
||
Then, define the next layer (`killall` and `eat`):
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
killall
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
killall
|
||
"
|
||
)
|
||
;
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
eat
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
eat
|
||
"
|
||
)
|
||
;
|
||
```
|
||
|
||
Add the child elements to their parents:
|
||
|
||
```java
|
||
killall
|
||
.
|
||
then
|
||
(
|
||
entities
|
||
)
|
||
;
|
||
killall
|
||
.
|
||
then
|
||
(
|
||
players
|
||
)
|
||
;
|
||
killall
|
||
.
|
||
then
|
||
(
|
||
zombies
|
||
)
|
||
;
|
||
eat
|
||
.
|
||
then
|
||
(
|
||
iceCream
|
||
)
|
||
;
|
||
eat
|
||
.
|
||
then
|
||
(
|
||
mainDish
|
||
)
|
||
;
|
||
```
|
||
|
||
Finally, create the root node and add the `killall` and `eat` subcommands:
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
advancedCommandRoot
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
advanced
|
||
"
|
||
)
|
||
;
|
||
advancedCommandRoot
|
||
.
|
||
then
|
||
(
|
||
killall
|
||
)
|
||
;
|
||
advancedCommandRoot
|
||
.
|
||
then
|
||
(
|
||
eat
|
||
)
|
||
;
|
||
```
|
||
|
||
### Chaining `then` Method Calls Together
|
||
|
||
The `.then()` method returns the same element it was called on, allowing for chaining:
|
||
|
||
```java
|
||
killall
|
||
.
|
||
then
|
||
(
|
||
entities
|
||
)
|
||
.
|
||
then
|
||
(
|
||
players
|
||
)
|
||
.
|
||
then
|
||
(
|
||
zombies
|
||
)
|
||
;
|
||
```
|
||
|
||
This avoids storing every child node in its own variable. It can also be written as:
|
||
|
||
```java
|
||
killall
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
entities
|
||
"
|
||
))
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
players
|
||
"
|
||
))
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
zombies
|
||
"
|
||
))
|
||
;
|
||
```
|
||
|
||
The entire command tree can be built using chained calls:
|
||
|
||
```java
|
||
LiteralArgumentBuilder
|
||
<
|
||
CommandSourceStack
|
||
>
|
||
advancedCommandRoot
|
||
=
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
advanced
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
eat
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
ice-cream
|
||
"
|
||
))
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
main-dish
|
||
"
|
||
))
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
killall
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
entities
|
||
"
|
||
))
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
players
|
||
"
|
||
))
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
zombies
|
||
"
|
||
))
|
||
)
|
||
;
|
||
```
|
||
|
||
## Arguments and Literals
|
||
|
||
This section explains the difference between arguments and literals in Brigadier commands and how to use them.
|
||
|
||
### Introduction
|
||
|
||
The `.then(...)` method of an `ArgumentBuilder<CommandSourceStack, ?>` takes another `ArgumentBuilder<CommandSourceStack, ?>` object.
|
||
|
||
Two implementations of `ArgumentBuilder` are:
|
||
|
||
* `RequiredArgumentBuilder`: Created with `Commands.argument(String, ArgumentType<T>)`.
|
||
* `LiteralArgumentBuilder`: Created with `Commands.literal(String)`.
|
||
|
||
**Argument:** A variable input by the user. It's semi-unpredictable but should always return a valid entry of the object it backs.
|
||
|
||
**Literal:** A non-variable input by the user. Used to define predictable input, as each literal is a new branch on the command tree.
|
||
|
||
### Literals
|
||
|
||
Literals cannot be accessed in code but define a specific branch in the command tree.
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
plant
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
tree
|
||
"
|
||
)
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
/* Here we are on /plant tree */
|
||
}
|
||
)
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
grass
|
||
"
|
||
)
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
/* Here we are on /plant grass */
|
||
}
|
||
))
|
||
;
|
||
```
|
||
|
||
The `executes` method declares logic for the branch. Without `executes`, the branch is not executable.
|
||
|
||
### Arguments
|
||
|
||
Arguments are created using `Commands.argument(String, ArgumentType<T>)`. This returns a `RequiredArgumentBuilder`. The `T` type parameter declares the return type of the argument, which can be used inside the `executes` method.
|
||
|
||
Built-in argument types:
|
||
|
||
| Name | Return value | Possible Input | Description |
|
||
| -------------------------- | ------------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||
| `BoolArgumentType.bool()` | `Boolean` | `true`/`false` | Only allows a boolean value |
|
||
| `IntegerArgumentType.integer()` | `Integer` | `253`, `-123`, `0` | Any valid integer |
|
||
| `LongArgumentType.longArg()` | `Long` | `25418263123783` | Any valid long |
|
||
| `FloatArgumentType.floatArg()` | `Float` | `253.2`, `-25.0` | Any valid float |
|
||
| `DoubleArgumentType.doubleArg()` | `Double` | `4123.242`, `-1.1` | Any valid double |
|
||
| `StringArgumentType.word()` | `String` | `letters-and+1234567` | A single word. May only contain letters and numbers and these characters: `+`, `-`, `_`, and `.`. |
|
||
| `StringArgumentType.string()` | `String` | `"with spaces"` | A single word, or, if quoted, any valid string with spaces |
|
||
| `StringArgumentType.greedyString()` | `String` | `unquoted spaces` | The literal written input. May contain any characters. Has to be the last argument |
|
||
|
||
### Boolean Argument Type and Argument Parsing
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
serverflight
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
allow
|
||
"
|
||
,
|
||
BoolArgumentType
|
||
.
|
||
bool
|
||
())
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
boolean
|
||
allowed
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
allow
|
||
"
|
||
,
|
||
boolean
|
||
.
|
||
class
|
||
)
|
||
;
|
||
/* Toggle server flying */
|
||
}
|
||
)
|
||
)
|
||
;
|
||
```
|
||
|
||
The `Commands.argument(String, ArgumentType)` method takes the node name. The `executes` lambda has a method called `T getArgument(String, Class<T>)`. The first parameter is the name of the method we want to retrieve. The second parameter is the return value of the argument.
|
||
|
||
### Number Arguments
|
||
|
||
Number arguments (`IntegerArgumentType.integer()`, etc.) have three overloads:
|
||
|
||
* `IntegerArgumentType.integer()`: Any value between `Integer.MIN_VALUE` and `Integer.MAX_VALUE`.
|
||
* `IntegerArgumentType.integer(int min)`: Any value between `min` and `Integer.MAX_VALUE`.
|
||
* `IntegerArgumentType.integer(int min, int max)`: Any value between `min` and `max`.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
flyspeed
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
speed
|
||
"
|
||
,
|
||
FloatArgumentType
|
||
.
|
||
floatArg
|
||
(
|
||
0
|
||
,
|
||
1.0f
|
||
))
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
float
|
||
speed
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
speed
|
||
"
|
||
,
|
||
float
|
||
.
|
||
class
|
||
)
|
||
;
|
||
/* Set player's flight speed */
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
)
|
||
)
|
||
;
|
||
```
|
||
|
||
Some arguments have special ways of being retrieved:
|
||
|
||
```java
|
||
float
|
||
speed
|
||
=
|
||
FloatArgumentType
|
||
.
|
||
getFloat
|
||
(
|
||
ctx,
|
||
"
|
||
speed
|
||
"
|
||
)
|
||
;
|
||
```
|
||
|
||
Parsers for Brigadier-native arguments exist:
|
||
|
||
* `BoolArgumentType.getBool`
|
||
* `IntegerArgumentType.getInteger`
|
||
* `LongArgumentType.getLong`
|
||
* `FloatArgumentType.getFloat`
|
||
* `DoubleArgumentType.getDouble`
|
||
* `StringArgumentType.getString`
|
||
|
||
Native arguments provide client-side error checking.
|
||
|
||
### String Arguments
|
||
|
||
Three string arguments: `word`, `string`, and `greedyString`.
|
||
|
||
* `word`: Only accepts a single word consisting of alphanumerical characters and these special characters: `+`, `-`, `_`, and `.`.
|
||
* `string`: If unquoted, it follows the same rules as `word`. If quoted, any combination of unicode characters is allowed. Quotes can be escaped using a backslash `\`.
|
||
* `greedyString`: Does not perform any parsing and allows any input. Must be the last argument. Quotes are counted as literal characters.
|
||
|
||
### Further Reference
|
||
|
||
#### Minecraft Arguments
|
||
|
||
Custom arguments are defined by Paper and accessed via the `ArgumentTypes` class.
|
||
|
||
#### Custom Arguments
|
||
|
||
Implement the `CustomArgumentType<T, N>` interface to define your own arguments.
|
||
|
||
## Registry
|
||
|
||
Registries in Minecraft hold information like item or block types, enchantments, potion effects, and more.
|
||
|
||
Two types of registry arguments:
|
||
|
||
* `resource`: Returns the parsed value.
|
||
* `resourceKey`: Returns a `TypedKey`, which can be used to retrieve the value.
|
||
|
||
### Resource Argument
|
||
|
||
Get a `ArgumentType<T>` reference using `ArgumentTypes.resource(RegistryKey<T>)`. Possible registry keys are accessed statically through the `RegistryKey` interface.
|
||
|
||
Example:
|
||
|
||
```java
|
||
public
|
||
static
|
||
LiteralCommandNode<
|
||
CommandSourceStack
|
||
>
|
||
enchantmentRegistry
|
||
()
|
||
{
|
||
return
|
||
Commands
|
||
.
|
||
literal
|
||
(
|
||
"
|
||
enchants-registry
|
||
"
|
||
)
|
||
.
|
||
then
|
||
(
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
enchantment
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
resource
|
||
(
|
||
RegistryKey
|
||
.
|
||
ENCHANTMENT
|
||
))
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
final
|
||
Enchantment
|
||
enchantment
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
enchantment
|
||
"
|
||
,
|
||
Enchantment
|
||
.
|
||
class
|
||
)
|
||
;
|
||
if
|
||
(
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getExecutor
|
||
()
|
||
instanceof
|
||
Player
|
||
player) {
|
||
final
|
||
ItemStack
|
||
stack
|
||
=
|
||
player
|
||
.
|
||
getInventory
|
||
()
|
||
.
|
||
getItemInMainHand
|
||
()
|
||
;
|
||
stack
|
||
.
|
||
addUnsafeEnchantment
|
||
(
|
||
enchantment,
|
||
10
|
||
)
|
||
;
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
Enchanted <player>'s <item> with <enchantment>!
|
||
"
|
||
,
|
||
Placeholder
|
||
.
|
||
component
|
||
(
|
||
"
|
||
player
|
||
"
|
||
,
|
||
player
|
||
.
|
||
name
|
||
())
|
||
,
|
||
Placeholder
|
||
.
|
||
component
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
Component
|
||
.
|
||
translatable
|
||
(
|
||
stack
|
||
))
|
||
,
|
||
Placeholder
|
||
.
|
||
component
|
||
(
|
||
"
|
||
enchantment
|
||
"
|
||
,
|
||
enchantment
|
||
.
|
||
displayName
|
||
(
|
||
10
|
||
))
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<red>This command requires a player!
|
||
"
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
))
|
||
.
|
||
build
|
||
()
|
||
;
|
||
}
|
||
```
|
||
|
||
This defines an `enchantment` argument using an enchantment registry key resource and retrieves the value using `ctx.getArgument("enchantment", Enchantment.class)`.
|
||
|
||
#### Caution
|
||
|
||
Using the `STRUCTURE` registry key with the `resource` argument can cause a Network Protocol Error due to missing client registries. Use `resourceKey(...)` instead and parse the values yourself.
|
||
|
||
### Resource Key Argument
|
||
|
||
The client sees little difference between `ArgumentTypes.resource` and `ArgumentTypes.resourceKey`. The main difference is that `ArgumentTypes.resourceKey` does not provide error checking.
|
||
|
||
The `resourceKey` argument provides the raw `TypedKey<T>` as an argument result, which contains all the information needed to retrieve a value from a registry.
|
||
|
||
#### Tip
|
||
|
||
Unless there's a specific reason to use `resourceKey`, `resource` is preferred due to client-side error checking and ease of use.
|
||
|
||
### Direct Code Comparison
|
||
|
||
Using `RegistryKey.ITEM` with a `resource` argument type:
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
resource
|
||
(
|
||
RegistryKey
|
||
.
|
||
ITEM
|
||
))
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
final
|
||
ItemType
|
||
item
|
||
=
|
||
ctx
|
||
.
|
||
getArgument
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
ItemType
|
||
.
|
||
class
|
||
)
|
||
;
|
||
if
|
||
(
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getExecutor
|
||
()
|
||
instanceof
|
||
Player
|
||
player) {
|
||
player
|
||
.
|
||
getInventory
|
||
()
|
||
.
|
||
addItem
|
||
(
|
||
item
|
||
.
|
||
createItemStack
|
||
())
|
||
;
|
||
}
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
)
|
||
;
|
||
```
|
||
|
||
Using `RegistryKey.ITEM` with a `resourceKey` argument type:
|
||
|
||
```java
|
||
Commands
|
||
.
|
||
argument
|
||
(
|
||
"
|
||
item
|
||
"
|
||
,
|
||
ArgumentTypes
|
||
.
|
||
resourceKey
|
||
(
|
||
RegistryKey
|
||
.
|
||
ITEM
|
||
))
|
||
.
|
||
executes
|
||
(
|
||
ctx
|
||
->
|
||
{
|
||
final
|
||
TypedKey
|
||
<
|
||
ItemType
|
||
>
|
||
itemKey
|
||
=
|
||
RegistryArgumentExtractor
|
||
.
|
||
getTypedKey
|
||
(
|
||
ctx,
|
||
RegistryKey
|
||
.
|
||
ITEM
|
||
,
|
||
"
|
||
item
|
||
"
|
||
)
|
||
;
|
||
ItemType
|
||
item
|
||
=
|
||
RegistryAccess
|
||
.
|
||
registryAccess
|
||
()
|
||
.
|
||
getRegistry
|
||
(
|
||
itemKey
|
||
.
|
||
registryKey
|
||
())
|
||
.
|
||
get
|
||
(
|
||
itemKey
|
||
.
|
||
key
|
||
())
|
||
;
|
||
if
|
||
(item
|
||
==
|
||
null
|
||
) {
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getSender
|
||
()
|
||
.
|
||
sendRichMessage
|
||
(
|
||
"
|
||
<red>Please provide a valid item!
|
||
"
|
||
)
|
||
;
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
if
|
||
(
|
||
ctx
|
||
.
|
||
getSource
|
||
()
|
||
.
|
||
getExecutor
|
||
()
|
||
instanceof
|
||
Player
|
||
player) {
|
||
player
|
||
.
|
||
getInventory
|
||
()
|
||
.
|
||
addItem
|
||
(
|
||
item
|
||
.
|
||
createItemStack
|
||
())
|
||
;
|
||
}
|
||
return
|
||
Command
|
||
.
|
||
SINGLE_SUCCESS
|
||
;
|
||
}
|
||
)
|
||
```
|
||
|
||
### Using a TypedKey
|
||
|
||
1. Get the correct registry using `RegistryAccess#getRegistry(RegistryKey)`. Get a `RegistryAccess` using `RegistryAccess.registryAccess()`. The `RegistryKey` is retrieved using `TypedKey#registryKey()`.
|
||
2. Get the final value `T` using `Registry#get(Key)`, where the key can be retrieved using `TypedKey#key()`. This returns the backing instance from that resource key or null if no value is found.
|
||
|
||
### Use Case Over Resource Argument
|
||
|
||
The main use case is the ability to store the key (the value returned by `TypedKey#key`). This allows storing the exact user input and retrieving the backed instance easily.
|
||
|
||
### Registry Key Previews
|
||
|
||
| RegistryKeys Field | Return Value |
|
||
| -------------------- | ------------------- |
|
||
| `ATTRIBUTE` | `Attribute` |
|
||
| `BANNER_PATTERN` | `PatternType` |
|
||
| `BIOME` | `Biome` |
|
||
| `BLOCK` | `BlockType` |
|
||
| `CAT_VARIANT` | `Cat.Type` |
|
||
| `CHICKEN_VARIANT` | `Chicken.Variant` |
|
||
| `COW_VARIANT` | `Cow.Variant` |
|
||
| `DAMAGE_TYPE` | `DamageType` |
|
||
| `DATA_COMPONENT_TYPE` | `DataComponentType` |
|
||
| `DIALOG` | `Dialog` |
|
||
| `ENCHANTMENT` | `Enchantment` |
|
||
| `ENTITY_TYPE` | `EntityType` |
|
||
| `FLUID` | `Fluid` |
|
||
| `FROG_VARIANT` | `Frog.Variant` |
|
||
| `GAME_EVENT` | `GameEvent` |
|
||
| `INSTRUMENT` | `MusicInstrument` |
|
||
| `ITEM` | `ItemType` |
|
||
| `JUKEBOX_SONG` | `JukeboxSong` |
|
||
| `MAP_DECORATION_TYPE` | `MapCursor.Type` |
|
||
| `MEMORY_MODULE_TYPE` | `MemoryKey<?>` |
|
||
| `MENU` | `MenuType` |
|
||
| `MOB_EFFECT` | `PotionEffectType` |
|
||
| `PAINTING_VARIANT` | `Art` |
|
||
| `PARTICLE_TYPE` | `Particle` |
|
||
| `PIG_VARIANT` | `Pig.Variant` |
|
||
| `POTION` | `PotionType` |
|
||
| `SOUND_EVENT` | `Sound` |
|
||
| `STRUCTURE` | `Structure` |
|
||
| `STRUCTURE_TYPE` | `StructureType` |
|
||
| `TRIM_MATERIAL` | `TrimMaterial` |
|
||
| `TRIM_PATTERN` | `TrimPattern` |
|
||
| `VILLAGER_PROFESSION` | `Villager.Profession`|
|
||
| `VILLAGER_TYPE` | `Villager.Type` |
|
||
| `WOLF_SOUND_VARIANT` | `Wolf.SoundVariant`|
|
||
| `WOLF_VARIANT`
|
||
|
||
```markdown
|
||
# Command API Knowledge Base
|
||
|
||
## Introduction
|
||
|
||
Paper's command system is built on top of Minecraft's Brigadier command system, providing a powerful and flexible way to define commands and arguments.
|
||
|
||
**Advantages over the Bukkit command system:**
|
||
|
||
* Less parsing or error checking required by the developer for arguments.
|
||
* Better user experience with client error checking.
|
||
* Integration with reload events, allowing commands usable in datapacks.
|
||
* Easier creation of subcommands.
|
||
|
||
**Resources:**
|
||
|
||
* [Command Tree](#command-trees)
|
||
* [Arguments and Literals](#arguments-and-literals)
|
||
* [Command Executors](#executors)
|
||
* [Command Registration](#registration)
|
||
* [Command Requirements](#requirements)
|
||
* [Argument Suggestions](#suggestions)
|
||
* [Custom Arguments](#custom-arguments)
|
||
* [Minecraft Arguments](#minecraft-specific)
|
||
|
||
**Future Pages:**
|
||
|
||
* Tutorial: Creating Utility Commands
|
||
* The Command Dispatcher
|
||
* Forks and Redirects
|
||
* Tutorial: Extending the vanilla execute command
|
||
|
||
**Additional Support:**
|
||
|
||
* Discord server: #paper-dev channel
|
||
|
||
## Command Trees
|
||
|
||
(This section's content is not available in the provided documents.)
|
||
|
||
## Arguments and Literals
|
||
|
||
(This section's content is not available in the provided documents.)
|
||
|
||
## Executors
|
||
|
||
This section covers the `executes(...)` method from the `ArgumentBuilder` class.
|
||
|
||
**Examining the `executes` method:**
|
||
|
||
```java
|
||
ArgumentBuilder.java
|
||
public T executes(Command<S> command);
|
||
```
|
||
|
||
The `Command<S>` interface is a `FunctionalInterface`, allowing the use of lambda statements.
|
||
|
||
```java
|
||
Command.java
|
||
@FunctionalInterface
|
||
public interface Command<S> {
|
||
int SINGLE_SUCCESS = 1;
|
||
int run(CommandContext<S> ctx) throws CommandSyntaxException;
|
||
}
|
||
```
|
||
|
||
The `run` method in the `Command<S>` interface has one parameter, `CommandContext<S>`, and returns an integer.
|
||
|
||
* `CommandContext<S>`: Provides information about the sender and command arguments.
|
||
* `S getSource()`: Returns the source of the command, which is always a `CommandSourceStack` for the `executes` method.
|
||
* `V getArgument(String, Class<V>)`: Retrieves arguments from the command.
|
||
|
||
`CommandSourceStack` methods:
|
||
|
||
* `Location getLocation()`
|
||
* `CommandSender getSender()`: Returns the command sender.
|
||
* `@Nullable Entity getExecutor()`: Returns the command executor, relevant when using `/execute as <entity> run <our_command>`.
|
||
|
||
**Example: Flyspeed Command**
|
||
|
||
```java
|
||
Commands.literal("flyspeed")
|
||
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
|
||
.executes(ctx -> {
|
||
float speed = FloatArgumentType.getFloat(ctx, "speed");
|
||
CommandSender sender = ctx.getSource().getSender();
|
||
Entity executor = ctx.getSource().getExecutor();
|
||
|
||
if (!(executor instanceof Player player)) {
|
||
sender.sendPlainMessage("Only players can fly!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
player.setFlySpeed(speed);
|
||
|
||
if (sender == executor) {
|
||
player.sendPlainMessage("Successfully set your flight speed to " + speed);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
sender.sendRichMessage("Successfully set <playername>'s flight speed to " + speed,
|
||
Placeholder.component("playername", player.name()));
|
||
player.sendPlainMessage("Your flight speed has been set to " + speed);
|
||
return Command.SINGLE_SUCCESS;
|
||
})
|
||
);
|
||
```
|
||
|
||
**Explanation:**
|
||
|
||
* Defines a `/flyspeed` command with a float argument "speed" (0-1).
|
||
* Retrieves the speed argument using `FloatArgumentType.getFloat`.
|
||
* Retrieves the `CommandSourceStack` and then the sender and executor.
|
||
* `CommandSender`: Interface implemented by entities (including players) and the `ConsoleCommandSender`.
|
||
* Checks if the executor is a `Player`.
|
||
* Sets the player's flight speed and sends a confirmation message.
|
||
* Handles cases where the command is executed by the player themselves or by another sender (using `/execute`).
|
||
* Returns `Command.SINGLE_SUCCESS` (value 1) upon successful execution.
|
||
|
||
**Logic Separation:**
|
||
|
||
If the command logic is too large, use method references to improve readability.
|
||
|
||
```java
|
||
public class FlightSpeedCommand {
|
||
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() {
|
||
return Commands.literal("flyspeed")
|
||
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
|
||
.executes(FlightSpeedCommand::runFlySpeedLogic));
|
||
}
|
||
|
||
private static int runFlySpeedLogic(CommandContext<CommandSourceStack> ctx) {
|
||
float speed = FloatArgumentType.getFloat(ctx, "speed");
|
||
CommandSender sender = ctx.getSource().getSender();
|
||
Entity executor = ctx.getSource().getExecutor();
|
||
|
||
if (!(executor instanceof Player player)) {
|
||
sender.sendPlainMessage("Only players can fly!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
player.setFlySpeed(speed);
|
||
|
||
if (sender == executor) {
|
||
player.sendPlainMessage("Successfully set your flight speed to " + speed);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
sender.sendRichMessage("Successfully set <playername>'s flight speed to " + speed,
|
||
Placeholder.component("playername", player.name()));
|
||
player.sendPlainMessage("Your flight speed has been set to " + speed);
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Registration
|
||
|
||
Brigadier commands are registered using the `LifecycleEventManager` in Paper. This ensures commands are re-registered after server reload events.
|
||
|
||
**Accessing the `LifecycleEventManager`:**
|
||
|
||
1. **Plugin Bootstrapper (Preferred):** Requires `paper-plugin.yml`.
|
||
|
||
```java
|
||
public class CustomPluginBootstrap implements PluginBootstrap {
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||
// register your commands here ...
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
* `context.getLifecycleManager()`: Returns a `LifecycleEventManager<BootstrapContext>` object.
|
||
* `LifecycleEventManager#registerEventHandler(LifecycleEventType, LifecycleEventHandler)`: Registers the lifecycle event.
|
||
* `LifecycleEvents.COMMANDS`: Specifies the event type for command registration.
|
||
* `LifecycleEventHandler`: A functional interface with the `run` method. The lambda parameter is a `ReloadableRegistrarEvent<Commands>`.
|
||
* `ReloadableRegistrarEvent<Commands>`:
|
||
* `ReloadableRegistrarEvent.Cause cause()`
|
||
* `Commands registrar()`: Provides access to the `Commands` class for registering commands.
|
||
|
||
2. **Plugin Main Class:**
|
||
|
||
```java
|
||
public final class PluginMainClass extends JavaPlugin {
|
||
@Override
|
||
public void onEnable() {
|
||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||
// register your commands here ...
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
* `JavaPlugin#getLifecycleManager()`: Returns a `LifecycleEventManager<Plugin>`.
|
||
* The rest of the methods work the same way as with the PluginBootstrap.
|
||
|
||
**Registering Commands using the `Commands` Class:**
|
||
|
||
The `Commands` class provides overloads for the `Commands#register` method.
|
||
|
||
**Registering a `LiteralCommandNode`:**
|
||
|
||
```java
|
||
LiteralCommandNode<CommandSourceStack> buildCommand = Commands.literal("testcmd")
|
||
.then(Commands.literal("argument_one"))
|
||
.then(Commands.literal("argument_two"))
|
||
.build();
|
||
|
||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||
commands.registrar().register(buildCommand);
|
||
});
|
||
```
|
||
|
||
**Registering a `BasicCommand`:**
|
||
|
||
```java
|
||
final BasicCommand basicCommand = ...;
|
||
|
||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||
commands.registrar().register("commandname", basicCommand);
|
||
});
|
||
```
|
||
|
||
**Further Reference:**
|
||
|
||
* [LifecycleEventManager](link not provided in the document)
|
||
|
||
## Requirements
|
||
|
||
(This section's content is not available in the provided documents.)
|
||
|
||
## Suggestions
|
||
|
||
This section describes how to send custom suggestions to users using the `suggests(SuggestionProvider<CommandSourceStack>)` method.
|
||
|
||
**Examining `SuggestionProvider<S>`:**
|
||
|
||
```java
|
||
SuggestionProvider.java
|
||
@FunctionalInterface
|
||
public interface SuggestionProvider<S> {
|
||
CompletableFuture<Suggestions> getSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) throws CommandSyntaxException;
|
||
}
|
||
```
|
||
|
||
* Similar to `Command<S>`, this is a functional interface.
|
||
* Lambda parameters: `CommandContext<S>` and `SuggestionsBuilder`.
|
||
* Return value: `CompletableFuture<Suggestions>`.
|
||
|
||
**Example of a simple lambda:**
|
||
|
||
```java
|
||
Commands.argument("name", StringArgumentType.word())
|
||
.suggests((ctx, builder) -> builder.buildFuture());
|
||
```
|
||
|
||
**The `SuggestionsBuilder`:**
|
||
|
||
Provides methods to construct suggestions.
|
||
|
||
**Input Retrieval Methods:**
|
||
|
||
| Method | Return Value | Description | Example Input: `/customsuggestions Asumm13Text` |
|
||
| ------------------------- | --------------------------- | --------------------------------------------- | ------------------------------------------------ |
|
||
| `getInput()` | `/customsuggestions Asumm13Text` | The full chat input | |
|
||
| `getStart()` | `19` | The index of the first character of the argument's input | |
|
||
| `getRemaining()` | `Asumm13Text` | The input for the current argument | |
|
||
| `getRemainingLowerCase()` | `asumm13text` | The input for the current argument, lowercased | |
|
||
|
||
**Suggestions Methods:**
|
||
|
||
| Overload | Description |
|
||
| ------------------------ | --------------------------------------------------------------------------- |
|
||
| `suggest(String)` | Adds a String to the suggestions |
|
||
| `suggest(String, Message)` | Adds a String with a tooltip to the suggestions |
|
||
| `suggest(int)` | Adds an int to the suggestions |
|
||
| `suggest(int, Message)` | Adds an int with a tooltip to the suggestions |
|
||
|
||
* `Message` can be retrieved using:
|
||
* `LiteralMessage`: For basic, non-formatted text.
|
||
* `MessageComponentSerializer`: For serializing `Component` objects into `Message` objects.
|
||
|
||
**Example of adding a suggestion with a tooltip:**
|
||
|
||
```java
|
||
builder.suggest("suggestion", MessageComponentSerializer.message().serialize(MiniMessage.miniMessage().deserialize("<green>Suggestion tooltip")));
|
||
```
|
||
|
||
**Building:**
|
||
|
||
* `SuggestionsBuilder#build()`: Returns the finished `Suggestions` object directly.
|
||
* `SuggestionsBuilder#buildFuture()`: Returns a `CompletableFuture<Suggestions>`.
|
||
|
||
The `SuggestionProvider` expects a `CompletableFuture<Suggestions>` return value. This allows for asynchronous construction of suggestions.
|
||
|
||
**Example of declaring suggestions:**
|
||
|
||
```java
|
||
// Synchronous
|
||
Commands.argument("name", StringArgumentType.word())
|
||
.suggests((ctx, builder) -> {
|
||
builder.suggest("first");
|
||
builder.suggest("second");
|
||
return builder.buildFuture();
|
||
});
|
||
|
||
// Asynchronous
|
||
Commands.argument("name", StringArgumentType.word())
|
||
.suggests((ctx, builder) -> CompletableFuture.supplyAsync(() -> {
|
||
builder.suggest("first");
|
||
builder.suggest("second");
|
||
return builder.build();
|
||
}));
|
||
```
|
||
|
||
**Example: Suggesting Amounts in a Give Item Command:**
|
||
|
||
```java
|
||
@NullMarked
|
||
public class SuggestionsTest {
|
||
public static LiteralCommandNode<CommandSourceStack> constructGiveItemCommand() {
|
||
return Commands.literal("giveitem")
|
||
.requires(ctx -> ctx.getExecutor() instanceof Player)
|
||
.then(Commands.argument("item", ArgumentTypes.itemStack())
|
||
.then(Commands.argument("amount", IntegerArgumentType.integer(1, 99))
|
||
.suggests(SuggestionsTest::getAmountSuggestions)
|
||
.executes(SuggestionsTest::executeCommandLogic)))
|
||
.build();
|
||
}
|
||
|
||
private static CompletableFuture<Suggestions> getAmountSuggestions(final CommandContext<CommandSourceStack> ctx, final SuggestionsBuilder builder) {
|
||
builder.suggest(1);
|
||
builder.suggest(16);
|
||
builder.suggest(32);
|
||
builder.suggest(64);
|
||
return builder.buildFuture();
|
||
}
|
||
|
||
private static int executeCommandLogic(final CommandContext<CommandSourceStack> ctx) {
|
||
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
final int firstEmptySlot = player.getInventory().firstEmpty();
|
||
if (firstEmptySlot == -1) {
|
||
player.sendRichMessage("<light_purple>You do not have enough space in your inventory!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
|
||
final ItemStack item = ctx.getArgument("item", ItemStack.class);
|
||
final int amount = IntegerArgumentType.getInteger(ctx, "amount");
|
||
|
||
item.setAmount(amount);
|
||
player.getInventory().setItem(firstEmptySlot, item);
|
||
|
||
player.sendRichMessage("<light_purple>You have been given <white><amount>x</white> <aqua><item></aqua>!",
|
||
Placeholder.component("amount", Component.text(amount)),
|
||
Placeholder.component("item", Component.translatable(item).hoverEvent(item)));
|
||
return Command.SINGLE_SUCCESS;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Example: Filtering by User Input:**
|
||
|
||
```java
|
||
public static LiteralCommandNode<CommandSourceStack> constructStringSuggestionsCommand() {
|
||
final List<String> names = List.of("Alex", "Andreas", "Stephanie", "Sophie", "Emily");
|
||
|
||
return Commands.literal("selectname")
|
||
.then(Commands.argument("name", StringArgumentType.word())
|
||
.suggests((ctx, builder) -> {
|
||
names.stream()
|
||
.filter(entry -> entry.toLowerCase().startsWith(builder.getRemainingLowerCase()))
|
||
.forEach(builder::suggest);
|
||
return builder.buildFuture();
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
## Custom Arguments
|
||
|
||
Custom arguments are wrappers around existing argument types, allowing developers to provide suggestions and reusable parsing logic.
|
||
|
||
**Why Use Custom Arguments?**
|
||
|
||
Example: An argument for an online operator player. Without custom arguments, it requires significant logic in the `executes(...)` method and duplication if used in multiple command nodes.
|
||
|
||
**Example without Custom Arguments:**
|
||
|
||
```java
|
||
Commands.argument("player", ArgumentTypes.player())
|
||
.suggests((ctx, builder) -> {
|
||
Bukkit.getOnlinePlayers().stream()
|
||
.filter(ServerOperator::isOp)
|
||
.map(Player::getName)
|
||
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(builder.getRemainingLowerCase()))
|
||
.forEach(builder::suggest);
|
||
return builder.buildFuture();
|
||
})
|
||
.executes(ctx -> {
|
||
final Player player = ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).getFirst();
|
||
if (!player.isOp()) {
|
||
final Message message = MessageComponentSerializer.message().serialize(text(player.getName() + " is not a server operator!"));
|
||
throw new SimpleCommandExceptionType(message).create();
|
||
}
|
||
ctx.getSource().getSender().sendRichMessage("Player <player> is an operator!", Placeholder.component("player", player.displayName()));
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
|
||
**Example with Custom Arguments:**
|
||
|
||
**OppedPlayerArgument.java**
|
||
|
||
```java
|
||
@NullMarked
|
||
public final class OppedPlayerArgument implements CustomArgumentType<Player, PlayerSelectorArgumentResolver> {
|
||
private static final SimpleCommandExceptionType ERROR_BAD_SOURCE = new SimpleCommandExceptionType(MessageComponentSerializer.message().serialize(Component.text("The source needs to be a CommandSourceStack!")));
|
||
private static final DynamicCommandExceptionType ERROR_NOT_OPERATOR = new DynamicCommandExceptionType(name -> {
|
||
return MessageComponentSerializer.message().serialize(Component.text(name + " is not a server operator!"));
|
||
});
|
||
|
||
@Override
|
||
public Player parse(StringReader reader) {
|
||
throw new UnsupportedOperationException("This method will never be called.");
|
||
}
|
||
|
||
@Override
|
||
public <S> Player parse(StringReader reader, S source) throws CommandSyntaxException {
|
||
if (!(source instanceof CommandSourceStack stack)) {
|
||
throw ERROR_BAD_SOURCE.create();
|
||
}
|
||
final Player player = getNativeType().parse(reader).resolve(stack).getFirst();
|
||
if (!player.isOp()) {
|
||
throw ERROR_NOT_OPERATOR.create(player.getName());
|
||
}
|
||
return player;
|
||
}
|
||
|
||
@Override
|
||
public ArgumentType<PlayerSelectorArgumentResolver> getNativeType() {
|
||
return ArgumentTypes.player();
|
||
}
|
||
|
||
@Override
|
||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> ctx, SuggestionsBuilder builder) {
|
||
Bukkit.getOnlinePlayers().stream()
|
||
.filter(ServerOperator::isOp)
|
||
.map(Player::getName)
|
||
.filter(name -> name.toLowerCase(Locale.ROOT).startsWith(builder.getRemainingLowerCase()))
|
||
.forEach(builder::suggest);
|
||
return builder.buildFuture();
|
||
}
|
||
}
|
||
```
|
||
|
||
**Command Declaration:**
|
||
|
||
```java
|
||
Commands.argument("player", new OppedPlayerArgument())
|
||
.executes(ctx -> {
|
||
final Player player = ctx.getArgument("player", Player.class);
|
||
ctx.getSource().getSender().sendRichMessage("Player <player> is an operator!", Placeholder.component("player", player.displayName()));
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
|
||
This is more readable, easier to understand, and reusable.
|
||
|
||
**Examining the `CustomArgumentType` Interface:**
|
||
|
||
```java
|
||
package io.papermc.paper.command.brigadier.argument;
|
||
|
||
@NullMarked
|
||
public interface CustomArgumentType<T, N> extends ArgumentType<T> {
|
||
@Override
|
||
T parse(final StringReader reader) throws CommandSyntaxException;
|
||
|
||
@Override
|
||
default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException {
|
||
return ArgumentType.super.parse(reader, source);
|
||
}
|
||
|
||
ArgumentType<N> getNativeType();
|
||
|
||
@Override
|
||
@ApiStatus.NonExtendable
|
||
default Collection<String> getExamples() {
|
||
return this.getNativeType().getExamples();
|
||
}
|
||
|
||
@Override
|
||
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
|
||
return ArgumentType.super.listSuggestions(context, builder);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Generic Types:**
|
||
|
||
* `T`: The type returned when `CommandContext#getArgument` is called.
|
||
* `N`: The native type of the underlying argument.
|
||
* `S`: The command source type (usually `CommandSourceStack`).
|
||
|
||
**Methods:**
|
||
|
||
| Method Declaration | Description |
|
||
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `ArgumentType<N> getNativeType()` | Declares the underlying argument type used as a base for client-side validation. |
|
||
| `T parse(final StringReader reader) throws CommandSyntaxException` | Used if `T parse(StringReader, S)` is not overridden. Runs conversion and validation logic. |
|
||
| `default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException` | If overridden, this method is preferred over `T parse(StringReader)`. Includes the source in parsing logic. |
|
||
| `default Collection<String> getExamples()` | Should **not** be overridden. Used internally for argument type differentiation. |
|
||
| `default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder)` | Equivalent to `RequiredArgumentBuilder#suggests(SuggestionProvider<S>)`. Override to send custom suggestions. |
|
||
|
||
**A Very Basic Implementation:**
|
||
|
||
```java
|
||
package io.papermc.commands;
|
||
|
||
import com.mojang.brigadier.StringReader;
|
||
import com.mojang.brigadier.arguments.ArgumentType;
|
||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||
import org.jspecify.annotations.NullMarked;
|
||
|
||
@NullMarked
|
||
public class BasicImplementation implements CustomArgumentType<String, String> {
|
||
@Override
|
||
public String parse(StringReader reader) {
|
||
return reader.readUnquotedString();
|
||
}
|
||
|
||
@Override
|
||
public ArgumentType<String> getNativeType() {
|
||
return StringArgumentType.word();
|
||
}
|
||
}
|
||
```
|
||
|
||
**`CustomArgumentType.Converted<T, N>`:**
|
||
|
||
Used when parsing the native type to a new type. Extends `CustomArgumentType` and adds two methods:
|
||
|
||
* `T convert(N nativeType) throws CommandSyntaxException;`
|
||
* `default <S> T convert(final N nativeType, final S source) throws CommandSyntaxException { return this.convert(nativeType); }`
|
||
|
||
These methods provide the parsed native type instead of a `StringReader`, reducing the need for manual string reader operations.
|
||
|
||
**Error Handling During the Suggestions Phase:**
|
||
|
||
Showing invalid input in red is **not possible** with custom arguments because the client can only validate known arguments, and a `CommandSyntaxException` cannot be thrown during the suggestions phase. Literals can achieve this, but they cannot be dynamically modified during server runtime.
|
||
|
||
**Example: Ice-cream Argument:**
|
||
|
||
```java
|
||
// IceCreamFlavor.java
|
||
package io.papermc.commands.icecream;
|
||
|
||
import org.jspecify.annotations.NullMarked;
|
||
|
||
@NullMarked
|
||
public enum IceCreamFlavor {
|
||
VANILLA,
|
||
CHOCOLATE,
|
||
STRAWBERRY;
|
||
|
||
@Override
|
||
public String toString() {
|
||
return name().toLowerCase();
|
||
}
|
||
}
|
||
|
||
// IceCreamArgument.java
|
||
package io.papermc.commands.icecream;
|
||
|
||
import com.mojang.brigadier.StringReader;
|
||
import com.mojang.brigadier.arguments.ArgumentType;
|
||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||
import com.mojang.brigadier.context.CommandContext;
|
||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||
import com.mojang.brigadier.suggestion.Suggestions;
|
||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||
import net.kyori.adventure.text.Component;
|
||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||
import org.jspecify.annotations.NullMarked;
|
||
|
||
import java.util.Locale;
|
||
import java.util.concurrent.CompletableFuture;
|
||
|
||
@NullMarked
|
||
public class IceCreamArgument implements CustomArgumentType.Converted<IceCreamFlavor, String> {
|
||
private static final DynamicCommandExceptionType ERROR_INVALID_FLAVOR = new DynamicCommandExceptionType(flavor -> {
|
||
return LegacyComponentSerializer.legacySection().serialize(Component.text(flavor + " is not a valid flavor!"));
|
||
});
|
||
|
||
@Override
|
||
public IceCreamFlavor convert(String nativeType) throws CommandSyntaxException {
|
||
try {
|
||
return IceCreamFlavor.valueOf(nativeType.toUpperCase(Locale.ROOT));
|
||
} catch (IllegalArgumentException ignored) {
|
||
throw ERROR_INVALID_FLAVOR.create(nativeType);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
||
for (IceCreamFlavor flavor : IceCreamFlavor.values()) {
|
||
String name = flavor.toString();
|
||
if (name.startsWith(builder.getRemainingLowerCase())) {
|
||
builder.suggest(flavor.toString());
|
||
}
|
||
}
|
||
return builder.buildFuture();
|
||
}
|
||
|
||
@Override
|
||
public ArgumentType<String> getNativeType() {
|
||
return StringArgumentType.word();
|
||
}
|
||
|
||
@Override
|
||
public IceCreamFlavor parse(StringReader reader) throws CommandSyntaxException {
|
||
return null;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Command Declaration:**
|
||
|
||
```java
|
||
Commands.literal("icecream")
|
||
.then(Commands.argument("flavor", new IceCreamArgument())
|
||
.executes(ctx -> {
|
||
final IceCreamFlavor flavor = ctx.getArgument("flavor", IceCreamFlavor.class);
|
||
ctx.getSource().getSender().sendRichMessage("<b><red>Y<green>U<aqua>M<light_purple>!</b> You just had a scoop of <flavor>!",
|
||
Placeholder.unparsed("flavor", flavor.toString()));
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
```
|
||
|
||
## Minecraft-specific
|
||
|
||
(This section's content is not available in the provided documents.)
|
||
|
||
```markdown
|
||
# API
|
||
|
||
## Introduction
|
||
|
||
This knowledge base provides information on various APIs available in Paper and Folia.
|
||
|
||
## Component API
|
||
|
||
### Introduction
|
||
Components offer a structured way to represent text in Minecraft, providing advantages over plain text strings. Paper and Velocity implement the Adventure API for enhanced component support.
|
||
|
||
### Why you should use Components
|
||
* Components are tree-like structures, inheriting styles and colors.
|
||
* Components can translate text based on client language or show client-specific keybinds.
|
||
* Components support RGB colors and interaction events.
|
||
* Mojang plans to remove client support for the legacy `§` format.
|
||
|
||
### Usage
|
||
* Components are used for item names, lore, bossbars, team prefixes/suffixes, and custom names.
|
||
* Deprecated methods dealing with the legacy format indicate better component alternatives.
|
||
|
||
### Creating components
|
||
|
||
* Components are interacted with as objects.
|
||
* Use builders to construct complex components efficiently.
|
||
* Example:
|
||
|
||
```java
|
||
// This is a sub-optimal construction of the
|
||
// component as each change creates a new component
|
||
final Component component = Component.text("Hello").color(TextColor.color(0x13f832)).append(Component.text("world!", NamedTextColor.GREEN));
|
||
|
||
/* This is an optimal use of the builder to create
|
||
the same component. Also note that Adventure
|
||
Components are designed for use with static method imports
|
||
to make code less verbose */
|
||
final Component component = text().content("Hello").color(color(0x13f832)).append(text("world!", GREEN)).build();
|
||
```
|
||
|
||
* Refer to the Adventure documentation for in-depth information.
|
||
|
||
### MiniMessage
|
||
|
||
* MiniMessage is a string representation of components.
|
||
* It is superior to the legacy string format, supporting style inheritance and complex component types.
|
||
|
||
* Example:
|
||
|
||
```java
|
||
final Component component = MiniMessage.miniMessage().deserialize("<#438df2><b>This is the parent component; its style is applied to all children.\n<u><!b>This is the first child, which is rendered after the parent</!b></u><key:key.inventory></b></#438df2>");
|
||
|
||
// if the syntax above is too verbose for you, create a helper method!
|
||
public final class Components {
|
||
public static Component mm(String miniMessageString) {
|
||
// mm, short for MiniMessage
|
||
return MiniMessage.miniMessage().deserialize(miniMessageString);
|
||
}
|
||
}
|
||
```
|
||
|
||
### JSON format
|
||
(No information provided in the document)
|
||
|
||
### Serializers
|
||
(No information provided in the document)
|
||
|
||
#### GsonComponentSerializer
|
||
(No information provided in the document)
|
||
|
||
#### MiniMessage
|
||
(No information provided in the document)
|
||
|
||
#### PlainTextComponentSerializer
|
||
(No information provided in the document)
|
||
|
||
#### LegacyComponentSerializer
|
||
(No information provided in the document)
|
||
|
||
### Internationalization
|
||
|
||
It is generally a good idea to support translations in your plugin to appeal to a larger user base. Adventure simplifies this by adding a server-side translation layer.
|
||
|
||
* **GlobalTranslator**: All translation is done through `GlobalTranslator`. You can render translations yourself and add new sources by creating instances of `TranslationStore` or implementing the `Translator` interface.
|
||
* **Where Translations Work**: Server-side translations work anywhere the component API exists, except for `ItemStack` display text. This includes chat, entity display names, scoreboards, and tab lists.
|
||
* **ResourceBundle Example**:
|
||
|
||
```java
|
||
src/main/resources/your/plugin/Bundle_en_US.properties
|
||
some.translation.key=Translated Message: {0}
|
||
|
||
TranslationStore.StringBased<MessageFormat> store = TranslationStore.messageFormat(Key.key("namespace:value"));
|
||
|
||
ResourceBundle bundle = ResourceBundle.getBundle("your.plugin.Bundle", Locale.US, UTF8ResourceBundleControl.get());
|
||
|
||
store.registerAll(Locale.US, bundle, true);
|
||
|
||
GlobalTranslator.translator().addSource(store);
|
||
|
||
Component.translatable("some.translation.key", Component.text("The Argument"))
|
||
```
|
||
|
||
This will show to clients using the US English language:
|
||
|
||
```text
|
||
Translated Message: The Argument
|
||
```
|
||
|
||
### Audiences
|
||
|
||
Audiences wrap a collection of recipients that can receive messages. They are used to send messages to individual players, groups of players, or the entire server.
|
||
|
||
* **Who is an Audience?**
|
||
* All `CommandSender`s are single audiences (players, console, command blocks).
|
||
* `Server`, `Team`, and `World` are forwarding audiences (made up of multiple audiences).
|
||
* All `Audience` methods are available on `CommandSender`, `Server`, `Team`, and `World`.
|
||
* **ForwardingAudience**: Wraps a collection of `Audience` instances and forwards messages.
|
||
|
||
```java
|
||
// Server is a ForwardingAudience which includes all online players and the console
|
||
ForwardingAudience audience = Bukkit.getServer();
|
||
|
||
// To construct an audience from a collection of players, use:
|
||
Audience audience = Audience.audience(Audience...);
|
||
|
||
// If you pass in a single Audience, it will be returned as-is. If you pass in a collection of Audiences, they will be
|
||
// wrapped in a ForwardingAudience.
|
||
```
|
||
* **What do Audiences do?**
|
||
* Interact with players by sending messages, playing sounds, and showing bossbars.
|
||
* Send other parts of the API to players (e.g., `Audience#sendMessage(Component)`).
|
||
* **Pointers**: Provide arbitrary information like display name or UUID.
|
||
|
||
```java
|
||
// Get the uuid from an audience member, returning an Optional<UUID>
|
||
Optional<UUID> uuid = audience.get(Identity.UUID);
|
||
|
||
// Get the display name, returning a default
|
||
Component name = audience.getOrDefault(Identity.DISPLAY_NAME, Component.text("no display name!"));
|
||
```
|
||
|
||
### Signed messages
|
||
(No information provided in the document)
|
||
|
||
## Event API
|
||
|
||
### Listeners
|
||
(No information provided in the document)
|
||
|
||
### Custom events
|
||
(No information provided in the document)
|
||
|
||
### Handler lists
|
||
(No information provided in the document)
|
||
|
||
### Chat events
|
||
(No information provided in the document)
|
||
|
||
## Entity API
|
||
|
||
### Teleportation
|
||
(No information provided in the document)
|
||
|
||
### Display entities
|
||
(No information provided in the document)
|
||
|
||
### Inventories
|
||
(No information provided in the document)
|
||
|
||
### Menu Type API
|
||
(No information provided in the document)
|
||
|
||
#### Experimental
|
||
(No information provided in the document)
|
||
|
||
### Custom InventoryHolders
|
||
(No information provided in the document)
|
||
|
||
## Lifecycle API
|
||
|
||
### Introduction
|
||
(No information provided in the document)
|
||
|
||
### Datapack discovery
|
||
(No information provided in the document)
|
||
|
||
#### Experimental
|
||
(No information provided in the document)
|
||
|
||
## Data components
|
||
|
||
#### Experimental
|
||
(No information provided in the document)
|
||
|
||
## Persistent data container (PDC)
|
||
(No information provided in the document)
|
||
|
||
## Scheduling
|
||
(No information provided in the document)
|
||
|
||
## Plugin messaging
|
||
(No information provided in the document)
|
||
|
||
## Plugin configuration
|
||
(No information provided in the document)
|
||
|
||
## Registries
|
||
|
||
#### Experimental
|
||
(No information provided in the document)
|
||
|
||
## Dialog API
|
||
|
||
#### Experimental
|
||
(No information provided in the document)
|
||
|
||
## Recipes
|
||
(No information provided in the document)
|
||
|
||
## Particles
|
||
(No information provided in the document)
|
||
|
||
## Supporting Paper and Folia
|
||
(No information provided in the document)
|
||
|
||
## Roadmap
|
||
(No information provided in the document)
|
||
|
||
## Miscellaneous
|
||
(No information provided in the document)
|
||
|
||
## Using databases
|
||
(No information provided in the document)
|
||
|
||
## Debugging your plugin
|
||
(No information provided in the document)
|
||
|
||
## Minecraft internals
|
||
(No information provided in the document)
|
||
|
||
### Reading stacktraces
|
||
(No information provided in the document)
|
||
|
||
## Contributing
|
||
(No information provided in the document)
|
||
|
||
## Events
|
||
(No information provided in the document)
|
||
|
||
## Command API
|
||
|
||
### Basics
|
||
|
||
#### Introduction
|
||
(No information provided in the document)
|
||
|
||
#### Command trees
|
||
(No information provided in the document)
|
||
|
||
#### Arguments and literals
|
||
(No information provided in the document)
|
||
|
||
#### Executors
|
||
(No information provided in the document)
|
||
|
||
#### Registration
|
||
|
||
##### Registering commands
|
||
|
||
Comparison of registering commands using the old Bukkit way and the new Paper way.
|
||
|
||
###### The old Bukkit way
|
||
|
||
* Define a class extending `BukkitCommand` and implement `execute(...)` and `tabComplete(...)` methods.
|
||
* Example:
|
||
|
||
```java
|
||
BukkitPartyCommand.java
|
||
package your.package.name;
|
||
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.command.CommandSender;
|
||
import org.bukkit.command.defaults.BukkitCommand;
|
||
import org.bukkit.entity.Player;
|
||
import org.jspecify.annotations.NullMarked;
|
||
|
||
import java.util.List;
|
||
|
||
@NullMarked
|
||
public class BukkitPartyCommand extends BukkitCommand {
|
||
|
||
public BukkitPartyCommand(String name, String description, String usageMessage, List<String> aliases) {
|
||
super(name, description, usageMessage, aliases);
|
||
}
|
||
|
||
@Override
|
||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
||
if (args.length == 0) {
|
||
sender.sendPlainMessage("Please provide a player!");
|
||
return false;
|
||
}
|
||
|
||
final Player targetPlayer = Bukkit.getPlayer(args[0]);
|
||
if (targetPlayer == null) {
|
||
sender.sendPlainMessage("Please provide a valid player!");
|
||
return false;
|
||
}
|
||
|
||
targetPlayer.sendPlainMessage(sender.getName() + " started partying with you!");
|
||
sender.sendPlainMessage("You are now partying with " + targetPlayer.getName() + "!");
|
||
return true;
|
||
}
|
||
|
||
@Override
|
||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||
if (args.length == 1) {
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||
}
|
||
return List.of();
|
||
}
|
||
}
|
||
```
|
||
|
||
* Register the command:
|
||
|
||
```java
|
||
PluginClass.java
|
||
this.getServer().getCommandMap().register(this.getName().toLowerCase(), new BukkitPartyCommand("bukkitparty", "Have a party", "/bukkitparty <player>", List.of()));
|
||
```
|
||
|
||
###### The new Paper way
|
||
|
||
* Retrieve a `LiteralCommandNode<CommandSourceStack>` using `Commands.literal(final String literal)`.
|
||
* Define arguments and executors using `LiteralArgumentBuilder<CommandSourceStack>`.
|
||
* Build the `LiteralCommandNode` using `LiteralArgumentBuilder#build()`.
|
||
* Example:
|
||
|
||
```java
|
||
PaperPartyCommand.java
|
||
public static LiteralCommandNode<CommandSourceStack> createCommand(final String commandName) {
|
||
return Commands.literal(commandName)
|
||
.then(Commands.argument("target", ArgumentTypes.player()).executes(ctx -> {
|
||
final PlayerSelectorArgumentResolver playerSelector = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
|
||
final Player targetPlayer = playerSelector.resolve(ctx.getSource()).getFirst();
|
||
final CommandSender sender = ctx.getSource().getSender();
|
||
|
||
targetPlayer.sendPlainMessage(sender.getName() + " started partying with you!");
|
||
sender.sendPlainMessage("You are now partying with " + targetPlayer.getName() + "!");
|
||
return Command.SINGLE_SUCCESS;
|
||
}))
|
||
.build();
|
||
}
|
||
```
|
||
|
||
* Register the command using `LifecycleEventManager`:
|
||
|
||
```java
|
||
PluginClass.java
|
||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
|
||
commands.registrar().register(PaperPartyCommand.createCommand("paperparty"), "Have a nice party");
|
||
});
|
||
```
|
||
|
||
#### Requirements
|
||
|
||
This section explains how to restrict commands using the `requires(Predicate<S>)` method of the `ArgumentBuilder<S>` class.
|
||
|
||
* **Defining permissions**:
|
||
* Check permissions on the `command sender`.
|
||
* Example:
|
||
|
||
```java
|
||
Commands.literal("testcmd")
|
||
.requires(sender -> sender.getSender().hasPermission("permission.test"))
|
||
.executes(ctx -> {
|
||
ctx.getSource().getSender().sendRichMessage("<gold>You have permission to run this command!</gold>");
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
* Require a sender to be a server operator:
|
||
|
||
```java
|
||
Commands.literal("testcmd")
|
||
.requires(sender -> sender.getSender().isOp())
|
||
.executes(ctx -> {
|
||
ctx.getSource().getSender().sendRichMessage("<gold>You are a server operator!</gold>");
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
* **Defining more advanced predicates**:
|
||
* Any boolean can be returned in the predicate.
|
||
* Example (checking for a diamond sword):
|
||
|
||
```java
|
||
Commands.literal("givesword")
|
||
.requires(sender -> sender.getExecutor() instanceof Player player && !player.getInventory().contains(Material.DIAMOND_SWORD))
|
||
.executes(ctx -> {
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.getInventory().addItem(ItemType.DIAMOND_SWORD.createItemStack());
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
* Client-side command visibility issue: The client might show the command as executable even if the requirement is not met.
|
||
* Solution: Use `Player#updateCommands()` to resend commands to the client.
|
||
|
||
```java
|
||
Commands.literal("reloadcommands")
|
||
.executes(ctx -> {
|
||
if (ctx.getSource().getExecutor() instanceof Player player) {
|
||
player.updateCommands();
|
||
player.sendRichMessage("<gold>Successfully updated your commands!</gold>");
|
||
}
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
* **Automating command reloads**:
|
||
* Automate command reloads instead of forcing players to reload manually.
|
||
* `updateCommands()` is thread-safe but should be used sparingly to avoid excessive bandwidth usage.
|
||
* **Restricted commands**:
|
||
* From 1.21.6 onwards, commands can be restricted, requiring confirmation from the player.
|
||
* Vanilla commands requiring operator status are restricted by default.
|
||
* **Restricting your commands**:
|
||
* Wrap the predicate with `Commands.restricted(...)`.
|
||
* Example:
|
||
|
||
```java
|
||
Commands.literal("test-req")
|
||
.requires(Commands.restricted(source -> true))
|
||
.executes(ctx -> {
|
||
ctx.getSource().getSender().sendRichMessage("You passed!");
|
||
return Command.SINGLE_SUCCESS;
|
||
});
|
||
```
|
||
* Complex example:
|
||
|
||
```java
|
||
Commands.literal("mycommand")
|
||
.requires(Commands.restricted(source -> source.getSender().hasPermission("my.custom.permission") && source.getExecutor() instanceof Player player && player.getGameMode() == GameMode.ADVENTURE))
|
||
.executes(ctx -> {
|
||
// Command logic
|
||
});
|
||
```
|
||
|
||
#### Suggestions
|
||
(No information provided in the document)
|
||
|
||
#### Custom arguments
|
||
(No information provided in the document)
|
||
|
||
##### Arguments
|
||
(No information provided in the document)
|
||
|
||
##### Minecraft-specific
|
||
(No information provided in the document)
|
||
|
||
###### Location
|
||
(No information provided in the document)
|
||
|
||
###### Entities and players
|
||
(No information provided in the document)
|
||
|
||
###### Registry
|
||
(No information provided in the document)
|
||
|
||
##### Paper-specific
|
||
(No information provided in the document)
|
||
|
||
###### Enums
|
||
(No information provided in the document)
|
||
|
||
###### Predicates
|
||
(No information provided in the document)
|
||
|
||
### Adventure
|
||
(No information provided in the document)
|
||
|
||
### Miscellaneous
|
||
|
||
#### Basic commands
|
||
|
||
Paper provides the `BasicCommand` interface for simple commands.
|
||
|
||
* Implement the `BasicCommand` interface.
|
||
* Override the `execute(CommandSourceStack source, String[] args)` method.
|
||
* Optional methods:
|
||
* `Collection<String> suggest(CommandSourceStack source, String[] args)`: Tab completion suggestions.
|
||
* `boolean canUse(CommandSender sender)`: Basic Brigadier `requires` structure.
|
||
* `@Nullable String permission()`: Sets the required permission.
|
||
* **Simple Usage Example**:
|
||
|
||
```java
|
||
YourCommand.java
|
||
package your.package.name;
|
||
|
||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||
import org.jspecify.annotations.NullMarked;
|
||
|
||
@NullMarked
|
||
public class YourCommand implements BasicCommand {
|
||
|
||
@Override
|
||
public void execute(CommandSourceStack source, String[] args) {
|
||
}
|
||
}
|
||
```
|
||
|
||
* `CommandSourceStack` provides information about the sender, location, and executor.
|
||
* **Optional Methods**:
|
||
* `suggest(CommandSourceStack, String[])`: Provides tab completion suggestions, similar to `TabCompleter`.
|
||
* `canUse(CommandSender)`: Defines a basic Brigadier `requires` structure. Overriding this negates the effect of overriding `permission()`.
|
||
* `permission()`: Sets the required permission.
|
||
|
||
```java
|
||
BasicCommand.java
|
||
default boolean canUse(final CommandSender sender) {
|
||
final String permission = this.permission();
|
||
return permission == null || sender.hasPermission(permission);
|
||
}
|
||
```
|
||
* **Registering Basic Commands**:
|
||
|
||
```java
|
||
YourPlugin.java
|
||
public class YourPlugin extends JavaPlugin {
|
||
|
||
@Override
|
||
public void onEnable() {
|
||
BasicCommand yourCommand = ...;
|
||
registerCommand("mycommand", yourCommand);
|
||
}
|
||
}
|
||
```
|
||
|
||
* **Functional Interfaces**: You can use lambda expressions for simple commands, but it is not recommended for readability.
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
registerCommand("quickcmd", (source, args) -> source.getSender().sendRichMessage("<yellow>Hello!</yellow>"));
|
||
}
|
||
```
|
||
|
||
* **Example: Broadcast Command**:
|
||
|
||
```java
|
||
BroadcastCommand.java
|
||
package your.package.name;
|
||
|
||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||
import net.kyori.adventure.text.Component;
|
||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||
import org.bukkit.Bukkit;
|
||
import org.jspecify.annotations.NullMarked;
|
||
import org.jspecify.annotations.Nullable;
|
||
|
||
@NullMarked
|
||
public class BroadcastCommand implements BasicCommand {
|
||
|
||
@Override
|
||
public void execute(CommandSourceStack source, String[] args) {
|
||
final Component name = source.getExecutor() != null ? source.getExecutor().name() : source.getSender().name();
|
||
|
||
if (args.length == 0) {
|
||
source.getSender().sendRichMessage("<red>You cannot send an empty broadcast!</red>");
|
||
return;
|
||
}
|
||
|
||
final String message = String.join(" ", args);
|
||
|
||
final Component broadcastMessage = MiniMessage.miniMessage().deserialize("<red><bold>BROADCAST</red> <name> <dark_gray>»</dark_gray> <message>",
|
||
Placeholder.component("name", name),
|
||
Placeholder.unparsed("message", message));
|
||
|
||
Bukkit.broadcast(broadcastMessage);
|
||
}
|
||
|
||
@Override
|
||
@Nullable
|
||
public String permission() {
|
||
return "example.broadcast.use";
|
||
}
|
||
}
|
||
```
|
||
|
||
Register the command:
|
||
|
||
```java
|
||
PluginMainClass.java
|
||
@Override
|
||
public void onEnable() {
|
||
registerCommand("broadcast", new BroadcastCommand());
|
||
}
|
||
```
|
||
|
||
* **Adding Suggestions**: Suggest player names.
|
||
|
||
```java
|
||
@Override
|
||
public Collection<String> suggest(CommandSourceStack source, String[] args) {
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||
}
|
||
```
|
||
|
||
Filter suggestions based on input:
|
||
|
||
```java
|
||
@Override
|
||
public Collection<String> suggest(CommandSourceStack source, String[] args) {
|
||
if (args.length == 0) {
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||
}
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).toList();
|
||
}
|
||
```
|
||
|
||
* **Final Code Example**:
|
||
|
||
```java
|
||
BroadcastCommand.java
|
||
package your.package.name;
|
||
|
||
import io.papermc.paper.command.brigadier.BasicCommand;
|
||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||
import net.kyori.adventure.text.Component;
|
||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.entity.Player;
|
||
import org.jspecify.annotations.NullMarked;
|
||
import org.jspecify.annotations.Nullable;
|
||
|
||
import java.util.Collection;
|
||
|
||
@NullMarked
|
||
public class BroadcastCommand implements BasicCommand {
|
||
|
||
@Override
|
||
public void execute(CommandSourceStack source, String[] args) {
|
||
final Component name = source.getExecutor() != null ? source.getExecutor().name() : source.getSender().name();
|
||
|
||
if (args.length == 0) {
|
||
source.getSender().sendRichMessage("<red>You cannot send an empty broadcast!</red>");
|
||
return;
|
||
}
|
||
|
||
final String message = String.join(" ", args);
|
||
|
||
final Component broadcastMessage = MiniMessage.miniMessage().deserialize("<red><bold>BROADCAST</red> <name> <dark_gray>»</dark_gray> <message>",
|
||
Placeholder.component("name", name),
|
||
Placeholder.unparsed("message", message));
|
||
|
||
Bukkit.broadcast(broadcastMessage);
|
||
}
|
||
|
||
@Override
|
||
@Nullable
|
||
public String permission() {
|
||
return "example.broadcast.use";
|
||
}
|
||
|
||
@Override
|
||
public Collection<String> suggest(CommandSourceStack source, String[] args) {
|
||
if (args.length == 0) {
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||
}
|
||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase())).toList();
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Comparison
|
||
(No information provided in the document)
|
||
```
|
||
|
||
```markdown
|
||
# Component API
|
||
|
||
This documentation page applies to both the Paper and Velocity projects. Since Minecraft 1.7, the game has utilized components to represent text to be displayed by clients. Paper and Velocity natively implements the Adventure API to add component support wherever possible.
|
||
|
||
## Why You Should Use Components
|
||
|
||
* Previously, text was a linear structure with confusing symbols like `§c` and `§k` to control basic colors and styles.
|
||
* Components are a tree-like structure that inherits style and colors from their parents.
|
||
* Components have several types which do different things than just display raw text, like translating text to the client’s language based on a key, or showing a client-specific keybind to a player.
|
||
* All these component types support more style options like any RGB color, interaction events (click and hover). The other component types and these style options have poor or missing representations in the legacy string format.
|
||
|
||
## Usage
|
||
|
||
Representing text as components is now the supported way of representing text for Paper and Velocity. They are used for almost all aspects of text being displayed to clients. Text like item names, lore, bossbars, team prefixes and suffixes, custom names, and much more all support components in respective APIs. Mojang has stated that client support for the legacy format with `§` will be removed in the future.
|
||
|
||
**Tip:** In the Paper API, there are lots of deprecated methods and types that deal with this legacy format. This is to signal that a better alternative in components is available and should be migrated to going forward.
|
||
|
||
## Creating Components
|
||
|
||
Components can be interacted with as objects. There are different interfaces for each type along with builders for all the types. These objects are immutable so when constructing more complex components, it’s recommended to use builders to avoid creating new Component instances with every change.
|
||
|
||
```java
|
||
// This is a sub-optimal construction of the
|
||
// component as each change creates a new component
|
||
final Component component = Component.text("Hello")
|
||
.color(TextColor.color(0x13f832))
|
||
.append(Component.text("world!", NamedTextColor.GREEN));
|
||
|
||
/* This is an optimal use of the builder to create
|
||
the same component. Also note that Adventure
|
||
Components are designed for use with static method imports
|
||
to make code less verbose */
|
||
final Component component = text()
|
||
.content("Hello")
|
||
.color(color(0x13f832))
|
||
.append(text("world!", GREEN))
|
||
.build();
|
||
```
|
||
|
||
**In-Depth Documentation:** For complete documentation on the Adventure Component API Paper and Velocity use, please look at the [Adventure documentation](https://adventure.kyori.net/).
|
||
|
||
## MiniMessage
|
||
|
||
Paper and Velocity include the MiniMessage library, which is a string representation of components. If you prefer working with strings rather than objects, MiniMessage is vastly superior to the legacy string format. It can utilize the tree structure for style inheritance and can represent the more complex component types while legacy cannot.
|
||
|
||
```java
|
||
final Component component = MiniMessage.miniMessage().deserialize(
|
||
"<#438df2><b>This is the parent component; its style is " +
|
||
"applied to all children.\n<u><!b>This is the first child, " +
|
||
"which is rendered after the parent</!b></u><key:key.inventory></b></#438df2>"
|
||
);
|
||
|
||
// if the syntax above is too verbose for you, create a helper method!
|
||
public final class Components {
|
||
public static Component mm(String miniMessageString) {
|
||
// mm, short for MiniMessage
|
||
return MiniMessage.miniMessage().deserialize(miniMessageString);
|
||
}
|
||
}
|
||
```
|
||
|
||
```java
|
||
import static io.papermc.docs.util.Components.mm;
|
||
// replace with your own package
|
||
final Component component = mm("<blue>Hello <red>World!");
|
||
```
|
||
|
||
We recommend using this format for user-facing input such as commands or configuration values.
|
||
|
||
**In-Depth Documentation:** MiniMessage is a part of Adventure, and you can find its documentation on [Adventure’s documentation](https://adventure.kyori.net/).
|
||
|
||
**Tip:** MiniMessage has a [web viewer](https://webui.advntr.dev/), which is useful for constructing more complicated components and seeing the results in real time.
|
||
|
||
## JSON Format
|
||
|
||
Components can be serialized and deserialized from a standard JSON format. This format is used in Vanilla in various commands which accept component arguments like `/tellraw`. Below is a simple example of this format.
|
||
|
||
```json
|
||
{
|
||
"text": "This is the parent component; its style is applied to all children.\n",
|
||
"color": "#438df2",
|
||
"bold": true,
|
||
"extra": [
|
||
{
|
||
"text": "This is this first child, which is rendered after the parent",
|
||
"underlined": true,
|
||
// This overrides the parent's "bold" value just for this component
|
||
"bold": false
|
||
},
|
||
{
|
||
// This is a keybind component which will display the client's keybind for that action
|
||
"keybind": "key.inventory"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**In-Depth Documentation:** The JSON format is fully documented on the [Minecraft Wiki](https://minecraft.wiki/w/Raw_JSON_text_format).
|
||
|
||
**Tip:** There are online tools to make generating this format much easier like [JSON Text Generator](https://minecraftjson.com/).
|
||
|
||
## Serializers
|
||
|
||
Paper and Velocity come bundled with different serializers for converting between `Component`s and other forms of serialized text.
|
||
|
||
### GsonComponentSerializer
|
||
|
||
Converts between `Component` and JSON-formatted strings with convenience methods to directly deal with Gson’s `JsonElement`. This conversion is lossless and is the preferred form of serialization for components that do not have to be edited by users regularly.
|
||
|
||
### MiniMessage
|
||
|
||
Converts between `Component` and a MiniMessage-formatted string. This conversion is lossless and is the preferred form of serialization for components that have to be edited by users. There is also extensive customization you can add to the serializer, which is [documented here](https://github.com/KyoriPowered/adventure/tree/4.x/mini-message#serializers).
|
||
|
||
### PlainTextComponentSerializer
|
||
|
||
Serializes a `Component` into a plain text string. This is very lossy as all style information as well as most other types of components will lose information. There may be special handling for `TranslatableComponent`s to be serialized into a default language, but generally this shouldn’t be used except in certain circumstances, like logging to a text file.
|
||
|
||
### LegacyComponentSerializer
|
||
|
||
**Caution:** This is not recommended for use as the legacy format may be removed in the future.
|
||
|
||
Converts between `Component` and the legacy string format. This conversion is very lossy as component types and events do not have a legacy string representation. A more useful use case is converting legacy text to MiniMessage format in a migration process.
|
||
|
||
```java
|
||
final String legacyString = ChatColor.RED + "This is a legacy" + ChatColor.GOLD + "string";
|
||
|
||
// runs the legacy string through two serializers to convert legacy -> MiniMessage
|
||
final String miniMessageString = MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(legacyString));
|
||
```
|
||
|
||
**Note:** There are 2 built-in legacy serializers, one dealing with `§` symbols and the other for `&` symbols. They have their own instances available through `LegacyComponentSerializer#legacySection()` and `LegacyComponentSerializer#legacyAmpersand()`.
|
||
|
||
---
|
||
|
||
# Signed Messages
|
||
|
||
Since Minecraft version 1.19, the client now signs any messages it sends so that they are uniquely identifiable and verifiable to be sent by a specific player. With this update, they also introduced the ability delete specific messages previously sent by a player.
|
||
|
||
**Note:** This guide does not go in-depth into the specifics of chat signing and its implementation on either client or server. For a full overview, you can refer to [this linked gist](https://gist.github.com/kennytv/c11519942f68458e8314803a6579ed6e).
|
||
|
||
## How are signed messages represented in code?
|
||
|
||
Paper uses Adventure’s `SignedMessage` object to represent a signed message. We differentiate two kinds of signed messages: system messages and non-system messages.
|
||
|
||
* System messages (checked with `SignedMessage#isSystem()`) are messages send by the server.
|
||
* Non-system messages are not.
|
||
|
||
You can also differentiate the signed plain text `String` content of the message (`SignedMessage#message()`) from the unsigned, nullable `Component` content (`SignedMessage#unsignedContent()`).
|
||
|
||
## Obtaining a signed message
|
||
|
||
Signed messages can be obtained in two ways:
|
||
|
||
* From an `AsyncChatEvent` using `AbstractChatEvent#signedMessage()`.
|
||
* From an `ArgumentTypes.signedMessage()` Brigadier argument type.
|
||
|
||
## Using signed messages
|
||
|
||
You can send signed message objects to an `Audience` using the `Audience#sendMessage(SignedMessage, ChatType.Bound)` method. You can obtain a `ChatType.Bound` object from the `ChatType` interface.
|
||
|
||
Deleting messages is much simpler. Adventure provides the `Audience#deleteMessage(SignedMessage)` or `Audience#deleteMessage(SignedMessage.Signature)` methods for that.
|
||
|
||
## Example: Making user sent messages deletable
|
||
|
||
For our example, we will create a chat format plugin which allows a user to delete their own messages in case they made a mistake. For this we will use the `AsyncChatEvent`.
|
||
|
||
### AsyncChatEvent
|
||
|
||
The `AsyncChatEvent` is covered in the [chat events](chat-events.md) documentation page. If you want to read up on more detail on the chat renderer, you can do so there.
|
||
|
||
### In-game preview
|
||
|
||
*(No preview provided in the original document)*
|
||
|
||
### Code
|
||
|
||
**SignedChatListener.java**
|
||
|
||
```java
|
||
package io.papermc.docs.signedmessages;
|
||
|
||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||
import net.kyori.adventure.text.Component;
|
||
import net.kyori.adventure.text.event.ClickEvent;
|
||
import net.kyori.adventure.text.format.NamedTextColor;
|
||
import net.kyori.adventure.text.format.TextDecoration;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.event.EventHandler;
|
||
import org.bukkit.event.Listener;
|
||
|
||
public class SignedChatListener implements Listener {
|
||
|
||
@EventHandler
|
||
void onPlayerChat(AsyncChatEvent event) {
|
||
// We modify the chat format, so we use a chat renderer.
|
||
event.renderer((player, playerName, message, viewer) -> {
|
||
// This is the base format of our message. It will format chat as "<player> » <message>".
|
||
final Component base = Component.textOfChildren(
|
||
playerName.colorIfAbsent(NamedTextColor.GOLD),
|
||
Component.text(" » ", NamedTextColor.DARK_GRAY),
|
||
message
|
||
);
|
||
|
||
// Send the base format to any player who is not the sender.
|
||
if (viewer != player) {
|
||
return base;
|
||
}
|
||
|
||
// Create a base delete suffix. The creation is separated into two
|
||
// parts purely for readability reasons.
|
||
final Component deleteCrossBase = Component.textOfChildren(
|
||
Component.text("[", NamedTextColor.DARK_GRAY),
|
||
Component.text("X", NamedTextColor.DARK_RED, TextDecoration.BOLD),
|
||
Component.text("]", NamedTextColor.DARK_GRAY)
|
||
);
|
||
|
||
// Add a hover and click event to the delete suffix.
|
||
final Component deleteCross = deleteCrossBase
|
||
.hoverEvent(Component.text("Click to delete your message!", NamedTextColor.RED))
|
||
// We retrieve the signed message with event.signedMessage() and request a server-wide deletion if the
|
||
// deletion cross were to be clicked.
|
||
.clickEvent(ClickEvent.callback(audience -> Bukkit.getServer().deleteMessage(event.signedMessage())));
|
||
|
||
// Send the base format but with the delete suffix.
|
||
return base.appendSpace().append(deleteCross);
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# Custom Events
|
||
|
||
Creating custom events is a great way to add functionality to your plugin. This will allow other plugins to listen to your custom events and add functionality to your plugin.
|
||
|
||
## Creating a custom event
|
||
|
||
To create a custom event, you need to create a class that extends `Event`. Each event requires a `HandlerList` that will contain all the listeners that are listening to that event. The only exception to this requirement is when you have an event class that cannot be fired, but serves as a parent for other events instead. An example of this is `BlockPistonEvent`, which cannot be listened to directly. This list is used to call the listeners when the event is called.
|
||
|
||
**Note:** Although `getHandlerList` is not inherited from `Event`, you need to add a static `getHandlerList()` method and return the `HandlerList` for your event. Both methods are required for your event to work.
|
||
|
||
**PaperIsCoolEvent.java**
|
||
|
||
```java
|
||
public class PaperIsCoolEvent extends Event {
|
||
|
||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||
|
||
public static HandlerList getHandlerList() {
|
||
return HANDLER_LIST;
|
||
}
|
||
|
||
@Override
|
||
public HandlerList getHandlers() {
|
||
return HANDLER_LIST;
|
||
}
|
||
}
|
||
```
|
||
|
||
Now that we have created our event, we can add some functionality to it. Perhaps this will contain a message that will be broadcast to the server when the event is called.
|
||
|
||
**PaperIsCoolEvent.java**
|
||
|
||
```java
|
||
public class PaperIsCoolEvent extends Event {
|
||
|
||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||
private Component message;
|
||
|
||
public PaperIsCoolEvent(Component message) {
|
||
this.message = message;
|
||
}
|
||
|
||
public Component getMessage() {
|
||
return this.message;
|
||
}
|
||
|
||
public void setMessage(Component message) {
|
||
this.message = message;
|
||
}
|
||
|
||
public static HandlerList getHandlerList() {
|
||
return HANDLER_LIST;
|
||
}
|
||
|
||
@Override
|
||
public HandlerList getHandlers() {
|
||
return HANDLER_LIST;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Calling the event
|
||
|
||
Now that we have created our event, we can call it.
|
||
|
||
**ExamplePlugin.java**
|
||
|
||
```java
|
||
public class ExamplePlugin extends JavaPlugin {
|
||
// ...
|
||
|
||
public void callCoolPaperEvent() {
|
||
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
|
||
coolEvent.callEvent();
|
||
|
||
// Plugins could have changed the message from inside their listeners here. So we need to get the message again.
|
||
// This event structure allows for other plugins to change the message to their taste.
|
||
// Like, for example, a plugin that adds a prefix to all messages.
|
||
Bukkit.broadcast(coolEvent.getMessage());
|
||
}
|
||
}
|
||
```
|
||
|
||
## Implementing cancellation
|
||
|
||
If you want to allow your event to be cancelled, you can implement the `Cancellable` interface.
|
||
|
||
**PaperIsCoolEvent.java**
|
||
|
||
```java
|
||
public class PaperIsCoolEvent extends Event implements Cancellable {
|
||
|
||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||
private Component message;
|
||
private boolean cancelled;
|
||
|
||
// ...
|
||
|
||
@Override
|
||
public boolean isCancelled() {
|
||
return this.cancelled;
|
||
}
|
||
|
||
@Override
|
||
public void setCancelled(boolean cancelled) {
|
||
this.cancelled = cancelled;
|
||
}
|
||
}
|
||
```
|
||
|
||
Now, when the event is called, you can check if it is cancelled and act accordingly.
|
||
|
||
**ExamplePlugin.java**
|
||
|
||
```java
|
||
public class ExamplePlugin extends JavaPlugin {
|
||
// ...
|
||
|
||
public void callCoolPaperEvent() {
|
||
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
|
||
coolEvent.callEvent();
|
||
|
||
if (!coolEvent.isCancelled()) {
|
||
Bukkit.broadcast(coolEvent.getMessage());
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
When an event is cancellable, `Event#callEvent()` will return false if the event was cancelled. This allows you to directly use `callEvent` in your `if` statement, instead of having to check `Cancellable#isCancelled()` manually.
|
||
|
||
**ExamplePlugin.java**
|
||
|
||
```java
|
||
public class ExamplePlugin extends JavaPlugin {
|
||
// ...
|
||
|
||
public void callCoolPaperEvent() {
|
||
PaperIsCoolEvent coolEvent = new PaperIsCoolEvent(Component.text("Paper is cool!"));
|
||
if (coolEvent.callEvent()) {
|
||
// Directly get the output from callEvent
|
||
Bukkit.broadcast(coolEvent.getMessage());
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# Custom InventoryHolders
|
||
|
||
`InventoryHolder`s are a way to identify your plugin’s inventories in events.
|
||
|
||
## Why use an `InventoryHolder`?
|
||
|
||
`InventoryHolder`s simplify the steps you need to do to make sure an inventory was created by your plugin. Using inventory names for identification is unreliable, as other plugins, or even players, can create inventories with names the exact same as yours. With components, you also need to make sure the name is exactly the same or serialize it to other formats. Custom `InventoryHolder`s have no such downsides and by using them you’re guaranteed to have methods available to handle your inventory.
|
||
|
||
## Creating a custom holder
|
||
|
||
The first step is to implement the `InventoryHolder` interface. We can do this the following way: create a new class that will create our `Inventory` in the constructor.
|
||
|
||
**Note:** The constructor takes your main plugin class as an argument in order to create the `Inventory`. If you wish, you can use the static method `Bukkit#createInventory(InventoryHolder, int)` instead and remove the argument.
|
||
|
||
**MyInventory.java**
|
||
|
||
```java
|
||
public class MyInventory implements InventoryHolder {
|
||
|
||
private final Inventory inventory;
|
||
|
||
public MyInventory(MyPlugin plugin) {
|
||
// Create an Inventory with 9 slots, `this` here is our InventoryHolder.
|
||
this.inventory = plugin.getServer().createInventory(this, 9);
|
||
}
|
||
|
||
@Override
|
||
public Inventory getInventory() {
|
||
return this.inventory;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Opening the inventory
|
||
|
||
To open the inventory, first we have to instantiate our `MyInventory` class and then open the inventory for the player. You can do that wherever you need.
|
||
|
||
**Note:** We pass an instance of our plugin’s main class as it’s required by the constructor. If you’ve used the static method and removed the constructor argument you don’t have to pass it here.
|
||
|
||
```java
|
||
Player player;
|
||
// Assume we have a Player instance.
|
||
// This can be a command, another event or anywhere else you have a Player.
|
||
|
||
MyInventory myInventory = new MyInventory(myPlugin);
|
||
player.openInventory(myInventory.getInventory());
|
||
```
|
||
|
||
## Listening to an event
|
||
|
||
Once we have the inventory open, we can listen to any inventory events we like and check if `Inventory#getHolder()` returns an instance of our `MyInventory`.
|
||
|
||
```java
|
||
@EventHandler
|
||
public void onInventoryClick(InventoryClickEvent event) {
|
||
Inventory inventory = event.getInventory();
|
||
|
||
// Check if the holder is our MyInventory,
|
||
// if yes, use instanceof pattern matching to store it in a variable immediately.
|
||
if (!(inventory.getHolder(false) instanceof MyInventory myInventory)) {
|
||
// It's not our inventory, ignore it.
|
||
return;
|
||
}
|
||
|
||
// Do what we need in the event.
|
||
}
|
||
```
|
||
|
||
## Storing data on the holder
|
||
|
||
You can store extra data for your inventories on the `InventoryHolder` by adding fields and methods to your class. Let’s make an inventory that counts the amount of times we clicked a stone inside it.
|
||
|
||
First, let’s modify our `MyInventory` class a little:
|
||
|
||
**MyInventory.java**
|
||
|
||
```java
|
||
public class MyInventory implements InventoryHolder {
|
||
|
||
private final Inventory inventory;
|
||
private int clicks = 0; // Store the amount of clicks.
|
||
|
||
public MyInventory(MyPlugin plugin) {
|
||
this.inventory = plugin.getServer().createInventory(this, 9);
|
||
|
||
// Set the stone that we're going to be clicking.
|
||
this.inventory.setItem(0, ItemStack.of(Material.STONE));
|
||
}
|
||
|
||
// A method we will call in the listener whenever the player clicks the stone.
|
||
public void addClick() {
|
||
this.clicks++;
|
||
this.updateCounter();
|
||
}
|
||
|
||
// A method that will update the counter item.
|
||
private void updateCounter() {
|
||
this.inventory.setItem(8, ItemStack.of(Material.BEDROCK, this.clicks));
|
||
}
|
||
|
||
@Override
|
||
public Inventory getInventory() {
|
||
return this.inventory;
|
||
}
|
||
}
|
||
```
|
||
|
||
Now, we can modify our listener to check if the player clicked the stone, and if so, add a click.
|
||
|
||
```java
|
||
@EventHandler
|
||
public void onInventoryClick(InventoryClickEvent event) {
|
||
// We're getting the clicked inventory to avoid situations where the player
|
||
// already has a stone in their inventory and clicks that one.
|
||
Inventory inventory = event.getClickedInventory();
|
||
|
||
// Add a null check in case the player clicked outside the window.
|
||
if (inventory == null || !(inventory.getHolder(false) instanceof MyInventory myInventory)) {
|
||
return;
|
||
}
|
||
|
||
event.setCancelled(true);
|
||
ItemStack clicked = event.getCurrentItem();
|
||
|
||
// Check if the player clicked the stone.
|
||
if (clicked != null && clicked.getType() == Material.STONE) {
|
||
// Use the method we have on MyInventory to increment the field
|
||
// and update the counter.
|
||
myInventory.addClick();
|
||
}
|
||
}
|
||
```
|
||
|
||
**Note:** You can store the created `MyInventory` instance, e.g. on a `Map<UUID, MyInventory>` for per-player use, or as a field to share the inventory between all players, and use it to persist the counter even when opening the inventory for the next time.
|
||
|
||
---
|
||
|
||
# Data Components
|
||
|
||
**(Experimental)** The data component API is currently experimental, and is additionally subject to change across versions.
|
||
|
||
The data component API provides a version-specific interface for accessing and manipulating item data that is otherwise not representable by the `ItemMeta` API. Through this API, you can read and modify properties of an item, so called data components, in a stable and object-oriented manner.
|
||
|
||
## Introduction
|
||
|
||
### What is a data component?
|
||
|
||
A data component represents a piece of data associated with an item. Vanilla items can have properties such as custom model data, container loot contents, banner patterns, or potion effects.
|
||
|
||
### Structure
|
||
|
||
For implementation details, [click here](https://github.com/PaperMC/Paper/pull/6913).
|
||
|
||
### The prototype (default values)
|
||
|
||
Items come with an initial set of components that we call the prototype. These components are defined on the `ItemType` of the `ItemStack`. They control the base behavior of the item, representing a brand new item without any modifications.
|
||
|
||
The prototype gives items their initial properties such as if they are food, a tool, a weapon, etc.
|
||
|
||
### The patch
|
||
|
||
The patch represents the modifications made to the item. This may include giving it a custom name, modifying the enchantments, damaging it, or adding to the lore. The patch is applied on top of the prototype, allowing us to make modifications to an item.
|
||
|
||
The patch also allows for removing components that were previously in the prototype. This is shown by the `minecraft:tool` example in red. We are removing this component, so this sword item will no longer break cobweb or other sword blocks faster.
|
||
|
||
We can also add new components, as seen from the new `minecraft:enchantment_glint_override` component, which allows us to make it appear as if it were enchanted.
|
||
|
||
## Differences compared to `ItemMeta`
|
||
|
||
The `ItemMeta` API provides methods to modify `ItemStack`s in a hierarchical manner, such as `CompassMeta`, which allows you to modify the components of a `minecraft:compass`.
|
||
|
||
While `ItemMeta` is still very useful, it does not properly represent the prototype/patch relationship that Minecraft items use.
|
||
|
||
### Key differences
|
||
|
||
#### Expanded data model
|
||
|
||
The data component API exposes a much broader and more detailed set of item properties than `ItemMeta`. Data components allow the entire item to be modified in a fashion that better represents how Minecraft does item modifications.
|
||
|
||
#### Version-specific
|
||
|
||
The data component API is designed to adapt to version changes. The data component API may experience breaking changes on version updates as Minecraft makes changes to components. Backwards compatibility is not promised.
|
||
|
||
Because `ItemMeta` is represented in a different format, breaking changes made to components by Mojang may not result in breaking changes to `ItemMeta`.
|
||
|
||
#### Builders and immutability
|
||
|
||
Many complex data components require a builder approach for construction and editing. All data types that are returned by the api are also immutable, so they will not directly modify the component.
|
||
|
||
#### Patch-only
|
||
|
||
`ItemMeta` only represents the patch of an `ItemStack`. This means that you cannot get the original properties (prototype) of the `ItemStack`, such as its default durability or default attributes.
|
||
|
||
#### No snapshots
|
||
|
||
Currently, `ItemMeta` represents a *snapshot* of an `ItemStack`’s patched map. This is expensive as it requires the entire patch to be read, even values that you may not be using.
|
||
|
||
The data component API integrates directly with `ItemStack`. Although conceptually similar, the data component API focuses on explicit, strongly typed data retrieval and updates without this additional overhead.
|
||
|
||
## When should I use `DataComponents` or `ItemMeta`?
|
||
|
||
You would want to use `ItemMeta` if you:
|
||
|
||
* Are doing only simple changes to `ItemStack`s
|
||
* Want to keep cross-version compatibility with your plugin
|
||
|
||
You would want to use data components if you:
|
||
|
||
* Want more complicated `ItemStack` modifications
|
||
* Do not care about cross-version compatibility
|
||
* Want to access default (prototype) values
|
||
* Want to remove components from an `ItemStack`’s prototype
|
||
|
||
## Basic usage
|
||
|
||
The data component API will fetch values according to the behavior seen in game. So, if the patch removes the `minecraft:tool` component, trying to get that component will return null.
|
||
|
||
### Retrieving a prototype value
|
||
|
||
```java
|
||
// Get the default durability of diamond sword
|
||
int defaultDurability = Material.DIAMOND_SWORD.getDefaultData(DataComponentTypes.MAX_DAMAGE);
|
||
```
|
||
|
||
### Checking for a data component
|
||
|
||
```java
|
||
// Check if this item has a custom name data component
|
||
boolean hasCustomName = stack.hasData(DataComponentTypes.CUSTOM_NAME);
|
||
logger.info("Has custom name? " + hasCustomName);
|
||
```
|
||
|
||
### Reading a valued data component
|
||
|
||
```java
|
||
// The damage of an item can be null, so we require a null check
|
||
Integer damageValue = stack.getData(DataComponentTypes.DAMAGE);
|
||
if (damageValue != null) {
|
||
logger.info("Current damage: " + damageValue);
|
||
} else {
|
||
logger.info("This item doesn't have a damage component set.");
|
||
}
|
||
|
||
// Certain components, like the max stack size, will always be present on an item
|
||
Integer maxStackSize = stack.getData(DataComponentTypes.MAX_STACK_SIZE);
|
||
```
|
||
|
||
### Setting a valued data component
|
||
|
||
```java
|
||
// Set a custom model data value on this item
|
||
stack.setData(DataComponentTypes.CUSTOM_MODEL_DATA,
|
||
CustomModelData.customModelData()
|
||
.addFloat(0.5f)
|
||
.addFlag(true)
|
||
.build());
|
||
```
|
||
|
||
### Removing or resetting a data component
|
||
|
||
```java
|
||
// Remove an existing component (e.g. tool)
|
||
stack.unsetData(DataComponentTypes.TOOL);
|
||
|
||
// Reset a component to the default (prototype) value for its item type (e.g. max stack size)
|
||
stack.resetData(DataComponentTypes.MAX_STACK_SIZE);
|
||
```
|
||
|
||
### Non-valued data components
|
||
|
||
Some components are only flags and don’t carry any sort of value:
|
||
|
||
```java
|
||
// Make the item a glider to be used like elytra (combined with the equippable component)
|
||
stack.setData(DataComponentTypes.GLIDER);
|
||
|
||
// Remove the glider flag
|
||
stack.unsetData(DataComponentTypes.GLIDER);
|
||
```
|
||
|
||
## Advanced usage with builders
|
||
|
||
Many data components have complex structures that require builders.
|
||
|
||
### Modifying prototype component values
|
||
|
||
```java
|
||
ItemStack helmet = ItemStack.of(Material.DIAMOND_HELMET);
|
||
|
||
// Get the equippable component for this item, and make it a builder.
|
||
// Note: Not all types have .toBuilder() methods
|
||
// This is the prototype value of the diamond helmet.
|
||
Equippable.Builder builder = helmet.getData(DataComponentTypes.EQUIPPABLE).toBuilder();
|
||
|
||
// Make the helmet look like netherite
|
||
// We get the prototype equippable value from NETHERITE_HELMET
|
||
builder.assetId(Material.NETHERITE_HELMET.getDefaultData(DataComponentTypes.EQUIPPABLE).assetId());
|
||
|
||
// And give it a spooky sound when putting it on
|
||
builder.equipSound(SoundEventKeys.ENTITY_GHAST_HURT);
|
||
|
||
// Set our new item
|
||
helmet.setData(DataComponentTypes.EQUIPPABLE, builder);
|
||
```
|
||
|
||
This will create a diamond helmet that looks like a netherite helmet and plays a spooky ghast sound when equipped.
|
||
|
||
### Example: Written book
|
||
|
||
```java
|
||
ItemStack book = ItemStack.of(Material.WRITTEN_BOOK);
|
||
|
||
WrittenBookContent.Builder builder = WrittenBookContent.writtenBookContent("My Book", "AuthorName");
|
||
|
||
// Add a page
|
||
builder.addPage(Component.text("This is a new page!"));
|
||
|
||
// Add a page that shows differently for people who have swear filtering on
|
||
// Players who have disabled filtering, will see "I hate Paper!", while those with filtering on will see the "I love Paper!".
|
||
builder.addFilteredPage(Filtered.of(Component.text("I hate Paper!"), Component.text("I love Paper!")));
|
||
|
||
// Change generation
|
||
builder.generation(1);
|
||
|
||
// Apply changes
|
||
book.setData(DataComponentTypes.WRITTEN_BOOK_CONTENT, builder.build());
|
||
```
|
||
|
||
### Example: Cool sword
|
||
|
||
```java
|
||
ItemStack sword = ItemStack.of(Material.DIAMOND_SWORD);
|
||
|
||
sword.setData(DataComponentTypes.LORE,
|
||
ItemLore.lore()
|
||
.addLine(Component.text("Cool sword!"))
|
||
.build());
|
||
|
||
sword.setData(DataComponentTypes.ENCHANTMENTS,
|
||
ItemEnchantments.itemEnchantments()
|
||
.add(Enchantment.SHARPNESS, 10)
|
||
.build());
|
||
|
||
sword.setData(DataComponentTypes.RARITY, ItemRarity.RARE);
|
||
|
||
sword.unsetData(DataComponentTypes.TOOL); // Remove the tool component
|
||
|
||
sword.setData(DataComponentTypes.MAX_DAMAGE, 10);
|
||
|
||
sword.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); // Make it glow!
|
||
```
|
||
|
||
### Matching items without certain data components
|
||
|
||
When comparing items, you sometimes want to ignore certain values. For this, we can use the `ItemStack#matchesWithoutData` method.
|
||
|
||
For example, here we compare two diamond swords whilst ignoring their durability:
|
||
|
||
```java
|
||
ItemStack originalSword = ItemStack.of(Material.DIAMOND_SWORD);
|
||
ItemStack damagedSword = ItemStack.of(Material.DIAMOND_SWORD);
|
||
damagedSword.setData(DataComponentTypes.DAMAGE, 100);
|
||
|
||
boolean match = damagedSword.matchesWithoutData(originalSword, Set.of(DataComponentTypes.DAMAGE), false);
|
||
logger.info("Do the sword match? " + match);
|
||
// -> true
|
||
```
|
||
|
||
---
|
||
|
||
# Debugging your plugin
|
||
|
||
Debugging your plugin is vital to being able to fix bugs and issues in your plugin. This page will cover some of the most common debugging techniques.
|
||
|
||
## Printing to the console
|
||
|
||
One of the most common debugging techniques is to print to the console. This is likely something you’ve done before, as it’s very simple. This has a few downsides, though. It can be hard to find the print statements in the console, and it can be hard to remove them all when you’re done debugging. Most notably, you have to recompile your plugin and restart the server to add or remove debugging.
|
||
|
||
When debugging, you can use `System.out.println("");` to print to the console. It is recommended to use your plugin’s logger instead though, as it will be easier to know which plugin the log has come from. This can be done simply with:
|
||
|
||
```java
|
||
plugin.getComponentLogger().debug(Component.text("SuperDuperBad Thing has happened"));
|
||
```
|
||
|
||
### Logger Levels
|
||
|
||
In some consoles, using the `warning` level will print the message in different colors. This can be useful for finding your print statements in the console.
|
||
|
||
## Using a remote debugger
|
||
|
||
A debugger is a tool that allows you to pause your code at a certain point and inspect the values of variables. This can be very useful for finding out why your code isn’t working as expected and also for finding out where your code is going wrong.
|
||
|
||
### Setting up the debugger
|
||
|
||
To use a debugger, you need to set up your IDE to use it. This is different for each IDE, but for the sake of this guide, we will be using IntelliJ IDEA.
|
||
|
||
To set up a debugger in IntelliJ, you need to create a new run configuration. You can do this by clicking the dropdown next to the run button and clicking `Edit Configurations...`:
|
||
|
||
*(Image of IntelliJ Edit Configurations)*
|
||
|
||
Then, click the `+` button in the top left and select `Remote JVM Debug`. You can then name the configuration whatever you want, and click `Apply`:
|
||
|
||
*(Image of IntelliJ Remote JVM Debug Configuration)*
|
||
|
||
Finally, copy the command line arguments from the window, and paste these into your server’s startup script. These will go after the `java` command and before `-jar`. Once you have done this, you can click `OK`. For example:
|
||
|
||
```terminal
|
||
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar paper-1.21.10.jar nogui
|
||
```
|
||
|
||
Once your server is running, you can use the bug icon in the top right to connect your debugger to the server:
|
||
|
||
*(Image of IntelliJ Debug Button)*
|
||
|
||
### Using the debugger
|
||
|
||
Let’s say we have this code:
|
||
|
||
```java
|
||
@EventHandler
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
Player player = event.getPlayer();
|
||
Location location = player.getLocation();
|
||
if (location.getWorld() == null) return;
|
||
if (location.getWorld().getEnvironment() == World.Environment.NETHER) {
|
||
player.sendMessage("You are in the nether!");
|
||
}
|
||
}
|
||
```
|
||
|
||
You can add a breakpoint to the line by clicking on the line number:
|
||
|
||
*(Image of IntelliJ Breakpoint)*
|
||
|
||
This will pause the code when it reaches that line. You can then use the debugger to inspect the values of variables:
|
||
|
||
*(Image of IntelliJ Debugger)*
|
||
|
||
You can inspect the values of each of the variables in the current scope. You can also use the buttons in the top to step from one breakpoint to the next. If needed, you can also use the text box at the top to evaluate expressions for debugging purposes.
|
||
|
||
## Using direct debugging
|
||
|
||
Direct debugging will allow you to run the server directly from your IDE, and will allow you to use breakpoints and step through your code.
|
||
|
||
We can achieve this by using [JPenilla’s Gradle plugin](https://github.com/jpenilla/run-paper) to run the server directly from the IDE.
|
||
|
||
See [here](https://github.com/jpenilla/run-paper) for instructions on how to set up the plugin.
|
||
|
||
---
|
||
|
||
# Display entities
|
||
|
||
Added in 1.19.4, display entities are a powerful way to display various things in the world, like blocks, items and text. By default, these entities have no hitbox, don’t move, make sounds or take damage, making them the perfect for all kinds of applications, like holograms.
|
||
|
||
## Types
|
||
|
||
### Text
|
||
|
||
Text can be displayed via a TextDisplay entity.
|
||
|
||
```java
|
||
TextDisplay display = world.spawn(location, TextDisplay.class, entity -> {
|
||
// customize the entity!
|
||
entity.text(Component.text("Some awesome content", NamedTextColor.BLACK));
|
||
entity.setBillboard(Display.Billboard.VERTICAL); // pivot only around the vertical axis
|
||
entity.setBackgroundColor(Color.RED); // make the background red
|
||
// see the Display and TextDisplay Javadoc, there are many more options
|
||
});
|
||
```
|
||
|
||
### Blocks
|
||
|
||
Blocks can be displayed
|
||
|
||
```markdown
|
||
# PaperMC Knowledge Base
|
||
|
||
## Configuration
|
||
|
||
### World Configuration
|
||
- Includes settings for worlds within the server.
|
||
|
||
### Global Configuration
|
||
- Includes settings that apply to the entire server.
|
||
|
||
### Configuration Files
|
||
|
||
* **bukkit.yml**: Configuration file for Bukkit settings.
|
||
* **spigot.yml**: Configuration file for Spigot settings.
|
||
* **commands.yml**: Configuration file for command aliases and settings.
|
||
* **permissions.yml**: Configuration file for managing permissions.
|
||
* **help.yml**: Configuration file for custom help messages.
|
||
* **server.properties**: Configuration file for basic server settings.
|
||
* **Vanilla data files**: Vanilla Minecraft data files.
|
||
* **Paper plugins**: Configuration for Paper-specific plugin settings.
|
||
* **Commands**: Defines server commands and their properties.
|
||
* **System properties**: System-level settings for the server.
|
||
* **Permissions**: Defines permissions and their assignments.
|
||
* **Miscellaneous**: Other configuration options.
|
||
* **Bug fixes**: Configuration related to bug fixes.
|
||
* **Frequently asked questions**: Configuration related to frequently asked questions.
|
||
|
||
## Development
|
||
|
||
### Getting Started
|
||
|
||
* Project Setup: Setting up the development environment.
|
||
* How Plugins Work: Explanation of plugin functionality.
|
||
* Paper Plugins: Information about Paper-specific plugins.
|
||
|
||
### Experimental Features
|
||
|
||
* plugin.yml: Plugin configuration file.
|
||
* paperweight-userdev: Information about paperweight user development.
|
||
|
||
### API
|
||
|
||
#### Command API
|
||
|
||
* **Basics**
|
||
* Introduction: Overview of the command API.
|
||
* Command Trees: Structuring commands in a tree-like format.
|
||
* Arguments and Literals: Defining command arguments and literals.
|
||
* Executors: Handling command execution.
|
||
* Registration: Registering commands with the server.
|
||
* Requirements: Setting requirements for command usage.
|
||
* Suggestions: Providing command suggestions.
|
||
* Custom Arguments: Creating custom argument types.
|
||
* **Arguments**
|
||
* Minecraft-Specific: Arguments related to Minecraft entities and objects.
|
||
* Location: Handling location-based arguments.
|
||
* Entities and Players: Specifying entities and players as arguments.
|
||
* Registry: Using Minecraft registries in arguments.
|
||
* Paper-Specific: Arguments specific to Paper.
|
||
* Enums: Using enums as arguments.
|
||
* Predicates: Using predicates for argument validation.
|
||
* Adventure: Using the Adventure library for command components.
|
||
* Miscellaneous: Other command API features.
|
||
* Basic commands: Basic command examples.
|
||
* Comparison: Command comparisons.
|
||
|
||
#### Component API
|
||
|
||
* Introduction: Overview of the Component API.
|
||
* Internationalization: Supporting multiple languages.
|
||
* Audiences: Sending messages to specific audiences.
|
||
* Signed Messages: Handling signed messages.
|
||
|
||
#### Event API
|
||
|
||
* Listeners: Creating event listeners.
|
||
* Custom Events: Defining custom events.
|
||
* Handler Lists: Managing event handlers.
|
||
* Chat Events: Handling chat-related events.
|
||
* Entity API: Interacting with entities.
|
||
* Teleportation: Managing entity teleportation.
|
||
* Display entities: Using display entities to show blocks, items, and text.
|
||
* Inventories: Managing inventories.
|
||
* Menu Type API: Creating custom menu types.
|
||
* Experimental: This API is experimental and may change.
|
||
* Custom InventoryHolders: Managing custom inventory holders.
|
||
|
||
#### Lifecycle API
|
||
|
||
* Introduction: Overview of the Lifecycle API.
|
||
* Datapack Discovery: Discovering and managing datapacks.
|
||
* Experimental: This API is experimental and may change.
|
||
* Data Components: Manipulating item data.
|
||
* Experimental: This API is experimental and may change.
|
||
* Persistent Data Container (PDC): Storing persistent data.
|
||
* Scheduling: Scheduling tasks within the plugin.
|
||
* Plugin Messaging: Sending messages between plugins.
|
||
* Plugin Configuration: Managing plugin configuration.
|
||
* Registries
|
||
* Experimental: This API is experimental and may change.
|
||
* Dialog API
|
||
* Experimental: This API is experimental and may change.
|
||
|
||
#### Dialog API
|
||
|
||
* **Overview:** Introduction to the Dialog API.
|
||
* **What is a dialog?** Dialogs are custom in-game menus for displaying information or gathering user input.
|
||
* **Showing dialogs:** Use `/dialog show <players> <dialog>` command or `Audience#showDialog(DialogLike)`. Get built-in dialogs from the `Dialog` interface or create new ones with `Dialog#create`.
|
||
* **Built-in dialogs:**
|
||
* Server Links
|
||
* Quick Actions
|
||
* Custom Options
|
||
* **Adding server links:** Retrieve the `ServerLinks` instance from `Bukkit.getServer().getServerLinks()` and use its methods.
|
||
* **Creating dialogs dynamically:** Use the `Dialog#create` method. Dialogs require a base and a type.
|
||
* **Dialog base:** Created using `DialogBase.builder(Component title)`. Can declare:
|
||
* `afterAction(DialogAfterAction)`: Action after dialog is closed.
|
||
* `canCloseWithEscape(boolean)`: Whether the dialog can be closed with the `esc` key.
|
||
* `externalTitle(Component)`: Title for buttons that open this dialog.
|
||
* `body(List<? extends DialogBody>)`: The body of the dialog.
|
||
* `inputs(List<? extends DialogInput>)`: The inputs of the dialog.
|
||
* **Dialog body:** Can contain `DialogBody.plainMessage(Component)` for text or `DialogBody.item(ItemStack)` for items.
|
||
* **Dialog input:**
|
||
* `DialogInput.bool`: Tick box (true/false).
|
||
* `DialogInput.singleOption`: Multiple-choice button.
|
||
* `DialogInput.text`: String input field.
|
||
* `DialogInput.numberRange`: Slider for number input.
|
||
* **Dialog type:**
|
||
* `notice()`: Simple dialog with one button.
|
||
* `confirmation(ActionButton yesButton, ActionButton noButton)`: Dialog with yes/no buttons.
|
||
* `dialogList(RegistrySet dialogs)`: Dialog for opening specified dialogs.
|
||
* `multiAction(List<ActionButton> actions)`: Dialog for displaying multiple buttons.
|
||
* `serverLinks(ActionButton exitAction, int columns, int buttonWidth)`: Server links dialog.
|
||
* **Registering dialogs in the registry:** Register during a registry modification lifecycle event in your plugin's bootstrapper. Allows referencing the dialog in commands.
|
||
* **Closing dialogs:**
|
||
* `Adventure#closeDialog()`: Closes the dialog and returns to the previous screen.
|
||
* `Player#closeInventory()`: Closes the dialog and any other open screens.
|
||
* **Example: A blocking confirmation dialog:** Shows a dialog before allowing a player to join.
|
||
* **The dialog:** A confirmation dialog registered in the bootstrapper.
|
||
* **Requiring the player to agree before allowing them to join:** Uses a `CompletableFuture` to await a response.
|
||
* **Example: Retrieving and parsing user input:** Shows a dialog with number range inputs.
|
||
* **Reading the input:** Listen to `PlayerCustomClickEvent` and retrieve values from `DialogResponseView`.
|
||
* **Using callbacks:** Use `DialogAction.customClick(DialogActionCallback, ClickCallback.Options)` to register a callback locally.
|
||
|
||
#### Data Components
|
||
|
||
* Experimental API for accessing and manipulating item data not representable by ItemMeta.
|
||
* Allows reading and modifying properties of an item in a stable, object-oriented manner.
|
||
* **Introduction**
|
||
* A data component represents a piece of data associated with an item (e.g., custom model data, loot contents).
|
||
* **Structure**
|
||
* **Prototype (default values):** Initial set of components defined on the ItemType of the ItemStack.
|
||
* **Patch:** Modifications made to the item (e.g., custom name, enchantments). Applied on top of the prototype. Allows for removing components from the prototype.
|
||
* **Differences compared to ItemMeta**
|
||
* ItemMeta doesn't represent the prototype/patch relationship.
|
||
* **Key differences**
|
||
* **Expanded data model:** Exposes a broader and more detailed set of item properties.
|
||
* **Version-specific:** Designed to adapt to version changes, may experience breaking changes on updates.
|
||
* **Builders and immutability:** Complex components require a builder approach. All data types returned by the API are immutable.
|
||
* **Patch-only:** ItemMeta only represents the patch, not the original properties (prototype).
|
||
* **No snapshots:** Data component API integrates directly with ItemStack for explicit, strongly typed data retrieval.
|
||
* **When should I use DataComponents or ItemMeta?**
|
||
* Use ItemMeta for simple changes and cross-version compatibility.
|
||
* Use DataComponents for complicated modifications, accessing prototype values, and removing components.
|
||
* **Basic usage**
|
||
* Fetches values according to in-game behavior.
|
||
* **Retrieving a prototype value:** Get default values directly from the Material.
|
||
* **Checking for a data component:** Use `stack.hasData(DataComponentTypes.COMPONENT_NAME)`.
|
||
* **Reading a valued data component:** Use `stack.getData(DataComponentTypes.COMPONENT_NAME)`. Requires null check.
|
||
* **Setting a valued data component:** Use `stack.setData(DataComponentTypes.COMPONENT_NAME, value)`.
|
||
* **Removing or resetting a data component:** Use `stack.unsetData(DataComponentTypes.COMPONENT_NAME)` to remove, `stack.resetData(DataComponentTypes.COMPONENT_NAME)` to reset to default.
|
||
* **Non-valued data components:** Some components are flags (e.g., `DataComponentTypes.GLIDER`).
|
||
* **Advanced usage with builders**
|
||
* Many components require builders for complex structures.
|
||
* **Modifying prototype component values:** Use `.toBuilder()` to modify the prototype.
|
||
* **Example: Written book:** Demonstrates creating a written book with custom content and filtering.
|
||
* **Example: Cool sword:** Demonstrates adding lore, enchantments, and removing the tool component.
|
||
* **Matching items without certain data components:** Use `ItemStack#matchesWithoutData` to compare items ignoring specific components.
|
||
|
||
* Recipes
|
||
* Particles
|
||
* Supporting Paper and Folia
|
||
* Roadmap
|
||
* Miscellaneous
|
||
* Using databases
|
||
* Debugging your plugin
|
||
* Minecraft internals
|
||
* Reading stacktraces
|
||
* Contributing
|
||
* Events
|
||
* GitHub
|
||
* Javadoc
|
||
* Discord
|
||
|
||
#### Custom InventoryHolders
|
||
|
||
* **Overview:** `InventoryHolder`s are a way to identify your plugin's inventories in events.
|
||
* **Why use an InventoryHolder?** Simplifies identifying inventories created by your plugin, more reliable than using inventory names.
|
||
* **Creating a custom holder:** Implement the `InventoryHolder` interface. Create a class that creates the `Inventory` in the constructor.
|
||
* **Opening the inventory:** Instantiate the `InventoryHolder` class and open the inventory for the player using `player.openInventory(inventoryHolder.getInventory())`.
|
||
* **Listening to an event:** Listen to inventory events and check if `Inventory#getHolder()` returns an instance of your custom `InventoryHolder`.
|
||
* **Storing data on the holder:** Add fields and methods to your `InventoryHolder` class to store extra data for your inventories.
|
||
|
||
#### Debugging Your Plugin
|
||
|
||
* **Overview:** Debugging is crucial for fixing bugs in your plugin.
|
||
* **Printing to the console:**
|
||
* Use `plugin.getComponentLogger().debug(Component.text("message"))` instead of `System.out.println("")`.
|
||
* Using `warning` level in the logger can make messages stand out.
|
||
* **Using a remote debugger:**
|
||
* A debugger allows you to pause code and inspect variables.
|
||
* **Setting up the debugger:**
|
||
* Create a new "Remote JVM Debug" run configuration in your IDE.
|
||
* Copy the command line arguments from the IDE and paste them into your server's startup script (after `java` and before `-jar`).
|
||
* **Using the debugger:**
|
||
* Add breakpoints by clicking on the line number in your IDE.
|
||
* Inspect variables and step through code using the debugger controls.
|
||
* **Using direct debugging:**
|
||
* Run the server directly from your IDE using a Gradle plugin.
|
||
|
||
#### Display Entities
|
||
|
||
* Added in 1.19.4, display entities are a powerful way to display blocks, items and text.
|
||
* By default, these entities have no hitbox, don’t move, make sounds or take damage.
|
||
* **Types:**
|
||
* **Text:** Display text via a `TextDisplay` entity.
|
||
* **Blocks:** Display blocks via a `BlockDisplay` entity.
|
||
* **Items:** Display items via an `ItemDisplay` entity. Can also display blocks, with the difference being the position in the model.
|
||
* **Transformation:**
|
||
* Displays can have an arbitrary affine transformation applied to them.
|
||
* Transformations are applied in this order: Scale, Rotation, Translation.
|
||
* Visualizing transformations: Use the Transformation Visualizer website for quick prototyping!
|
||
* **Scale:** Scale the display entity.
|
||
* **Rotation:** Rotate the display entity.
|
||
* **Translation:** Apply a position offset to the display entity.
|
||
* **Interpolation:**
|
||
* Transformations and teleports can be linearly interpolated by the client to create a smooth animation.
|
||
* **Transformation:** Smoothly rotate a block/item/text in-place.
|
||
* **Teleportation:** Interpolate the movement of the entire display entity between two points.
|
||
* **Use cases:**
|
||
* Displays have many different use cases, ranging from stationary decoration to complex animation.
|
||
* Make a decoration that’s visible to only specific players using `Entity#setVisibleByDefault()` and `Player#showEntity()` / `Player#hideEntity()`.
|
||
* They can also be added as passengers to entities with the `Entity#addPassenger()` / `Entity#removePassenger()` methods, useful for making styled name tags!
|
||
* If the display is only used temporarily, its persistence can be disabled with `Entity#setPersistent()`.
|
||
* Make sure to remove the display afterward with `Entity#remove()`.
|
||
|
||
```markdown
|
||
# Teleportation
|
||
|
||
Entities can be instantaneously teleported to specific positions, synchronously and asynchronously with the `teleport` and `teleportAsync` API.
|
||
|
||
## Performance
|
||
|
||
If you expect to teleport into unloaded chunks, it is recommended to use the `teleportAsync` API, as it avoids doing synchronous chunk loads, which put a lot of stress on the server’s main thread - hurting overall performance.
|
||
|
||
```java
|
||
entity.teleport(location);
|
||
// loads chunks synchronously and teleports the entity
|
||
|
||
entity.teleportAsync(location)
|
||
.thenAccept(success -> {
|
||
// loads chunks asynchronously and teleports the entity
|
||
// this code is ran when the teleport completes
|
||
// the Future is completed on the main thread, so it is safe to use the API here
|
||
if (success) {
|
||
// the entity was teleported successfully!
|
||
}
|
||
});
|
||
```
|
||
|
||
**Danger:** You should **NEVER** call `.get()` / `.join()` on the `teleportAsync` Future on the main thread, as it **WILL** deadlock your server if the chunk you're teleporting into is not loaded.
|
||
|
||
## Look at
|
||
|
||
The `lookAt` API allows you to make a player look at a certain position or entity.
|
||
|
||
```java
|
||
player.lookAt(position, LookAnchor.EYES // the player's eyes will be facing the position);
|
||
|
||
player.lookAt(entity, LookAnchor.EYES // the player's eyes will be facing the entity
|
||
, LookAnchor.FEET // the player will be facing the entity's feet);
|
||
```
|
||
|
||
## Teleport flags
|
||
|
||
Teleport flags offer a way to teleport entities whilst being able to customize behavior. This allows you to do things like teleport players using relative flags and being able to retain passengers. All available teleport flags can be found in the `TeleportFlag` class.
|
||
|
||
## Relative teleportation
|
||
|
||
Teleport a player relatively, preventing velocity from being reset in the X, Y and Z axes.
|
||
|
||
```java
|
||
player.teleport(location, TeleportFlag.Relative.VELOCITY_X, TeleportFlag.Relative.VELOCITY_Y, TeleportFlag.Relative.VELOCITY_Z);
|
||
```
|
||
|
||
## Retaining passengers
|
||
|
||
Teleport an entity with the `RETAIN_PASSENGERS` flag, allowing its passengers to be transferred with the entity.
|
||
|
||
```java
|
||
entity.teleport(location, TeleportFlag.EntityState.RETAIN_PASSENGERS);
|
||
```
|
||
|
||
---
|
||
|
||
# Supporting Paper and Folia
|
||
|
||
Folia is a fork of Paper, which is currently maintained by the PaperMC team. It adds the ability to split the world into regions.
|
||
|
||
## Checking for Folia
|
||
|
||
Depending on what platform your plugin is running on, you may need to implement features differently. For this, you can use this utility method to check if the current server is running Folia:
|
||
|
||
```java
|
||
private static boolean isFolia() {
|
||
try {
|
||
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
|
||
return true;
|
||
} catch (ClassNotFoundException e) {
|
||
return false;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Schedulers
|
||
|
||
In order to support Paper and Folia, you must use the correct scheduler. Folia has different types of schedulers that can be used for different things:
|
||
|
||
* Global
|
||
* Region
|
||
* Async
|
||
* Entity
|
||
|
||
If you use these schedulers when running Paper, they will be internally handled to provide the same functionality as if you were running Folia.
|
||
|
||
### Global scheduler
|
||
|
||
The tasks that you run on the global scheduler will be executed on the global region. You should use this scheduler for any tasks that do not belong to any particular region. These can be fetched with:
|
||
|
||
```java
|
||
GlobalRegionScheduler globalScheduler = server.getGlobalRegionScheduler();
|
||
```
|
||
|
||
### Region scheduler
|
||
|
||
The region scheduler will be in charge of running tasks for the region that owns a certain location. Do not use this scheduler for operations on entities, as this scheduler is tied to the region. Each entity has its own scheduler which will follow it across regions.
|
||
|
||
Example: Setting a block to a beehive.
|
||
|
||
```java
|
||
Location locationToChange = ...;
|
||
RegionScheduler scheduler = server.getRegionScheduler();
|
||
scheduler.execute(plugin, locationToChange, () -> {
|
||
locationToChange.getBlock().setType(Material.BEEHIVE);
|
||
});
|
||
```
|
||
|
||
We pass the location as a parameter to the `RegionScheduler` as it needs to work out which region to execute on.
|
||
|
||
### Async scheduler
|
||
|
||
The async scheduler can be used for running tasks independent of the server tick process. This can be fetched with:
|
||
|
||
```java
|
||
AsyncScheduler asyncScheduler = server.getAsyncScheduler();
|
||
```
|
||
|
||
### Entity scheduler
|
||
|
||
Entity schedulers are used for executing tasks on an entity. These will follow the entity wherever it goes, so you must use these instead of the region schedulers.
|
||
|
||
```java
|
||
EntityScheduler scheduler = entity.getScheduler();
|
||
```
|
||
|
||
---
|
||
|
||
# Listeners
|
||
|
||
Events are an efficient way to listen for specific actions that happen in the game. They can be called by the server or by plugins. Plugins are able to call custom events, such as a player completing a quest, for other plugins to listen for.
|
||
|
||
## Your listener class
|
||
|
||
To listen for events, you need to create a class that implements `Listener`. It is recommended to name it something related to the events you are listening for.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
## @EventHandler
|
||
|
||
To listen for an event, you need to create a method that is annotated with `@EventHandler`. This method can be named anything you want, but it is recommended to name it something meaningful related to the event it is listening for.
|
||
|
||
## The listener method
|
||
|
||
The method body does not need to return any data, for this reason, use `void` as the return type. Listeners take in a single parameter, which is the event that is being listened to.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
## Events
|
||
|
||
There is no list of events that can be listened to, however take a [look](https://www.google.com) to see all classes that extend `Event`. An event can only be listened to if it has a static `getHandlerList` method.
|
||
|
||
## Registering the listener
|
||
|
||
To register the listener, you need to call `Bukkit.getPluginManager().registerEvents()` and pass in your listener class instance and an instance of your plugin. This is commonly done in the `onEnable()` method of your plugin.
|
||
|
||
```java
|
||
public class ExamplePlugin extends JavaPlugin {
|
||
@Override
|
||
public void onEnable() {
|
||
getServer().getPluginManager().registerEvents(new ExampleListener(), this);
|
||
}
|
||
}
|
||
```
|
||
|
||
## Event priority
|
||
|
||
You can also specify the priority of the event.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler(priority = EventPriority.HIGH)
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
There are six different priorities that you can use:
|
||
|
||
* `EventPriority.LOWEST`
|
||
* `EventPriority.LOW`
|
||
* `EventPriority.NORMAL`
|
||
* `EventPriority.HIGH`
|
||
* `EventPriority.HIGHEST`
|
||
* `EventPriority.MONITOR`
|
||
|
||
The higher the priority, the later the event is called. If it is important that your plugin has the last say in a certain event, you should use `EventPriority.HIGHEST`.
|
||
|
||
**Note:** The `MONITOR` priority is used to monitor the event, but not change it. It is called after all other priorities have been called.
|
||
|
||
## Event cancellation
|
||
|
||
Some events can be cancelled, preventing the given action from being completed. These events implement `Cancellable`.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
event.setCancelled(true);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Caution:** It is important to consider that another plugin could have cancelled or changed the event before your plugin is called. Always check the event before doing anything with it.
|
||
|
||
Once an event is cancelled, it will continue to call any other listeners for that event unless they add `ignoreCancelled = true` to the `@EventHandler` annotation to ignore cancelled events.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler(ignoreCancelled = true)
|
||
public void onPlayerMove(PlayerMoveEvent event) {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# Handler lists
|
||
|
||
Every `Event` that can be listened to has a `HandlerList` containing all the listeners that are listening to that event. This list is used to call the listeners when the event is called.
|
||
|
||
## Getting the handler list for an event
|
||
|
||
To get the handler list for an event, you can call `getHandlerList()` on the specific event class.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler
|
||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||
HandlerList handlerList = event.getHandlerList();
|
||
// ...
|
||
}
|
||
|
||
// Or:
|
||
public ExampleListener() {
|
||
// Access the handler list through the static getter
|
||
HandlerList handlerList = PlayerJoinEvent.getHandlerList();
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
## Unregistering a listener
|
||
|
||
To unregister a listener, you can call `unregister()` on the `HandlerList` that the listener is registered to.
|
||
|
||
```java
|
||
public class ExampleListener implements Listener {
|
||
@EventHandler
|
||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||
HandlerList handlerList = event.getHandlerList();
|
||
handlerList.unregister(this);
|
||
// ...
|
||
}
|
||
|
||
// Or:
|
||
public ExampleListener() {
|
||
// Access the handler list through the static getter
|
||
HandlerList handlerList = PlayerJoinEvent.getHandlerList();
|
||
handlerList.unregister(this);
|
||
// Granted this is a pretty stupid example...
|
||
}
|
||
}
|
||
```
|
||
|
||
You can unregister based on `Listener` or `Plugin` for more convenience.
|
||
|
||
Likewise, you can also unregister all listeners for a specific event by calling `unregisterAll()` on the `HandlerList`.
|
||
|
||
---
|
||
|
||
# Paper plugins
|
||
|
||
Paper plugins allow developers to take advantage of more modern concepts introduced by Mojang, such as datapacks, to expand the field of what the Paper API is able to introduce.
|
||
|
||
**Experimental:** This is experimental and may be subject to change.
|
||
|
||
## How do I use them?
|
||
|
||
Similarly to Bukkit plugins, you have to introduce a `paper-plugin.yml` file into your JAR resources folder. This will not act as a drop-in replacement for `plugin.yml`, as some things need to be declared differently. You still have the ability to include both `paper-plugin.yml` and `plugin.yml` in the same JAR.
|
||
|
||
Example configuration:
|
||
|
||
```yaml
|
||
name: Paper-Test-Plugin
|
||
version: '1.0'
|
||
main: io.papermc.testplugin.TestPlugin
|
||
description: Paper Test Plugin
|
||
api-version: '1.21.10'
|
||
bootstrapper: io.papermc.testplugin.TestPluginBootstrap
|
||
loader: io.papermc.testplugin.TestPluginLoader
|
||
```
|
||
|
||
## Dependency declaration
|
||
|
||
Paper plugins change how to declare dependencies in your `paper-plugin.yml`:
|
||
|
||
```yaml
|
||
dependencies:
|
||
bootstrap:
|
||
# Let's say that RegistryPlugin registers some data that your plugin needs to use
|
||
# We don't need this during runtime, so it's not required in the server section.
|
||
# However, can be added to both if needed
|
||
RegistryPlugin:
|
||
load: BEFORE
|
||
required: true
|
||
join-classpath: true # Defaults to true
|
||
server:
|
||
# Add a required "RequiredPlugin" dependency, which will load AFTER your plugin.
|
||
RequiredPlugin:
|
||
load: AFTER
|
||
required: true
|
||
# This means that your plugin will not have access to their classpath
|
||
join-classpath: false
|
||
```
|
||
|
||
With Paper plugins, dependencies are split into two sections:
|
||
|
||
* **bootstrap**: Dependencies that you will be using in the `bootstrap`.
|
||
* **server**: Dependencies that are used for the core functionality of your plugin, whilst the server is running.
|
||
|
||
Let’s take a look at a dependency:
|
||
|
||
```yaml
|
||
RegistryPlugin:
|
||
load: BEFORE # Defaults to OMIT
|
||
required: true # Defaults to true
|
||
join-classpath: true # Defaults to true
|
||
```
|
||
|
||
* `load` (`BEFORE` | `AFTER` | `OMIT`): Whether this plugin should load before or after your plugin. Note: `OMIT` has undefined ordering behavior.
|
||
* `required`: Whether this plugin is required for your plugin to load.
|
||
* `join-classpath`: Whether your plugin should have access to their classpath. This is used for plugins that need to access other plugins internals directly.
|
||
|
||
**Cyclic Loading**: In certain cases, plugins may be able to introduce cyclic loading loops, which will prevent the server from starting. Please read the [cyclic loading guide](https://www.google.com) for more information.
|
||
|
||
Examples:
|
||
|
||
```yaml
|
||
# Suppose we require ProtocolLib to be loaded for our plugin
|
||
ProtocolLib:
|
||
load: BEFORE
|
||
required: true
|
||
|
||
# Now, we are going to register some details for a shop plugin
|
||
# So the shop plugin should load after our plugin
|
||
SuperShopsXUnlimited:
|
||
load: AFTER
|
||
required: false
|
||
|
||
# Now, we are going to need to access a plugins classpath
|
||
# So that we can properly interact with it.
|
||
SuperDuperTacoParty:
|
||
required: true
|
||
join-classpath: true
|
||
```
|
||
|
||
## What is it used for?
|
||
|
||
Paper plugins lay down the framework for some future API. Our goals are to open more modern API that better aligns with Vanilla. Paper plugins allow us to do just that by making a new way to load plugin resources before the server has started by using `bootstrappers`.
|
||
|
||
## Bootstrapper
|
||
|
||
Paper plugins are able to identify their own bootstrapper by implementing `PluginBootstrap` and adding the class of your implementation to the `bootstrapper` field in the `paper-plugin.yml`.
|
||
|
||
```java
|
||
public class TestPluginBootstrap implements PluginBootstrap {
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
}
|
||
|
||
@Override
|
||
public JavaPlugin createPlugin(PluginProviderContext context) {
|
||
return new TestPlugin("My custom parameter");
|
||
}
|
||
}
|
||
```
|
||
|
||
A bootstrapper also allows you to change the way your plugin is initialized, allowing you to pass values into your plugin constructor.
|
||
|
||
Currently, bootstrappers do not offer much new API and are highly experimental. This may be subject to change once more API is introduced.
|
||
|
||
## Loaders
|
||
|
||
Paper plugins are able to identify their own plugin loader by implementing `PluginLoader` and adding the class of your implementation to the `loader` field in the `paper-plugin.yml`.
|
||
|
||
The goal of the plugin loader is the creation of an expected/dynamic environment for the plugin to load into. This, as of right now, only applies to creating the expected classpath for the plugin, e.g. supplying external libraries to the plugin.
|
||
|
||
```java
|
||
public class TestPluginLoader implements PluginLoader {
|
||
@Override
|
||
public void classloader(PluginClasspathBuilder classpathBuilder) {
|
||
classpathBuilder.addLibrary(new JarLibrary(Path.of("dependency.jar")));
|
||
|
||
MavenLibraryResolver resolver = new MavenLibraryResolver();
|
||
resolver.addDependency(new Dependency(new DefaultArtifact("com.example:example:version"), null));
|
||
resolver.addRepository(new RemoteRepository.Builder("paper", "default", "https://repo.papermc.io/repository/maven-public/").build());
|
||
classpathBuilder.addLibrary(resolver);
|
||
}
|
||
}
|
||
```
|
||
|
||
Currently, you are able to add two different library types: `JarLibrary` and `MavenLibraryResolver`.
|
||
|
||
**Danger:** If you wish to resolve libraries from Maven Central, use a mirror, as using Maven Central directly as a CDN is against the Maven Central Terms of Service, and users of your plugin may hit rate limits.
|
||
|
||
You should use Paper’s default mirror, configured by the `PAPER_DEFAULT_CENTRAL_REPOSITORY` environment variable and `org.bukkit.plugin.java.LibraryLoader.centralURL` system property:
|
||
|
||
```java
|
||
resolver.addRepository(new RemoteRepository.Builder("central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR).build());
|
||
```
|
||
|
||
Using the Maven Central repository (i.e. `*.maven.org` or `*.maven.apache.org`) will cause a warning to be shown in the console.
|
||
|
||
## Differences
|
||
|
||
### Bukkit serialization system
|
||
|
||
Paper plugins still support the serialization system (`org.bukkit.configuration.serialization`) that Bukkit uses. However, custom classes will not be automatically registered for serialization. In order to use `ConfigurationSection#getObject`, you **must** call `ConfigurationSerialization#registerClass(Class)` before you attempt to fetch objects from configurations.
|
||
|
||
### Classloading isolation
|
||
|
||
Paper plugins are not able to access each other unless given explicit access by depending on another plugin, etc. This helps prevent Paper plugins from accidentally accessing each other’s dependencies, and in general helps ensure that plugins are only able to access what they explicitly depend on.
|
||
|
||
Paper plugins have the ability to bypass this, being able to access OTHER plugins’ classloaders by adding a `join-classpath` option to their `paper-plugin.yml`.
|
||
|
||
```yaml
|
||
Plugin:
|
||
join-classpath: true # Means you have access to their classpath
|
||
```
|
||
|
||
Note, other Paper plugins will still be unable to access your classloader.
|
||
|
||
### Load order logic split
|
||
|
||
In order to better take advantage of classloading isolation, Paper plugins do **not** use the `dependencies` field to determine load order. This was done for a variety of reasons, mostly to allow better control and allow plugins to properly share classloaders. See [declaring dependencies](https://www.google.com) for more information on how to declare the load order of your plugin.
|
||
|
||
### Commands
|
||
|
||
Paper plugins do not use the `commands` field to register commands. This means that you do not need to include all of your commands in the `paper-plugin.yml` file. Instead, you can register commands using the [Brigadier Command API](https://www.google.com).
|
||
|
||
### Cyclic plugin loading
|
||
|
||
Cyclic loading describes the phenomenon when a plugin loading causes a loop that eventually cycles back to the original plugin.
|
||
|
||
Unlike Bukkit plugins, Paper plugins will not attempt to resolve cyclic loading issues.
|
||
|
||
However, if Paper detects a loop that cannot be resolved, you will get an error that looks like this:
|
||
|
||
```
|
||
[ERROR]: [LoadOrderTree] =================================
|
||
[ERROR]: [LoadOrderTree] Circular plugin loading detected:
|
||
[ERROR]: [LoadOrderTree] 1) Paper-Test-Plugin1 -> Paper-Test-Plugin -> Paper-Test-Plugin1
|
||
[ERROR]: [LoadOrderTree] Paper-Test-Plugin1 loadbefore: [Paper-Test-Plugin]
|
||
[ERROR]: [LoadOrderTree] Paper-Test-Plugin loadbefore: [Paper-Test-Plugin1]
|
||
[ERROR]: [LoadOrderTree] Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help.
|
||
[ERROR]: [LoadOrderTree] =================================
|
||
```
|
||
|
||
It is up to you to resolve these cyclical loading issues.
|
||
|
||
---
|
||
|
||
# How plugins work
|
||
|
||
Plugins are a way to extend the functionality of a Minecraft server. They are written in JVM-based languages such as Java, Kotlin, Groovy or Scala. Plugins are loaded from the `plugins` folder in the server directory. Plugins will be loaded from a `.jar` file. Each plugin has a main class that is specified in the plugin’s `plugin.yml` file. This class must extend JavaPlugin, and is the entry point for the plugin and is where the plugin’s lifecycle methods are defined.
|
||
|
||
**Caution:** We do not recommend writing code inside your main class’s constructor as there are no guarantees about what API is available at that point. Instead, you should use the `onLoad` method to initialize your plugin. Also, do not call your plugin’s constructor directly. This will cause issues with your plugin.
|
||
|
||
## Plugin lifecycle
|
||
|
||
Plugins are loaded and unloaded at runtime. When a plugin is loaded, it is initialized and enabled. When a plugin is unloaded, it is disabled and finalized.
|
||
|
||
### Initialization
|
||
|
||
When a plugin is loaded, it is initialized. This means that the plugin is loaded into memory and its `onLoad` method is called. This method is used to initialize the plugin and set up any resources that it needs. Most of the Bukkit API is not available at this point, so it is not safe to interact with it.
|
||
|
||
### Enabling
|
||
|
||
When a plugin is enabled, its `onEnable` method is called. This method is used to set up any resources that the plugin needs to run. This method is called when the plugin is initialized but before the server has started ticking, so it is safe to register event listeners and other resources that the plugin needs to run, however often not safe to interact with a lot of APIs.
|
||
|
||
This is when you can also open database connections, start threads, and other things that are not safe to do in the `onLoad` method.
|
||
|
||
### Disabling
|
||
|
||
When a plugin is disabled, its `onDisable` method is called. This method is used to clean up any resources that the plugin has allocated. This method is called before all plugins are unloaded, and is meant for any cleanup that needs to be done before the plugin is unloaded. This may include saving data to disk or closing connections to databases.
|
||
|
||
## Event listeners
|
||
|
||
Events are a way for plugins to listen to things that happen in the server and run code when they are fired. For example, `PlayerJoinEvent` is fired when a player joins the server. This is a more performant way to run code when something happens, as opposed to constantly checking. See our [event listener page](https://www.google.com) for more.
|
||
|
||
Some events are cancellable. This means that when the event is fired, it can be cancelled which negates or stops the effect of the event. For example, `PlayerMoveEvent` is cancellable. This means that when it is cancelled, the player will not move. This is useful for things like anti-cheat, where you want to cancel the event if the player is moving too fast.
|
||
|
||
It is important to think about how “hot” an event is when writing event listeners. A “hot” event is an event that is fired very often. For example, `PlayerMoveEvent` is fired every time a player moves. This means that if you have a lot of expensive code in your event listener, it will be run every time a player moves. This can cause a lot of lag. It is important to keep event listeners as lightweight as possible. One possible way is to quickly check if the event should be handled, and if not, return. For example, if you only want to handle the event if the player is moving from one block to another, you can check if the player’s location has changed blocks. If it hasn’t, you can return from the listener.
|
||
|
||
## Commands
|
||
|
||
Commands are a way for players, the console, RCON and command blocks to run code on the server. Commands are registered by plugins and can be run by command senders. For example, the `/help` command is registered by the server and can be run by players. Commands can be run by players by typing them in the chat or by running them from a command block.
|
||
|
||
Commands can have arguments. For example, the `/give` command takes an argument for the player to give the item to and an argument for the item to give. Arguments are separated by spaces. For example, the command `/give Notch diamond` will give the player named Notch a diamond. Note here that the arguments are `["Notch", "diamond"]`.
|
||
|
||
## Permissions
|
||
|
||
Permissions are a way to control who can run commands and who can listen to events. Permissions are registered by plugins and can be checked by other plugins. Permissions can be granted to players and groups. Permissions can have a hierarchical nature, if defined so by the plugin in their `plugin.yml`. For example, a plugin can define `example.command.help` as a sub-permission of `example.command`. This means that if a player has the `example.command` permission, they will also have the `example.command.help` permission.
|
||
|
||
**Note:** Permission plugins can allow the usage of wildcard permissions using the `*` character to grant any permission or sub-permission available, allowing hierarchical permissions even if not set by the plugin itself. For example, granting `example.command.*` through a permission plugin with wildcard support will grant access to all permissions starting with `example.command` itself.
|
||
|
||
It is **not** recommended to use wildcard permissions, especially `*` (All permissions), as it can be a huge security risk, as well as potentially causing unwanted side effects to a player. Use with caution.
|
||
|
||
## Configuration
|
||
|
||
Plugins can have configuration files. These files are used to store data that the plugin needs to run. For example, a plugin that adds a new block to the game might have a configuration file that stores the block’s ID. Configuration files should be stored in the plugin’s data folder, within the `plugins` folder. The server offers a YAML configuration API that can be used to read and write configuration files. See [here](https://www.google.com) for more information.
|
||
|
||
## Scheduling tasks
|
||
|
||
Plugins can schedule tasks to run at a later time. This is useful for things like running code after a certain amount of time has passed. For example, a plugin might want to run code after 5 seconds. This can be done by scheduling a task to run after 100 ticks - one second is 20 ticks during normal operation. It is important to note that tasks might be delayed if the server is lagging. For example, if the server is only running at 10 ticks per second, a task that is scheduled to run after 100 ticks will take 10 seconds.
|
||
|
||
In Java, typically you could use `Thread#sleep()` to delay the execution of code. However, if the code is running on the main thread, this will cause the server to pause for the delay. Instead, you should use the `Scheduler` API to schedule tasks to run later.
|
||
|
||
Learn more about the [Scheduler API](https://www.google.com).
|
||
|
||
## Components
|
||
|
||
Since Minecraft 1.7 and the introduction of “components”, plugins can now send messages to players that contain rich text. This means that plugins can send messages that contain things like colors, bold text, and clickable links. Colors were always possible, but only through the use of legacy color codes.
|
||
|
||
Paper implements a library called [Adventure](https://www.google.com) that makes it easy to create and send messages to players. Learn more about the [Adventure API](https://www.google.com) from their docs or our docs [here](https://www.google.com).
|
||
|
||
---
|
||
|
||
# Minecraft internals
|
||
|
||
The code that runs Minecraft is not open source. Bukkit is an API that allows plugins to interact with the server. This is implemented by CraftBukkit and interacts with Minecraft’s code. You will often hear the terms NMS and CraftBukkit when talking about Minecraft internals.
|
||
|
||
**Using Minecraft internals is not recommended.** This is because using internal code directly is not guaranteed to be stable and it changes often. This means that your plugin may break when a new version of Minecraft is released. Whenever possible, you should use API instead of internals.
|
||
|
||
**PaperMC will offer no direct support for programming against Minecraft internals.**
|
||
|
||
## What is NMS?
|
||
|
||
NMS stands for `net.minecraft.server` and refers to a Java package that contains a lot of Mojang’s code. This code is proprietary and is not open source. This code is not guaranteed to be stable when invoked externally and may change at any time.
|
||
|
||
## Accessing Minecraft internals
|
||
|
||
In order to use Mojang and CraftBukkit code, you may either use the `paperweight-userdev` Gradle plugin or use reflection. `paperweight-userdev` is the recommended way to access internal code as it is easier to use due to being able to have the remapped code in your IDE. You can find out more about this in the [`paperweight-userdev`](https://www.google.com) section.
|
||
|
||
However, if you are unable to use `paperweight-userdev`, you can use reflection.
|
||
|
||
## Reflection
|
||
|
||
Reflection is a way to access code at runtime. This allows you to access code that may not be available at compile time. Reflection is often used to access internal code across multiple versions. However, reflection does come with performance impacts if used improperly. For example, if you are accessing a method or field more than once, you should cache the `Field` / `Method` to prevent the performance impact of looking up the field/method each time.
|
||
|
||
### 1.20.4 and older
|
||
|
||
The internal CraftBukkit code was relocated to `org.bukkit.craftbukkit.<version>` unless you ran a Mojang-mapped version of Paper. This was unlikely to be the case in most production environments until 1.20.5. This means that any attempts to reflect had to include the version. For example, `org.bukkit.craftbukkit.v1_20_R2.CraftServer` was the full class and package name for the CraftServer class in version 1.20.2. You could access these classes easily with some reflection utilities.
|
||
|
||
```java
|
||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
|
||
|
||
public static String cbClass(String clazz) {
|
||
return CRAFTBUKKIT_PACKAGE + "." + clazz;
|
||
}
|
||
|
||
// You can then use this method to get the CraftBukkit class:
|
||
Class.forName(cbClass("entity.CraftBee"));
|
||
```
|
||
|
||
Minecraft’s code is obfuscated. This means that the names of classes and methods are changed to make them harder to understand. Paper deobfuscates these identifiers for development and since 1.20.5, also for runtime.
|
||
|
||
### 1.20.4 and older
|
||
|
||
Previously, to provide compatibility with legacy plugins, Paper was reobfuscated at runtime. You could use a library like [`reflection-remapper`](https://www.google.com) to automatically remap the reflection references. This allowed you to use the deobfuscated, Mojang-mapped names in your code. This was recommended as it made the code easier to understand.
|
||
|
||
## Mojang-mapped servers
|
||
|
||
Running a Mojang-mapped (moj-map) server is an excellent way to streamline your processes because you can develop using the same mappings that will be present at runtime. This eliminates the need for remapping in your compilation, which in turn simplifies debugging and allows you to hotswap plugins.
|
||
|
||
As of 1.20.5, Paper ships with a Mojang-mapped runtime by default instead of reobfuscating the server to Spigot mappings. By adopting Mojang mappings, you ensure that your plugin won’t require internal remapping at runtime.
|
||
|
||
For more information, see the [plugin remapping](https://www.google.com) section and [userdev](https://www.google.com) documentation covering these changes.
|
||
|
||
## Getting the current Minecraft version
|
||
|
||
You can get the current Minecraft version to allow you to use the correct code for a specific version. This can be done with one of the following methods:
|
||
|
||
```java
|
||
// Example value: 1.21.10
|
||
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
|
||
|
||
// Example value: 1.21.10-R0.1-SNAPSHOT
|
||
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
|
||
|
||
// Example value for 1.20.1: 3465
|
||
int dataVersion = Bukkit.getUnsafe().getDataVersion();
|
||
```
|
||
|
||
### Parsing the version
|
||
|
||
Parsing the version from the package name of classes is no longer possible as of 1.20.5 as Paper stopped relocating the CraftBukkit package. See the [reflection](https://www.google.com) section for more information.
|
||
```
|
||
|
||
```markdown
|
||
# How Plugins Work
|
||
|
||
## Overview
|
||
|
||
Plugins are a way to extend the functionality of a Minecraft server. They are written in JVM-based languages such as Java, Kotlin, Groovy, or Scala. Plugins are loaded from the `plugins` folder in the server directory, specifically from `.jar` files. Each plugin has a main class, specified in the `plugin.yml` file, which extends `JavaPlugin`. This class serves as the entry point for the plugin and defines its lifecycle methods.
|
||
|
||
**Caution:** Avoid writing code inside the main class's constructor, as API availability is not guaranteed at that point. Instead, use the `onLoad` method for initialization. Do not call the plugin's constructor directly.
|
||
|
||
## Plugin Lifecycle
|
||
|
||
Plugins are loaded and unloaded at runtime.
|
||
|
||
* **Loading:** The plugin is initialized and enabled.
|
||
* **Unloading:** The plugin is disabled and finalized.
|
||
|
||
### Initialization
|
||
|
||
When a plugin is loaded, it is initialized, meaning:
|
||
|
||
* The plugin is loaded into memory.
|
||
* Its `onLoad` method is called.
|
||
|
||
The `onLoad` method is used to:
|
||
|
||
* Initialize the plugin.
|
||
* Set up any resources it needs.
|
||
|
||
Most of the Bukkit API is not available at this point, so interacting with it is not safe.
|
||
|
||
### Enabling
|
||
|
||
When a plugin is enabled:
|
||
|
||
* Its `onEnable` method is called.
|
||
|
||
The `onEnable` method is used to:
|
||
|
||
* Set up resources needed for the plugin to run.
|
||
* Register event listeners and other resources.
|
||
* Open database connections and start threads.
|
||
|
||
This method is called when the plugin is initialized but before the server has started ticking.
|
||
|
||
### Disabling
|
||
|
||
When a plugin is disabled:
|
||
|
||
* Its `onDisable` method is called.
|
||
|
||
The `onDisable` method is used to:
|
||
|
||
* Clean up any allocated resources.
|
||
* Save data to disk.
|
||
* Close database connections.
|
||
|
||
This method is called before all plugins are unloaded.
|
||
|
||
## Event Listeners
|
||
|
||
Events allow plugins to listen to actions happening on the server and execute code when these events are triggered.
|
||
|
||
* Example: `PlayerJoinEvent` is fired when a player joins the server.
|
||
|
||
Events are more performant than constantly checking for actions.
|
||
|
||
Some events are cancellable, stopping the effect of the event.
|
||
|
||
* Example: `PlayerMoveEvent` can be cancelled to prevent player movement.
|
||
|
||
It's important to consider how "hot" an event is (how often it's fired) and keep event listeners lightweight to avoid lag.
|
||
|
||
* "Hot" events like `PlayerMoveEvent` fire very often.
|
||
* Optimize listeners by quickly checking if the event should be handled and returning if not.
|
||
|
||
## Commands
|
||
|
||
Commands are a way for players, the console, RCON, and command blocks to run code on the server.
|
||
|
||
* Registered by plugins.
|
||
* Run by command senders.
|
||
* Example: `/help` is registered by the server.
|
||
|
||
Commands can have arguments, separated by spaces.
|
||
|
||
* Example: `/give Notch diamond` gives the player named Notch a diamond.
|
||
|
||
## Permissions
|
||
|
||
Permissions control who can run commands and listen to events.
|
||
|
||
* Registered by plugins.
|
||
* Checked by other plugins.
|
||
* Granted to players and groups.
|
||
|
||
Permissions can be hierarchical.
|
||
|
||
* Example: `example.command.help` is a sub-permission of `example.command`.
|
||
|
||
**Note:** Wildcard permissions (e.g., `example.command.*`) should be used with caution due to security risks and potential side effects.
|
||
|
||
## Configuration
|
||
|
||
Plugins can have configuration files to store data.
|
||
|
||
* Stored in the plugin's data folder within the `plugins` folder.
|
||
* The server offers a YAML configuration API for reading and writing configuration files.
|
||
|
||
## Scheduling Tasks
|
||
|
||
Plugins can schedule tasks to run at a later time.
|
||
|
||
* Useful for running code after a certain amount of time has passed.
|
||
* Tasks might be delayed if the server is lagging.
|
||
|
||
Use the `Scheduler` API instead of `Thread#sleep()` to avoid pausing the server.
|
||
|
||
* One second is 20 ticks during normal operation.
|
||
|
||
## Components
|
||
|
||
Plugins can send messages to players with rich text (colors, bold text, clickable links).
|
||
|
||
* Paper implements the `Adventure` library for creating and sending messages.
|
||
|
||
---
|
||
|
||
# Minecraft Internals
|
||
|
||
## Overview
|
||
|
||
The code that runs Minecraft is not open source. Bukkit provides an API for plugins to interact with the server, implemented by CraftBukkit. The terms NMS and CraftBukkit are commonly used when discussing Minecraft internals.
|
||
|
||
**Caution:** Using Minecraft internals is not recommended due to instability and potential breakage with new Minecraft versions. Use the API instead whenever possible. PaperMC offers no direct support for programming against Minecraft internals.
|
||
|
||
## What is NMS?
|
||
|
||
NMS stands for `net.minecraft.server` and refers to a Java package containing much of Mojang's code. This code is proprietary, not open source, and not guaranteed to be stable when invoked externally. It may change at any time.
|
||
|
||
## Accessing Minecraft Internals
|
||
|
||
Mojang and CraftBukkit code can be accessed using either the `paperweight-userdev` Gradle plugin or reflection.
|
||
|
||
* `paperweight-userdev` is recommended for easier use with remapped code in your IDE.
|
||
* Reflection can be used if `paperweight-userdev` is not an option.
|
||
|
||
### Reflection
|
||
|
||
Reflection allows accessing code at runtime, including code that may not be available at compile time. It is often used to access internal code across multiple versions.
|
||
|
||
* **Performance Impact:** Reflection can have performance impacts if used improperly.
|
||
* **Caching:** Cache `Field` / `Method` objects when accessing them more than once to prevent performance issues.
|
||
|
||
#### 1.20.4 and Older
|
||
|
||
CraftBukkit code was located at `org.bukkit.craftbukkit.<version>` unless using a Mojang-mapped version of Paper. Reflection required including the version. For example:
|
||
|
||
`org.bukkit.craftbukkit.v1_20_R2.CraftServer`
|
||
|
||
Minecraft's code is obfuscated. Paper deobfuscates these identifiers for development and, since 1.20.5, also for runtime.
|
||
|
||
Previously, Paper was reobfuscated at runtime to provide compatibility with legacy plugins. Libraries like `reflection-remapper` could be used to automatically remap reflection references.
|
||
|
||
### Mojang-Mapped Servers
|
||
|
||
Running a Mojang-mapped (moj-map) server streamlines development by using the same mappings at runtime, simplifying debugging and allowing hotswapping of plugins. As of 1.20.5, Paper ships with a Mojang-mapped runtime by default.
|
||
|
||
## Getting the Current Minecraft Version
|
||
|
||
You can get the current Minecraft version to use the correct code for a specific version.
|
||
|
||
```java
|
||
// Example value: 1.21.10
|
||
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();
|
||
|
||
// Example value: 1.21.10-R0.1-SNAPSHOT
|
||
String bukkitVersion = Bukkit.getServer().getBukkitVersion();
|
||
|
||
// Example value for 1.20.1: 3465
|
||
int dataVersion = Bukkit.getUnsafe().getDataVersion();
|
||
```
|
||
|
||
Parsing the version from the package name of classes is no longer possible as of 1.20.5.
|
||
|
||
---
|
||
|
||
# Lifecycle API
|
||
|
||
## Overview
|
||
|
||
The Lifecycle API is used for lifecycle-related registration. It is currently used by the Brigadier command API and is planned for use with the Registry Modification API. This API is beneficial for systems initialized very early in the startup process.
|
||
|
||
## LifecycleEventManager
|
||
|
||
The `LifecycleEventManager` is tied to either a `Plugin` instance or a `BootstrapContext`, depending on where you access it.
|
||
|
||
**Example (Plugin):**
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
|
||
}
|
||
```
|
||
|
||
**Example (Bootstrap):**
|
||
|
||
```java
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
final LifecycleEventManager<BootstrapContext> lifecycleManager = context.getLifecycleManager();
|
||
}
|
||
```
|
||
|
||
## LifecycleEvents
|
||
|
||
After obtaining the `LifecycleEventManager`, create an event handler by selecting an event type from `LifecycleEvents`.
|
||
|
||
**Example:**
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
|
||
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
|
||
// Handler for the event
|
||
});
|
||
}
|
||
```
|
||
|
||
## Configuration
|
||
|
||
Each handler can be configured in several ways, depending on the event type.
|
||
|
||
### Priority
|
||
|
||
Sets the execution order relative to other handlers of the same event type. Lower numbers are run earlier. The default priority is 0.
|
||
|
||
### Monitor
|
||
|
||
Marks the handler to be called after all other non-monitor handlers have been called. Use this only to inspect state, not to modify it.
|
||
|
||
Priority and monitor state are exclusive. Setting one resets the other.
|
||
|
||
**Example:**
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
|
||
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
|
||
// Handler for the event
|
||
});
|
||
config.priority(10); // sets a priority of 10
|
||
// or
|
||
config.monitor(); // marks the handler as a monitor
|
||
}
|
||
```
|
||
|
||
## Registering
|
||
|
||
After configuring the handler, register it with the lifecycle manager.
|
||
|
||
**Example:**
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
|
||
PrioritizedLifecycleEventHandlerConfiguration<LifecycleEventOwner> config = LifecycleEvents.SOME_EVENT.newHandler((event) -> {
|
||
// Handler for the event
|
||
}).priority(10);
|
||
lifecycleManager.registerEventHandler(config);
|
||
}
|
||
```
|
||
|
||
A shorthand method exists for registering the handler without configuration:
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
final LifecycleEventManager<Plugin> lifecycleManager = this.getLifecycleManager();
|
||
lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS, (event) -> {
|
||
// Handler for the event
|
||
});
|
||
}
|
||
```
|
||
|
||
**Note:** Some event types have special behaviors that restrict certain mechanics. Plugin reloading (via `/bukkit:reload` or `Server#reload()`) is disabled if plugins register handlers in certain situations.
|
||
|
||
## Why Does This Exist?
|
||
|
||
This API is necessary because some events fire before `JavaPlugin` instances or the `MinecraftServer` instance are created, right at the start of server startup. The existing Bukkit event system is not designed to exist at this time.
|
||
|
||
**Technical Explanation:**
|
||
|
||
* Bukkit events cannot have generics due to reflective registration, leading to many useless classes for similar patterns like registry modification events.
|
||
* The existing system always has priorities. Lifecycle events may not always want priorities.
|
||
* The existing system exists too late, relying on the `Plugin` instance, which does not exist during bootstrapping.
|
||
* A new system allows using interfaces and server implementations for events, simplifying them.
|
||
* A new system enforces, at compile time, which events you can register where based on the context of the registration.
|
||
|
||
---
|
||
|
||
# Menu Type API
|
||
|
||
**Experimental**
|
||
|
||
The Menu Type API is experimental and may change in the future.
|
||
|
||
## Overview
|
||
|
||
The Menu Type API allows developers to replicate various Minecraft menu types, such as chests, crafting tables, and villager trade menus, which were not perfectly replicable with the old Bukkit inventory API.
|
||
|
||
## What is a menu?
|
||
|
||
Menus, also referred to as views, are user interfaces that can be created and viewed by players. The `MenuType` interface declares all possible menu types.
|
||
|
||
Menus are the visual representations of inventories, which are the containers.
|
||
|
||
Menus created using this API follow the same logic as Vanilla. Some menus can directly represent a block, like a furnace, while `MERCHANT` can represent a merchant entity.
|
||
|
||
## What are inventory views?
|
||
|
||
An `InventoryView` is a specific view created from a menu type.
|
||
|
||
An inventory view links together two separate inventories and always has a player viewing them. The bottom linked inventory is the player’s inventory.
|
||
|
||
Some views have specialized subinterfaces for quickly checking their type, like `FurnaceView`. For other views, you can use the `InventoryView#getMenuType` method.
|
||
|
||
## Building inventory views from menu types
|
||
|
||
The most common way to create inventory views is by using their respective builders. Every menu type has a builder which can be used to customize the resulting view.
|
||
|
||
**Example:**
|
||
|
||
```java
|
||
// MenuType.CRAFTING is used to open a crafting table.
|
||
MenuType.CRAFTING.builder()
|
||
// Set the title of the view, which will be displayed at the top.
|
||
.title(Component.text("The inventory view's title"))
|
||
// Determines whether the server should check if the player can reach the location.
|
||
.checkReachable(true)
|
||
// Set the location. Because of checkReachable being set to `true`, this has to be a valid
|
||
// crafting table. The server will check and make sure that the player does not get pushed
|
||
// away too far to use the crafting table and will close the player's inventory if the
|
||
// crafting table were to be pushed away.
|
||
.location(location)
|
||
// Build this view for the provided player, linking the inventory of the crafting table
|
||
// together with the player's own inventory into an inventory view.
|
||
.build(player)
|
||
// Open the view.
|
||
.open();
|
||
```
|
||
|
||
Builders can be reused to reduce code repetition.
|
||
|
||
## Opening blocks that have menus
|
||
|
||
Almost all inventory views have a block attached to them, except for `MenuType.MERCHANT`, which has a `Merchant` attached to it.
|
||
|
||
There are two types of blocks that have menus:
|
||
|
||
* **Block entity blocks (tile entity blocks):** Have a state associated with them (e.g., beacon, chests, furnaces).
|
||
* **Stateless blocks:** Do not have any state associated with them (e.g., crafting tables, grindstones, anvils).
|
||
|
||
When you open a specific location with the `#location` builder method, and the block matches the expected block from the menu type, the state of that block can change, and all players can see the change live.
|
||
|
||
## Persistent inventory views
|
||
|
||
Inventory views can be reused for persistent operations.
|
||
|
||
**Example:** A `/persistent` command that opens a player’s own, persistent, stash.
|
||
|
||
```java
|
||
// A map to store all inventory views in.
|
||
private static final Map<Player, InventoryView> VIEWS = new HashMap<>();
|
||
|
||
public static LiteralCommandNode<CommandSourceStack> createCommand() {
|
||
return Commands.literal("persistent")
|
||
.executes(ctx -> {
|
||
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
|
||
return 0;
|
||
}
|
||
|
||
// First, attempt to get a stored view.
|
||
InventoryView view = VIEWS.get(player);
|
||
|
||
// If there is no view currently stored, create it.
|
||
if (view == null) {
|
||
view = MenuType.GENERIC_9X6.builder()
|
||
.title(Component.text(player.getName() + "'s stash", NamedTextColor.DARK_RED))
|
||
.build(player);
|
||
|
||
// And finally store it in the map.
|
||
VIEWS.put(player, view);
|
||
}
|
||
|
||
// As the inventory view is directly bound to the player, we do not have
|
||
// to reassign the player and can just open it.
|
||
view.open();
|
||
return Command.SINGLE_SUCCESS;
|
||
})
|
||
.build();
|
||
}
|
||
|
||
@EventHandler
|
||
void onPlayerQuit(PlayerQuitEvent event) {
|
||
InventoryView view = VIEWS.remove(event.getPlayer());
|
||
if (view != null) {
|
||
Inventory topInventory = view.getTopInventory();
|
||
// Save the contents of the inventory to a file or database.
|
||
}
|
||
}
|
||
```
|
||
|
||
On the quit event:
|
||
|
||
1. Remove the player entry from the map.
|
||
2. Store the top inventory somewhere so it persists across server restarts.
|
||
|
||
---
|
||
|
||
# Datapack Discovery
|
||
|
||
**Experimental**
|
||
|
||
The Datapack Discovery API is experimental and may change in the future.
|
||
|
||
## Overview
|
||
|
||
The Datapack Discovery API, part of the Lifecycle API, allows developers to modify core server aspects, specifically datapacks. This allows including datapacks directly within plugin JAR files.
|
||
|
||
**Note:** This feature requires understanding of the Lifecycle API, datapack format, and the use of a `paper-plugin.yml` file.
|
||
|
||
## The Datapack Discovery Lifecycle Event
|
||
|
||
The `LifecycleEvents.DATAPACK_DISCOVERY` lifecycle event allows adding, checking for, and removing datapacks about to be loaded by the server.
|
||
|
||
**Tip:** Code examples are assumed to be inside a `PluginBootstrap`'s `bootstrap(BootstrapContext context)` method and within an event handler.
|
||
|
||
```java
|
||
@NullMarked
|
||
public class CustomPluginBootstrap implements PluginBootstrap {
|
||
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
context.getLifecycleManager().registerEventHandler(LifecycleEvents.DATAPACK_DISCOVERY.newHandler(
|
||
event -> {
|
||
// All code is contained here.
|
||
}
|
||
));
|
||
}
|
||
}
|
||
```
|
||
|
||
## Retrieving All Currently Discovered Datapacks
|
||
|
||
Use `DatapackRegistrar#getDiscoveredPacks()` to retrieve discovered datapacks.
|
||
|
||
```java
|
||
context.getLogger().info("The following datapacks were found: {}",
|
||
String.join(", ", event.registrar().getDiscoveredPacks().keySet()));
|
||
```
|
||
|
||
Output example:
|
||
|
||
`[00:26:12 INFO]: [PaperDocsTestProject] The following datapacks were found: file/bukkit, minecart_improvements, paper, redstone_experiments, trade_rebalance, vanilla`
|
||
|
||
## Removing Discovered Datapacks
|
||
|
||
Use `DatapackRegistrar#removeDiscoveredPack(String name)` to prevent datapacks from being discovered.
|
||
|
||
```java
|
||
final Set<String> datapacksToRemove = Set.of("minecart_improvements", "redstone_experiments", "trade_rebalance");
|
||
|
||
datapacksToRemove.forEach(datapack -> event.registrar().removeDiscoveredPack(datapack));
|
||
|
||
context.getLogger().info("The following datapacks were found: {}",
|
||
String.join(", ", event.registrar().getDiscoveredPacks().keySet()));
|
||
```
|
||
|
||
Output example:
|
||
|
||
`[00:35:39 INFO]: [PaperDocsTestProject] The following datapacks were found: file/bukkit, paper, vanilla`
|
||
|
||
## Registering Custom Datapacks
|
||
|
||
The primary use case is adding plugin-included datapacks. You must include the datapack's *source files* (not the zip) in the plugin's JAR file, typically under `src/main/resources`.
|
||
|
||
**Tip:** If you don't have a datapack, check out [Vanilla Tweaks](https://vanillatweaks.net/) for examples.
|
||
|
||
### Including the Datapack in Your Plugin
|
||
|
||
Add the datapack to your plugin's `src/main/resources` folder, with at least one extra folder level. For example, `resources/custom_datapack`.
|
||
|
||
Folder structure:
|
||
|
||
```
|
||
src/main/resources
|
||
custom_datapack
|
||
pack.mcmeta
|
||
data/
|
||
...
|
||
```
|
||
|
||
Build your plugin and verify that there is a `custom_datapack` folder in the root of your plugin's JAR file.
|
||
|
||
### Discovering the Datapack
|
||
|
||
Call `DatapackRegistrar#discoverPack(URI uri, String id)`. The URI should point to your datapack’s folder in your JAR. Use `getClass().getResource("/custom_datapack").toURI()` to get the URI.
|
||
|
||
**Important:** The preceding slash is crucial.
|
||
|
||
The ID can be set to whatever you want to identify your datapack with. The final name of the loaded pack will be `<YourPluginName>/<id>`.
|
||
|
||
```java
|
||
try {
|
||
URI uri = Objects.requireNonNull(getClass().getResource("/custom_datapack")).toURI();
|
||
event.registrar().discoverPack(uri, "provided");
|
||
} catch (URISyntaxException | IOException e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
```
|
||
|
||
### Verifying that the Datapack Loaded Correctly
|
||
|
||
Verify by executing the command `/datapack list enabled`.
|
||
|
||
Alternatively, check the loaded status during plugin execution in the `onLoad` method:
|
||
|
||
```java
|
||
public final class CustomJavaPlugin extends JavaPlugin {
|
||
|
||
@Override
|
||
public void onLoad() {
|
||
Datapack pack = this.getServer().getDatapackManager().getPack(getPluginMeta().getName() + "/provided");
|
||
if (pack != null) {
|
||
if (pack.isEnabled()) {
|
||
this.getLogger().info("The datapack loaded successfully!");
|
||
} else {
|
||
this.getLogger().warn("The datapack failed to load.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Example console output:
|
||
|
||
```
|
||
[01:10:12 INFO]: [PaperDocsTestProject] Loading server plugin PaperDocsTestProject v1.0-DEV
|
||
[01:10:12 INFO]: [PaperDocsTestProject] The datapack loaded successfully!
|
||
```
|
||
|
||
---
|
||
|
||
# Persistent Data Container (PDC)
|
||
|
||
## Overview
|
||
|
||
The Persistent Data Container (PDC) is a way to store custom data on a variety of objects, including items, entities, and block entities.
|
||
|
||
**Supported Classes:**
|
||
|
||
* `ItemStack`
|
||
* `Chunk`
|
||
* `World`
|
||
* `Entity`
|
||
* `TileState`
|
||
* `Structure`
|
||
* `GeneratedStructure`
|
||
* `Raid`
|
||
* `OfflinePlayer`
|
||
* `ItemMeta`
|
||
|
||
## What is it used for?
|
||
|
||
The PDC provides a reliable and performant way to store arbitrary data on objects, replacing older methods like:
|
||
|
||
* **NBT tags:** Requires reflection and is unreliable long-term.
|
||
* **Lore and display names:** Prone to collisions and slow to access.
|
||
|
||
The PDC doesn't rely on server internals and handles the data lifecycle automatically.
|
||
|
||
## Adding data
|
||
|
||
To store data in the PDC:
|
||
|
||
1. Create a `NamespacedKey` to identify the data.
|
||
2. Get the `PersistentDataContainer` from the object.
|
||
3. Use the `set` method to store the data.
|
||
|
||
```java
|
||
NamespacedKey key = new NamespacedKey(pluginInstance, "example-key");
|
||
|
||
World world = Bukkit.getServer().getWorlds().getFirst();
|
||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
|
||
```
|
||
|
||
`ItemStack` requires using its builder-style consumer:
|
||
|
||
```java
|
||
NamespacedKey key = ...;
|
||
ItemStack item = ItemStack.of(Material.DIAMOND);
|
||
item.editPersistentDataContainer(pdc -> {
|
||
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
|
||
});
|
||
```
|
||
|
||
**Note:** `ItemStack#editPersistentDataContainer()` is available in 1.21.4+. For older versions, modify the `ItemMeta` instead. For 1.16.5+, there’s the `ItemStack#editMeta()` method.
|
||
|
||
**Note:** Reuse `NamespacedKey` objects whenever possible. Construct them with either a `Plugin` instance and a `String` identifier, or a `String` namespace and a `String` identifier.
|
||
|
||
## Getting data
|
||
|
||
To retrieve data from the PDC:
|
||
|
||
1. Know the `NamespacedKey` and `PersistentDataType` of the data.
|
||
2. Use the `get` or `getOrDefault` method to retrieve the data.
|
||
|
||
```java
|
||
NamespacedKey key = ...;
|
||
World world = ...;
|
||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||
|
||
String value = pdc.getOrDefault(key, PersistentDataType.STRING, "<null>");
|
||
|
||
player.sendPlainMessage(value);
|
||
```
|
||
|
||
## Data types
|
||
|
||
The PDC supports a wide range of data types:
|
||
|
||
* Byte
|
||
* Byte Array
|
||
* Double
|
||
* Float
|
||
* Integer
|
||
* Integer Array
|
||
* Long
|
||
* Long Array
|
||
* Short
|
||
* String
|
||
* Boolean
|
||
* **Tag Containers:** Nested PDCs.
|
||
|
||
```java
|
||
PersistentDataContainer container = ...;
|
||
PersistentDataContainer newContainer = container.getAdapterContext().newPersistentDataContainer();
|
||
```
|
||
|
||
* **Lists:** Lists of data that can be stored via another persistent data type.
|
||
|
||
```java
|
||
container.set(key, PersistentDataType.LIST.listTypeFrom(PersistentDataType.STRING), List.of("a", "list", "of", "strings"));
|
||
|
||
container.set(key, PersistentDataType.LIST.strings(), List.of("a", "list", "of", "strings"));
|
||
|
||
List<String> strings = container.get(key, PersistentDataType.LIST.strings());
|
||
```
|
||
|
||
```markdown
|
||
# Persistent Data Container (PDC)
|
||
|
||
The Persistent Data Container (PDC) is a way to store custom data on various objects like items, entities, and block entities.
|
||
|
||
**Supported Classes:**
|
||
|
||
* `ItemStack`
|
||
* `Chunk`
|
||
* `World`
|
||
* `Entity`
|
||
* `TileState`
|
||
* `Structure`
|
||
* `GeneratedStructure`
|
||
* `Raid`
|
||
* `OfflinePlayer`
|
||
* `ItemMeta`
|
||
|
||
## What is it used for?
|
||
|
||
* **Reliable Storage:** Provides a reliable and performant way to store arbitrary data.
|
||
* **No Server Internals:** Doesn't rely on accessing server internals, reducing breakage on future versions.
|
||
* **Lifecycle Management:** Removes the need to manually track data lifecycle; PDC data is saved when the entity unloads.
|
||
* **Replaces Older Methods:** A better alternative to NBT tags (requires reflection, unreliable) and lore/display names (prone to collisions, slow access).
|
||
|
||
## Adding data
|
||
|
||
To store data in the PDC, you need:
|
||
|
||
* `NamespacedKey`: To identify the data.
|
||
* `PersistentDataContainer`: The object to store data on.
|
||
* The data itself.
|
||
|
||
```java
|
||
NamespacedKey key = new NamespacedKey(pluginInstance, "example-key"); // Create a NamespacedKey
|
||
World world = Bukkit.getServer().getWorlds().getFirst();
|
||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
|
||
```
|
||
|
||
For `ItemStack` (1.21.4+):
|
||
|
||
```java
|
||
NamespacedKey key = ...;
|
||
ItemStack item = ItemStack.of(Material.DIAMOND);
|
||
item.editPersistentDataContainer(pdc -> {
|
||
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
|
||
});
|
||
```
|
||
|
||
**Important Notes:**
|
||
|
||
* `ItemStack#editPersistentDataContainer()` is available in 1.21.4+. For older versions, use `ItemMeta`.
|
||
* For 1.16.5+, `ItemStack#editMeta()` is available.
|
||
* Reuse `NamespacedKey` objects. They can be constructed with a `Plugin` instance and a `String` identifier, or a `String` namespace and a `String` identifier. The first option is preferred for automatic namespacing.
|
||
|
||
## Getting data
|
||
|
||
To retrieve data:
|
||
|
||
* Know the `NamespacedKey`.
|
||
* Know the `PersistentDataType`.
|
||
|
||
```java
|
||
NamespacedKey key = ...; // Use the same key as the adding-data example
|
||
World world = ...; // Use the same world as the adding-data example
|
||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||
|
||
String value = pdc.getOrDefault(key, PersistentDataType.STRING, "<null>"); // Utilize the data from the PDC
|
||
player.sendPlainMessage(value); // Do something with the value
|
||
```
|
||
|
||
Use `getOrDefault` when needing non-null values, especially for Adventure's `Component.text(String)`.
|
||
|
||
## Data types
|
||
|
||
The PDC supports:
|
||
|
||
* `Byte`, `Byte Array`
|
||
* `Double`
|
||
* `Float`
|
||
* `Integer`, `Integer Array`
|
||
* `Long`, `Long Array`
|
||
* `Short`
|
||
* `String`
|
||
* `Boolean`
|
||
* **Tag Containers:** Nested PDCs.
|
||
|
||
```java
|
||
PersistentDataContainer container = ...; // Get an existing container
|
||
PersistentDataContainer newContainer = container.getAdapterContext().newPersistentDataContainer(); // Create a new container
|
||
```
|
||
|
||
* **Lists:** Lists of data that can be stored via another persistent data type.
|
||
|
||
```java
|
||
// Storing a list of strings in a container by verbosely creating
|
||
// a list data type wrapping the string data type.
|
||
container.set(key, PersistentDataType.LIST.listTypeFrom(PersistentDataType.STRING), List.of("a", "list", "of", "strings"));
|
||
|
||
// Storing a list of strings in a container by using the API
|
||
// provided pre-definitions of commonly used list types.
|
||
container.set(key, PersistentDataType.LIST.strings(), List.of("a", "list", "of", "strings"));
|
||
|
||
// Retrieving a list of strings from the container.
|
||
List<String> strings = container.get(key, PersistentDataType.LIST.strings());
|
||
```
|
||
|
||
### Boolean
|
||
|
||
The `Boolean` PDC type exists for convenience; you cannot make more complex types distill to a `Boolean`.
|
||
|
||
## Custom data types
|
||
|
||
Implement your own `PersistentDataType` for complex data. The `PersistentDataType` handles "deconstructing" a complex data type into natively supported types and vice versa.
|
||
|
||
**Example (UUID):**
|
||
|
||
```java
|
||
@NullMarked
|
||
public class UUIDDataType implements PersistentDataType<byte[], UUID> {
|
||
public static final UUIDDataType INSTANCE = new UUIDDataType();
|
||
|
||
private UUIDDataType() {} // Singleton
|
||
|
||
@Override
|
||
public Class<byte[]> getPrimitiveType() {
|
||
return byte[].class;
|
||
}
|
||
|
||
@Override
|
||
public Class<UUID> getComplexType() {
|
||
return UUID.class;
|
||
}
|
||
|
||
@Override
|
||
public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) {
|
||
ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2);
|
||
bb.putLong(complex.getMostSignificantBits());
|
||
bb.putLong(complex.getLeastSignificantBits());
|
||
return bb.array();
|
||
}
|
||
|
||
@Override
|
||
public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
|
||
ByteBuffer bb = ByteBuffer.wrap(primitive);
|
||
long firstLong = bb.getLong();
|
||
long secondLong = bb.getLong();
|
||
return new UUID(firstLong, secondLong);
|
||
}
|
||
}
|
||
```
|
||
|
||
**Usage:**
|
||
|
||
```java
|
||
container.set(key, UUIDDataType.INSTANCE, uuid);
|
||
```
|
||
|
||
## Read-only containers
|
||
|
||
`ItemStack` and `OfflinePlayer` provide read-only views of their PDC. `OfflinePlayer` cannot be modified due to disk read operations. Mutable objects generally need to be re-saved, so read-only "views" are preferred.
|
||
|
||
```java
|
||
NamespacedKey key = ...;
|
||
ItemStack item = ...;
|
||
PersistentDataContainerView pdcView = item.getPersistentDataContainer();
|
||
|
||
String value = pdcView.getOrDefault(key, PersistentDataType.STRING, "<null>");
|
||
player.sendPlainMessage(value);
|
||
```
|
||
|
||
**Note:** PDC-view support for `ItemStack` was introduced in 1.21.1. Use `ItemMeta` for older versions.
|
||
|
||
## Storing on different objects
|
||
|
||
**Caution:** Data is **not** copied across holders automatically. Manual copying is required when 'moving' between `PersistentDataHolder`s. Placing an `ItemStack` as a Block (with a `TileState`) does **not** copy PDC data.
|
||
|
||
Objects that implement `PersistentDataHolder` have a PDC fetched with `PersistentDataHolder#getPersistentDataContainer()`.
|
||
|
||
### ItemStack
|
||
|
||
* Prior to 1.21.1, the PDC was accessed via `ItemMeta` (overhead).
|
||
* 1.21.1+ exposes a read-only view via `ItemStack#getPersistentDataContainer()`.
|
||
* 1.21.4+ simplifies edits using `ItemStack#editPersistentDataContainer(java.util.function.Consumer)`.
|
||
|
||
```java
|
||
ItemStack itemStack = ...;
|
||
itemStack.editPersistentDataContainer(pdc -> {
|
||
pdc.set(key, PersistentDataType.STRING, "I love tacos!");
|
||
});
|
||
```
|
||
|
||
### Chunk
|
||
|
||
`Chunk#getPersistentDataContainer()`
|
||
|
||
### World
|
||
|
||
`World#getPersistentDataContainer()`
|
||
|
||
### Entity
|
||
|
||
`Entity#getPersistentDataContainer()`
|
||
|
||
### TileState
|
||
|
||
Requires casting the block's state to a `TileState` extension (only works for blocks with block entities).
|
||
|
||
```java
|
||
Block block = ...;
|
||
if (block.getState() instanceof Chest chest) {
|
||
chest.getPersistentDataContainer().set(key, PersistentDataType.STRING, "I love tacos!");
|
||
chest.update();
|
||
}
|
||
```
|
||
|
||
### Structure
|
||
|
||
`Structure#getPersistentDataContainer()`
|
||
|
||
### GeneratedStructure
|
||
|
||
`GeneratedStructure#getPersistentDataContainer()`
|
||
|
||
### Raid
|
||
|
||
`Raid#getPersistentDataContainer()`
|
||
|
||
### OfflinePlayer
|
||
|
||
`OfflinePlayer` exposes a read-only PDC via `OfflinePlayer#getPersistentDataContainer()`.
|
||
|
||
### ItemMeta
|
||
|
||
`ItemMeta#getPersistentDataContainer()`
|
||
|
||
---
|
||
|
||
# Particles
|
||
|
||
This guide explains how to spawn different types of particles. If a particle isn't mentioned, it likely has no special behavior.
|
||
|
||
**Methods for Spawning Particles:**
|
||
|
||
1. **ParticleBuilder (Preferred):** Reusable, offers improved readability, and includes the `receivers()` method for greater control over receivers.
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(2, 0.2, 2)
|
||
.count(14)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
```
|
||
|
||
* `ParticleBuilder.receivers(32, true)` selects all players within 32 blocks (sphere). `false` selects players in a cube.
|
||
|
||
2. **spawnParticle() Methods:**
|
||
|
||
* `World.spawnParticle()`: Spawns the particle for all players.
|
||
* `Player.spawnParticle()`: Spawns the particle only for the player.
|
||
|
||
## count argument behavior
|
||
|
||
When spawning particles, the Minecraft client behaves differently based on the `count` argument:
|
||
|
||
* **`count = 0`**: A singular particle spawns at the provided location without modification. The offset values are multiplied by the `extra` argument.
|
||
* **`count > 0`**: `count` number of particles are spawned. New offset values are generated using a Gaussian (normal) distribution, multiplied by the `extra` argument.
|
||
|
||
## Directional particles
|
||
|
||
These particles have an initial velocity when spawned. Effective speed varies between particles.
|
||
|
||
```java
|
||
Particle.FLAME.builder()
|
||
.location(someLocation)
|
||
.offset(0.5, 0.5, 0.5)
|
||
.count(8)
|
||
.extra(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.FLAME, someLocation, 8, 0.5, 0.5, 0.5, 0);
|
||
```
|
||
|
||
**Caution:** Leaving the `extra` parameter unset will default it to `1`, likely resulting in unexpected behavior.
|
||
|
||
### Random direction
|
||
|
||
Setting the `count` parameter to anything positive will yield a random direction for the velocity.
|
||
|
||
```java
|
||
Particle.CRIT.builder()
|
||
.location(someLocation)
|
||
.count(6)
|
||
.extra(0.6)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.CRIT, someLocation, 6, 0, 0, 0, 0.6);
|
||
```
|
||
|
||
### Specified direction
|
||
|
||
To specify the velocity's direction, set the `count` argument to `0` and use the offset arguments as the direction vector.
|
||
|
||
```java
|
||
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
|
||
.location(someLocation)
|
||
.offset(0, 1, 0)
|
||
.count(0)
|
||
.extra(0.1);
|
||
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
|
||
|
||
// or
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, 1, 0, 0.1), 0, 4);
|
||
```
|
||
|
||
To make the smoke go down:
|
||
|
||
```java
|
||
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
|
||
.location(someLocation)
|
||
.offset(0, -1, 0)
|
||
.count(0)
|
||
.extra(0.1);
|
||
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
|
||
|
||
// or
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, -1, 0, 0.1), 0, 4);
|
||
```
|
||
|
||
### List of directional particles
|
||
|
||
* `BLOCK`
|
||
* `BUBBLE`
|
||
* `BUBBLE_COLUMN_UP`
|
||
* `BUBBLE_POP`
|
||
* `CAMPFIRE_COSY_SMOKE`
|
||
* `CAMPFIRE_SIGNAL_SMOKE`
|
||
* `CLOUD`
|
||
* `CRIT`
|
||
* `DAMAGE_INDICATOR`
|
||
* `DRAGON_BREATH`
|
||
* `DUST`
|
||
* `DUST_COLOR_TRANSITION`
|
||
* `DUST_PLUME`
|
||
* `ELECTRIC_SPARK`
|
||
* `ENCHANTED_HIT`
|
||
* `END_ROD`
|
||
* `FIREWORK`
|
||
* `FISHING`
|
||
* `FLAME`
|
||
* `FLASH`
|
||
* `GLOW_SQUID_INK`
|
||
* `ITEM`
|
||
* `LARGE_SMOKE`
|
||
* `POOF`
|
||
* `REVERSE_PORTAL`
|
||
* `SCRAPE`
|
||
* `SCULK_CHARGE`
|
||
* `SCULK_CHARGE_POP`
|
||
* `SCULK_SOUL`
|
||
* `SMALL_FLAME`
|
||
* `SMOKE`
|
||
* `SNEEZE`
|
||
* `SNOWFLAKE`
|
||
* `SOUL`
|
||
* `SOUL_FIRE_FLAME`
|
||
* `SPIT`
|
||
* `SQUID_INK`
|
||
* `TOTEM_OF_UNDYING`
|
||
* `TRIAL_SPAWNER_DETECTION`
|
||
* `TRIAL_SPAWNER_DETECTION_OMINOUS`
|
||
* `WAX_OFF`
|
||
* `WAX_ON`
|
||
* `WHITE_SMOKE`
|
||
|
||
## Colored particles
|
||
|
||
These particles can be colored by passing a `Color` object as the `data` argument.
|
||
|
||
```java
|
||
Particle.ENTITY_EFFECT.builder()
|
||
.location(someLocation)
|
||
.offset(1, 1, 1)
|
||
.count(10)
|
||
.data(Color.fromARGB(200, 255, 128, 0))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.ENTITY_EFFECT, someLocation, 10, 1, 1, 1, Color.fromARGB(200, 255, 128, 0));
|
||
```
|
||
|
||
* Only `ENTITY_EFFECT` supports the alpha channel for translucent particles.
|
||
* `FLASH` and `TINTED_LEAVES` ignore the alpha channel.
|
||
|
||
## Dust particles
|
||
|
||
Vanilla uses the dust particle for redstone particles. They can have a custom color by passing `Particle.DustOptions` as `data`.
|
||
|
||
* Scale factor must be in the range of `0.01` to `4.0`.
|
||
|
||
```java
|
||
ParticleBuilder particleBuilder = Particle.DUST.builder().color(Color.BLUE, 2.0f);
|
||
|
||
for (double i = -1.0; i <= 1.0; i += 0.25) {
|
||
particleBuilder.location(someLocation.clone().add(0, i, 0)).receivers(32, true).spawn();
|
||
}
|
||
|
||
// or
|
||
for (double i = -1.0; i <= 1.0; i += 0.25) {
|
||
someWorld.spawnParticle(Particle.DUST, someLocation.clone().add(0, i, 0), 1, new Particle.DustOptions(Color.BLUE, 2.0f));
|
||
}
|
||
```
|
||
|
||
* Adding a size argument controls the dust particle's lifetime (in ticks). Default is a random integer between 8 and 40, multiplied by the scale (minimum of 1).
|
||
|
||
## Dust transition particles
|
||
|
||
Dust transition particles work like dust particles, but transition their color from one to another using `Particle.DustTransition`.
|
||
|
||
```java
|
||
Particle.DUST_COLOR_TRANSITION.builder()
|
||
.location(someLocation)
|
||
.offset(0.5, 0, 0)
|
||
.count(3)
|
||
.colorTransition(Color.RED, Color.BLUE)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.DUST_COLOR_TRANSITION, someLocation, 3, 0.5, 0, 0, new Particle.DustTransition(Color.RED, Color.BLUE, 1.0f));
|
||
```
|
||
|
||
## Note particles
|
||
|
||
Note particles use the `offsetX` argument to determine the color. `offsetY` and `offsetZ` are ignored.
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(0.4f, 0, 0)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 0.4f, 0, 0);
|
||
```
|
||
|
||
### Note particle color picker
|
||
|
||
`offsetX` values between `-1.0` and `1.0` determine the color. Values outside this range repeat the color pattern.
|
||
|
||
* `offsetX = 0`
|
||
|
||
To achieve the Vanilla note particle colors, set the `offsetX` to a fraction of 24.
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(2.0f / 24.0f, 0, 0)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 2.0f / 24.0f, 0, 0);
|
||
```
|
||
|
||
## Trail particles
|
||
|
||
Trail particles require you to pass a `Particle.Trail` object as `data`.
|
||
|
||
```java
|
||
Particle.TRAIL.builder()
|
||
.location(someLocation)
|
||
.offset(1, 1, 1)
|
||
.count(8)
|
||
.data(new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.TRAIL, someLocation, 8, 1, 1, 1, new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40));
|
||
```
|
||
|
||
## Converging particles
|
||
|
||
Converge to a single point (location). Offset arguments determine the relative spawn location.
|
||
|
||
```java
|
||
Particle.ENCHANT.builder()
|
||
.location(someLocation)
|
||
.offset(-2, 0, 2)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.ENCHANT, someLocation, 0, -2, 0, 2);
|
||
```
|
||
|
||
* **Curving:** `ENCHANT`, `NAUTILUS`, `PORTAL`, `VAULT_CONNECTION`
|
||
* **Straight:** `OMINOUS_SPAWNING`
|
||
|
||
### List of converging particles
|
||
|
||
* `ENCHANT`
|
||
* `NAUTILUS`
|
||
* `OMINOUS_SPAWNING`
|
||
* `PORTAL`
|
||
* `VAULT_CONNECTION`
|
||
|
||
## Material particles
|
||
|
||
### BlockData
|
||
|
||
Spawn particles that require `BlockData` by passing `BlockData` as its `data` argument.
|
||
|
||
```java
|
||
Particle.BLOCK_CRUMBLE.builder()
|
||
.location(someLocation)
|
||
.count(4)
|
||
.data(BlockType.GLOWSTONE.createBlockData())
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.BLOCK_CRUMBLE, someLocation, 4, BlockType.GLOWSTONE.createBlockData());
|
||
```
|
||
|
||
* Use `BlockType.createBlockData()`. `Material.createBlockData()` or `Bukkit.createBlockData(Material)` are legacy.
|
||
* `BLOCK` is a directional particle; velocity matters.
|
||
|
||
### ItemStack
|
||
|
||
Spawn particles that require an `ItemStack` by passing an `ItemStack` as its `data` argument.
|
||
|
||
```java
|
||
Particle.ITEM.builder()
|
||
.location(someLocation)
|
||
.count(4)
|
||
.data(ItemStack.of(Material.DIAMOND_PICKAXE))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.ITEM, someLocation, 4, ItemStack.of(Material.DIAMOND_PICKAXE));
|
||
```
|
||
|
||
* Use `ItemStack.of(Material)`. `new ItemStack(Material)` is legacy.
|
||
* `ITEM` is a directional particle.
|
||
|
||
## Sculk particles
|
||
|
||
### Sculk charge
|
||
|
||
`SCULK_CHARGE` takes a `float` as its `data` argument, representing the particle's "roll" in radians.
|
||
|
||
```java
|
||
Particle.SCULK_CHARGE.builder()
|
||
.location(someLocation)
|
||
.data((float) Math.toRadians(45))
|
||
.extra(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.SCULK_CHARGE, someLocation, 1, 0, 0, 0, 0, (float) Math.toRadians(45));
|
||
```
|
||
|
||
* `SCULK_CHARGE` is a directional particle.
|
||
|
||
### Shriek
|
||
|
||
`SHRIEK` takes an `integer` as its `data` argument, setting the delay (in ticks) before the particle spawns.
|
||
|
||
```java
|
||
Particle.SHRIEK.builder()
|
||
.location(someLocation)
|
||
.data(20)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.SHRIEK, someLocation, 1, 20);
|
||
```
|
||
|
||
### Vibration
|
||
|
||
Vibration particles require a `Vibration` object as `data`, with a `Vibration.Destination` (either `BlockDestination` or `EntityDestination`) and a travel time in ticks.
|
||
|
||
```java
|
||
Particle.VIBRATION.builder()
|
||
.location(someLocation)
|
||
.data(new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.VIBRATION, someLocation, 1, new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40));
|
||
```
|
||
|
||
## Rising particles
|
||
|
||
These particles use `offsetY` as the particle's y-axis velocity. If `offsetX` AND `offsetZ` are `0`, the particle will have almost no x or z-axis velocity.
|
||
|
||
**Caution:** `EFFECT` and `INSTANT_EFFECT` are powered particles and use `Particle.Spell` as their data. Due to the low vertical velocity range [-0.056, 0.056] and powered particle calculations, the resulting vertical velocity will always be the opposite of the `power` value's sign.
|
||
|
||
```java
|
||
Particle.GLOW.builder()
|
||
.location(someLocation)
|
||
.count(0)
|
||
.offset(0, 2, 0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.GLOW, someLocation, 0, 0, 2, 0);
|
||
```
|
||
|
||
These particles rise up, meaning that the initial velocity will be used only briefly, and the particle will start to travel up after a short time. Therefore, negative vertical velocity will only stop the particle from rising temporarily, while a positive vertical velocity will make the particle rise immediately.
|
||
|
||
### List of rising particles
|
||
|
||
* `EFFECT`
|
||
* `ENTITY_EFFECT`
|
||
* `GLOW`
|
||
* `INFESTED`
|
||
* `INSTANT_EFFECT`
|
||
* `RAID_OMEN`
|
||
* `TRIAL_OMEN`
|
||
* `WITCH`
|
||
|
||
## Scalable particles
|
||
|
||
These particles can be scaled with `offsetX`, while `offsetY` and `offsetZ` are ignored.
|
||
|
||
* If the final calculated scale is negative, the particle will appear mirrored.
|
||
|
||
### Sweep attack particles
|
||
|
||
`SWEEP_ATTACK` particle's scale is calculated as `1.0 - offsetX * 0.5`.
|
||
|
||
```java
|
||
ParticleBuilder sweepAttackParticleBuilder = Particle.SWEEP_ATTACK.builder()
|
||
.location(someLocation)
|
||
.count(0)
|
||
.receivers(32, true);
|
||
|
||
sweepAttackParticleBuilder.spawn();
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> sweepAttackParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0);
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0, -2.0, 0, 0), 10);
|
||
```
|
||
|
||
### Explosion particles
|
||
|
||
`EXPLOSION` particle's scale is calculated as `2.0 * (1.0 - offsetX * 0.5)`.
|
||
|
||
```java
|
||
ParticleBuilder explosionParticleBuilder = Particle.EXPLOSION.builder()
|
||
.location(someLocation)
|
||
.offset(1, 0, 0)
|
||
.count(0)
|
||
.receivers(32, true);
|
||
|
||
explosionParticleBuilder.spawn();
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> explosionParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
|
||
|
||
// or
|
||
someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, 1, 0, 0);
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, -2.0, 0, 0), 10);
|
||
```
|
||
|
||
## Miscellaneous behaviors
|
||
|
||
Particles with unique spawning behaviors.
|
||
|
||
### Angry villager particles
|
||
|
||
`ANGRY_VILLAGER` always spawns `0.5` higher (y-axis) than the supplied location.
|
||
|
||
### Cloud particles
|
||
|
||
`CLOUD` and `SNEEZE` move towards the player's y level if within two blocks distance. Their vertical velocity will be greatly reduced upon reaching the player's y level. If the player is moving vertically, the particles will attempt to match the player's vertical velocity.
|
||
|
||
### Damage indicator particles
|
||
|
||
`DAMAGE_INDICATOR` adds `1.0` to the provided `offsetY`.
|
||
|
||
### Dust pillar particles
|
||
|
||
`DUST_PILLAR` uses `offsetY` for the y-axis velocity, while `offsetX` and `offsetZ` are ignored.
|
||
|
||
### Dust plume particles
|
||
|
||
`DUST_PLUME` adds `0.15` to the provided `offsetY`.
|
||
|
||
### Firefly particles
|
||
|
||
`FIREFLY` uses `offsetY` as the particle's initial y-axis velocity, but there is a 50% chance for the `offsetY`'s sign to be inverted.
|
||
|
||
### Powered particles
|
||
|
||
The powered particles multiply the particle's velocity vector by the supplied argument. The y component of the vector is calculated as `(verticalVelocity - 0.1) * power + 0.1`.
|
||
|
||
#### List of powered particles
|
||
|
||
* `EFFECT`
|
||
* `INSTANT_EFFECT`
|
||
* `DRAGON_BREATH`
|
||
|
||
### Splash particles
|
||
|
||
`SPLASH` uses the `offsetX` and `offsetZ` arguments to determine the particle's velocity vector if:
|
||
|
||
* `offsetY` is `0`
|
||
* Either `offsetX` or `offsetZ` are not `0`
|
||
|
||
---
|
||
|
||
# Plugin configuration
|
||
|
||
Configuration files allow users to change certain behavior and functionality of plugins.
|
||
|
||
## Format
|
||
|
||
By default, plugins use a YAML configuration format (`.yml` file). Other formats (JSON, TOML) are not natively supported.
|
||
|
||
YAML uses a tree-like `key: value` pair structure, as seen in `plugin.yml`.
|
||
|
||
**Example:**
|
||
|
||
```yaml
|
||
root:
|
||
one-key: 10
|
||
another-key: David
|
||
```
|
||
|
||
Accessing indented values uses dots (`.`). The key for "David" is `root.another-key`.
|
||
|
||
## Creating a config.yml
|
||
|
||
Place a `config.yml` file in the `resources` directory to specify default settings.
|
||
|
||
```
|
||
example-plugin/
|
||
└── src/
|
||
└── main/
|
||
└── java/
|
||
└── resources/
|
||
├── config.yml
|
||
└── plugin.yml
|
||
```
|
||
|
||
Save this resource to the plugin's data directory during initialization so users can edit it.
|
||
|
||
```java
|
||
public class TestPlugin extends JavaPlugin {
|
||
@Override
|
||
public void onEnable() {
|
||
saveResource("config.yml", /* replace */ false);
|
||
// You can also use this for configuration files:
|
||
saveDefaultConfig(); // Saves config.yml if it doesn't exist
|
||
// getConfig()...
|
||
}
|
||
}
|
||
```
|
||
|
||
* The `replace` parameter specifies whether to overwrite an existing file (if set to true, the configuration will be overwritten on every call).
|
||
|
||
## Getting and setting data
|
||
|
||
Fetch the plugin's `FileConfiguration` with `JavaPlugin#getConfig()` after saving. Use `#get...(key)` and `#set(key, value)` to fetch and set data.
|
||
|
||
Basic data types are supported by YAML (e.g., `#getString(key)`, `#getBoolean(key)`). More complex Bukkit data types (e.g., `ItemStack`, `Location`, `Vector`) are also supported.
|
||
|
||
**Example (Loading a Location):**
|
||
|
||
```java
|
||
public class TestPlugin extends JavaPlugin {
|
||
public void teleportPlayer(Player player) {
|
||
Location to = getConfig().getLocation("target_location");
|
||
player.teleport(to);
|
||
}
|
||
}
|
||
```
|
||
|
||
These complex types implement `ConfigurationSerializable`. You can use this for custom classes.
|
||
|
||
```java
|
||
public class TeleportOptions implements ConfigurationSerializable {
|
||
private int chunkX;
|
||
private int chunkZ;
|
||
private String name;
|
||
|
||
public TeleportOptions(int chunkX, int chunkZ, String name) {
|
||
// Set the values
|
||
}
|
||
|
||
public Map<String, Object> serialize() {
|
||
Map<String, Object> data = new HashMap<>();
|
||
data.put("chunk-x", this.chunkX);
|
||
data.put("chunk-z", this.chunkZ);
|
||
data.put("name", this.name);
|
||
return data;
|
||
}
|
||
|
||
public static TeleportOptions deserialize(Map<String, Object> args) {
|
||
return new TeleportOptions((int) args.get("chunk-x"), (int) args.get("chunk-z"), (String) args.get("name"));
|
||
}
|
||
}
|
||
```
|
||
|
||
To register the custom class:
|
||
|
||
```java
|
||
ConfigurationSerialization.registerClass(TeleportOptions.class);
|
||
```
|
||
|
||
**Caution:** If you do not call `ConfigurationSerialization#registerClass(Class)` with Paper plugins, you will not be able to load nor save your custom classes.
|
||
|
||
**Saving Configs:**
|
||
|
||
Call `FileConfiguration#save(File/String)` to persist changes to disk.
|
||
|
||
## Custom configuration files
|
||
|
||
Split configurations across multiple files using the Bukkit `FileConfiguration` API.
|
||
|
||
```java
|
||
File file = new File(plugin.getDataFolder(), "items.yml");
|
||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||
|
||
// Work with config here
|
||
config.save(file);
|
||
```
|
||
|
||
This example reads `items.yml` from the plugin's data directory. The file must exist.
|
||
|
||
**Blocking I/O:**
|
||
|
||
Loading and saving files on the main thread will slow down your server. `load` and `save` operations should be executed asynchronously.
|
||
|
||
## Configurate
|
||
|
||
Configurate is a third-party library for working with configurations, maintained by the Sponge project. It's used internally by Paper and offers features the `FileConfiguration` API lacks. More information can be found [here](https://github.com/SpongePowered/Configurate).
|
||
|
||
---
|
||
|
||
# plugin.yml
|
||
|
||
The `plugin.yml` file is the main configuration file for your plugin, containing information such as name, version, description, dependencies, permissions, and commands. It's located in the `resources` directory.
|
||
|
||
```
|
||
example-plugin/
|
||
├── build.gradle.kts
|
||
├── settings.gradle.kts
|
||
└── src/
|
||
└── main/
|
||
└── java/
|
||
└── resources/
|
||
└── plugin.yml
|
||
```
|
||
|
||
## Example
|
||
|
||
```yaml
|
||
name: ExamplePlugin
|
||
version: 1.0.0
|
||
main: io.papermc.testplugin.ExamplePlugin
|
||
description: An example plugin
|
||
author: PaperMC
|
||
website: https://papermc.io
|
||
api-version: '1.21.10'
|
||
```
|
||
|
||
## Fields
|
||
|
||
\* indicates a required field.
|
||
|
||
### name\*
|
||
|
||
The name of your plugin. Displayed in the plugin list and log messages (overridden by `prefix` if set).
|
||
|
||
```yaml
|
||
name: ExamplePlugin
|
||
```
|
||
|
||
### version\*
|
||
|
||
The current version of the plugin. Shown in plugin info messages and server logs.
|
||
|
||
```yaml
|
||
version: 1.0.0
|
||
```
|
||
|
||
### main\*
|
||
|
||
The main class of your plugin. Extends `JavaPlugin` and is the entry point. Package path and class name of your main class.
|
||
|
||
```yaml
|
||
main: io.papermc.testplugin.ExamplePlugin
|
||
```
|
||
|
||
### description
|
||
|
||
A short description of your plugin. Shown in plugin info commands.
|
||
|
||
```yaml
|
||
description: An example plugin
|
||
```
|
||
|
||
### author / authors
|
||
|
||
The author(s) of the plugin. Can be a single author or a list. Shown in plugin info commands.
|
||
|
||
```yaml
|
||
author: PaperMC
|
||
authors: [PaperMC, SpigotMC, Bukkit]
|
||
```
|
||
|
||
### contributors
|
||
|
||
The contributors to the plugin that aren't the managing author(s). Shown in plugin info commands.
|
||
|
||
```yaml
|
||
contributors: [PaperMC, SpigotMC, Bukkit]
|
||
```
|
||
|
||
### website
|
||
|
||
The website of the plugin (GitHub repository, plugin page). Shown in plugin info commands.
|
||
|
||
```yaml
|
||
website: https://papermc.io
|
||
```
|
||
|
||
### api-version
|
||
|
||
The Paper API version your plugin uses. Doesn’t include minor version until 1.20.5. From 1.20.5 and onward, a minor version is supported. Servers with lower versions refuse to load the plugin. Valid versions are 1.13 - 1.21.10.
|
||
|
||
```yaml
|
||
api-version: '1.21.10'
|
||
```
|
||
|
||
* If not specified, the plugin is loaded as legacy, with a console warning.
|
||
|
||
### load
|
||
|
||
Tells the server when to load the plugin: `STARTUP` or `POSTWORLD`. Defaults to `POSTWORLD` if not specified.
|
||
|
||
```yaml
|
||
load: STARTUP
|
||
```
|
||
|
||
### prefix
|
||
|
||
The prefix of the plugin. Displayed in the log instead of the plugin name.
|
||
|
||
```yaml
|
||
prefix: EpicPaperMCHypePlugin
|
||
```
|
||
|
||
### libraries
|
||
|
||
A list of libraries your plugin depends on. Downloaded from Maven Central and added to the classpath, removing the need to shade and relocate.
|
||
|
||
```yaml
|
||
libraries:
|
||
- com.google.guava:guava:30.1.1-jre
|
||
- com.google.code.gson:gson:2.8.6
|
||
```
|
||
|
||
* The central repository is configurable using the `PAPER_DEFAULT_CENTRAL_
|
||
|
||
```markdown
|
||
# PaperMC Knowledge Base
|
||
|
||
## Project Setup
|
||
|
||
### Overview
|
||
|
||
This guide focuses on setting up a Paper plugin development environment using IntelliJ IDEA and Gradle. While tailored for IntelliJ, the principles apply to other IDEs with minor adjustments. Gradle is the preferred build system, though alternatives like Maven can be adapted.
|
||
|
||
### Creating a New Project
|
||
|
||
1. **Open IntelliJ IDEA** and select "New Project".
|
||
2. **Choose Gradle - Kotlin DSL** as the project type.
|
||
3. Click **Create**. This opens the `build.gradle.kts` file for dependency management.
|
||
|
||
### Adding Paper as a Dependency
|
||
|
||
Add the Paper repository and dependency to your `build.gradle.kts` (Kotlin), `build.gradle` (Groovy), or `pom.xml` (Maven) file.
|
||
|
||
#### Gradle (Kotlin)
|
||
|
||
```kotlin
|
||
repositories {
|
||
maven {
|
||
name = "papermc"
|
||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||
}
|
||
}
|
||
|
||
dependencies {
|
||
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
|
||
}
|
||
|
||
java {
|
||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||
}
|
||
```
|
||
|
||
#### Gradle (Groovy)
|
||
|
||
```groovy
|
||
repositories {
|
||
maven {
|
||
name = 'papermc'
|
||
url = 'https://repo.papermc.io/repository/maven-public/'
|
||
}
|
||
}
|
||
|
||
dependencies {
|
||
compileOnly 'io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT'
|
||
}
|
||
```
|
||
|
||
#### Maven
|
||
|
||
```xml
|
||
<project>
|
||
<repositories>
|
||
<repository>
|
||
<id>papermc</id>
|
||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||
</repository>
|
||
</repositories>
|
||
<dependencies>
|
||
<dependency>
|
||
<groupId>io.papermc.paper</groupId>
|
||
<artifactId>paper-api</artifactId>
|
||
<version>1.21.10-R0.1-SNAPSHOT</version>
|
||
<scope>provided</scope>
|
||
</dependency>
|
||
</dependencies>
|
||
</project>
|
||
```
|
||
|
||
### Setting up the `src` Directory
|
||
|
||
1. Create a directory named `src`.
|
||
2. Inside `src`, create a directory named `main`.
|
||
3. Inside `main`, create two directories: `java` and `resources`.
|
||
|
||
```
|
||
example-plugin/
|
||
├── build.gradle.kts
|
||
├── settings.gradle.kts
|
||
└── src/
|
||
└── main/
|
||
├── java/
|
||
└── resources/
|
||
```
|
||
|
||
### Setting up the `java` Directory
|
||
|
||
Place your Java source files in the `java` directory. Organize code into packages.
|
||
|
||
1. Create packages to organize your code (e.g., `io.papermc.testplugin`).
|
||
2. Create Java classes within these packages (e.g., `ExamplePlugin.java`).
|
||
|
||
```
|
||
example-plugin/
|
||
├── build.gradle.kts
|
||
├── settings.gradle.kts
|
||
└── src/
|
||
└── main/
|
||
├── java/
|
||
│ └── io/
|
||
│ └── papermc/
|
||
│ └── testplugin/
|
||
│ └── ExamplePlugin.java
|
||
└── resources/
|
||
```
|
||
|
||
### Packages
|
||
|
||
Packages are used to organize code. Java packages group related classes. Package names should follow a reverse domain name convention (e.g., `io.papermc.testplugin`). If you don't have a domain, use your GitHub username (e.g., `io.github.yourname`).
|
||
|
||
### The `main` Class
|
||
|
||
The main class extends `JavaPlugin` and serves as the entry point for the plugin.
|
||
|
||
```java
|
||
package io.papermc.testplugin;
|
||
|
||
import net.kyori.adventure.text.Component;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.event.EventHandler;
|
||
import org.bukkit.event.Listener;
|
||
import org.bukkit.event.player.PlayerJoinEvent;
|
||
import org.bukkit.plugin.java.JavaPlugin;
|
||
|
||
public class ExamplePlugin extends JavaPlugin implements Listener {
|
||
|
||
@Override
|
||
public void onEnable() {
|
||
Bukkit.getPluginManager().registerEvents(this, this);
|
||
}
|
||
|
||
@EventHandler
|
||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||
event.getPlayer().sendMessage(Component.text("Hello, " + event.getPlayer().getName() + "!"));
|
||
}
|
||
}
|
||
```
|
||
|
||
### Setting up the `resources` Directory
|
||
|
||
Place the `plugin.yml` file in the `resources` directory.
|
||
|
||
### Using the Minecraft Development IntelliJ Plugin
|
||
|
||
An alternative to manual setup is using the Minecraft Development IntelliJ plugin.
|
||
|
||
1. **Install the Plugin:** Go to `File > Settings > Plugins`, search for "Minecraft Development" in the Marketplace, and install it. Restart IntelliJ.
|
||
2. **Create a New Project:** Go to `File > New > Project...` and select "Minecraft".
|
||
3. **Provide Project Information:**
|
||
|
||
* **Name:** Project name.
|
||
* **Location:** Project directory.
|
||
* **Platform Type:** "Plugin".
|
||
* **Platform:** "Paper".
|
||
* **Minecraft Version:** Target Minecraft version.
|
||
* **Plugin Name:** Plugin name.
|
||
* **Main Class:** Main class extending `JavaPlugin`.
|
||
* **Optional Settings:** Author, website, description.
|
||
* **Build System:** Gradle (recommended) or Maven.
|
||
* **Paper Manifest:** Leave unchecked for now (experimental).
|
||
* **Group ID:** Reverse domain name (e.g., `io.github.yourname`).
|
||
* **Artifact ID:** Project name.
|
||
* **Version:** `1.0-SNAPSHOT`.
|
||
* **JDK:** Java 21 or higher.
|
||
4. Click **Create**.
|
||
|
||
### Plugin Remapping
|
||
|
||
Paper uses a Mojang-mapped runtime since 1.20.5. Spigot/Bukkit plugins are assumed to be Spigot-mapped and will be remapped on first load.
|
||
|
||
### Mojang Mappings
|
||
|
||
To indicate your plugin is Mojang-mapped, add the following to your build script:
|
||
|
||
#### Gradle (Kotlin)
|
||
|
||
```kotlin
|
||
tasks.jar {
|
||
manifest {
|
||
attributes["paperweight-mappings-namespace"] = "mojang"
|
||
}
|
||
}
|
||
|
||
// if you have shadowJar configured
|
||
tasks.shadowJar {
|
||
manifest {
|
||
attributes["paperweight-mappings-namespace"] = "mojang"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Gradle (Groovy)
|
||
|
||
```groovy
|
||
jar {
|
||
manifest {
|
||
attributes('paperweight-mappings-namespace': 'mojang')
|
||
}
|
||
}
|
||
|
||
// if you have shadowJar configured
|
||
shadowJar {
|
||
manifest {
|
||
attributes('paperweight-mappings-namespace': 'mojang')
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Maven
|
||
|
||
```xml
|
||
<plugin>
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
<artifactId>maven-jar-plugin</artifactId>
|
||
<version>3.4.1</version>
|
||
<configuration>
|
||
<archive>
|
||
<manifestEntries>
|
||
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
|
||
</manifestEntries>
|
||
</archive>
|
||
</configuration>
|
||
</plugin>
|
||
```
|
||
|
||
### Spigot Mappings
|
||
|
||
To explicitly indicate your plugin is Spigot-mapped, add the following to your build script:
|
||
|
||
#### Gradle (Kotlin)
|
||
|
||
```kotlin
|
||
tasks.jar {
|
||
manifest {
|
||
attributes["paperweight-mappings-namespace"] = "spigot"
|
||
}
|
||
}
|
||
|
||
// if you have shadowJar configured
|
||
tasks.shadowJar {
|
||
manifest {
|
||
attributes["paperweight-mappings-namespace"] = "spigot"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Gradle (Groovy)
|
||
|
||
```groovy
|
||
jar {
|
||
manifest {
|
||
attributes('paperweight-mappings-namespace': 'spigot')
|
||
}
|
||
}
|
||
|
||
// if you have shadowJar configured
|
||
shadowJar {
|
||
manifest {
|
||
attributes('paperweight-mappings-namespace': 'spigot')
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Maven
|
||
|
||
```xml
|
||
<plugin>
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
<artifactId>maven-jar-plugin</artifactId>
|
||
<version>3.4.1</version>
|
||
<configuration>
|
||
<archive>
|
||
<manifestEntries>
|
||
<paperweight-mappings-namespace>spigot</paperweight-mappings-namespace>
|
||
</manifestEntries>
|
||
</archive>
|
||
</configuration>
|
||
</plugin>
|
||
```
|
||
|
||
### Conclusion
|
||
|
||
You should now have a project set up with Paper as a dependency. Compile your plugin and run it on a Paper server.
|
||
|
||
* **Run-Task Gradle Plugin:** Streamlines testing by automatically downloading and running a Paper server.
|
||
* **IntelliJ Build Menu:** Compile your plugin using the Gradle GUI "Build" menu. The output JAR will be in the `build/libs` directory.
|
||
|
||
---
|
||
|
||
## Reading Stacktraces
|
||
|
||
### Overview
|
||
|
||
A stacktrace displays the call stack of a thread in Java, showing the execution path leading to a specific point in the program. It's crucial for debugging.
|
||
|
||
### What is a Stacktrace?
|
||
|
||
In Java, a stacktrace reveals the call stack of a thread, detailing the sequence of method calls that led to the current execution point. Stacktraces are printed when an exception isn't handled correctly and are invaluable for debugging. They pinpoint the exact line causing the error and the preceding calls.
|
||
|
||
### Example
|
||
|
||
```
|
||
[15:20:42 ERROR]: Could not pass event PluginEnableEvent to TestPlugin v1.0
|
||
java.lang.NullPointerException: Cannot invoke "Object.toString()" because "player" is null
|
||
at io.papermc.testplugin.TestPlugin.onPluginEnable(TestPlugin.java:23) ~[TestPlugin-1.0-SNAPSHOT.jar:?]
|
||
at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor1.execute(Unknown Source) ~[?:?]
|
||
at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:git-Paper-49]
|
||
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:200) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugin(CraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugins(CraftServer.java:547) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at java.lang.Thread.run(Thread.java:833) ~[?:?]
|
||
```
|
||
|
||
* **Error Context:** The error occurred during `PluginEnableEvent` handling by `TestPlugin`.
|
||
* **Exception Cause:** `NullPointerException` due to calling `toString()` on a null "player" object.
|
||
* **Error Location:** `TestPlugin.java:23`.
|
||
* **Execution Path:** The stacktrace shows the sequence of calls that led to the error, starting from `TestPlugin.onPluginEnable` and traversing through Paper/Bukkit internals. You can ignore server internals typically.
|
||
|
||
### Omitted Stacktraces
|
||
|
||
In JDK 5+, the JVM may omit stacktraces for optimized code, leading to `NullPointerException`s without a stacktrace. To resolve this, use the JVM flag:
|
||
|
||
```
|
||
java -XX:-OmitStackTraceInFastThrow -jar paper.jar
|
||
```
|
||
|
||
---
|
||
|
||
## Particles
|
||
|
||
### Overview
|
||
|
||
This guide explains how to spawn different types of particles. If a particle isn't mentioned, it likely has no special behavior.
|
||
|
||
Particles can be spawned in two ways:
|
||
|
||
1. **`ParticleBuilder` Class (Preferred):** Reusable, readable, and includes `receivers()` for controlling receivers.
|
||
2. **`World.spawnParticle()` and `Player.spawnParticle()`:** Spawn particles for all players or a specific player, respectively.
|
||
|
||
Example using `ParticleBuilder`:
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(2, 0.2, 2)
|
||
.count(14)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
```
|
||
|
||
`ParticleBuilder.receivers(32, true)` selects players within a 32-block radius (sphere). `false` would select players within a cube.
|
||
|
||
### `count` Argument Behavior
|
||
|
||
* **`count = 0`:** Spawns a single particle at the provided location without modification. Offset values are multiplied by the `extra` argument.
|
||
* **`count > 0`:** Spawns `count` particles. Offset values are generated using a Gaussian distribution and multiplied by the `extra` argument.
|
||
|
||
### Directional Particles
|
||
|
||
These particles have an initial velocity when spawned. Effective speed varies between particles.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.FLAME.builder()
|
||
.location(someLocation)
|
||
.offset(0.5, 0.5, 0.5)
|
||
.count(8)
|
||
.extra(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.FLAME, someLocation, 8, 0.5, 0.5, 0.5, 0);
|
||
```
|
||
|
||
Leaving `extra` unset defaults to `1`, which may result in unexpected behavior.
|
||
|
||
#### Random Direction
|
||
|
||
Setting `count` to a positive value yields a random direction for the velocity.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.CRIT.builder()
|
||
.location(someLocation)
|
||
.count(6)
|
||
.extra(0.6)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.CRIT, someLocation, 6, 0, 0, 0, 0.6);
|
||
```
|
||
|
||
#### Specified Direction
|
||
|
||
Set `count` to `0` and use the offset arguments as the direction vector.
|
||
|
||
Example:
|
||
|
||
```java
|
||
ParticleBuilder particleBuilder = Particle.CAMPFIRE_SIGNAL_SMOKE.builder()
|
||
.location(someLocation)
|
||
.offset(0, 1, 0)
|
||
.count(0)
|
||
.extra(0.1);
|
||
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> particleBuilder.receivers(32, true).spawn(), 0, 4);
|
||
|
||
//or
|
||
|
||
Bukkit.getScheduler().runTaskTimer(plugin, () -> someWorld.spawnParticle(Particle.CAMPFIRE_SIGNAL_SMOKE, someLocation, 0, 0, 1, 0, 0.1), 0, 4);
|
||
```
|
||
|
||
#### List of Directional Particles
|
||
|
||
* BLOCK
|
||
* BUBBLE
|
||
* BUBBLE_COLUMN_UP
|
||
* BUBBLE_POP
|
||
* CAMPFIRE_COSY_SMOKE
|
||
* CAMPFIRE_SIGNAL_SMOKE
|
||
* CLOUD
|
||
* CRIT
|
||
* DAMAGE_INDICATOR
|
||
* DRAGON_BREATH
|
||
* DUST
|
||
* DUST_COLOR_TRANSITION
|
||
* DUST_PLUME
|
||
* ELECTRIC_SPARK
|
||
* ENCHANTED_HIT
|
||
* END_ROD
|
||
* FIREWORK
|
||
* FISHING
|
||
* FLAME
|
||
* FLASH
|
||
* GLOW_SQUID_INK
|
||
* ITEM
|
||
* LARGE_SMOKE
|
||
* POOF
|
||
* REVERSE_PORTAL
|
||
* SCRAPE
|
||
* SCULK_CHARGE
|
||
* SCULK_CHARGE_POP
|
||
* SCULK_SOUL
|
||
* SMALL_FLAME
|
||
* SMOKE
|
||
* SNEEZE
|
||
* SNOWFLAKE
|
||
* SOUL
|
||
* SOUL_FIRE_FLAME
|
||
* SPIT
|
||
* SQUID_INK
|
||
* TOTEM_OF_UNDYING
|
||
* TRIAL_SPAWNER_DETECTION
|
||
* TRIAL_SPAWNER_DETECTION_OMINOUS
|
||
* WAX_OFF
|
||
* WAX_ON
|
||
* WHITE_SMOKE
|
||
|
||
### Colored Particles
|
||
|
||
These particles can be colored by passing a `Color` object as the `data` argument.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.ENTITY_EFFECT.builder()
|
||
.location(someLocation)
|
||
.offset(1, 1, 1)
|
||
.count(10)
|
||
.data(Color.fromARGB(200, 255, 128, 0))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.ENTITY_EFFECT, someLocation, 10, 1, 1, 1, Color.fromARGB(200, 255, 128, 0));
|
||
```
|
||
|
||
Only `ENTITY_EFFECT` supports the alpha channel for translucency. `FLASH` and `TINTED_LEAVES` ignore the alpha channel.
|
||
|
||
### Dust Particles
|
||
|
||
Vanilla uses dust particles for redstone. Custom colors are set using `Particle.DustOptions` as `data`. The scale factor must be between `0.01` and `4.0`.
|
||
|
||
Example:
|
||
|
||
```java
|
||
ParticleBuilder particleBuilder = Particle.DUST.builder().color(Color.BLUE, 2.0f);
|
||
|
||
for (double i = -1.0; i <= 1.0; i += 0.25) {
|
||
particleBuilder.location(someLocation.clone().add(0, i, 0)).receivers(32, true).spawn();
|
||
}
|
||
|
||
//or
|
||
for (double i = -1.0; i <= 1.0; i += 0.25) {
|
||
someWorld.spawnParticle(Particle.DUST, someLocation.clone().add(0, i, 0), 1, new Particle.DustOptions(Color.BLUE, 2.0f));
|
||
}
|
||
```
|
||
|
||
A size argument controls the dust particle's lifetime in ticks.
|
||
|
||
### Dust Transition Particles
|
||
|
||
Dust transition particles transition their color. A `Particle.DustTransition` is used to specify the transition.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.DUST_COLOR_TRANSITION.builder()
|
||
.location(someLocation)
|
||
.offset(0.5, 0, 0)
|
||
.count(3)
|
||
.colorTransition(Color.RED, Color.BLUE)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.DUST_COLOR_TRANSITION, someLocation, 3, 0.5, 0, 0, new Particle.DustTransition(Color.RED, Color.BLUE, 1.0f));
|
||
```
|
||
|
||
### Note Particles
|
||
|
||
Note particles use the `offsetX` argument to determine the color. `offsetY` and `offsetZ` are ignored.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(0.4f, 0, 0)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 0.4f, 0, 0);
|
||
```
|
||
|
||
#### Note Particle Color Picker
|
||
|
||
`offsetX` values between `-1.0` and `1.0` determine the color. Values outside this range repeat the pattern. For Vanilla note particle colors, set `offsetX` to a fraction of 24 (e.g., `2.0f / 24.0f`).
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.NOTE.builder()
|
||
.location(someLocation)
|
||
.offset(2.0f / 24.0f, 0, 0)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.NOTE, someLocation, 0, 2.0f / 24.0f, 0, 0);
|
||
```
|
||
|
||
### Trail Particles
|
||
|
||
Trail particles require a `Particle.Trail` object as `data`.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.TRAIL.builder()
|
||
.location(someLocation)
|
||
.offset(1, 1, 1)
|
||
.count(8)
|
||
.data(new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.TRAIL, someLocation, 8, 1, 1, 1, new Particle.Trail(someLocation.clone().add(-4, 0, 4), Color.YELLOW, 40));
|
||
```
|
||
|
||
### Converging Particles
|
||
|
||
These particles converge to a single point. Offset arguments determine the relative spawn location.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.ENCHANT.builder()
|
||
.location(someLocation)
|
||
.offset(-2, 0, 2)
|
||
.count(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.ENCHANT, someLocation, 0, -2, 0, 2);
|
||
```
|
||
|
||
* **Curving:** `ENCHANT`, `NAUTILUS`, `PORTAL`, `VAULT_CONNECTION`
|
||
* **Straight:** `OMINOUS_SPAWNING`
|
||
|
||
#### List of Converging Particles
|
||
|
||
* ENCHANT
|
||
* NAUTILUS
|
||
* OMINOUS_SPAWNING
|
||
* PORTAL
|
||
* VAULT_CONNECTION
|
||
|
||
### Material Particles
|
||
|
||
#### BlockData
|
||
|
||
To spawn particles requiring `BlockData`, use `BlockData` as the `data` argument.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.BLOCK_CRUMBLE.builder()
|
||
.location(someLocation)
|
||
.count(4)
|
||
.data(BlockType.GLOWSTONE.createBlockData())
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.BLOCK_CRUMBLE, someLocation, 4, BlockType.GLOWSTONE.createBlockData());
|
||
```
|
||
|
||
Using `BlockType.createBlockData()` is preferred over legacy methods.
|
||
|
||
The `BLOCK` particle is a directional particle, so velocity matters.
|
||
|
||
#### ItemStack
|
||
|
||
To spawn particles requiring an `ItemStack`, use an `ItemStack` as the `data` argument.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.ITEM.builder()
|
||
.location(someLocation)
|
||
.count(4)
|
||
.data(ItemStack.of(Material.DIAMOND_PICKAXE))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.ITEM, someLocation, 4, ItemStack.of(Material.DIAMOND_PICKAXE));
|
||
```
|
||
|
||
Using `ItemStack.of(Material)` is preferred over legacy methods.
|
||
|
||
The `ITEM` particle is a directional particle.
|
||
|
||
### Sculk Particles
|
||
|
||
#### Sculk Charge
|
||
|
||
The `SCULK_CHARGE` particle takes a `float` as `data`, representing the particle's roll in radians.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.SCULK_CHARGE.builder()
|
||
.location(someLocation)
|
||
.data((float) Math.toRadians(45))
|
||
.extra(0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.SCULK_CHARGE, someLocation, 1, 0, 0, 0, 0, (float) Math.toRadians(45));
|
||
```
|
||
|
||
`SCULK_CHARGE` is a directional particle.
|
||
|
||
#### Shriek
|
||
|
||
The `SHRIEK` particle takes an `integer` as `data`, representing the delay in ticks before the particle spawns.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.SHRIEK.builder()
|
||
.location(someLocation)
|
||
.data(20)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.SHRIEK, someLocation, 1, 20);
|
||
```
|
||
|
||
#### Vibration
|
||
|
||
Vibration particles require a `Vibration` object as `data`, specifying a location (`Vibration.Destination.BlockDestination`) or entity target (`Vibration.Destination.EntityDestination`). The constructor's second argument is the travel time in ticks.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.VIBRATION.builder()
|
||
.location(someLocation)
|
||
.data(new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40))
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.VIBRATION, someLocation, 1, new Vibration(new Vibration.Destination.BlockDestination(otherLocation), 40));
|
||
```
|
||
|
||
### Rising Particles
|
||
|
||
These particles use `offsetY` as the y-axis velocity. Setting `offsetX` and `offsetZ` to `0` results in minimal x/z velocity.
|
||
|
||
Caution: `EFFECT` and `INSTANT_EFFECT` are powered particles.
|
||
|
||
Example:
|
||
|
||
```java
|
||
Particle.GLOW.builder()
|
||
.location(someLocation)
|
||
.count(0)
|
||
.offset(0, 2, 0)
|
||
.receivers(32, true)
|
||
.spawn();
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.GLOW, someLocation, 0, 0, 2, 0);
|
||
```
|
||
|
||
Initial velocity is used briefly, then the particle rises.
|
||
|
||
#### List of Rising Particles
|
||
|
||
* EFFECT
|
||
* ENTITY_EFFECT
|
||
* GLOW
|
||
* INFESTED
|
||
* INSTANT_EFFECT
|
||
* RAID_OMEN
|
||
* TRIAL_OMEN
|
||
* WITCH
|
||
|
||
### Scalable Particles
|
||
|
||
These particles can be scaled with `offsetX`. `offsetY` and `offsetZ` are ignored.
|
||
|
||
#### Sweep Attack Particles
|
||
|
||
The `SWEEP_ATTACK` particle's scale is calculated as `1.0 - offsetX * 0.5`.
|
||
|
||
Example:
|
||
|
||
```java
|
||
ParticleBuilder sweepAttackParticleBuilder = Particle.SWEEP_ATTACK.builder().location(someLocation).count(0).receivers(32, true);
|
||
sweepAttackParticleBuilder.spawn();
|
||
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> sweepAttackParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0);
|
||
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.SWEEP_ATTACK, someLocation, 0, -2.0, 0, 0), 10);
|
||
```
|
||
|
||
#### Explosion Particles
|
||
|
||
The `EXPLOSION` particle's scale is calculated as `2.0 * (1.0 - offsetX * 0.5)`.
|
||
|
||
Example:
|
||
|
||
```java
|
||
ParticleBuilder explosionParticleBuilder = Particle.EXPLOSION.builder().location(someLocation).offset(1, 0, 0).count(0).receivers(32, true);
|
||
explosionParticleBuilder.spawn();
|
||
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> explosionParticleBuilder.offset(-2.0, 0, 0).spawn(), 10);
|
||
|
||
//or
|
||
someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, 1, 0, 0);
|
||
|
||
Bukkit.getScheduler().runTaskLater(plugin, () -> someWorld.spawnParticle(Particle.EXPLOSION, someLocation, 0, -2.0, 0, 0), 10);
|
||
```
|
||
|
||
### Miscellaneous Behaviors
|
||
|
||
#### Angry Villager Particles
|
||
|
||
The `ANGRY_VILLAGER` particle spawns `0.5` higher (y-axis) than the supplied location.
|
||
|
||
#### Cloud Particles
|
||
|
||
The `CLOUD` and `SNEEZE` particles move towards the player's y level if within two blocks. They attempt to match vertical velocity.
|
||
|
||
#### Damage Indicator Particles
|
||
|
||
The `DAMAGE_INDICATOR` particle adds `1.0` to the provided `offsetY`.
|
||
|
||
#### Dust Pillar Particles
|
||
|
||
The `DUST_PILLAR` particle uses `offsetY` for the y-axis velocity. `offsetX` and `offsetZ` are ignored.
|
||
|
||
#### Dust Plume Particles
|
||
|
||
The `DUST_PLUME` particle adds `0.15` to the provided `offsetY`.
|
||
|
||
#### Firefly Particles
|
||
|
||
The `FIREFLY` particle uses `offsetY` as the initial y-axis velocity, but there's a 50% chance for the sign to be inverted.
|
||
|
||
#### Powered Particles
|
||
|
||
Powered particles multiply the velocity vector by the supplied argument. The y component is calculated as `(verticalVelocity - 0.1) * power + 0.1`.
|
||
|
||
##### List of Powered Particles
|
||
|
||
* EFFECT
|
||
* INSTANT_EFFECT
|
||
* DRAGON_BREATH
|
||
|
||
#### Splash Particles
|
||
|
||
The `SPLASH` particle uses `offsetX` and `offsetZ` to determine the velocity vector if `offsetY` is `0` and either `offsetX` or `offsetZ` are not `0`.
|
||
|
||
---
|
||
|
||
## plugin.yml
|
||
|
||
### Overview
|
||
|
||
The `plugin.yml` file is the primary configuration file for a Paper plugin. It contains metadata about the plugin, its dependencies, permissions, and commands. It resides in the `resources` directory of the project.
|
||
|
||
### Example
|
||
|
||
```yaml
|
||
name: ExamplePlugin
|
||
version: 1.0.0
|
||
main: io.papermc.testplugin.ExamplePlugin
|
||
description: An example plugin
|
||
author: PaperMC
|
||
website: https://papermc.io
|
||
api-version: '1.21.10'
|
||
```
|
||
|
||
### Fields
|
||
|
||
*Note: Asterisks (*) indicate required fields.*
|
||
|
||
#### name*
|
||
|
||
The name of the plugin. Displayed in plugin lists and log messages (unless `prefix` is set).
|
||
|
||
```yaml
|
||
name: ExamplePlugin
|
||
```
|
||
|
||
#### version*
|
||
|
||
The plugin's current version. Shown in plugin info messages and server logs.
|
||
|
||
```yaml
|
||
version: 1.0.0
|
||
```
|
||
|
||
#### main*
|
||
|
||
The main class of the plugin. Extends `JavaPlugin` and serves as the plugin's entry point. It's the package path and class name.
|
||
|
||
```yaml
|
||
main: io.papermc.testplugin.ExamplePlugin
|
||
```
|
||
|
||
#### description
|
||
|
||
A brief description of the plugin. Shown in plugin info commands.
|
||
|
||
```yaml
|
||
description: An example plugin
|
||
```
|
||
|
||
#### author / authors
|
||
|
||
The plugin's author(s). Can be a single author or a list.
|
||
|
||
```yaml
|
||
author: PaperMC
|
||
authors: [PaperMC, SpigotMC, Bukkit]
|
||
```
|
||
|
||
#### contributors
|
||
|
||
Contributors to the plugin who aren't managing authors.
|
||
|
||
```yaml
|
||
contributors: [PaperMC, SpigotMC, Bukkit]
|
||
```
|
||
|
||
#### website
|
||
|
||
The plugin's website (e.g., GitHub repository or plugin page).
|
||
|
||
```yaml
|
||
website: https://papermc.io
|
||
```
|
||
|
||
#### api-version
|
||
|
||
The Paper API version the plugin uses. Includes minor version support from 1.20.5 onwards. Servers with lower versions refuse to load the plugin. Valid versions are 1.13 - 1.21.10.
|
||
|
||
```yaml
|
||
api-version: '1.21.10'
|
||
```
|
||
|
||
*Note: If unspecified, the plugin loads as a legacy plugin with a console warning.*
|
||
|
||
#### load
|
||
|
||
Determines when the server loads the plugin: `STARTUP` or `POSTWORLD`. Defaults to `POSTWORLD`.
|
||
|
||
```yaml
|
||
load: STARTUP
|
||
```
|
||
|
||
#### prefix
|
||
|
||
A prefix displayed in the log instead of the plugin name.
|
||
|
||
```yaml
|
||
prefix: EpicPaperMCHypePlugin
|
||
```
|
||
|
||
#### libraries
|
||
|
||
A list of libraries the plugin depends on. Downloaded from Maven Central and added to the classpath, eliminating the need for shading and relocation.
|
||
|
||
```yaml
|
||
libraries:
|
||
- com.google.guava:guava:30.1.1-jre
|
||
- com.google.code.gson:gson:2.8.6
|
||
```
|
||
|
||
*Note: The central repository is configurable via the `PAPER_DEFAULT_CENTRAL_REPOSITORY` environment variable and `org.bukkit.plugin.java.LibraryLoader.centralURL` system property.*
|
||
|
||
#### permissions
|
||
|
||
A list of permissions the plugin uses to restrict access to features.
|
||
|
||
```yaml
|
||
permissions:
|
||
permission.node:
|
||
description: "This is a permission node"
|
||
default: op
|
||
children:
|
||
permission.node.child: true
|
||
another.permission.node:
|
||
description: "This is another permission node"
|
||
default: notop
|
||
```
|
||
|
||
* **description:** Description of the permission node.
|
||
* **default:** Default value (`op`, `notop`, `true`, `false`). Defaults to `default-permission` if unspecified, which defaults to `op`.
|
||
* **children:** Inherits the parent permission if set to `true`.
|
||
|
||
#### default-permission
|
||
|
||
The default value for permissions without a specified `default` value (`op`,
|
||
|
||
```markdown
|
||
# Reading Stacktraces
|
||
|
||
## Overview
|
||
|
||
This page explains what a stacktrace is and how to read it for debugging purposes.
|
||
|
||
## What is a stacktrace?
|
||
|
||
* A stacktrace shows the call stack of a thread in Java.
|
||
* The call stack represents the path of execution that led to the current point in the program.
|
||
* Stacktraces are usually printed to the console when an exception is not handled correctly.
|
||
* They are useful for debugging because they show the exact line of code that caused an error and the lines of code that called that line, revealing the path of execution.
|
||
|
||
## Example
|
||
|
||
Here's an example of a stacktrace caused by a `NullPointerException`:
|
||
|
||
```
|
||
[15:20:42 ERROR]: Could not pass event PluginEnableEvent to TestPlugin v1.0
|
||
java.lang.NullPointerException: Cannot invoke "Object.toString()" because "player" is null
|
||
at io.papermc.testplugin.TestPlugin.onPluginEnable(TestPlugin.java:23) ~[TestPlugin-1.0-SNAPSHOT.jar:?]
|
||
at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor1.execute(Unknown Source) ~[?:?]
|
||
at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:git-Paper-49]
|
||
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at io.papermc.paper.plugin.manager.PaperPluginInstanceManager.enablePlugin(PaperPluginInstanceManager.java:200) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.enablePlugin(PaperPluginManagerImpl.java:104) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:507) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?]
|
||
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugin(CraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at org.bukkit.craftbukkit.v1_20_R2.CraftServer.enablePlugins(CraftServer.java:547) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.loadWorld0(MinecraftServer.java:636) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.loadLevel(MinecraftServer.java:435) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:308) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1101) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:318) ~[paper-1.20.2.jar:git-Paper-49]
|
||
at java.lang.Thread.run(Thread.java:833) ~[?:?]
|
||
```
|
||
|
||
* The error occurred when a `PluginEnableEvent` was being handled by `TestPlugin`.
|
||
* The cause was a `NullPointerException` when trying to call `toString()` on a null "player" object.
|
||
* The error was thrown at line 23 of `TestPlugin.java`.
|
||
* The rest of the stacktrace shows server internals, which can generally be ignored in this case.
|
||
|
||
## Omitted stacktraces
|
||
|
||
* In JDK 5, the JVM started omitting stacktraces for certain exceptions when the code was optimized.
|
||
* To fix this, you can pass the `-XX:-OmitStackTraceInFastThrow` flag to the JVM:
|
||
|
||
```
|
||
java -XX:-OmitStackTraceInFastThrow -jar paper.jar
|
||
```
|
||
|
||
---
|
||
|
||
# Recipes
|
||
|
||
## Overview
|
||
|
||
Recipes define how to craft specific items in Minecraft. This page covers how to define recipes using plugins.
|
||
|
||
## ShapedRecipe
|
||
|
||
* A shaped recipe requires a specific pattern of items in the crafting grid.
|
||
* Created using pattern strings and a map of characters to items.
|
||
* Pattern strings are 3, 3-character strings representing the rows of the crafting grid.
|
||
|
||
Example:
|
||
|
||
```java
|
||
public class TestPlugin extends JavaPlugin {
|
||
@Override
|
||
public void onEnable() {
|
||
NamespacedKey key = new NamespacedKey(this, "television");
|
||
ItemStack item = ItemStack.of(Material.BLACK_WOOL);
|
||
item.setData(DataComponentTypes.ITEM_NAME, Component.text("Television"));
|
||
ShapedRecipe recipe = new ShapedRecipe(key, item);
|
||
recipe.shape(
|
||
"AAA",
|
||
"ABA",
|
||
"AAA"
|
||
);
|
||
recipe.setIngredient('A', Material.WHITE_CONCRETE);
|
||
recipe.setIngredient('B', Material.BLACK_STAINED_GLASS_PANE);
|
||
getServer().addRecipe(recipe);
|
||
}
|
||
}
|
||
```
|
||
|
||
This recipe requires a black stained glass pane surrounded by white concrete to craft a television.
|
||
|
||
```
|
||
AAA
|
||
ABA
|
||
AAA
|
||
```
|
||
|
||
**Note:**
|
||
|
||
* Recipes can be registered at any time, not just in `onEnable`.
|
||
* If registering after the plugin has been enabled and players are online, resend recipes to players or use the boolean parameter in `addRecipe` to update players.
|
||
|
||
**Caution:**
|
||
|
||
* `Air` cannot be used as a material in a shaped recipe.
|
||
|
||
## ShapelessRecipe
|
||
|
||
* A shapeless recipe requires a specific number of items in the crafting grid, but not in a specific pattern.
|
||
* Created using a list of items.
|
||
|
||
Example:
|
||
|
||
```java
|
||
public class TestPlugin extends JavaPlugin {
|
||
@Override
|
||
public void onEnable() {
|
||
NamespacedKey key = new NamespacedKey(this, "WarriorSword");
|
||
ItemStack item = ItemStack.of(Material.DIAMOND_SWORD);
|
||
ShapelessRecipe recipe = new ShapelessRecipe(key, item);
|
||
recipe.addIngredient(3, Material.DIAMOND);
|
||
recipe.addIngredient(2, Material.STICK);
|
||
getServer().addRecipe(recipe);
|
||
}
|
||
}
|
||
```
|
||
|
||
This recipe requires 3 diamonds and 2 sticks to craft a diamond sword, without any specific orientation.
|
||
|
||
Possible crafting grid layouts:
|
||
|
||
```
|
||
DSS | SDS | S D
|
||
D | D | D
|
||
D | D | D S
|
||
```
|
||
|
||
---
|
||
|
||
# Registries
|
||
|
||
## Overview
|
||
|
||
This page explains the Registry API in Paper, which is used to manage game data.
|
||
|
||
**Experimental:** The Registry API is currently experimental and subject to change.
|
||
|
||
## What is a registry?
|
||
|
||
* A registry holds a set of values of the same type, each identified by a key.
|
||
* Example: `ItemType` registry holds all known item types.
|
||
* Registries are accessed via the `RegistryAccess` class.
|
||
* Registries are defined by the server and sent to the client, allowing servers and plugins to define custom content.
|
||
* Notable examples include enchantments and biomes.
|
||
|
||
## Retrieving values from a registry
|
||
|
||
* Elements are retrieved using their respective keys.
|
||
* Two types of keys:
|
||
* `net.kyori.adventure.key.Key`: Represents a namespace and a key.
|
||
* `TypedKey`: Wraps an Adventure key and includes the key of the registry it belongs to.
|
||
|
||
Example (retrieving the Sharpness enchantment):
|
||
|
||
```java
|
||
// Fetch the enchantment registry from the registry access
|
||
final Registry<Enchantment> enchantmentRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT);
|
||
|
||
// Get the sharpness enchantment using its key.
|
||
// getOrThrow may be replaced with get if the registry may not contain said value
|
||
final Enchantment enchantment = enchantmentRegistry.getOrThrow(TypedKey.create(RegistryKey.ENCHANTMENT, Key.key("minecraft:sharpness")));
|
||
|
||
// Same as above, but using the instance's method
|
||
final Enchantment enchantment = enchantmentRegistry.getOrThrow(RegistryKey.ENCHANTMENT.typedKey(Key.key("minecraft:sharpness")));
|
||
|
||
// Same as above, but using generated create method
|
||
// available for data-driven registries or "writable" ones
|
||
// (those bound to a lifecycle event in RegistryEvents).
|
||
final Enchantment enchantment = enchantmentRegistry.getOrThrow(EnchantmentKeys.create(Key.key("minecraft:sharpness")));
|
||
|
||
// Same as above too, but using generated typed keys.
|
||
// Only Vanilla entries have generated keys, for custom entries, the above method must be used.
|
||
final Enchantment enchantment = enchantmentRegistry.getOrThrow(EnchantmentKeys.SHARPNESS);
|
||
```
|
||
|
||
## Referencing registry values
|
||
|
||
* Referencing registry entries can be done using different approaches.
|
||
* `RegistrySet`: Defines a collection of elements that relate to a registry.
|
||
* `RegistryKeySet`: A subtype of `RegistrySet` that holds `TypedKey` instances. It remains valid even if registry values change.
|
||
|
||
Example (creating a `RegistryKeySet`):
|
||
|
||
```java
|
||
// Create a new registry key set that holds a collection enchantments
|
||
final RegistryKeySet<Enchantment> bestEnchantments = RegistrySet.keySet(RegistryKey.ENCHANTMENT,
|
||
// Arbitrary keys of enchantments to store in the key set.
|
||
EnchantmentKeys.CHANNELING,
|
||
EnchantmentKeys.create(Key.key("papermc:softspoon"))
|
||
);
|
||
```
|
||
|
||
* `Tag`: Follows the concept of a `RegistryKeySet` but is named and can be referenced.
|
||
|
||
## Mutating registries
|
||
|
||
* Paper allows plugins to modify registries.
|
||
|
||
**Caution:**
|
||
|
||
* Mutating registries must be done during the server's bootstrap phase.
|
||
* Only applicable to Paper plugins.
|
||
* Exceptions thrown during this phase will cause the server to shut down.
|
||
|
||
**Note:**
|
||
|
||
* Mutating registries is done via the `LifecycleEventManager`.
|
||
|
||
* The general entry point for mutating registries is the `RegistryEvents` type.
|
||
* Modification can take two forms: creating new entries and modifying existing entries.
|
||
|
||
### Create new entries
|
||
|
||
* Done via the `compose` lifecycle event on registries.
|
||
* The `compose` event is called after a registry's content has been loaded from vanilla sources and datapacks.
|
||
|
||
Example (creating a new enchantment):
|
||
|
||
```java
|
||
public class TestPluginBootstrap implements PluginBootstrap {
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
// Register a new handler for the compose lifecycle event on the enchantment registry
|
||
context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.compose().newHandler(event -> {
|
||
event.registry().register(
|
||
// The key of the registry
|
||
// Plugins should use their own namespace instead of minecraft or papermc
|
||
EnchantmentKeys.create(Key.key("papermc:pointy")),
|
||
b -> b.description(Component.text("Pointy"))
|
||
.supportedItems(event.getOrCreateTag(ItemTypeTagKeys.SWORDS))
|
||
.anvilCost(1)
|
||
.maxLevel(25)
|
||
.weight(10)
|
||
.minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1))
|
||
.maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(3, 1))
|
||
.activeSlots(EquipmentSlotGroup.ANY)
|
||
);
|
||
}));
|
||
}
|
||
}
|
||
```
|
||
|
||
### Modifying existing entries
|
||
|
||
* Useful for plugins that want to change the behavior of Vanilla entries.
|
||
* Use the `entryAdd` lifecycle event.
|
||
* The event is called for any entry added to a registry.
|
||
|
||
Example (increasing the maximum level of the Sharpness enchantment):
|
||
|
||
```java
|
||
@Override
|
||
public void bootstrap(BootstrapContext context) {
|
||
context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.entryAdd()
|
||
// Increase the max level to 20
|
||
.newHandler(event -> event.builder().maxLevel(20))
|
||
// Configure the handler to only be called for the Vanilla sharpness enchantment.
|
||
.filter(EnchantmentKeys.SHARPNESS)
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
# Roadmap
|
||
|
||
## Overview
|
||
|
||
This page documents planned API changes and potential deprecations in Paper.
|
||
|
||
## Future plans
|
||
|
||
### Interface ItemStacks
|
||
|
||
* Currently, creating `ItemStack`s using the constructor creates an API representation that delegates to an NMS-backed object.
|
||
* Use `ItemStack#of` to get the NMS-backed object directly.
|
||
* In the future, `ItemStack` will be converted to an interface, and the constructor will be removed.
|
||
|
||
#### Precautions
|
||
|
||
* Avoid directly extending the `ItemStack` class. Custom implementations are not supported and will break.
|
||
|
||
### ServerPlayer reuse
|
||
|
||
* **Note:** Only applies to NMS usage, not API.
|
||
* Avoid directly storing player (`ServerPlayer`) entity instances.
|
||
* Currently, the player instance is reused when switching worlds, but this will be reverted to match Vanilla behavior.
|
||
* API entities (wrappers) will continue to function, and their underlying instance will be replaced automatically.
|
||
|
||
## Deprecation policy
|
||
|
||
**Caution:** Avoid using deprecated APIs. It may cause instability and performance issues.
|
||
|
||
* API marked with `@Deprecated` should not be used, as alternative API may be available.
|
||
* Deprecated API may be marked for removal in the future.
|
||
|
||
```java
|
||
@Deprecated
|
||
public void exampleMethod(); // Example deprecated method
|
||
```
|
||
|
||
### Deprecated for removal
|
||
|
||
* API may be marked as `@Deprecated` and `forRemoval` with a `@ApiStatus.ScheduledForRemoval` version.
|
||
* Removal will only occur within major Minecraft release versions.
|
||
* Migrate away from API scheduled for removal.
|
||
* Adequate time will be given to allow plugin developers to migrate.
|
||
|
||
```java
|
||
@ApiStatus.ScheduledForRemoval(inVersion = "1.20")
|
||
@Deprecated(forRemoval = true)
|
||
public void exampleMethod(); // Example method marked for removal in 1.20
|
||
```
|
||
|
||
## Deprecation reasons
|
||
|
||
Common reasons for API deprecation:
|
||
|
||
### Old API
|
||
|
||
* API represents concepts that no longer exist in the core game.
|
||
* May not be functional or may behave unexpectedly.
|
||
|
||
### Duplicate API
|
||
|
||
* API added by Spigot that clashes with existing Paper API.
|
||
* Paper will typically deprecate Spigot's API in favor of its own.
|
||
|
||
### Obsolete API
|
||
|
||
* Paper has built new APIs to offer as replacements.
|
||
* Obsolete API is expected to function for the far future and may not be scheduled for removal for a long time.
|
||
|
||
---
|
||
|
||
# paperweight-userdev
|
||
|
||
## Overview
|
||
|
||
`paperweight` is Paper's custom build tooling. The `paperweight-userdev` Gradle plugin provides access to internal code (NMS) during development.
|
||
|
||
**Note:** This guide uses the Gradle Kotlin DSL and assumes basic Gradle knowledge. See the [example plugin](link to example plugin) for a fully-functioning example.
|
||
|
||
## Why this is useful
|
||
|
||
* Only supported way of accessing server internals. Redistributing the server JAR is against the Minecraft EULA.
|
||
* Avoids issues with Spigot mappings (pre-1.20.5 Paper), which are a mix of obfuscated and mapped names.
|
||
* Allows using fully deobfuscated types, names, and fields during development, then remaps the plugin for use with the obfuscated server.
|
||
* Does not apply to reflection. Use a library like [this library](link to reflection library) for non-obfuscated names in reflection.
|
||
* As of Minecraft 1.20.5, Paper ships with a Mojang-mapped runtime.
|
||
|
||
## Adding the plugin
|
||
|
||
Add the plugin to your `build.gradle.kts` file:
|
||
|
||
```kotlin
|
||
plugins {
|
||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
|
||
}
|
||
```
|
||
|
||
**Gradle Version:** Use the latest stable version of Gradle. See the [Gradle Wrapper documentation](link to Gradle Wrapper documentation) for upgrading.
|
||
|
||
**Keep up to date:** The latest version of `paperweight-userdev` supports dev bundles for Minecraft 1.17.1 and newer.
|
||
|
||
**Support:** Only the latest version of `paperweight-userdev` is officially supported. Ask in the `#build-tooling-help` channel in the [Discord server](link to Discord server) for issues.
|
||
|
||
**Snapshots:** `paperweight-userdev` SNAPSHOT (pre-release) versions are only available through Paper's Maven repository:
|
||
|
||
```kotlin
|
||
pluginManagement {
|
||
repositories {
|
||
gradlePluginPortal()
|
||
maven("https://repo.papermc.io/repository/maven-public/")
|
||
}
|
||
}
|
||
```
|
||
|
||
## Adding the dev bundle dependency
|
||
|
||
Add a dev bundle dependency to your `dependencies` block in `build.gradle.kts`:
|
||
|
||
```kotlin
|
||
dependencies {
|
||
// Other Dependencies
|
||
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
|
||
}
|
||
```
|
||
|
||
**Tip:** Remove any dependency on the Paper API, as the dev bundle includes that.
|
||
|
||
### Configuring the Java toolchain for userdev setup
|
||
|
||
If the dev bundle doesn't support the Gradle's Java toolchain, configure `paperweight`'s `javaLauncher` property:
|
||
|
||
```kotlin
|
||
paperweight {
|
||
javaLauncher = javaToolchains.launcherFor {
|
||
// Example scenario:
|
||
// Paper 1.17.1 was originally built with JDK 16
|
||
languageVersion = JavaLanguageVersion.of(17)
|
||
}
|
||
}
|
||
```
|
||
|
||
## Gradle tasks
|
||
|
||
### reobfJar
|
||
|
||
* Creates a plugin JAR re-obfuscated to Spigot's runtime mappings.
|
||
* Works on standard Paper servers.
|
||
* Output is in the `build/libs` folder.
|
||
* The JAR whose filename includes `-dev` is Mojang-mapped and will not work on most servers.
|
||
|
||
#### Shadow
|
||
|
||
* If the shadow Gradle plugin is applied, `paperweight-userdev` will detect it and use the shaded JAR as input for `reobfJar`.
|
||
* The `-dev-all.jar` file in `build/libs` is the shaded, but not re-obfuscated JAR.
|
||
|
||
Run the `reobfJar` task on the default `build` task:
|
||
|
||
```kotlin
|
||
tasks.assemble {
|
||
dependsOn(tasks.reobfJar)
|
||
}
|
||
```
|
||
|
||
### 1.20.5 and beyond
|
||
|
||
* As of 1.20.5, Paper ships with a Mojang-mapped runtime.
|
||
* CraftBukkit classes are no longer relocated into a versioned package.
|
||
* Plugins must be deobfuscated before loading when necessary.
|
||
|
||
### Default mappings assumption
|
||
|
||
* Spigot/Bukkit plugins are assumed to be Spigot-mapped if they don't specify their mappings namespace in the manifest.
|
||
* Paper plugins are assumed to be Mojang-mapped if they don't specify their mappings namespace in the manifest.
|
||
* Spigot-mapped plugins will need to be deobfuscated on first load.
|
||
|
||
### Compiling to Mojang mappings
|
||
|
||
* Preferred option: skips the one-time plugin remapping process and may allow version compatibility across smaller updates.
|
||
* Makes the plugin incompatible with Spigot servers.
|
||
* Remove all `dependsOn(reobfJar)` lines and add:
|
||
|
||
```kotlin
|
||
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
|
||
```
|
||
|
||
### Compiling to Spigot mappings
|
||
|
||
* Add:
|
||
|
||
```kotlin
|
||
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
|
||
```
|
||
|
||
* Useful for plugins that have loaders for both Spigot and Paper.
|
||
|
||
**Note:** If using Gradle with the Groovy DSL, access the fields via static methods like `getMOJANG_PRODUCTION()`.
|
||
|
||
---
|
||
|
||
# Using databases
|
||
|
||
## Overview
|
||
|
||
This guide covers using databases in Paper plugins for storing larger amounts of data.
|
||
|
||
## What is a database?
|
||
|
||
A database is a collection of information stored electronically on a computer system. The main categories are SQL and NoSQL.
|
||
|
||
## NoSQL vs SQL
|
||
|
||
* **NoSQL (Not Only SQL):**
|
||
* Schema-less and offers flexible data models.
|
||
* Designed to handle large volumes of unstructured or semi-structured data.
|
||
* Uses various data models (key-value, document, column-family, graph).
|
||
* **SQL:**
|
||
* Follows the relational database model.
|
||
* Organizes data into structured tables with predefined schemas.
|
||
* Uses SQL (Structured Query Language) for interaction.
|
||
|
||
## File-based vs standalone databases
|
||
|
||
* **File-based:** Stored in a file on disk, used for smaller databases.
|
||
* **Standalone:** Operates in a separate process, used for larger data models.
|
||
|
||
## File-based databases
|
||
|
||
* Stored within a single file on disk.
|
||
* Easier to set up and use, but offer lesser performance.
|
||
* Examples: SQLite and H2.
|
||
|
||
### Simple SQLite Setup
|
||
|
||
#### SQLite
|
||
|
||
Requires a driver to connect/initialize the database.
|
||
|
||
**Note:** The JDBC Driver is bundled with Paper.
|
||
|
||
#### Usage
|
||
|
||
Invoke `Class#forName(String)` on the driver to initialize and create the connection:
|
||
|
||
```java
|
||
public class DatabaseManager {
|
||
public void connect() {
|
||
Class.forName("org.sqlite.JDBC");
|
||
Connection connection = DriverManager.getConnection("jdbc:sqlite:plugins/TestPlugin/database.db");
|
||
}
|
||
}
|
||
```
|
||
|
||
You then have access to a `Connection` object, which can be used to create a `Statement` and execute SQL queries.
|
||
|
||
## Standalone databases
|
||
|
||
* Operate in a separate process.
|
||
* Harder to set up, but offer better performance.
|
||
* Examples: MySQL, MariaDB, and PostgreSQL.
|
||
|
||
These databases often have connection pooling for improved performance.
|
||
|
||
### Simple MySQL Setup
|
||
|
||
#### MySQL
|
||
|
||
Requires a running MySQL database.
|
||
|
||
First, add the dependency to your project:
|
||
|
||
##### Maven
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.zaxxer</groupId>
|
||
<artifactId>HikariCP</artifactId>
|
||
<version>4.0.3</version>
|
||
<scope>compile</scope>
|
||
</dependency>
|
||
```
|
||
|
||
##### Gradle
|
||
|
||
```kotlin
|
||
dependencies {
|
||
implementation("com.zaxxer:HikariCP:4.0.3")
|
||
}
|
||
```
|
||
|
||
**Caution:** The Hikari library is not bundled with Paper, so you will need to shade/relocate it using the [Shadow plugin](link to Shadow plugin). Alternatively, use the library loader.
|
||
|
||
#### Usage
|
||
|
||
```java
|
||
public class DatabaseManager {
|
||
public void connect() {
|
||
HikariConfig config = new HikariConfig();
|
||
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydatabase"); // Address of your running MySQL database
|
||
config.setUsername("username"); // Username
|
||
config.setPassword("password"); // Password
|
||
config.setMaximumPoolSize(10); // Pool size defaults to 10
|
||
config.addDataSourceProperty("", ""); // MISC settings to add
|
||
|
||
HikariDataSource dataSource = new HikariDataSource(config);
|
||
|
||
try (Connection connection = dataSource.getConnection()) {
|
||
// Use a try-with-resources here to autoclose the connection.
|
||
PreparedStatement sql = connection.prepareStatement("SQL");
|
||
// Execute statement
|
||
} catch (Exception e) {
|
||
// Handle any exceptions that arise from getting / handing the exception.
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Security
|
||
|
||
### SQL Injection
|
||
|
||
SQL injection is a malicious technique where attackers exploit improper input validation to execute unauthorized SQL commands.
|
||
|
||
Example:
|
||
|
||
```java
|
||
public void login(String username, String password) {
|
||
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
|
||
// Execute SQL
|
||
}
|
||
```
|
||
|
||
If the user enters `' OR 1=1; --` as their username, the SQL statement becomes:
|
||
|
||
```sql
|
||
SELECT * FROM users WHERE username = '' OR 1=1; -- AND password = 'password'
|
||
```
|
||
|
||
This will return all users in the database.
|
||
|
||
### Prepared statements
|
||
|
||
Using prepared statements with `PreparedStatement` helps prevent SQL injection by separating SQL code from user input using placeholders. Always use prepared statements to ensure security and integrity.
|
||
|
||
---
|
||
|
||
# paperweight-userdev
|
||
|
||
## Overview
|
||
|
||
`paperweight` is Paper's custom build tooling. The `paperweight-userdev` Gradle plugin provides access to internal code (NMS) during development.
|
||
|
||
**Note:** This guide uses the Gradle Kotlin DSL and assumes basic Gradle knowledge. See the [example plugin](link to example plugin) for a fully-functioning example.
|
||
|
||
## Why this is useful
|
||
|
||
* Only supported way of accessing server internals. Redistributing the server JAR is against the Minecraft EULA.
|
||
* Avoids issues with Spigot mappings (pre-1.20.5 Paper), which are a mix of obfuscated and mapped names.
|
||
* Allows using fully deobfuscated types, names, and fields during development, then remaps the plugin for use with the obfuscated server.
|
||
* Does not apply to reflection. Use a library like [this library](link to reflection library) for non-obfuscated names in reflection.
|
||
* As of Minecraft 1.20.5, Paper ships with a Mojang-mapped runtime.
|
||
|
||
## Adding the plugin
|
||
|
||
Add the plugin to your `build.gradle.kts` file:
|
||
|
||
```kotlin
|
||
plugins {
|
||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
|
||
}
|
||
```
|
||
|
||
**Gradle Version:** Use the latest stable version of Gradle. See the [Gradle Wrapper documentation](link to Gradle Wrapper documentation) for upgrading.
|
||
|
||
**Keep up to date:** The latest version of `paperweight-userdev` supports dev bundles for Minecraft 1.17.1 and newer.
|
||
|
||
**Support:** Only the latest version of `paperweight-userdev` is officially supported. Ask in the `#build-tooling-help` channel in the [Discord server](link to Discord server) for issues.
|
||
|
||
**Snapshots:** `paperweight-userdev` SNAPSHOT (pre-release) versions are only available through Paper's Maven repository:
|
||
|
||
```kotlin
|
||
pluginManagement {
|
||
repositories {
|
||
gradlePluginPortal()
|
||
maven("https://repo.papermc.io/repository/maven-public/")
|
||
}
|
||
}
|
||
```
|
||
|
||
## Adding the dev bundle dependency
|
||
|
||
Add a dev bundle dependency to your `dependencies` block in `build.gradle.kts`:
|
||
|
||
```kotlin
|
||
dependencies {
|
||
// Other Dependencies
|
||
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
|
||
}
|
||
```
|
||
|
||
**Tip:** Remove any dependency on the Paper API, as the dev bundle includes that.
|
||
|
||
### Configuring the Java toolchain for userdev setup
|
||
|
||
If the dev bundle doesn't support the Gradle's Java toolchain, configure `paperweight`'s `javaLauncher` property:
|
||
|
||
```kotlin
|
||
paperweight {
|
||
javaLauncher = javaToolchains.launcherFor {
|
||
// Example scenario:
|
||
// Paper 1.17.1 was originally built with JDK 16
|
||
languageVersion = JavaLanguageVersion.of(17)
|
||
}
|
||
}
|
||
```
|
||
|
||
## Gradle tasks
|
||
|
||
### reobfJar
|
||
|
||
* Creates a plugin JAR re-obfuscated to Spigot's runtime mappings.
|
||
* Works on standard Paper servers.
|
||
* Output is in the `build/libs` folder.
|
||
* The JAR whose filename includes `-dev` is Mojang-mapped and will not work on most servers.
|
||
|
||
#### Shadow
|
||
|
||
* If the shadow Gradle plugin is applied, `paperweight-userdev` will detect it and use the shaded JAR as input for `reobfJar`.
|
||
* The `-dev-all.jar` file in `build/libs` is the shaded, but not re-obfuscated JAR.
|
||
|
||
Run the `reobfJar` task on the default `build` task:
|
||
|
||
```kotlin
|
||
tasks.assemble {
|
||
dependsOn(tasks.reobfJar)
|
||
}
|
||
```
|
||
|
||
### 1.20.5 and beyond
|
||
|
||
* As of 1.20.5, Paper ships with a Mojang-mapped runtime.
|
||
* CraftBukkit classes are no longer relocated into a versioned package.
|
||
* Plugins must be deobfuscated before loading when necessary.
|
||
|
||
### Default mappings assumption
|
||
|
||
* Spigot/Bukkit plugins are assumed to be Spigot-mapped if they don't specify their mappings namespace in the manifest.
|
||
* Paper plugins are assumed to be Mojang-mapped if they don't specify their mappings namespace in the manifest.
|
||
* Spigot-mapped plugins will need to be deobfuscated on first load.
|
||
|
||
### Compiling to Mojang mappings
|
||
|
||
* Preferred option: skips the one-time plugin remapping process and may allow version compatibility across smaller updates.
|
||
* Makes the plugin incompatible with Spigot servers.
|
||
* Remove all `dependsOn(reobfJar)` lines and add:
|
||
|
||
```kotlin
|
||
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
|
||
```
|
||
|
||
### Compiling to Spigot mappings
|
||
|
||
* Add:
|
||
|
||
```kotlin
|
||
paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
|
||
```
|
||
|
||
* Useful for plugins that have loaders for both Spigot and Paper.
|
||
|
||
**Note:** If using Gradle with the Groovy DSL, access the fields via static methods like `getMOJANG_PRODUCTION()`.
|
||
|
||
---
|
||
|
||
# Scheduling
|
||
|
||
## Overview
|
||
|
||
This page explains how to schedule tasks in Paper plugins.
|
||
|
||
## What is a tick?
|
||
|
||
[Content missing - Document incomplete]
|
||
```
|
||
|
||
```markdown
|
||
# Scheduling in Bukkit
|
||
|
||
The `BukkitScheduler` is used to schedule code execution later or repeatedly.
|
||
|
||
**Note:** This guide is for non-Folia Bukkit servers. Folia servers should use their respective schedulers.
|
||
|
||
## What is a Tick?
|
||
|
||
* Every game runs a game loop to execute game logic repeatedly.
|
||
* In Minecraft, a single execution of this loop is called a 'tick'.
|
||
* Minecraft runs at 20 ticks per second, or one tick every 50 milliseconds.
|
||
* If a tick takes longer than 50ms, the server lags.
|
||
* A task scheduled to run after 100 ticks will run after 5 seconds (at 20 TPS). If the server is running at 10 TPS, it will take 10 seconds.
|
||
|
||
## Converting Between Human Units and Minecraft Ticks
|
||
|
||
* Scheduler methods use ticks as the unit of time for delay or period.
|
||
* Conversion formulas:
|
||
* `ticks = seconds * 20`
|
||
* `seconds = ticks / 20`
|
||
|
||
* Using `TimeUnit` for readability:
|
||
|
||
```java
|
||
TimeUnit.MINUTES.toSeconds(5) * 20 // Converts 5 minutes to ticks
|
||
TimeUnit.SECONDS.toMinutes(ticks / 20) // Converts ticks to minutes
|
||
```
|
||
|
||
* Using `Tick` class from Paper:
|
||
|
||
```java
|
||
Tick.tick().fromDuration(Duration.ofMinutes(5)) // Converts 5 minutes to ticks, yields 6000 ticks
|
||
```
|
||
|
||
## Obtaining the Scheduler
|
||
|
||
* Use the `getScheduler` method on the `Server` class.
|
||
|
||
```java
|
||
@Override
|
||
public void onEnable() {
|
||
BukkitScheduler scheduler = this.getServer().getScheduler();
|
||
}
|
||
```
|
||
|
||
## Scheduling Tasks
|
||
|
||
Scheduling a task requires passing the following:
|
||
|
||
* Your plugin's instance.
|
||
* The code to run, as either a `Runnable` or `Consumer<BukkitTask>`.
|
||
* The delay in ticks before the first execution.
|
||
* The period in ticks between executions for repeating tasks.
|
||
|
||
## Difference Between Synchronous and Asynchronous Tasks
|
||
|
||
### Synchronous Tasks (On the Main Thread)
|
||
|
||
* Executed on the main server thread, which handles all game logic.
|
||
* Tasks on the main thread affect server performance.
|
||
* Use asynchronous tasks for time-consuming operations like web requests, file access, or database interactions.
|
||
|
||
### Asynchronous Tasks (Off the Main Thread)
|
||
|
||
* Executed on separate threads, minimizing impact on server performance.
|
||
* **Caution:** Large parts of the Bukkit API are not thread-safe and should not be used in asynchronous tasks if they change or access the world state.
|
||
* **Note:** Asynchronous tasks are still started from the main thread and affected by server lag.
|
||
* For a scheduler independent of the server, use a `ScheduledExecutorService`. See [this guide](link to guide).
|
||
|
||
## Different Ways to Schedule Tasks
|
||
|
||
### Using `Runnable`
|
||
|
||
* The `Runnable` interface is for simple tasks not requiring a `BukkitTask` instance.
|
||
* Can be implemented in a separate class:
|
||
|
||
```java
|
||
// MyRunnableTask.java
|
||
public class MyRunnableTask implements Runnable {
|
||
private final MyPlugin plugin;
|
||
|
||
public MyRunnableTask(MyPlugin plugin) {
|
||
this.plugin = plugin;
|
||
}
|
||
|
||
@Override
|
||
public void run() {
|
||
this.plugin.getServer().broadcast(Component.text("Hello, World!"));
|
||
}
|
||
}
|
||
|
||
scheduler.runTaskLater(plugin, new MyRunnableTask(plugin), 20);
|
||
```
|
||
|
||
* Or use a lambda expression:
|
||
|
||
```java
|
||
scheduler.runTaskLater(plugin,
|
||
/* Lambda: */
|
||
() -> {
|
||
this.plugin.getServer().broadcast(Component.text("Hello, World!"));
|
||
},
|
||
/* End of the lambda */
|
||
20
|
||
);
|
||
```
|
||
|
||
### Using `Consumer<BukkitTask>`
|
||
|
||
* The `Consumer` interface is for tasks needing a `BukkitTask` instance (e.g., repeated tasks where you want to cancel the task from within).
|
||
* Implement it in a separate class:
|
||
|
||
```java
|
||
// MyConsumerTask.java
|
||
public class MyConsumerTask implements Consumer<BukkitTask> {
|
||
private final UUID entityId;
|
||
|
||
public MyConsumerTask(UUID uuid) {
|
||
this.entityId = uuid;
|
||
}
|
||
|
||
@Override
|
||
public void accept(BukkitTask task) {
|
||
Entity entity = Bukkit.getServer().getEntity(this.entityId);
|
||
if (entity instanceof LivingEntity livingEntity) {
|
||
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
|
||
return;
|
||
}
|
||
task.cancel();
|
||
// The entity is no longer valid, there's no point in continuing to run this task
|
||
}
|
||
}
|
||
|
||
scheduler.runTaskTimer(plugin, new MyConsumerTask(someEntityId), 0, 20);
|
||
```
|
||
|
||
* Or use a lambda expression:
|
||
|
||
```java
|
||
scheduler.runTaskTimer(plugin,
|
||
/* Lambda: */
|
||
task -> {
|
||
Entity entity = Bukkit.getServer().getEntity(entityId);
|
||
if (entity instanceof LivingEntity livingEntity) {
|
||
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
|
||
return;
|
||
}
|
||
task.cancel();
|
||
// The entity is no longer valid, there's no point in continuing to run this task
|
||
}
|
||
/* End of the lambda */
|
||
, 0, 20
|
||
);
|
||
```
|
||
|
||
### Using `BukkitRunnable`
|
||
|
||
* `BukkitRunnable` implements `Runnable` and holds a `BukkitTask` instance, allowing you to use `this.cancel()` within the `run()` method.
|
||
|
||
```java
|
||
// CustomRunnable.java
|
||
public class CustomRunnable extends BukkitRunnable {
|
||
private final UUID entityId;
|
||
|
||
public CustomRunnable(UUID uuid) {
|
||
this.entityId = uuid;
|
||
}
|
||
|
||
@Override
|
||
public void run() {
|
||
Entity entity = Bukkit.getServer().getEntity(this.entityId);
|
||
if (entity instanceof LivingEntity livingEntity) {
|
||
livingEntity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1));
|
||
return;
|
||
}
|
||
this.cancel();
|
||
// The entity is no longer valid, there's no point in continuing to run this task
|
||
}
|
||
}
|
||
|
||
// Example usage (not shown in the original document but inferred)
|
||
new CustomRunnable(someEntityId).runTaskTimer(plugin, 0, 20); // Adds a potion effect until the entity dies.
|
||
```
|
||
|
||
### Using a Delay of 0 Ticks
|
||
|
||
* A delay of 0 ticks runs the task on the next tick.
|
||
* If scheduled during server startup or before the server is enabled, the task executes before the server is enabled.
|
||
|
||
---
|
||
|
||
# Using Databases
|
||
|
||
When storing larger amounts of data inside a plugin, it is recommended to use a database.
|
||
|
||
## What is a Database?
|
||
|
||
A database is a collection of information stored electronically on a computer system. The main two categories are SQL and NoSQL.
|
||
|
||
## NoSQL vs SQL
|
||
|
||
* **NoSQL (Not Only SQL)**:
|
||
* Schema-less and offers flexible data models.
|
||
* Designed to handle large volumes of unstructured or semi-structured data.
|
||
* Uses data models like key-value, document, column-family, or graph.
|
||
* **SQL**:
|
||
* Follows the relational database model.
|
||
* Organizes data into structured tables with predefined schemas.
|
||
* Uses SQL (Structured Query Language) to interact with the database.
|
||
|
||
## File-based vs Standalone Databases
|
||
|
||
* **File-based**: Stored in a file on disk, used for smaller databases.
|
||
* **Standalone**: Operate in a separate process, used for larger data models.
|
||
|
||
## File-based Databases
|
||
|
||
* Stored within a single file on the disk.
|
||
* Easier to set up and use, suitable for smaller databases.
|
||
* Offer lesser performance than standalone databases.
|
||
* Examples: SQLite and H2.
|
||
|
||
### Simple SQLite Setup
|
||
|
||
#### SQLite
|
||
|
||
* Requires a driver to connect to/initialize the database.
|
||
* **Note:** The JDBC Driver is bundled with Paper, so you don't need to shade/relocate it.
|
||
|
||
#### Usage
|
||
|
||
* Invoke `Class#forName(String)` on the driver to allow it to initialize and then create the connection to the database:
|
||
|
||
```java
|
||
// DatabaseManager.java
|
||
public class DatabaseManager {
|
||
public void connect() {
|
||
try {
|
||
Class.forName("org.sqlite.JDBC");
|
||
Connection connection = DriverManager.getConnection("jdbc:sqlite:plugins/TestPlugin/database.db");
|
||
// You then have access to a Connection object,
|
||
// which you can use to create a Statement and execute SQL queries.
|
||
} catch (ClassNotFoundException | SQLException e) {
|
||
e.printStackTrace(); // Handle exceptions appropriately
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
* You then have access to a `Connection` object, which you can use to create a `Statement` and execute SQL queries.
|
||
* Learn more about the Java Database Connectivity API [here](link to Java Database Connectivity API).
|
||
|
||
## Standalone Databases
|
||
|
||
* Operate in a separate process.
|
||
* Harder to set up and use, but offer better performance than file-based databases.
|
||
* Examples: MySQL, MariaDB, and PostgreSQL.
|
||
* Connectors often have connection pooling:
|
||
* Creates a pool of pre-established and reusable database connections.
|
||
* Reduces the overhead of creating and tearing down connections repeatedly.
|
||
* Improves application performance and scalability.
|
||
|
||
### Simple MySQL Setup
|
||
|
||
#### MySQL
|
||
|
||
* Requires more steps but offers performance benefits for larger databases.
|
||
* This is a short setup guide for using the `Hikari` library with MySQL.
|
||
* **Note:** Requires a running MySQL database to connect to.
|
||
|
||
* First, add the dependency to your project:
|
||
|
||
##### Maven
|
||
|
||
```xml
|
||
<!-- pom.xml -->
|
||
<dependency>
|
||
<groupId>com.zaxxer</groupId>
|
||
<artifactId>HikariCP</artifactId>
|
||
<version>4.0.3</version>
|
||
<scope>compile</scope>
|
||
</dependency>
|
||
```
|
||
|
||
##### Gradle
|
||
|
||
```kotlin
|
||
// build.gradle(.kts)
|
||
dependencies {
|
||
implementation("com.zaxxer:HikariCP:4.0.3")
|
||
}
|
||
```
|
||
|
||
* **Caution:** The Hikari library is not bundled with Paper, so you will need to shade/relocate it. Use the Shadow plugin in Gradle.
|
||
* Alternatively, you can use the library loader with your Paper plugin to load the library at runtime. See [here](link to Paper plugin library loader) for more information.
|
||
|
||
#### Usage
|
||
|
||
* Once you have the dependency added, you can work with the connector in your code:
|
||
|
||
```java
|
||
// DatabaseManager.java
|
||
import com.zaxxer.hikari.HikariConfig;
|
||
import com.zaxxer.hikari.HikariDataSource;
|
||
|
||
import java.sql.Connection;
|
||
import java.sql.PreparedStatement;
|
||
import java.sql.SQLException;
|
||
|
||
public class DatabaseManager {
|
||
private HikariDataSource dataSource;
|
||
|
||
public void connect() {
|
||
HikariConfig config = new HikariConfig();
|
||
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydatabase"); // Address of your running MySQL database
|
||
config.setUsername("username"); // Username
|
||
config.setPassword("password"); // Password
|
||
config.setMaximumPoolSize(10); // Pool size defaults to 10
|
||
config.addDataSourceProperty("cachePrepStmts", "true"); // Example MISC setting
|
||
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||
|
||
dataSource = new HikariDataSource(config);
|
||
}
|
||
|
||
public void executeStatement(String sql) {
|
||
try (Connection connection = dataSource.getConnection();
|
||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||
statement.execute();
|
||
} catch (SQLException e) {
|
||
e.printStackTrace(); // Handle any exceptions that arise from getting / handing the exception.
|
||
}
|
||
}
|
||
|
||
public void close() {
|
||
if (dataSource != null) {
|
||
dataSource.close();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Security
|
||
|
||
### SQL Injection
|
||
|
||
* SQL injection is a malicious technique that exploits improper input validation to execute unauthorized SQL commands.
|
||
* Can cause data breaches or damage to the database.
|
||
|
||
* **Example Vulnerable Code:**
|
||
|
||
```java
|
||
public void login(String username, String password) {
|
||
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
|
||
// Execute SQL
|
||
}
|
||
```
|
||
|
||
* **Exploit Example:**
|
||
|
||
If the user enters the following as their username: `' OR 1=1; --`
|
||
|
||
The SQL statement becomes:
|
||
|
||
```sql
|
||
SELECT * FROM users WHERE username = '' OR 1=1; -- AND password = 'password'
|
||
```
|
||
|
||
This returns all users in the database.
|
||
|
||
### Prepared Statements
|
||
|
||
* Using prepared statements with `PreparedStatement`s helps prevent SQL injection.
|
||
* Separates SQL code from user input using placeholders.
|
||
* Reduces the risk of executing unintended SQL commands.
|
||
* **Always use prepared statements for security and data integrity.**
|
||
* Read more about SQL injection [here](link to SQL injection resource).
|
||
``` |