Skip to content

[Feat] AsyncOfflinePlayerArgument Documentation #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion docs/en/create-commands/arguments/types/entities-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,41 @@ And there we have it! One thing to note is that entity selectors are still a val

## OfflinePlayer argument

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).
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.

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

## AsyncOfflinePlayer argument

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.

:::info
The `AsyncOfflinePlayerArgument` returns a `CompletableFuture<OfflinePlayer>` object, which can be used to retrieve the `OfflinePlayer` object when the API call is complete.
:::

::::tip Example - Checking if a player has joined before

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:

```mccmd
/playedbefore <player>
```

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:

:::tabs
===Java
<<< @/../reference-code/src/main/java/createcommands/arguments/types/EntitiesArguments.java#playedBeforeArgumentExample
===Kotlin
<<< @/../reference-code/src/main/kotlin/createcommands/arguments/types/EntitiesArguments.kt#playedBeforeArgumentExample
===Kotlin DSL
<<< @/../reference-code/src/main/kotlin/createcommands/arguments/types/EntitiesArguments.kt#playedBeforeArgumentExampleDSL
:::

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.

::::

## Entity type argument

![An image of an entity argument displaying a list of entity type suggestions](/images/arguments/entitytype.png)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.Argument;
import dev.jorel.commandapi.arguments.AsyncOfflinePlayerArgument;
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
import dev.jorel.commandapi.arguments.EntityTypeArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.PlayerArgument;
import dev.jorel.commandapi.arguments.SafeSuggestions;
import dev.jorel.commandapi.executors.CommandArguments;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;

import java.util.Collection;
import java.util.concurrent.CompletableFuture;

class EntitiesArguments {
static {
Expand Down Expand Up @@ -51,6 +54,33 @@ class EntitiesArguments {
.register();
// #endregion noSelectorSuggestionsExample

// #region playedBeforeArgumentExample
new CommandAPICommand("playedbefore")
.withArguments(new AsyncOfflinePlayerArgument("player"))
.executes((sender, args) -> {
CompletableFuture<OfflinePlayer> player = (CompletableFuture<OfflinePlayer>) args.get("player");

// Directly sends a message to the sender, indicating that the command is running to prevent confusion
sender.sendMessage("Checking if the player has played before...");

player.thenAccept(offlinePlayer -> {
if (offlinePlayer.hasPlayedBefore()) {
sender.sendMessage("Player has played before");
} else {
sender.sendMessage("Player has never played before");
}
}).exceptionally(throwable -> {
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
Throwable cause = throwable.getCause();
Throwable rootCause = cause instanceof RuntimeException ? cause.getCause() : cause;

sender.sendMessage(rootCause.getMessage());
return null;
});
})
.register();
// #endregion playedBeforeArgumentExample

// #region entityTypeArgumentExample
new CommandAPICommand("spawnmob")
.withArguments(new EntityTypeArgument("entity"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package createcommands.arguments.types

import dev.jorel.commandapi.CommandAPICommand
import dev.jorel.commandapi.arguments.AsyncOfflinePlayerArgument
import dev.jorel.commandapi.arguments.EntitySelectorArgument
import dev.jorel.commandapi.arguments.EntityTypeArgument
import dev.jorel.commandapi.arguments.IntegerArgument
Expand All @@ -10,14 +11,17 @@ import dev.jorel.commandapi.executors.CommandExecutor
import dev.jorel.commandapi.executors.PlayerCommandExecutor
import dev.jorel.commandapi.kotlindsl.anyExecutor
import dev.jorel.commandapi.kotlindsl.commandAPICommand
import dev.jorel.commandapi.kotlindsl.asyncOfflinePlayerArgument
import dev.jorel.commandapi.kotlindsl.entitySelectorArgumentManyEntities
import dev.jorel.commandapi.kotlindsl.entityTypeArgument
import dev.jorel.commandapi.kotlindsl.integerArgument
import dev.jorel.commandapi.kotlindsl.playerExecutor
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Entity
import org.bukkit.entity.EntityType
import org.bukkit.entity.Player
import java.util.concurrent.CompletableFuture

fun entitiesArguments() {
// #region entitySelectorArgumentExample
Expand Down Expand Up @@ -53,6 +57,33 @@ fun entitiesArguments() {
.register()
// #endregion noSelectorSuggestionsExample

// #region playedBeforeArgumentExample
CommandAPICommand("playedbefore")
.withArguments(AsyncOfflinePlayerArgument("player"))
.executes(CommandExecutor { sender, args ->
val player = args["player"] as CompletableFuture<OfflinePlayer>

// Directly sends a message to the sender, indicating that the command is running to prevent confusion
sender.sendMessage("Checking if the player has played before...")

player.thenAccept { offlinePlayer ->
if (offlinePlayer.hasPlayedBefore()) {
sender.sendMessage("Player has played before")
} else {
sender.sendMessage("Player has never played before")
}
}.exceptionally { throwable ->
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
val cause = throwable.cause
val rootCause = if (cause is RuntimeException) cause.cause else cause

sender.sendMessage(rootCause?.message ?: "An error occurred")
null
}
})
.register()
// #endregion playedBeforeArgumentExample

// #region entityTypeArgumentExample
CommandAPICommand("spawnmob")
.withArguments(EntityTypeArgument("entity"))
Expand Down Expand Up @@ -83,6 +114,33 @@ fun entitiesArgumentsDSL() {
}
// #endregion entitySelectorArgumentExampleDSL

// #region playedBeforeArgumentExampleDSL
commandAPICommand("playedbefore") {
asyncOfflinePlayerArgument("player")
anyExecutor { sender, args ->
val player = args["player"] as CompletableFuture<OfflinePlayer>

// Directly sends a message to the sender, indicating that the command is running to prevent confusion
sender.sendMessage("Checking if the player has played before...")

player.thenAccept { offlinePlayer ->
if (offlinePlayer.hasPlayedBefore()) {
sender.sendMessage("Player has played before")
} else {
sender.sendMessage("Player has never played before")
}
}.exceptionally { throwable ->
// We have to partly handle exceptions ourselves, since we are using a CompletableFuture
val cause = throwable.cause
val rootCause = if (cause is RuntimeException) cause.cause else cause

sender.sendMessage(rootCause?.message ?: "An error occurred")
null
}
}
}
// #endregion playedBeforeArgumentExampleDSL

// #region entityTypeArgumentExampleDSL
commandAPICommand("spawnmob") {
entityTypeArgument("entity")
Expand Down