feat: code

This commit is contained in:
2026-01-28 18:14:15 -08:00
parent d4ec8ab5ee
commit 0d7537becf
14 changed files with 336 additions and 215 deletions

View File

@@ -0,0 +1,3 @@
package online.mineroo.common.request;
public class CurrencyRequest {}

View File

@@ -0,0 +1,179 @@
package online.mineroo.common.request;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import online.mineroo.common.NetworkServiceInterface;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class ManagementRequest {
private final Logger logger;
private final NetworkServiceInterface networkService;
// State cache
private String motdToken = "";
private String sessionToken = "";
/**
* Constructs a ManagementRequest instance.
*
* @param logger Logger for logging errors and information
* @param networkService The network service instance (Local or Proxy) that
* handles transport and auth
*/
public ManagementRequest(Logger logger, NetworkServiceInterface networkService) {
this.logger = logger;
this.networkService = networkService;
}
/**
* Checks the bind status asynchronously.
*
* @return A future that completes with true if status is 'bound', false
* otherwise
*/
public CompletableFuture<Boolean> checkBindStatus(String hostname, String port) {
// NetworkService is already configured with BaseURL, only need the path here
String path = "/server/management/status";
Map<String, String> params = new HashMap<>();
params.put("address", hostname);
params.put("port", port);
return networkService.getData(path, params)
.thenApply(response -> {
if (response.getStatusCode() != 200) {
return false;
}
String responseBody = response.getBody();
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean();
if (bound) {
logger.warn("Mineroo Bind: Server already bound.");
}
return bound;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse bind status response", e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error while checking status", e);
return false;
});
}
/**
* Initiates a MOTD verification request asynchronously.
*
* @param hostname The server hostname
* @param port The server port
* @return A future that completes with true if tokens are received
*/
public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, String port) {
String path = "/server/management/motd-verify";
Integer num_port = -1;
if (!"$port".equals(port)) {
try {
num_port = Integer.valueOf(port);
} catch (NumberFormatException ignored) {
num_port = 25565;
}
}
JsonObject json = new JsonObject();
json.addProperty("server_address", hostname);
if (num_port == -1) {
json.addProperty("server_port", port);
} else {
json.addProperty("server_port", num_port);
}
return networkService.postData(path, json)
.thenApply(response -> {
String responseBody = response.getBody();
if (response.getStatusCode() != 200) {
logger.error("Mineroo Bind: Api fetch failed.");
return false;
}
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
if (responseData.has("motd_token")) {
this.motdToken = responseData.get("motd_token").getAsString();
} else {
logger.error("Mineroo Bind: MOTD Token not received.");
return false;
}
if (responseData.has("session_token")) {
this.sessionToken = responseData.get("session_token").getAsString();
} else {
logger.error("Mineroo Bind: Session Token not received.");
return false;
}
return true;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse MOTD verify response: " + responseBody, e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error during MOTD verify", e);
return false;
});
}
/**
* Finalizes the server binding asynchronously.
*
* @param name The server name (nullable)
* @param description The server description (nullable)
* @return A future that completes with true if the binding is successful
*/
public CompletableFuture<Boolean> finalizeServerBinding(
@Nullable String name, @Nullable String description
) {
String path = "/server/management/bind";
JsonObject json = new JsonObject();
json.addProperty("session_token", this.sessionToken);
json.addProperty("name", name);
json.addProperty("description", description);
return networkService.postData(path, json)
.thenApply(response -> {
// Assume as long as the request returns successfully and no exception is
// thrown, it is considered successful (HTTP 200)
// If the API returns a specific {"success": false}, you need to parse the JSON
// and check here
return true;
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Failed to finalize binding", e);
return false;
});
}
/**
* Gets the MOTD token received from the API.
*
* @return the motdToken string
*/
public String getMotdToken() {
return motdToken;
}
}

View File

@@ -0,0 +1,22 @@
package online.mineroo.common.request;
import online.mineroo.common.NetworkServiceInterface;
import org.slf4j.Logger;
public class RequestClient {
private final ManagementRequest managementRequest;
private final UserRequest userRequest;
public RequestClient(Logger logger, NetworkServiceInterface networkService) {
this.managementRequest = new ManagementRequest(logger, networkService);
this.userRequest = new UserRequest(logger, networkService);
}
public ManagementRequest management() {
return this.managementRequest;
}
public UserRequest user() {
return this.userRequest;
}
}

View File

@@ -1,183 +1,32 @@
package online.mineroo.common;
package online.mineroo.common.request;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.Nullable;
import online.mineroo.common.NetworkServiceInterface;
import org.slf4j.Logger;
/**
* BindRequest handles server binding and verification with the Mineroo API.
* <p>
* This class operates asynchronously and relies on the injected
* {@link NetworkService}
* to handle the actual HTTP transport and authentication.
*/
public class BindRequest {
public class UserRequest {
private final Logger logger;
private final NetworkServiceInterface networkService;
// State cache
private String motdToken = "";
private String sessionToken = "";
/**
* Constructs a BindRequest instance.
* Constructs a UserRequest instance.
*
* @param logger Logger for logging errors and information
* @param networkService The network service instance (Local or Proxy) that
* handles transport and auth
*/
public BindRequest(Logger logger, NetworkServiceInterface networkService) {
public UserRequest(Logger logger, NetworkServiceInterface networkService) {
this.logger = logger;
this.networkService = networkService;
}
/**
* Checks the bind status asynchronously.
*
* @return A future that completes with true if status is 'bound', false
* otherwise
*/
public CompletableFuture<Boolean> checkBindStatus(String hostname, String port) {
// NetworkService is already configured with BaseURL, only need the path here
String path = "/server/server-bind-status";
Map<String, String> params = new HashMap<>();
params.put("address", hostname);
params.put("port", port);
return networkService.getData(path, params)
.thenApply(response -> {
if (response.getStatusCode() != 200) {
return false;
}
String responseBody = response.getBody();
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
boolean bound = responseData.has("bound") && responseData.get("bound").getAsBoolean();
if (bound) {
logger.warn("Mineroo Bind: Server already bound.");
}
return bound;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse bind status response", e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error while checking status", e);
return false;
});
}
/**
* Initiates a MOTD verification request asynchronously.
*
* @param hostname The server hostname
* @param port The server port
* @return A future that completes with true if tokens are received
*/
public CompletableFuture<Boolean> initialMotdVerifyRequest(String hostname, String port) {
String path = "/server/motd-verify";
Integer num_port = -1;
if (!"$port".equals(port)) {
try {
num_port = Integer.valueOf(port);
} catch (NumberFormatException ignored) {
num_port = 25565;
}
}
JsonObject json = new JsonObject();
json.addProperty("server_address", hostname);
if (num_port == -1) {
json.addProperty("server_port", port);
} else {
json.addProperty("server_port", num_port);
}
return networkService.postData(path, json)
.thenApply(response -> {
String responseBody = response.getBody();
if (response.getStatusCode() != 200) {
logger.error("Mineroo Bind: Api fetch failed.");
return false;
}
try {
JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject();
if (responseData.has("motd_token")) {
this.motdToken = responseData.get("motd_token").getAsString();
} else {
logger.error("Mineroo Bind: MOTD Token not received.");
return false;
}
if (responseData.has("session_token")) {
this.sessionToken = responseData.get("session_token").getAsString();
} else {
logger.error("Mineroo Bind: Session Token not received.");
return false;
}
return true;
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse MOTD verify response: " + responseBody, e);
return false;
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network error during MOTD verify", e);
return false;
});
}
/**
* Finalizes the server binding asynchronously.
*
* @param name The server name (nullable)
* @param description The server description (nullable)
* @return A future that completes with true if the binding is successful
*/
public CompletableFuture<Boolean> finalizeServerBinding(
@Nullable String name, @Nullable String description
) {
String path = "/server/server-bind";
JsonObject json = new JsonObject();
json.addProperty("session_token", this.sessionToken);
json.addProperty("name", name);
json.addProperty("description", description);
return networkService.postData(path, json)
.thenApply(response -> {
// Assume as long as the request returns successfully and no exception is
// thrown, it is considered successful (HTTP 200)
// If the API returns a specific {"success": false}, you need to parse the JSON
// and check here
return true;
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Failed to finalize binding", e);
return false;
});
}
public class PlayerBindStatusResponse implements Serializable {
@SerializedName("status") private PlayerBindStatusEnum status;
@@ -278,7 +127,7 @@ public class BindRequest {
public CompletableFuture<PlayerBindStatusResponse> checkPlayerBindStatus(
UUID playerUuid, String userEmail
) {
String path = "/server/user-bind-status";
String path = "/server/user/status";
Map<String, String> params = new HashMap<>();
@@ -360,7 +209,7 @@ public class BindRequest {
public CompletableFuture<PlayerBindResponse> PlayerBindRequest(
String webEmail, UUID playerUuid, String playerUsername
) {
String path = "/server/user-bind";
String path = "/server/user/bind";
JsonObject json = new JsonObject();
@@ -395,13 +244,4 @@ public class BindRequest {
return createPlayerBindErrorResponse("Network Error: " + e.getMessage());
});
}
/**
* Gets the MOTD token received from the API.
*
* @return the motdToken string
*/
public String getMotdToken() {
return motdToken;
}
}

View File

@@ -8,6 +8,7 @@ plugins {
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.extendedclip.com/releases/")
}
dependencies {
@@ -15,6 +16,7 @@ dependencies {
implementation(project(":common"))
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.7")
}
java {

View File

@@ -1,22 +1,27 @@
package online.mineroo.paper;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import online.mineroo.common.BindRequest;
import java.util.HashMap;
import java.util.UUID;
import online.mineroo.common.HttpNetworkService;
import online.mineroo.common.MessageManager;
import online.mineroo.common.NetworkServiceInterface;
import online.mineroo.common.request.RequestClient;
import online.mineroo.paper.commands.MainCommand;
import online.mineroo.paper.listeners.BindListener;
import online.mineroo.paper.listeners.PlayerBindListener;
import online.mineroo.paper.structure.PlayerInfo;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class MinerooCore extends JavaPlugin implements Listener {
private MessageManager messageManager;
private NetworkServiceInterface networkService;
private BindRequest bindRequest;
private RequestClient requestClient;
private Config config;
private HashMap<UUID, PlayerInfo> playerCache = new HashMap<>();
private BindListener bindListener;
@Override
@@ -57,7 +62,7 @@ public class MinerooCore extends JavaPlugin implements Listener {
getLogger().info("Using direct HTTP network [ " + hostname + " ]");
}
bindRequest = new BindRequest(this.getSLF4JLogger(), this.getNetworkService());
requestClient = new RequestClient(this.getSLF4JLogger(), this.getNetworkService());
if (this.messageManager == null) {
this.messageManager = new MessageManager();
@@ -82,7 +87,7 @@ public class MinerooCore extends JavaPlugin implements Listener {
return this.bindListener;
}
public BindRequest getBindRequest() {
return bindRequest;
public RequestClient getRequestClient() {
return requestClient;
}
}

View File

@@ -12,9 +12,9 @@ import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import java.util.UUID;
import net.kyori.adventure.key.Key;
import online.mineroo.common.BindRequest;
import online.mineroo.common.BindRequest.PlayerBindStatusEnum;
import online.mineroo.common.MessageManager;
import online.mineroo.common.request.RequestClient;
import online.mineroo.common.request.UserRequest.PlayerBindStatusEnum;
import online.mineroo.paper.MinerooCore;
import online.mineroo.paper.ProxyNetworkService;
import org.bukkit.entity.Player;
@@ -59,7 +59,10 @@ public class BindCommand {
}
try {
this.plugin.getBindRequest().checkPlayerBindStatus(uuid, null).thenAccept(response -> {
this.plugin.getRequestClient()
.user()
.checkPlayerBindStatus(uuid, null)
.thenAccept(response -> {
PlayerBindStatusEnum status = response.getStatus();
plugin.getSLF4JLogger().debug(status.toString());
@@ -112,7 +115,7 @@ public class BindCommand {
// Run the binding process asynchronously to avoid blocking the main thread
org.bukkit.Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Logger logger = plugin.getSLF4JLogger();
BindRequest bindRequest = plugin.getBindRequest();
RequestClient requestClient = plugin.getRequestClient();
try {
String address = plugin.getConfigObject().getServer().getServerBind().getAddress();
String port =
@@ -123,7 +126,7 @@ public class BindCommand {
}
// 1. Check binding status
if (bindRequest.checkBindStatus(address, port).join()) {
if (requestClient.management().checkBindStatus(address, port).join()) {
plugin.getMessageManager().get("command.bind.server.already-bound");
context.getSource().getSender().sendMessage(
plugin.getMessageManager().get("command.bind.server.already-bound")
@@ -131,9 +134,9 @@ public class BindCommand {
return;
}
// 2. Initiate MOTD verification
boolean motdOk = bindRequest.initialMotdVerifyRequest(address, port).join();
boolean motdOk = requestClient.management().initialMotdVerifyRequest(address, port).join();
if (motdOk) {
String motdToken = bindRequest.getMotdToken();
String motdToken = requestClient.management().getMotdToken();
// Send MOTD token to Velocity
applyMotdToken(motdToken);
context.getSource().getSender().sendMessage(
@@ -149,7 +152,7 @@ public class BindCommand {
boolean success = false;
int maxRetries = 3;
for (int i = 1; i <= maxRetries; i++) {
if (bindRequest
if (requestClient.management()
.finalizeServerBinding(
plugin.getConfigObject().getServer().getServerName(),
plugin.getConfigObject().getServer().getDescription()

View File

@@ -0,0 +1,51 @@
package online.mineroo.paper.expansions;
import java.util.UUID;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import online.mineroo.paper.MinerooCore;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MinerooUserExpansion extends PlaceholderExpansion {
private final MinerooCore plugin;
public MinerooUserExpansion(MinerooCore plugin) {
this.plugin = plugin;
}
@Override
@NotNull
public String getAuthor() {
return String.join(", ", plugin.getPluginMeta().getAuthors());
}
@Override
@NotNull
public String getIdentifier() {
return "example";
}
@Override
@NotNull
public String getVersion() {
return plugin.getPluginMeta().getVersion();
}
@Override
public boolean persist() {
return true;
}
@Override
public String onPlaceholderRequest(Player player, @NotNull String params) {
if (player == null)
return "";
UUID uuid = player.getUniqueId();
if (params.equals("name")) {
}
return null;
}
}

View File

@@ -16,11 +16,11 @@ import java.util.concurrent.TimeUnit;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import online.mineroo.common.BindRequest;
import online.mineroo.common.BindRequest.PlayerBindEnum;
import online.mineroo.common.BindRequest.PlayerBindResponse;
import online.mineroo.common.BindRequest.PlayerBindStatusEnum;
import online.mineroo.common.MessageManager;
import online.mineroo.common.request.UserRequest;
import online.mineroo.common.request.UserRequest.PlayerBindEnum;
import online.mineroo.common.request.UserRequest.PlayerBindResponse;
import online.mineroo.common.request.UserRequest.PlayerBindStatusEnum;
import online.mineroo.paper.Config;
import online.mineroo.paper.MinerooCore;
import org.bukkit.Bukkit;
@@ -72,7 +72,8 @@ public class PlayerBindListener implements Listener {
return;
}
this.plugin.getBindRequest()
this.plugin.getRequestClient()
.user()
.checkPlayerBindStatus(uuid, null)
.thenAccept(response -> {
Bukkit.getScheduler().runTask(plugin, () -> {
@@ -175,20 +176,21 @@ public class PlayerBindListener implements Listener {
final UUID targetUuid = playerUuid;
plugin.getBindRequest()
plugin.getRequestClient()
.user()
.PlayerBindRequest(userEmail, targetUuid, playerName)
.thenAccept(response -> setConnectionJoinResult(targetUuid, response))
.exceptionally(ex -> {
setConnectionJoinResult(
targetUuid,
BindRequest.createPlayerBindErrorResponse("Network Error: " + ex.getMessage())
UserRequest.createPlayerBindErrorResponse("Network Error: " + ex.getMessage())
);
return null;
});
} else if (key.equals(Key.key("mineroo:bind_user/cancel"))) {
setConnectionJoinResult(
playerUuid,
BindRequest.createPlayerBindErrorResponse(
UserRequest.createPlayerBindErrorResponse(
messageManager.getString("info.bind.player.canceled")
)
);

View File

@@ -0,0 +1,13 @@
package online.mineroo.paper.structure;
public class PlayerInfo {
private String minerooUsername;
public PlayerInfo(String minerooUsername) {
this.minerooUsername = minerooUsername;
}
public String getMinerooUsername() {
return this.minerooUsername;
}
}

View File

@@ -6,6 +6,7 @@ author: YuKun Liu
website: https://mineroo.online
description: Mineroo Base Plugin
api-version: "1.21.10"
softdepend: ["PlaceholderAPI"]
permissions:
mineroo.admin.reload:
description: "Reload plugin config files."

View File

@@ -1,36 +1,36 @@
<factorypath>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.33/2cd0a87ff7df953f810c344bdf2fe3340b954c69/snakeyaml-1.33.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/b421526c5f297295adef1c886e5246c39d4ac629/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-key/4.26.1/6ded614dc07cc6c2da418a8e907ee42325badcda/adventure-key-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.spongepowered/configurate-gson/4.1.2/3e5c7a0ea73e95ce6139fa72f1b6d36eb531ab81/configurate-gson-4.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-key/4.25.0/eadeff9eeaa46f76de3f31fdff1d8e952273cf04/adventure-key-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-logger-slf4j/4.25.0/7800e20f422798c0011eadce9bfa6bef159d7ae9/adventure-text-logger-slf4j-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-json/4.26.1/7f98d4d9105254b1567379629a52236f5ebaf215/adventure-text-serializer-json-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-api/4.26.1/907ea365968cae9bdd84d19f2c258f65cf5f12a4/adventure-api-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.guava/failureaccess/1.0.1/1dcf1de382a0bf95a3d8b0849546c88bac1292c9/failureaccess-1.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/io.leangen.geantyref/geantyref/1.3.11/bc9c03b53917314d21fe6276aceb08aa84bf80dd/geantyref-1.3.11.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.spongepowered/configurate-core/4.1.2/d6728b04738e73847f6a26349cf4368362feab97/configurate-core-4.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-legacy/4.25.0/b12eaaac78d2534b9b1556049a8d95a046b0812d/adventure-text-serializer-legacy-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/31.0.1-jre/119ea2b2bc205b138974d351777b20f02b92704b/guava-31.0.1-jre.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-plain/4.26.1/cce5ad32da24b824edc8518535431059340873d5/adventure-text-serializer-plain-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/javax.inject/javax.inject/1/6975da39a7040257bd51d21a231b76c915872d38/javax.inject-1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.21.1/6d9b10773b5237df178a7b3c1b4208df7d0e7f94/error_prone_annotations-2.21.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-gson/4.25.0/e312e240fe82f4207ff2232b33ee4433855bdfff/adventure-text-serializer-gson-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-api/4.25.0/6bd10494eeb2f8eadce7226db4445e8728985cbb/adventure-api-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-commons/4.25.0/58708c96ea4292800f08360ca1ce8a31ef0cdf97/adventure-text-serializer-commons-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.typesafe/config/1.4.1/19058a07624a87f90d129af7cd9c68bee94535a9/config-1.4.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-ansi/4.25.0/bdd2546ac76e725072b26bb22a73afbfc077ba73/adventure-text-serializer-ansi-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-gson/4.26.1/3523abdae3098630e4ce9e808bb25e3f71ca60d1/adventure-text-serializer-gson-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/examination-api/1.3.0/8a2d185275307f1e2ef2adf7152b9a0d1d44c30b/examination-api-1.3.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-commons/4.26.1/ef0ca2d1307415c9700d8e2f04adf283d6da46fa/adventure-text-serializer-commons-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.moandjiezana.toml/toml4j/0.7.2/a03337911d0bd2c40932aca3946edb30d0e7d0c/toml4j-0.7.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.10.1/b3add478d4382b78ea20b1671390a858002feb6c/gson-2.10.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.inject/guice/6.0.0/9b422c69c4fa1ea95b2615444a94fede9b02fc40/guice-6.0.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-minimessage/4.25.0/38f8f778c92f1ea848f79f992c99c4b98f96f23b/adventure-text-minimessage-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-legacy/4.26.1/3267b14ac7fa167b97beb8c114d87d83609847af/adventure-text-serializer-legacy-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/option/1.1.0/593fecb9c42688eebc7d8da5d6ea127f4d4c92a2/option-1.1.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/aopalliance/aopalliance/1.0/235ba8b489512805ac13a8f9ea77a1ca5ebe3e8/aopalliance-1.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.17/d9e58ac9c7779ba3bf8142aff6c830617a7fe60f/slf4j-api-2.0.17.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-logger-slf4j/4.26.1/f18d40094ec8f64087f420069f06e024558d74d6/adventure-text-logger-slf4j-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.velocitypowered/velocity-api/3.4.0-SNAPSHOT/f8b024e83389b5293b1efa55f1753de8151b79fa/velocity-api-3.4.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-minimessage/4.26.1/9983d86668d6adba7e600dac20a5e61af703a6c6/adventure-text-minimessage-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.spongepowered/configurate-hocon/4.1.2/3953a4aef8ff62c72d34e405d6df333f3876592a/configurate-hocon-4.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.velocitypowered/velocity-api/3.4.0-SNAPSHOT/623b21e318b5fe8ad20ae62d52e55e34a6fc184/velocity-api-3.4.0-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/ansi/1.1.1/beeb71e49b25cac87c22975014e74c7b5940d1b7/ansi-1.1.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-json/4.25.0/ff6b4381dd8be9a40a1127937a4b71b9b010fcd6/adventure-text-serializer-json-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/examination-string/1.3.0/6f34afef5c54ccce4996bc321abf77518b55b4bd/examination-string-1.3.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-plain/4.25.0/82f5d4188f3cb6da9654b4ceea8b4093af5f1243/adventure-text-serializer-plain-4.25.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/net.kyori/adventure-text-serializer-ansi/4.26.1/7b441092ed57af2445872daa652ba2fce5deba31/adventure-text-serializer-ansi-4.26.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/jakarta.inject/jakarta.inject-api/2.0.1/4c28afe1991a941d7702fe1362c365f0a8641d1e/jakarta.inject-api-2.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.3/ba035118bc8bac37d7eff77700720999acd9986d/j2objc-annotations-1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="EXTJAR" id="/Users/mrxzx/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.42.0/638ec33f363a94d41a4f03c3e7d3dcfba64e402d/checker-qual-3.42.0.jar" enabled="true" runInBatchMode="false"/>