Skip to content

Commit 65e7272

Browse files
authored
Merge pull request #8 from CommandAPI/dev/dev
dev/dev into master to see merge conflicts
2 parents 1f42ad4 + b93a898 commit 65e7272

File tree

9 files changed

+249
-6
lines changed

9 files changed

+249
-6
lines changed

docs/en/create-commands/arguments/types/entities-arguments.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,41 @@ And there we have it! One thing to note is that entity selectors are still a val
103103

104104
## OfflinePlayer argument
105105

106-
The `OfflinePlayerArgument` class is identical to the `PlayerArgument` class, but instead of returning a `Player` object, it returns an `OfflinePlayer` object. Internally, this argument makes calls to Mojang servers (via Mojang's authlib), meaning it can be slightly slower than alternative methods (such as using a `StringArgument` and suggesting a list of existing offline players).
106+
The `OfflinePlayerArgument` class is identical to the `PlayerArgument` class, but instead of returning a `Player` object, it returns an `OfflinePlayer` object. Internally, this argument makes calls to Mojang servers (via Mojang's authlib), meaning it can be slightly slower than alternative methods such as using a `AsyncOfflinePlayerArgument`, which runs the API call asynchronously, or using a `StringArgument` and suggesting a list of existing offline players.
107107

108108
The `OfflinePlayerArgument` _should_ be able to retrieve players that have never joined the server before.
109109

110+
## AsyncOfflinePlayer argument
111+
112+
The `AsyncOfflinePlayerArgument` class is identical to the `OfflinePlayerArgument` class, but instead of making the API call synchronously, it makes the API call asynchronously. This means that the command will not block the main thread while waiting for the API call to complete.
113+
114+
:::info
115+
The `AsyncOfflinePlayerArgument` returns a `CompletableFuture<OfflinePlayer>` object, which can be used to retrieve the `OfflinePlayer` object when the API call is complete.
116+
:::
117+
118+
::::tip Example - Checking if a player has joined before
119+
120+
Say we want to create a command that tells us if a player has joined the server before. We can use the `AsyncOfflinePlayerArgument` to fetch the `OfflinePlayer` object asynchronously. That way we simply wait for the request to complete, and once it does, we can check if the player has joined the server before. We want to create a command of the following form:
121+
122+
```mccmd
123+
/playedbefore <player>
124+
```
125+
126+
We now want to get the `CompletableFuture<OfflinePlayer>` object from the `AsyncOfflinePlayerArgument` and then use it to get the `OfflinePlayer` object. We can define it like this:
127+
128+
:::tabs
129+
===Java
130+
<<< @/../reference-code/src/main/java/createcommands/arguments/types/EntitiesArguments.java#playedBeforeArgumentExample
131+
===Kotlin
132+
<<< @/../reference-code/src/main/kotlin/createcommands/arguments/types/EntitiesArguments.kt#playedBeforeArgumentExample
133+
===Kotlin DSL
134+
<<< @/../reference-code/src/main/kotlin/createcommands/arguments/types/EntitiesArguments.kt#playedBeforeArgumentExampleDSL
135+
:::
136+
137+
We now successfully ran a command that asynchronously checks if a player has joined the server before without blocking the main thread despite making an API call.
138+
139+
::::
140+
110141
## Entity type argument
111142

112143
![An image of an entity argument displaying a list of entity type suggestions](/images/arguments/entitytype.png)

docs/en/create-commands/executors/native-sender.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,23 @@ This can now be used via the following command examples:
6767
/execute in minecraft:overworld positioned 20 60 -20 run break
6868
```
6969

70-
::::
70+
::::
71+
72+
## Constructing a `NativeProxyCommandSender`
73+
74+
You can create a `NativeProxyCommandSender` object yourself using the static `from` method:
75+
76+
```java
77+
NativeProxyCommandSender from(CommandSender caller, CommandSender callee, Location location, World world);
78+
```
79+
80+
This `CommandSender` will work the same as any other `NativeProxyCommandSender` you would get while using `executesNative`. For example, you could use it to make a simple version of `/execute`, like so:
81+
82+
:::tabs
83+
===Java
84+
<<< @/../reference-code/src/main/java/createcommands/executors/NativeSender.java#constructorExample
85+
===Kotlin
86+
<<< @/../reference-code/src/main/kotlin/createcommands/executors/NativeSender.kt#constructorExample
87+
===Kotlin DSL
88+
<<< @/../reference-code/src/main/kotlin/createcommands/executors/NativeSender.kt#constructorExampleDSL
89+
:::

docs/en/intro.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The CommandAPI does not follow the "standard" method of registering commands. In
2727

2828
- **Commands should not be declared in the `plugin.yml` file.**
2929
- Commands are automatically registered under the namespace of the plugin that registered the command. For example, if you register a command `/hello`, you can also run it using `/<pluginname>:hello`. However, you can change the default namespace. More about this [on the command registration page](./create-commands/registration#registering-the-command).
30-
- Commands are not "linked" to a certain plugin. In other words, you can’t look up which commands are registered by which plugin.
30+
- Commands are not "linked" to a certain plugin. In other words, you can’t look up which commands are registered by which plugin. This is not the case for commands on Paper versions that have Paper's Brigadier API, or in other words, any Paper build starting in 1.20.6 with build 65.
3131

3232
## How this documentation works
3333

docs/en/upgrading-parts/9.7.0-to-10.0.0.md

+20
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,23 @@ For 10.0.0, all previously deprecated methods have been removed. Please make sur
99
The default namespace has been updated from `minecraft` to the plugin's name. If you are not shading, the default namespace is going to be `commandapi`. If you are shading, the default namespace is going to be your plugin's name.
1010

1111
Along with this change, the `CommandAPIBukkitConfig#usePluginNamespace()` has been deprecated since it is now default behaviour.
12+
13+
-----
14+
15+
### `NativeProxyCommandSender` changes
16+
17+
`NativeProxyCommandSender` used to be a class, but this version changed it to an interface. Any code compiled against an earlier version that references any method of `NativeProxyCommandSender` may throw the following `IncompatibleClassChangeError` when run using the new version of the API:
18+
19+
```
20+
java.lang.IncompatibleClassChangeError: Found interface dev.jorel.commandapi.wrappers.NativeProxyCommandSender, but class was expected
21+
```
22+
23+
If this happens, the original code simply needs to be recompiled using the new API version.
24+
25+
Additionally, the constructor of `NativeProxyCommandSender` is no longer available. Instead, the static `from` method should be used:
26+
27+
```java
28+
new NativeProxyCommandSender(caller, callee, location, world); // [!code --]
29+
30+
NativeProxyCommandSender.from(caller, callee, location, world); // [!code ++]
31+
```

docs/en/velocity/intro.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ preferences: ['build-system']
44
authors:
55
- JorelAli
66
- DerEchtePilz
7+
- WillKroboth
78
---
89

910
# Velocity
@@ -103,4 +104,14 @@ To accomplish that, we register the command like this:
103104
<<< @/../reference-code/src/main/kotlin/velocity/Intro.kt#registerCommandExample
104105
:::
105106

106-
::::
107+
::::
108+
109+
## Updating Requirements
110+
111+
As explained on the page about command and argument [requirements](../create-commands/requirements.md#updating-requirements), you should run `CommandAPI.updateRequirements(player)` whenever conditions that affect whether a player can access a command change. However, Velocity is not able to resend commands to a client by itself; it only knows how to add commands to a packet the backend server sent. So, in order for `updateRequirements` to work, the CommandAPI needs to be able to tell the backend server to resend commands.
112+
113+
The CommandAPI Velocity plugin handles this by sending a plugin message to the backend server. In order for the backend server to process this message, you must add a plugin that understands CommandAPI plugin messages. You can easily do this by installing the CommandAPI Bukkit plugin or any plugin that shades CommandAPI version 10.0.0 or later to each backend server. However, the CommandAPI Bukkit plugin includes a lot of extra stuff to make registering commands work, which isn't necessary if you only need `updateRequirements` to work on Velocity. In this case, you can install the special CommandAPI Bukkit Networking plugin, which simply contains the code necessary to respond to plugin messages.
114+
115+
Whether you want to install the CommandAPI Bukkit plugin or the CommandAPI Bukkit Networking plugin, you can find the latest releases [here](https://github.com/CommandAPI/CommandAPI/releases/latest).
116+
117+
So far, the CommandAPI only uses the plugin messaging system to implement `updateRequirements` on Velocity. If you're not using `updateRequirements` on Velocity, you don't need to install a CommandAPI plugin on your backend servers. It is possible that future features will take advantage of the plugin messaging system as CommandAPI Velocity is further developed. If this ever happens, you should see a clear error message prompting you to update to a compatible version.

reference-code/src/main/java/createcommands/arguments/types/EntitiesArguments.java

+30
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
import dev.jorel.commandapi.CommandAPICommand;
44
import dev.jorel.commandapi.arguments.Argument;
5+
import dev.jorel.commandapi.arguments.AsyncOfflinePlayerArgument;
56
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
67
import dev.jorel.commandapi.arguments.EntityTypeArgument;
78
import dev.jorel.commandapi.arguments.IntegerArgument;
89
import dev.jorel.commandapi.arguments.PlayerArgument;
910
import dev.jorel.commandapi.arguments.SafeSuggestions;
1011
import dev.jorel.commandapi.executors.CommandArguments;
1112
import org.bukkit.Bukkit;
13+
import org.bukkit.OfflinePlayer;
1214
import org.bukkit.entity.Entity;
1315
import org.bukkit.entity.EntityType;
1416
import org.bukkit.entity.Player;
1517

1618
import java.util.Collection;
19+
import java.util.concurrent.CompletableFuture;
1720

1821
class EntitiesArguments {
1922
static {
@@ -51,6 +54,33 @@ class EntitiesArguments {
5154
.register();
5255
// #endregion noSelectorSuggestionsExample
5356

57+
// #region playedBeforeArgumentExample
58+
new CommandAPICommand("playedbefore")
59+
.withArguments(new AsyncOfflinePlayerArgument("player"))
60+
.executes((sender, args) -> {
61+
CompletableFuture<OfflinePlayer> player = (CompletableFuture<OfflinePlayer>) args.get("player");
62+
63+
// Directly sends a message to the sender, indicating that the command is running to prevent confusion
64+
sender.sendMessage("Checking if the player has played before...");
65+
66+
player.thenAccept(offlinePlayer -> {
67+
if (offlinePlayer.hasPlayedBefore()) {
68+
sender.sendMessage("Player has played before");
69+
} else {
70+
sender.sendMessage("Player has never played before");
71+
}
72+
}).exceptionally(throwable -> {
73+
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
74+
Throwable cause = throwable.getCause();
75+
Throwable rootCause = cause instanceof RuntimeException ? cause.getCause() : cause;
76+
77+
sender.sendMessage(rootCause.getMessage());
78+
return null;
79+
});
80+
})
81+
.register();
82+
// #endregion playedBeforeArgumentExample
83+
5484
// #region entityTypeArgumentExample
5585
new CommandAPICommand("spawnmob")
5686
.withArguments(new EntityTypeArgument("entity"))

reference-code/src/main/java/createcommands/executors/NativeSender.java

+29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package createcommands.executors;
22

33
import dev.jorel.commandapi.CommandAPICommand;
4+
import dev.jorel.commandapi.arguments.CommandArgument;
5+
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
6+
import dev.jorel.commandapi.arguments.LocationArgument;
7+
import dev.jorel.commandapi.arguments.WorldArgument;
8+
import dev.jorel.commandapi.wrappers.CommandResult;
9+
import dev.jorel.commandapi.wrappers.NativeProxyCommandSender;
410
import org.bukkit.Location;
11+
import org.bukkit.World;
12+
import org.bukkit.command.CommandSender;
513

614
class NativeSender {
715
static {
@@ -15,5 +23,26 @@ class NativeSender {
1523
})
1624
.register();
1725
// #endregion breakCommandExample
26+
27+
// #region constructorExample
28+
new CommandAPICommand("executeAs")
29+
.withArguments(
30+
new EntitySelectorArgument.OneEntity("target"),
31+
new LocationArgument("location"),
32+
new WorldArgument("world"),
33+
new CommandArgument("command")
34+
)
35+
.executes((caller, args) -> {
36+
CommandSender callee = (CommandSender) args.get("target");
37+
Location location = (Location) args.get("location");
38+
World world = (World) args.get("world");
39+
CommandResult command = (CommandResult) args.get("command");
40+
41+
assert callee != null && location != null && world != null && command != null;
42+
43+
command.execute(NativeProxyCommandSender.from(caller, callee, location, world));
44+
})
45+
.register();
46+
// #endregion constructorExample
1847
}
1948
}

reference-code/src/main/kotlin/createcommands/arguments/types/EntitiesArguments.kt

+58
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package createcommands.arguments.types
22

33
import dev.jorel.commandapi.CommandAPICommand
4+
import dev.jorel.commandapi.arguments.AsyncOfflinePlayerArgument
45
import dev.jorel.commandapi.arguments.EntitySelectorArgument
56
import dev.jorel.commandapi.arguments.EntityTypeArgument
67
import dev.jorel.commandapi.arguments.IntegerArgument
@@ -10,14 +11,17 @@ import dev.jorel.commandapi.executors.CommandExecutor
1011
import dev.jorel.commandapi.executors.PlayerCommandExecutor
1112
import dev.jorel.commandapi.kotlindsl.anyExecutor
1213
import dev.jorel.commandapi.kotlindsl.commandAPICommand
14+
import dev.jorel.commandapi.kotlindsl.asyncOfflinePlayerArgument
1315
import dev.jorel.commandapi.kotlindsl.entitySelectorArgumentManyEntities
1416
import dev.jorel.commandapi.kotlindsl.entityTypeArgument
1517
import dev.jorel.commandapi.kotlindsl.integerArgument
1618
import dev.jorel.commandapi.kotlindsl.playerExecutor
1719
import org.bukkit.Bukkit
20+
import org.bukkit.OfflinePlayer
1821
import org.bukkit.entity.Entity
1922
import org.bukkit.entity.EntityType
2023
import org.bukkit.entity.Player
24+
import java.util.concurrent.CompletableFuture
2125

2226
fun entitiesArguments() {
2327
// #region entitySelectorArgumentExample
@@ -53,6 +57,33 @@ fun entitiesArguments() {
5357
.register()
5458
// #endregion noSelectorSuggestionsExample
5559

60+
// #region playedBeforeArgumentExample
61+
CommandAPICommand("playedbefore")
62+
.withArguments(AsyncOfflinePlayerArgument("player"))
63+
.executes(CommandExecutor { sender, args ->
64+
val player = args["player"] as CompletableFuture<OfflinePlayer>
65+
66+
// Directly sends a message to the sender, indicating that the command is running to prevent confusion
67+
sender.sendMessage("Checking if the player has played before...")
68+
69+
player.thenAccept { offlinePlayer ->
70+
if (offlinePlayer.hasPlayedBefore()) {
71+
sender.sendMessage("Player has played before")
72+
} else {
73+
sender.sendMessage("Player has never played before")
74+
}
75+
}.exceptionally { throwable ->
76+
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
77+
val cause = throwable.cause
78+
val rootCause = if (cause is RuntimeException) cause.cause else cause
79+
80+
sender.sendMessage(rootCause?.message ?: "An error occurred")
81+
null
82+
}
83+
})
84+
.register()
85+
// #endregion playedBeforeArgumentExample
86+
5687
// #region entityTypeArgumentExample
5788
CommandAPICommand("spawnmob")
5889
.withArguments(EntityTypeArgument("entity"))
@@ -83,6 +114,33 @@ fun entitiesArgumentsDSL() {
83114
}
84115
// #endregion entitySelectorArgumentExampleDSL
85116

117+
// #region playedBeforeArgumentExampleDSL
118+
commandAPICommand("playedbefore") {
119+
asyncOfflinePlayerArgument("player")
120+
anyExecutor { sender, args ->
121+
val player = args["player"] as CompletableFuture<OfflinePlayer>
122+
123+
// Directly sends a message to the sender, indicating that the command is running to prevent confusion
124+
sender.sendMessage("Checking if the player has played before...")
125+
126+
player.thenAccept { offlinePlayer ->
127+
if (offlinePlayer.hasPlayedBefore()) {
128+
sender.sendMessage("Player has played before")
129+
} else {
130+
sender.sendMessage("Player has never played before")
131+
}
132+
}.exceptionally { throwable ->
133+
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
134+
val cause = throwable.cause
135+
val rootCause = if (cause is RuntimeException) cause.cause else cause
136+
137+
sender.sendMessage(rootCause?.message ?: "An error occurred")
138+
null
139+
}
140+
}
141+
}
142+
// #endregion playedBeforeArgumentExampleDSL
143+
86144
// #region entityTypeArgumentExampleDSL
87145
commandAPICommand("spawnmob") {
88146
entityTypeArgument("entity")

reference-code/src/main/kotlin/createcommands/executors/NativeSender.kt

+47-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
package createcommands.executors
22

33
import dev.jorel.commandapi.CommandAPICommand
4+
import dev.jorel.commandapi.arguments.CommandArgument
5+
import dev.jorel.commandapi.arguments.EntitySelectorArgument
6+
import dev.jorel.commandapi.arguments.LocationArgument
7+
import dev.jorel.commandapi.arguments.WorldArgument
8+
import dev.jorel.commandapi.executors.CommandExecutor
49
import dev.jorel.commandapi.executors.NativeCommandExecutor
5-
import dev.jorel.commandapi.kotlindsl.commandAPICommand
6-
import dev.jorel.commandapi.kotlindsl.nativeExecutor
10+
import dev.jorel.commandapi.kotlindsl.*
11+
import dev.jorel.commandapi.wrappers.CommandResult
12+
import dev.jorel.commandapi.wrappers.NativeProxyCommandSender
13+
import org.bukkit.Location
14+
import org.bukkit.World
15+
import org.bukkit.command.CommandSender
716

817
fun nativeSender() {
918
// #region breakCommandExample
@@ -14,6 +23,25 @@ fun nativeSender() {
1423
})
1524
.register()
1625
// #endregion breakCommandExample
26+
27+
// #region constructorExample
28+
CommandAPICommand("executeAs")
29+
.withArguments(
30+
EntitySelectorArgument.OneEntity("target"),
31+
LocationArgument("location"),
32+
WorldArgument("world"),
33+
CommandArgument("command")
34+
)
35+
.executes(CommandExecutor { caller, args ->
36+
val callee = args["target"] as CommandSender
37+
val location = args["location"] as Location
38+
val world = args["world"] as World
39+
val command = args["command"] as CommandResult
40+
41+
command.execute(NativeProxyCommandSender.from(caller, callee, location, world))
42+
})
43+
.register()
44+
// #endregion constructorExample
1745
}
1846

1947
fun nativeSenderDSL() {
@@ -25,4 +53,21 @@ fun nativeSenderDSL() {
2553
}
2654
}
2755
// #endregion breakCommandExampleDSL
56+
57+
// #region constructorExampleDSL
58+
commandAPICommand("executeAs") {
59+
entitySelectorArgumentOneEntity("target")
60+
locationArgument("location")
61+
worldArgument("world")
62+
commandArgument("command")
63+
anyExecutor { caller, args ->
64+
val callee = args["target"] as CommandSender
65+
val location = args["location"] as Location
66+
val world = args["world"] as World
67+
val command = args["command"] as CommandResult
68+
69+
command.execute(NativeProxyCommandSender.from(caller, callee, location, world))
70+
}
71+
}
72+
// #endregion constructorExampleDSL
2873
}

0 commit comments

Comments
 (0)