-
-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
55997df
commit 686529e
Showing
6 changed files
with
284 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.41 MB
docs/paper/dev/api/command-api/misc/assets/broadcast-suggestions-finished.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.41 MB
docs/paper/dev/api/command-api/misc/assets/broadcast-suggestions-none.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.16 MB
docs/paper/dev/api/command-api/misc/assets/broadcast-suggestions-unfinished.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
--- | ||
slug: /dev/command-api/misc/basic-command | ||
description: Bukkit commands styled command declaration | ||
--- | ||
|
||
import BroadcastCommand from "./assets/broadcast-command.png" | ||
import BroadcastSuggestionsFinished from "./assets/broadcast-suggestions-finished.png" | ||
import BroadcastSuggestionsUnfinished from "./assets/broadcast-suggestions-unfinished.png" | ||
import BroadcastSuggestionsNone from "./assets/broadcast-suggestions-none.png" | ||
|
||
# Basic Commands | ||
For very simple commands Paper has a way to declare Bukkit-style command by implementing the `BasicCommand` interface. | ||
|
||
This interface has one method you have to override: | ||
- `void execute(CommandSourceStack commandSourceStack, String[] args)` | ||
|
||
And three more, optional methods which you can override: | ||
- `Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args)` | ||
- `boolean canUse(CommandSender sender)` | ||
- `@Nullable String permission()` | ||
|
||
## Simple usage | ||
Implementing the execute method, your class might look like this: | ||
```java title="YourCommand.java" | ||
package your.package.name; | ||
|
||
import io.papermc.paper.command.brigadier.BasicCommand; | ||
import io.papermc.paper.command.brigadier.CommandSourceStack; | ||
|
||
public class YourCommand implements BasicCommand { | ||
|
||
@Override | ||
public void execute(CommandSourceStack commandSourceStack, String[] args) { | ||
|
||
} | ||
} | ||
``` | ||
|
||
If you have seen the `CommandContext<CommandSourceStack>` class before, you might recognize the first parameter of the execute method as the generic | ||
parameter `S` from our `CommandContext<S>`, which is also used in the `executes` method from the `ArgumentBuilder`. | ||
|
||
With a `CommandSourceStack`, we can retrieve basic information about the sender of the command, the location the command was send from, and the executing entity. | ||
For more information, check out [basics/command-executors](../basics/command-executors.mdx). | ||
|
||
|
||
## The optional methods | ||
You can freely choose whether to implement either of the at the top mentioned, optional methods. Here is a quick overview on what which one does: | ||
|
||
### `suggest(CommandSourceStack, String[])` | ||
This method returns some sort of `Collection<String>` and takes in a `CommandSourceStack` and a `String[] args` as parameters. This is similar to the | ||
`onTabComplete(CommandSender, Command, String, String[])` method of the `TabCompleter` interface, which is commonly used for tab completion on Bukkit commands. | ||
|
||
Yeah entry in the collection that you return will be send to the client to be shown as suggestions, the same way as with Bukkit commands. | ||
|
||
### `canUse(CommandSender)` | ||
With this method, you can set up a basic `requires` structure from Brigadier commands. ([Read more on that here] WIP). This method returns a `boolean`, which is | ||
required to return `true` in order for a command sender to execute that command. | ||
|
||
### `permission()` | ||
With the permission method you can set the required permission required to be able to execute and view this command. | ||
|
||
|
||
## Example: Broadcast command | ||
As an example, we can create a simple broadcast command. We start by declaring creating a class which implements BasicCommand and overrides `execute` and `permission`: | ||
```java | ||
package your.package.name; | ||
|
||
import io.papermc.paper.command.brigadier.BasicCommand; | ||
import io.papermc.paper.command.brigadier.CommandSourceStack; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
public class BroadcastCommand implements BasicCommand { | ||
|
||
@Override | ||
public void execute(CommandSourceStack commandSourceStack, String[] args) { | ||
|
||
} | ||
|
||
@Override | ||
public @Nullable String permission() { | ||
return "example.broadcast.use"; | ||
} | ||
} | ||
``` | ||
|
||
Our permission is set to `example.broadcast.use`. In order to give yourself that permission, it is suggested that you use a plugin like **LuckPerms** or just give yourself | ||
operator permissions. You can also set this permission to be `true` by default. For this, please check out the [plugin.yml documentation](../../../getting-started/plugin-yml.mdx). | ||
|
||
Now, in our `execute` method, we can retrieve the name of the executor of that command. If we do not find one, we can just get the name of the command sender, like this: | ||
```java | ||
final Component name; | ||
if (commandSourceStack.getExecutor() != null) { | ||
name = commandSourceStack.getExecutor().name(); | ||
} | ||
else { | ||
name = commandSourceStack.getSender().name(); | ||
} | ||
``` | ||
|
||
This makes sure that we cover all cases and even allow the command to work correctly with `/execute as`. | ||
|
||
Next, we retrieve all arguments and join them to a String or telling the sender that at least one argument is required in order to send a broadcast | ||
(sending empty broadcasts make no sense, after all), if they defined no arguents (`args` has a length of 0): | ||
```java | ||
if (args.length == 0) { | ||
commandSourceStack.getSender().sendRichMessage("<red>You cannot send an empty broadcast!"); | ||
return; | ||
} | ||
|
||
final String message = String.join(" ", args); | ||
``` | ||
|
||
Finally, we can build our broadcast message and send if via `Bukkit.broadcast(Component)`: | ||
```java | ||
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); | ||
``` | ||
|
||
And we are done! As you can see, this is a very simple way to define commands. Here is the final result of our class: | ||
```java title="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.jetbrains.annotations.Nullable; | ||
|
||
public class BroadcastCommand implements BasicCommand { | ||
|
||
@Override | ||
public void execute(CommandSourceStack commandSourceStack, String[] args) { | ||
final Component name; | ||
if (commandSourceStack.getExecutor() != null) { | ||
name = commandSourceStack.getExecutor().name(); | ||
} | ||
else { | ||
name = commandSourceStack.getSender().name(); | ||
} | ||
|
||
if (args.length == 0) { | ||
commandSourceStack.getSender().sendRichMessage("<red>You cannot send an empty broadcast!"); | ||
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 | ||
public @Nullable String permission() { | ||
return "example.broadcast.use"; | ||
} | ||
} | ||
``` | ||
|
||
Registering our command looks like this: | ||
```java title="PluginMainClass.java" | ||
@Override | ||
public void onEnable() { | ||
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, | ||
event -> event.registrar().register("broadcast", new BroadcastCommand()) | ||
); | ||
} | ||
``` | ||
|
||
And this is how it looks like in-game: | ||
<img src={BroadcastCommand}/> | ||
|
||
|
||
## Example: Adding suggestions | ||
Our broadcast command works pretty well, but it is lacking on suggestions. A very common kind of suggestion for text based commands are player names. | ||
In order to suggest player names, we can just map all online players to their name, like this: | ||
```java | ||
@Override | ||
public Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) { | ||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); | ||
} | ||
``` | ||
|
||
This works great, but as you can see here, it will always suggest all players, regardless of user input, which can feel unnatural at times: | ||
<img src={BroadcastSuggestionsUnfinished}/> | ||
|
||
In order to fix this, we have to do some changes: | ||
|
||
First, we early return what we already have in case there is no arguments, as we can not filter by input then: | ||
```java | ||
if (args.length == 0) { | ||
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); | ||
} | ||
``` | ||
|
||
After this, we can add a `filter` clause to our stream, where we filter all names by whether they start with our current input, which is `args[args.length - 1]`: | ||
```java | ||
return Bukkit.getOnlinePlayers().stream() | ||
.map(Player::getName) | ||
.filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase())) | ||
.toList(); | ||
``` | ||
|
||
And we are done! As you can see, suggestions still work fine: | ||
<img src={BroadcastSuggestionsFinished}/> | ||
|
||
But when there is no player who starts with an input, it just suggests nothing: | ||
<img src={BroadcastSuggestionsNone}/> | ||
|
||
### Final code | ||
Here is the final code for our whole `BroadcastCommand` class, including the suggestions: | ||
```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.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Collection; | ||
|
||
public class BroadcastCommand implements BasicCommand { | ||
|
||
@Override | ||
public void execute(CommandSourceStack commandSourceStack, String[] args) { | ||
final Component name; | ||
if (commandSourceStack.getExecutor() != null) { | ||
name = commandSourceStack.getExecutor().name(); | ||
} | ||
else { | ||
name = commandSourceStack.getSender().name(); | ||
} | ||
|
||
if (args.length == 0) { | ||
commandSourceStack.getSender().sendRichMessage("<red>You cannot send an empty broadcast!"); | ||
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 | ||
public @Nullable String permission() { | ||
return "example.broadcast.use"; | ||
} | ||
|
||
@Override | ||
public Collection<String> suggest(CommandSourceStack commandSourceStack, 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(); | ||
} | ||
} | ||
``` |