feat: code

This commit is contained in:
2025-12-18 19:15:23 -08:00
parent 4049bc6a0d
commit 71a9b971d0
9 changed files with 265 additions and 233 deletions

View File

@@ -7,3 +7,4 @@ AlignAfterOpenBracket: BlockIndent
ColumnLimit: 100 ColumnLimit: 100
BinPackArguments: false BinPackArguments: false
AllowAllArgumentsOnNextLine: true AllowAllArgumentsOnNextLine: true
BreakStringLiterals: true

View File

@@ -4,15 +4,13 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
/** /**
* BindRequest handles server binding and verification with the Mineroo API. * BindRequest handles server binding and verification with the Mineroo API.
@@ -22,7 +20,6 @@ import java.util.concurrent.CompletableFuture;
* to handle the actual HTTP transport and authentication. * to handle the actual HTTP transport and authentication.
*/ */
public class BindRequest { public class BindRequest {
private final Logger logger; private final Logger logger;
private final NetworkServiceInterface networkService; private final NetworkServiceInterface networkService;
@@ -59,7 +56,6 @@ public class BindRequest {
return networkService.getData(path, params) return networkService.getData(path, params)
.thenApply(response -> { .thenApply(response -> {
if (response.getStatusCode() != 200) { if (response.getStatusCode() != 200) {
return false; return false;
} }
@@ -158,7 +154,9 @@ public class BindRequest {
* @param description The server description (nullable) * @param description The server description (nullable)
* @return A future that completes with true if the binding is successful * @return A future that completes with true if the binding is successful
*/ */
public CompletableFuture<Boolean> finalizeServerBinding(@Nullable String name, @Nullable String description) { public CompletableFuture<Boolean> finalizeServerBinding(
@Nullable String name, @Nullable String description
) {
String path = "/server/server-bind"; String path = "/server/server-bind";
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
@@ -180,26 +178,20 @@ public class BindRequest {
}); });
} }
public class PlayerBindResponse implements Serializable { public class PlayerBindStatusResponse implements Serializable {
@SerializedName("status") private PlayerBindStatusEnum status;
@SerializedName("status") @SerializedName("message") private String message;
private PlayerBindStatus status;
@SerializedName("message") @SerializedName("cached") private boolean cached;
private String message;
@SerializedName("cached") @SerializedName("timestamp") private long timestamp;
private boolean cached;
@SerializedName("timestamp") @SerializedName("info") private PlayerBindStatusInfo info;
private long timestamp;
@SerializedName("info")
private PlayerBindInfo info;
// --- Getters --- // --- Getters ---
public PlayerBindStatus getStatus() { public PlayerBindStatusEnum getStatus() {
return status; return status;
} }
@@ -215,11 +207,11 @@ public class BindRequest {
return timestamp; return timestamp;
} }
public PlayerBindInfo getInfo() { public PlayerBindStatusInfo getInfo() {
return info; return info;
} }
public void setStatus(PlayerBindStatus status) { public void setStatus(PlayerBindStatusEnum status) {
this.status = status; this.status = status;
} }
@@ -228,35 +220,26 @@ public class BindRequest {
} }
} }
public static enum PlayerBindStatus { public static enum PlayerBindStatusEnum {
@SerializedName("notBound") @SerializedName("notBound") NOT_BOUND,
NOT_BOUND,
@SerializedName("bound") @SerializedName("bound") BOUND,
BOUND,
@SerializedName("conflictUser") @SerializedName("conflictUser") CONFLICT_USER,
CONFLICT_USER,
@SerializedName("conflictPlayer") @SerializedName("conflictPlayer") CONFLICT_PLAYER,
CONFLICT_PLAYER,
@SerializedName("error") @SerializedName("error") ERROR
ERROR
} }
public static class PlayerBindInfo implements Serializable { public static class PlayerBindStatusInfo implements Serializable {
@SerializedName("bound_uuid") @SerializedName("bound_uuid") private String boundUuid;
private String boundUuid;
@SerializedName("minecraft_uuid") @SerializedName("minecraft_uuid") private String minecraftUuid;
private String minecraftUuid;
@SerializedName("bound_user_id") @SerializedName("bound_user_id") private Integer boundUserId;
private Integer boundUserId;
@SerializedName("user_id") @SerializedName("user_id") private Integer userId;
private Integer userId;
// --- Getters --- // --- Getters ---
@@ -285,20 +268,99 @@ public class BindRequest {
} }
} }
private PlayerBindResponse createErrorResponse(String errorMessage) { private PlayerBindStatusResponse createBindStatusErrorResponse(String errorMessage) {
PlayerBindResponse errorRes = new PlayerBindResponse(); PlayerBindStatusResponse errorRes = new PlayerBindStatusResponse();
errorRes.setStatus(PlayerBindStatus.ERROR); errorRes.setStatus(PlayerBindStatusEnum.ERROR);
errorRes.setMessage(errorMessage); errorRes.setMessage(errorMessage);
return errorRes; return errorRes;
} }
public CompletableFuture<PlayerBindResponse> checkPlayerBindStatus(UUID player_uuid, String user_email) { public CompletableFuture<PlayerBindStatusResponse> checkPlayerBindStatus(
UUID player_uuid, String user_email
) {
String path = "/server/user-bind-status"; String path = "/server/user-bind-status";
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("user_email", user_email); params.put("web_email", user_email);
params.put("player_uuid", player_uuid.toString()); params.put("mc_uuid", player_uuid.toString());
return networkService.getData(path, params)
.thenApply(response -> {
Gson gson = new Gson();
String responseBody = response.getBody();
if (response.getStatusCode() != 200) {
try {
return gson.fromJson(responseBody, PlayerBindStatusResponse.class);
} catch (Exception ignored) {
return createBindStatusErrorResponse("HTTP Error: " + response.getStatusCode());
}
}
try {
return gson.fromJson(responseBody, PlayerBindStatusResponse.class);
} catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse JSON", e);
return createBindStatusErrorResponse("JSON Parse Error");
}
})
.exceptionally(e -> {
logger.error("Mineroo Bind: Network exception", e);
return createBindStatusErrorResponse("Network Error: " + e.getMessage());
});
}
public class PlayerBindResponse implements Serializable {
@SerializedName("status") private PlayerBindEnum status;
@SerializedName("timestamp") private long timestamp;
@SerializedName("message") private String message;
public PlayerBindEnum getStatus() {
return status;
}
public String getMessage() {
return message;
}
public void setStatus(PlayerBindEnum status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
}
public static enum PlayerBindEnum {
@SerializedName("pending") PENDING,
@SerializedName("bound") BOUND,
@SerializedName("expired") EXPIRED,
@SerializedName("error") ERROR
}
private PlayerBindResponse createPlayerBindErrorResponse(String errorMessage) {
PlayerBindResponse errorRes = new PlayerBindResponse();
errorRes.setStatus(PlayerBindEnum.ERROR);
errorRes.setMessage(errorMessage);
return errorRes;
}
public CompletableFuture<PlayerBindResponse> PlayerBindRequest(
String webEmail, String playerUuid, String playerUsername
) {
String path = "/server/user-bind";
Map<String, String> params = new HashMap<>();
params.put("web_email", webEmail);
params.put("mc_uuid", playerUuid);
params.put("mc_username", playerUsername);
return networkService.getData(path, params) return networkService.getData(path, params)
.thenApply(response -> { .thenApply(response -> {
@@ -309,7 +371,7 @@ public class BindRequest {
try { try {
return gson.fromJson(responseBody, PlayerBindResponse.class); return gson.fromJson(responseBody, PlayerBindResponse.class);
} catch (Exception ignored) { } catch (Exception ignored) {
return createErrorResponse("HTTP Error: " + response.getStatusCode()); return createPlayerBindErrorResponse("HTTP Error: " + response.getStatusCode());
} }
} }
@@ -317,12 +379,12 @@ public class BindRequest {
return gson.fromJson(responseBody, PlayerBindResponse.class); return gson.fromJson(responseBody, PlayerBindResponse.class);
} catch (Exception e) { } catch (Exception e) {
logger.error("Mineroo Bind: Failed to parse JSON", e); logger.error("Mineroo Bind: Failed to parse JSON", e);
return createErrorResponse("JSON Parse Error"); return createPlayerBindErrorResponse("JSON Parse Error");
} }
}) })
.exceptionally(e -> { .exceptionally(e -> {
logger.error("Mineroo Bind: Network exception", e); logger.error("Mineroo Bind: Network exception", e);
return createErrorResponse("Network Error: " + e.getMessage()); return createPlayerBindErrorResponse("Network Error: " + e.getMessage());
}); });
} }

View File

@@ -4,6 +4,7 @@ import io.papermc.paper.plugin.bootstrap.BootstrapContext;
import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
import io.papermc.paper.registry.data.dialog.ActionButton; import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase; import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.body.DialogBody; import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.input.DialogInput; import io.papermc.paper.registry.data.dialog.input.DialogInput;
import io.papermc.paper.registry.data.dialog.type.DialogType; import io.papermc.paper.registry.data.dialog.type.DialogType;
@@ -16,16 +17,25 @@ import online.mineroo.common.MessageManager;
public class MinerooCoreBootstrap implements PluginBootstrap { public class MinerooCoreBootstrap implements PluginBootstrap {
@Override @Override
public void bootstrap(BootstrapContext context) { public void bootstrap(BootstrapContext context) {
// TODO: language auto check from config
MessageManager messageManager = new MessageManager(); MessageManager messageManager = new MessageManager();
ActionButton confirmButton = ActionButton confirmButton = ActionButton.create(
ActionButton.create(messageManager.get("dialog.bind.player.confirm"), null, 100, null); messageManager.get("dialog.bind.player.confirm"),
null,
100,
DialogAction.customClick(Key.key("mineroo:bind_user/confirm"), null)
);
ActionButton cancelButton = ActionButton cancelButton = ActionButton.create(
ActionButton.create(messageManager.get("dialog.bind.player.cancel"), null, 100, null); messageManager.get("dialog.bind.player.cancel"),
null,
100,
DialogAction.customClick(Key.key("mineroo:bind_user/cancel"), null)
);
DialogInput usernameInput = DialogInput usernameInput =
DialogInput.text("username", messageManager.get("dialog.bind.player.email")).build(); DialogInput.text("user_email", messageManager.get("dialog.bind.player.email")).build();
DialogBase dialogBase = DialogBase dialogBase =
DialogBase.builder(messageManager.get("dialog.bind.player.title")) DialogBase.builder(messageManager.get("dialog.bind.player.title"))
@@ -40,7 +50,7 @@ public class MinerooCoreBootstrap implements PluginBootstrap {
context.getLifecycleManager().registerEventHandler(RegistryEvents.DIALOG.compose().newHandler( context.getLifecycleManager().registerEventHandler(RegistryEvents.DIALOG.compose().newHandler(
event event
-> event.registry().register( -> event.registry().register(
DialogKeys.create(Key.key("papermc:bind_mineroo_user")), DialogKeys.create(Key.key("mineroo:bind_user")),
builder -> builder.base(dialogBase).type(confirmationType) builder -> builder.base(dialogBase).type(confirmationType)
) )
)); ));

View File

@@ -1,16 +1,13 @@
package online.mineroo.paper.listeners; package online.mineroo.paper.listeners;
import java.util.concurrent.atomic.AtomicReference;
import net.kyori.adventure.text.Component;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent; import org.bukkit.event.server.ServerListPingEvent;
import net.kyori.adventure.text.Component;
import java.util.concurrent.atomic.AtomicReference;
public class BindListener implements Listener { public class BindListener implements Listener {
private final AtomicReference<String> verificationToken = new AtomicReference<>(null); private final AtomicReference<String> verificationToken = new AtomicReference<>(null);
public void setVerificationToken(String token) { public void setVerificationToken(String token) {

View File

@@ -1,175 +1,167 @@
package online.mineroo.paper.listeners; package online.mineroo.paper.listeners;
import java.util.HashSet; import com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent;
import java.util.Set; import io.papermc.paper.connection.PlayerConfigurationConnection;
import io.papermc.paper.connection.PlayerGameConnection;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.dialog.DialogResponseView;
import io.papermc.paper.event.connection.configuration.AsyncPlayerConnectionConfigureEvent;
import io.papermc.paper.event.player.PlayerCustomClickEvent;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Location; import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player; import java.util.concurrent.TimeUnit;
import org.bukkit.event.EventHandler; import net.kyori.adventure.audience.Audience;
import org.bukkit.event.EventPriority; import net.kyori.adventure.key.Key;
import org.bukkit.event.Listener; import net.kyori.adventure.text.Component;
import org.bukkit.event.block.BlockBreakEvent; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.event.block.BlockPlaceEvent; import online.mineroo.common.BindRequest;
import org.bukkit.event.entity.EntityDamageEvent; import online.mineroo.common.BindRequest.PlayerBindStatusEnum;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jspecify.annotations.NullMarked;
import online.mineroo.common.BindRequest.PlayerBindStatus;
import online.mineroo.common.MessageManager;
import online.mineroo.paper.Config; import online.mineroo.paper.Config;
import online.mineroo.paper.MinerooCore; import online.mineroo.paper.MinerooCore;
import online.mineroo.paper.utils.PlayerBindDialog; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.jspecify.annotations.NullMarked;
import org.slf4j.Logger;
@NullMarked @NullMarked
public class PlayerBindListener implements Listener { public class PlayerBindListener implements Listener {
private final MinerooCore plugin; private final MinerooCore plugin;
private Set<UUID> pendingBindPlayers = new HashSet<>(); private final Map<UUID, CompletableFuture<Boolean>> awaitingResponse = new ConcurrentHashMap<>();
public PlayerBindListener(MinerooCore plugin) { public PlayerBindListener(MinerooCore plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerConfigure(AsyncPlayerConnectionConfigureEvent event) {
MessageManager messageManager = this.plugin.getMessageManager();
Config config = this.plugin.getConfigObject(); Config config = this.plugin.getConfigObject();
Player player = event.getPlayer(); if (!config.getPlayer().getPlayerBind().isRequired()) {
boolean isForceBind = config.getPlayer().getPlayerBind().isRequired();
if (isForceBind) {
lockPlayer(player);
}
this.plugin.getBindRequest().checkPlayerBindStatus(player.getUniqueId(), null)
.thenAccept(response -> {
// 4. 切回主线程 (Bukkit API 必须在主线程调用)
// 这一步非常重要,否则报错 "Asynchronous player tracker update"
this.plugin.getServer().getScheduler().runTask(this.plugin, () -> {
// 检查玩家是否还在同一个服务器 (防止请求期间玩家退出了)
if (!player.isOnline())
return;
PlayerBindStatus status = response.getStatus();
if (status == PlayerBindStatus.BOUND) {
if (isForceBind) {
unlockPlayer(player);
}
} else {
if (status == PlayerBindStatus.ERROR) {
this.plugin.getLogger()
.warning("Bind check failed for " + player.getName() + ": " + response.getMessage());
}
PlayerBindDialog dialog = new PlayerBindDialog();
player.showDialog(dialog.getDialog(messageManager));
}
});
});
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
if (!pendingBindPlayers.contains(player.getUniqueId())) {
return; return;
} }
Location from = event.getFrom(); UUID uuid = event.getConnection().getProfile().getId();
Location to = event.getTo(); if (uuid == null)
return;
if (from.getX() == to.getX() && from.getY() == to.getY() && from.getZ() == to.getZ()) { try {
var response = this.plugin.getBindRequest()
.checkPlayerBindStatus(uuid, event.getConnection().getProfile().getName())
.join();
PlayerBindStatusEnum status = response.getStatus();
if (status == PlayerBindStatusEnum.BOUND) {
return;
} else if (status == PlayerBindStatusEnum.NOT_BOUND) {
Dialog dialog = RegistryAccess.registryAccess()
.getRegistry(RegistryKey.DIALOG)
.get(Key.key("mineroo:bind_user"));
if (dialog == null) {
return;
}
PlayerConfigurationConnection connection = event.getConnection();
UUID uniqueId = connection.getProfile().getId();
if (uniqueId == null) {
return;
}
CompletableFuture<Boolean> dialogResponse = new CompletableFuture<>();
dialogResponse.completeOnTimeout(false, 1, TimeUnit.MINUTES);
awaitingResponse.put(uniqueId, dialogResponse);
Audience audience = connection.getAudience();
audience.showDialog(dialog);
if (!dialogResponse.join()) {
audience.closeDialog();
connection.disconnect(Component.text("You hate Paper-chan :(", NamedTextColor.RED));
}
awaitingResponse.remove(uniqueId);
} else {
event.getConnection().disconnect(
Component.text("服务器内部错误,请稍后重试", NamedTextColor.RED)
);
return;
}
} catch (Exception e) {
this.plugin.getLogger().severe("Failed to check bind status for " + uuid);
e.printStackTrace();
event.getConnection().disconnect(
Component.text("服务器内部错误,请稍后重试", NamedTextColor.RED)
);
}
}
@EventHandler
void onHandleDialog(PlayerCustomClickEvent event) {
Logger logger = plugin.getSLF4JLogger();
logger.info("12313213131231");
// Handle custom click only for configuration connection.
if (!(event.getCommonConnection()
instanceof PlayerConfigurationConnection configurationConnection)) {
return; return;
} }
Location newLocation = from.clone(); UUID uniqueId = configurationConnection.getProfile().getId();
newLocation.setYaw(to.getYaw());
newLocation.setPitch(to.getPitch());
event.setTo(newLocation);
}
@EventHandler if (uniqueId == null) {
public void onInteract(PlayerInteractEvent event) { return;
if (isLocked(event.getPlayer())) }
event.setCancelled(true);
}
@EventHandler Key key = event.getIdentifier();
public void onDrop(PlayerDropItemEvent event) {
if (isLocked(event.getPlayer()))
event.setCancelled(true);
}
@EventHandler if (key.equals(Key.key("mineroo:bind_user/confirm"))) {
public void onInventoryClick(InventoryClickEvent event) { DialogResponseView view = event.getDialogResponseView();
if (event.getWhoClicked() instanceof Player player && isLocked(player)) { if (view == null) {
event.setCancelled(true); return;
}
String user_email = view.getText("user_email");
if (event.getCommonConnection() instanceof PlayerConfigurationConnection conn) {
BindRequest bindRequest = plugin.getBindRequest();
}
setConnectionJoinResult(uniqueId, true);
} else if (key.equals(Key.key("mineroo:bind_user/cancel"))) {
// If it is the same as the agree one, set the result to true.
setConnectionJoinResult(uniqueId, false);
} }
} }
@EventHandler @EventHandler
public void onDamage(EntityDamageEvent event) { void onConnectionClose(PlayerConnectionCloseEvent event) {
if (event.getEntity() instanceof Player player && isLocked(player)) { awaitingResponse.remove(event.getPlayerUniqueId());
event.setCancelled(true);
}
} }
@EventHandler /**
public void onBlockBreak(BlockBreakEvent event) {
if (isLocked(event.getPlayer()))
event.setCancelled(true);
}
@EventHandler * Simple utility method for setting a connection's dialog response result.
public void onBlockPlace(BlockPlaceEvent event) {
if (isLocked(event.getPlayer()))
event.setCancelled(true);
}
@EventHandler */
public void onChat(io.papermc.paper.event.player.AsyncChatEvent event) {
if (isLocked(event.getPlayer()))
event.setCancelled(true);
}
private boolean isLocked(Player player) { private void setConnectionJoinResult(UUID uniqueId, boolean value) {
return pendingBindPlayers.contains(player.getUniqueId()); CompletableFuture<Boolean> future = awaitingResponse.get(uniqueId);
}
private void lockPlayer(Player player) { if (future != null) {
pendingBindPlayers.add(player.getUniqueId()); future.complete(value);
player.setWalkSpeed(0f);
player.setFlySpeed(0f);
// player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP,
// Integer.MAX_VALUE, 200, false, false));
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Integer.MAX_VALUE, 1, false, false));
}
public void unlockPlayer(Player player) {
if (pendingBindPlayers.remove(player.getUniqueId())) {
player.setWalkSpeed(0.2f);
player.setFlySpeed(0.1f);
// player.removePotionEffect(PotionEffectType.JUMP);
player.removePotionEffect(PotionEffectType.BLINDNESS);
} }
} }
} }

View File

@@ -1,34 +0,0 @@
package online.mineroo.paper.utils;
import java.util.List;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.input.DialogInput;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import online.mineroo.common.MessageManager;
public class PlayerBindDialog {
public Dialog getDialog(MessageManager messageManager) {
Dialog dialog = Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(messageManager.get("dialog.bind.player.title"))
.body(List.of(DialogBody.plainMessage(messageManager.get("dialog.bind.player.content"))))
.inputs(
List.of(
// username
DialogInput.text("username", messageManager.get("dialog.bind.player.email")).build()
// ...
))
.build())
.type(DialogType.confirmation(
ActionButton.create(messageManager.get("dialog.bind.player.confirm"), null, 100, null),
ActionButton.create(messageManager.get("dialog.bind.player.cancel"), null, 100, null)
// ...
)));
return dialog;
}
}

View File

@@ -0,0 +1,6 @@
name: MinerooCore
version: "1.0"
main: "online.mineroo.paper.MinerooCore"
description: Paper Test Plugin
api-version: "1.21.10"
bootstrapper: "online.mineroo.paper.MinerooCoreBootstrap"

View File

@@ -3,12 +3,10 @@ package online.mineroo.velocity.listeners;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.proxy.server.ServerPing;
import java.util.concurrent.atomic.AtomicReference;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.concurrent.atomic.AtomicReference;
public class BindListener { public class BindListener {
private final AtomicReference<String> verificationToken = new AtomicReference<>(null); private final AtomicReference<String> verificationToken = new AtomicReference<>(null);
public void setVerificationToken(String token) { public void setVerificationToken(String token) {