diff --git a/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerInfoParser.java b/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerInfoParser.java
new file mode 100644
index 00000000..5c857d02
--- /dev/null
+++ b/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerInfoParser.java
@@ -0,0 +1,50 @@
+/*
+ * Distributor, a feature-rich framework for Mindustry plugins.
+ *
+ * Copyright (C) 2024 Xpdustry
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.xpdustry.distributor.command.cloud.parser;
+
+import com.xpdustry.distributor.core.DistributorProvider;
+import com.xpdustry.distributor.core.player.PlayerLookup;
+import java.util.concurrent.CompletableFuture;
+import mindustry.net.Administration;
+import org.incendo.cloud.context.CommandContext;
+import org.incendo.cloud.context.CommandInput;
+import org.incendo.cloud.parser.ArgumentParseResult;
+import org.incendo.cloud.parser.ArgumentParser;
+
+public final class PlayerInfoParser implements ArgumentParser.FutureArgumentParser {
+
+ @Override
+ public CompletableFuture> parseFuture(
+ final CommandContext ctx, final CommandInput input) {
+ final var query = input.readString();
+ return DistributorProvider.get()
+ .getService(PlayerLookup.class)
+ .orElseThrow()
+ .findOfflinePlayers(query, true)
+ .thenApply(result -> {
+ if (result.isEmpty()) {
+ return ArgumentParseResult.failure(new PlayerParser.PlayerNotFoundException(query, ctx));
+ } else if (result.size() > 1) {
+ return ArgumentParseResult.failure(new PlayerParser.TooManyPlayersFoundException(query, ctx));
+ } else {
+ return ArgumentParseResult.success(result.get(0));
+ }
+ });
+ }
+}
diff --git a/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerParser.java b/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerParser.java
new file mode 100644
index 00000000..2600fc73
--- /dev/null
+++ b/distributor-command-cloud/src/main/java/com/xpdustry/distributor/command/cloud/parser/PlayerParser.java
@@ -0,0 +1,118 @@
+/*
+ * Distributor, a feature-rich framework for Mindustry plugins.
+ *
+ * Copyright (C) 2024 Xpdustry
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.xpdustry.distributor.command.cloud.parser;
+
+import arc.Core;
+import com.xpdustry.distributor.command.cloud.ArcCaptionKeys;
+import com.xpdustry.distributor.core.DistributorProvider;
+import com.xpdustry.distributor.core.player.PlayerLookup;
+import java.util.concurrent.CompletableFuture;
+import mindustry.gen.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.incendo.cloud.caption.Caption;
+import org.incendo.cloud.caption.CaptionVariable;
+import org.incendo.cloud.context.CommandContext;
+import org.incendo.cloud.context.CommandInput;
+import org.incendo.cloud.exception.parsing.ParserException;
+import org.incendo.cloud.parser.ArgumentParseResult;
+import org.incendo.cloud.parser.ArgumentParser;
+
+public final class PlayerParser implements ArgumentParser {
+
+ @Override
+ public ArgumentParseResult parse(final CommandContext ctx, final CommandInput input) {
+ final var query = input.readString();
+ final var result = DistributorProvider.get()
+ .getService(PlayerLookup.class)
+ .orElseThrow()
+ .findOnlinePlayers(query, true);
+ if (result.isEmpty()) {
+ return ArgumentParseResult.failure(new PlayerNotFoundException(query, ctx));
+ } else if (result.size() > 1) {
+ return ArgumentParseResult.failure(new TooManyPlayersFoundException(query, ctx));
+ } else {
+ return ArgumentParseResult.success(result.get(0));
+ }
+ }
+
+ @Override
+ public @NonNull CompletableFuture<@NonNull ArgumentParseResult> parseFuture(
+ final CommandContext ctx, final CommandInput input) {
+ return CompletableFuture.supplyAsync(() -> this.parse(ctx, input), Core.app::post);
+ }
+
+ /**
+ * An exception thrown when a parsing error occurs while searching for a player.
+ */
+ public static sealed class PlayerParseException extends ParserException {
+
+ private final String input;
+
+ /**
+ * Creates a new {@link PlayerParseException}.
+ *
+ * @param input the input string
+ * @param ctx the command context
+ * @param caption the error caption of this exception
+ */
+ public PlayerParseException(final String input, final CommandContext> ctx, final Caption caption) {
+ super(PlayerParser.class, ctx, caption, CaptionVariable.of("input", input));
+ this.input = input;
+ }
+
+ /**
+ * Returns the input string.
+ */
+ public final String getInput() {
+ return this.input;
+ }
+ }
+
+ /**
+ * An exception thrown when too many players are found for the given input.
+ */
+ public static final class TooManyPlayersFoundException extends PlayerParseException {
+
+ /**
+ * Creates a new {@link TooManyPlayersFoundException}.
+ *
+ * @param input the input string
+ * @param ctx the command context
+ */
+ public TooManyPlayersFoundException(final String input, final CommandContext> ctx) {
+ super(input, ctx, ArcCaptionKeys.ARGUMENT_PARSE_FAILURE_PLAYER_TOO_MANY);
+ }
+ }
+
+ /**
+ * An exception thrown when no player was found for the given input.
+ */
+ public static final class PlayerNotFoundException extends PlayerParseException {
+
+ /**
+ * Creates a new {@link PlayerNotFoundException}.
+ *
+ * @param input the input string
+ * @param ctx the command context
+ */
+ public PlayerNotFoundException(final String input, final CommandContext> ctx) {
+ super(input, ctx, ArcCaptionKeys.ARGUMENT_PARSE_FAILURE_PLAYER_NOT_FOUND);
+ }
+ }
+}
diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/core/Distributor.java b/distributor-common/src/main/java/com/xpdustry/distributor/core/Distributor.java
index 6d8bd2ab..04c13c29 100644
--- a/distributor-common/src/main/java/com/xpdustry/distributor/core/Distributor.java
+++ b/distributor-common/src/main/java/com/xpdustry/distributor/core/Distributor.java
@@ -18,4 +18,9 @@
*/
package com.xpdustry.distributor.core;
-public interface Distributor {}
+import java.util.Optional;
+
+public interface Distributor {
+
+ Optional getService(final Class clazz);
+}
diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorInitializationException.java b/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorInitializationException.java
new file mode 100644
index 00000000..175efe13
--- /dev/null
+++ b/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorInitializationException.java
@@ -0,0 +1,29 @@
+/*
+ * Distributor, a feature-rich framework for Mindustry plugins.
+ *
+ * Copyright (C) 2024 Xpdustry
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.xpdustry.distributor.core;
+
+/**
+ * This exception is thrown when the distributor fails to initialize.
+ */
+public final class DistributorInitializationException extends RuntimeException {
+
+ DistributorInitializationException(final String message) {
+ super(message);
+ }
+}
diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorProvider.java b/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorProvider.java
new file mode 100644
index 00000000..6dcc51e8
--- /dev/null
+++ b/distributor-common/src/main/java/com/xpdustry/distributor/core/DistributorProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Distributor, a feature-rich framework for Mindustry plugins.
+ *
+ * Copyright (C) 2024 Xpdustry
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.xpdustry.distributor.core;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A holder for the global {@link Distributor} instance.
+ */
+public final class DistributorProvider {
+
+ private static @Nullable Distributor instance = null;
+
+ private DistributorProvider() {}
+
+ /**
+ * Returns the global {@link Distributor} instance.
+ * @throws DistributorInitializationException if the API hasn't been initialized yet
+ */
+ public static Distributor get() {
+ if (DistributorProvider.instance == null) {
+ throw new DistributorInitializationException("The API hasn't been initialized yet.");
+ }
+ return DistributorProvider.instance;
+ }
+
+ /**
+ * Sets the global {@link Distributor} instance.
+ * @throws DistributorInitializationException if the API has already been initialized
+ */
+ public static void set(final Distributor distributor) {
+ if (DistributorProvider.instance != null) {
+ throw new DistributorInitializationException("The API has already been initialized.");
+ }
+ DistributorProvider.instance = distributor;
+ }
+
+ /**
+ * Clears the global {@link Distributor} instance.
+ * @throws DistributorInitializationException if the API hasn't been initialized yet
+ */
+ public static void clear() {
+ if (DistributorProvider.instance != null) {
+ DistributorProvider.instance = null;
+ } else {
+ throw new DistributorInitializationException("The API hasn't been initialized yet.");
+ }
+ }
+
+ /**
+ * Returns whether the API has been initialized.
+ */
+ public static boolean isInitialized() {
+ return DistributorProvider.instance != null;
+ }
+}
diff --git a/distributor-common/src/main/java/com/xpdustry/distributor/core/player/SimplePlayerLookup.java b/distributor-common/src/main/java/com/xpdustry/distributor/core/player/SimplePlayerLookup.java
index fe1d60fa..e7b711d8 100644
--- a/distributor-common/src/main/java/com/xpdustry/distributor/core/player/SimplePlayerLookup.java
+++ b/distributor-common/src/main/java/com/xpdustry/distributor/core/player/SimplePlayerLookup.java
@@ -18,6 +18,7 @@
*/
package com.xpdustry.distributor.core.player;
+import arc.Core;
import arc.util.Strings;
import com.xpdustry.distributor.core.collection.ArcCollections;
import java.text.Normalizer;
@@ -85,17 +86,21 @@ public List findOnlinePlayers(final String query, final boolean admin) {
@Override
public CompletableFuture> findOfflinePlayers(
final String query, final boolean admin) {
- final Set result = new LinkedHashSet<>();
- for (final var online : findOnlinePlayers(query, admin)) {
- result.add(online.getInfo());
- }
- if (admin && MUUID.isUuid(query)) {
- final var info = Vars.netServer.admins.getInfoOptional(query);
- if (info != null) {
- result.add(info);
- }
- }
- return CompletableFuture.completedFuture(List.copyOf(result));
+ return CompletableFuture.supplyAsync(
+ () -> {
+ final Set result = new LinkedHashSet<>();
+ for (final var online : findOnlinePlayers(query, admin)) {
+ result.add(online.getInfo());
+ }
+ if (admin && MUUID.isUuid(query)) {
+ final var info = Vars.netServer.admins.getInfoOptional(query);
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ return List.copyOf(result);
+ },
+ Core.app::post);
}
// https://stackoverflow.com/a/4122207
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e3b07ea5..8af0cbed 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,7 @@ mindustry = "v146"
slf4j = "2.0.11"
# command
-cloud = "2.0.0-beta.2"
+cloud = "2.0.0-beta.3"
# utilities
immutables = "2.9.2"