feat: code
This commit is contained in:
@@ -90,7 +90,7 @@ public class ProxyNetworkService implements NetworkServiceInterface, PluginMessa
|
||||
if (f != null) {
|
||||
f.complete(new NetworkResponse(504, "Proxy request timed out (Proxy didn't respond or no player online)"));
|
||||
}
|
||||
}, 100L); // 5 seconds timeout
|
||||
}, 200L); // 10 seconds timeout
|
||||
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
|
||||
package online.mineroo.paper.listeners;
|
||||
|
||||
import com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent;
|
||||
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.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@@ -26,9 +22,18 @@ import online.mineroo.common.BindRequest.PlayerBindResponse;
|
||||
import online.mineroo.common.BindRequest.PlayerBindStatusEnum;
|
||||
import online.mineroo.paper.Config;
|
||||
import online.mineroo.paper.MinerooCore;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
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.event.player.PlayerQuitEvent;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
@@ -38,190 +43,211 @@ public class PlayerBindListener implements Listener {
|
||||
private final Map<UUID, CompletableFuture<PlayerBindResponse>> awaitingResponse =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// Set to store UUIDs of players who are currently being checked or binding
|
||||
private final Set<UUID> restrictedPlayers = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public PlayerBindListener(MinerooCore plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerConfigure(AsyncPlayerConnectionConfigureEvent event) {
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Config config = this.plugin.getConfigObject();
|
||||
|
||||
if (!config.getPlayer().getPlayerBind().isRequired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uuid = event.getConnection().getProfile().getId();
|
||||
if (uuid == null)
|
||||
return;
|
||||
Player player = event.getPlayer();
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
try {
|
||||
var response = this.plugin.getBindRequest().checkPlayerBindStatus(uuid, null).join();
|
||||
// Lock player immediately upon join
|
||||
restrictedPlayers.add(uuid);
|
||||
|
||||
PlayerBindStatusEnum status = response.getStatus();
|
||||
|
||||
plugin.getSLF4JLogger().info(status.toString());
|
||||
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<PlayerBindResponse> dialogResponse = new CompletableFuture<>();
|
||||
dialogResponse.completeOnTimeout(null, 1, TimeUnit.MINUTES);
|
||||
awaitingResponse.put(uniqueId, dialogResponse);
|
||||
|
||||
Audience audience = connection.getAudience();
|
||||
audience.showDialog(dialog);
|
||||
|
||||
PlayerBindResponse bindResponse = dialogResponse.join();
|
||||
if (bindResponse == null) {
|
||||
audience.closeDialog();
|
||||
connection.disconnect(
|
||||
Component.text("Operation timed out, please try again.", NamedTextColor.RED)
|
||||
);
|
||||
awaitingResponse.remove(uniqueId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bindResponse.getStatus() == PlayerBindEnum.BOUND) {
|
||||
return;
|
||||
} else if (bindResponse.getStatus() == PlayerBindEnum.PENDING) {
|
||||
audience.closeDialog();
|
||||
String msg = "Binding request submitted!\nPlease go to Mineroo.Online to confirm the "
|
||||
+ "binding. After confirmation, please re-enter the server.";
|
||||
connection.disconnect(Component.text(msg, NamedTextColor.GREEN));
|
||||
} else {
|
||||
audience.closeDialog();
|
||||
|
||||
String statusStr = (bindResponse.getStatus() != null)
|
||||
? bindResponse.getStatus().toString()
|
||||
: "UNKNOWN_STATUS";
|
||||
String msg = bindResponse.getMessage();
|
||||
if (msg == null)
|
||||
msg = "Bind failed.";
|
||||
|
||||
String kickMessage = statusStr + " : " + msg;
|
||||
|
||||
connection.disconnect(Component.text(kickMessage, NamedTextColor.RED));
|
||||
}
|
||||
awaitingResponse.remove(uniqueId);
|
||||
|
||||
} else {
|
||||
event.getConnection().disconnect(
|
||||
Component.text("Internal server error, please try again later.", NamedTextColor.RED)
|
||||
);
|
||||
// 延迟 2 秒 (40 ticks) 再发送检查请求,给予 Proxy 通道建立缓冲时间,防止发包过快导致丢失 (504)
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
if (!player.isOnline()) {
|
||||
restrictedPlayers.remove(uuid); // 玩家中途退出,清理缓存
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
this.plugin.getLogger().severe("Failed to check bind status for " + uuid);
|
||||
e.printStackTrace();
|
||||
event.getConnection().disconnect(
|
||||
Component.text("Internal server error, please try again later.", NamedTextColor.RED)
|
||||
);
|
||||
}
|
||||
}
|
||||
// 异步检查,避免阻塞主线程
|
||||
this.plugin.getBindRequest()
|
||||
.checkPlayerBindStatus(uuid, null)
|
||||
.thenAccept(response -> {
|
||||
// 回到主线程处理 UI 和 踢出逻辑
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (!player.isOnline())
|
||||
return; // 玩家可能在检查期间退出了
|
||||
|
||||
@EventHandler
|
||||
void onHandleDialog(PlayerCustomClickEvent event) {
|
||||
// Main handlr only use for PlayerConfigureationConnection,
|
||||
// Inside Block use for handle command bind request.
|
||||
if (event.getCommonConnection() instanceof PlayerGameConnection playerConn) {
|
||||
// handle command bind
|
||||
Player player = playerConn.getPlayer();
|
||||
UUID playerUuid = player.getUniqueId();
|
||||
this.PlayerBindProcess(event, playerUuid, response -> {
|
||||
if (response.getStatus() == PlayerBindEnum.PENDING) {
|
||||
player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.pending"));
|
||||
} else if (response.getStatus() == PlayerBindEnum.BOUND) {
|
||||
player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.success"));
|
||||
} else {
|
||||
String statusText =
|
||||
(response.getStatus() != null) ? response.getStatus().toString() : "UNKNOWN_STATUS";
|
||||
String messageText =
|
||||
(response.getMessage() != null) ? response.getMessage() : "Bind Failed";
|
||||
String fullMessage = "[ " + statusText + " ]: " + messageText;
|
||||
player.sendMessage(Component.text(fullMessage, NamedTextColor.RED));
|
||||
}
|
||||
});
|
||||
} else if (event.getCommonConnection()
|
||||
instanceof PlayerConfigurationConnection configurationConnection) {
|
||||
// handle required configuration event bind
|
||||
UUID playerUuid = configurationConnection.getProfile().getId();
|
||||
this.PlayerBindProcess(event, playerUuid, response -> {
|
||||
setConnectionJoinResult(playerUuid, response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerBindProcess(
|
||||
PlayerCustomClickEvent event, UUID playerUuid, Consumer<PlayerBindResponse> callback
|
||||
) {
|
||||
if (playerUuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Key key = event.getIdentifier();
|
||||
|
||||
if (key.equals(Key.key("mineroo:bind_user/confirm"))) {
|
||||
DialogResponseView view = event.getDialogResponseView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String userEmail = view.getText("user_email");
|
||||
String playerName = "Unknown";
|
||||
|
||||
if (event.getCommonConnection() instanceof PlayerConfigurationConnection conn) {
|
||||
playerName = conn.getProfile().getName();
|
||||
} else if (event.getCommonConnection() instanceof PlayerGameConnection conn) {
|
||||
playerName = conn.getPlayer().getName();
|
||||
}
|
||||
|
||||
BindRequest bindRequest = plugin.getBindRequest();
|
||||
|
||||
bindRequest.PlayerBindRequest(userEmail, playerUuid, playerName)
|
||||
.thenAccept(response -> { callback.accept(response); })
|
||||
.exceptionally(ex -> {
|
||||
callback.accept(BindRequest.createPlayerBindErrorResponse(
|
||||
"Network request failed: " + ex.getMessage()
|
||||
));
|
||||
handleBindStatus(player, response.getStatus(), response.getMessage());
|
||||
});
|
||||
})
|
||||
.exceptionally(e -> {
|
||||
plugin.getLogger().severe("Failed to check bind status for " + uuid);
|
||||
e.printStackTrace();
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.kick(
|
||||
Component.text("Internal server error, please try again later.", NamedTextColor.RED)
|
||||
);
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}, 40L);
|
||||
}
|
||||
|
||||
} else if (key.equals(Key.key("mineroo:bind_user/cancel"))) {
|
||||
callback.accept(BindRequest.createPlayerBindErrorResponse(
|
||||
this.plugin.getMessageManager().getString("info.bind.player.canceled")
|
||||
private void handleBindStatus(Player player, PlayerBindStatusEnum status, String message) {
|
||||
if (status == PlayerBindStatusEnum.BOUND) {
|
||||
// Player is bound, release restriction
|
||||
restrictedPlayers.remove(player.getUniqueId());
|
||||
return;
|
||||
} else if (status == PlayerBindStatusEnum.NOT_BOUND) {
|
||||
Dialog dialog = RegistryAccess.registryAccess()
|
||||
.getRegistry(RegistryKey.DIALOG)
|
||||
.get(Key.key("mineroo:bind_user"));
|
||||
if (dialog == null) {
|
||||
plugin.getLogger().severe("Dialog 'mineroo:bind_user' not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID uniqueId = player.getUniqueId();
|
||||
CompletableFuture<PlayerBindResponse> dialogResponse = new CompletableFuture<>();
|
||||
dialogResponse.completeOnTimeout(null, 2, TimeUnit.MINUTES);
|
||||
awaitingResponse.put(uniqueId, dialogResponse);
|
||||
|
||||
// 使用 player 直接显示 Dialog
|
||||
player.showDialog(dialog);
|
||||
|
||||
// 异步等待 Dialog 结果,不阻塞主线程
|
||||
dialogResponse.thenAccept(bindResponse -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
awaitingResponse.remove(uniqueId);
|
||||
if (bindResponse == null) {
|
||||
player.closeDialog();
|
||||
player.kick(
|
||||
Component.text("Operation timed out, please try again.", NamedTextColor.RED)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bindResponse.getStatus() == PlayerBindEnum.BOUND) {
|
||||
// 极其罕见的情况:请求瞬间已绑定(通常不会发生,除非已有自动绑定逻辑)
|
||||
restrictedPlayers.remove(uniqueId);
|
||||
player.sendMessage(this.plugin.getMessageManager().get("info.bind.player.success"));
|
||||
} else if (bindResponse.getStatus() == PlayerBindEnum.PENDING) {
|
||||
player.closeDialog();
|
||||
// 提交成功,踢出玩家,让其去网站确认
|
||||
player.kick(this.plugin.getMessageManager().get("info.bind.player.pending"));
|
||||
} else {
|
||||
player.closeDialog();
|
||||
String statusStr = (bindResponse.getStatus() != null)
|
||||
? bindResponse.getStatus().toString()
|
||||
: "UNKNOWN";
|
||||
String msg =
|
||||
bindResponse.getMessage() != null ? bindResponse.getMessage() : "Bind failed";
|
||||
player.kick(Component.text(statusStr + " : " + msg, NamedTextColor.RED));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
player.kick(Component.text(
|
||||
"Bind Check Error: " + (message != null ? message : status.toString()), NamedTextColor.RED
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
void onConnectionClose(PlayerConnectionCloseEvent event) {
|
||||
awaitingResponse.remove(event.getPlayerUniqueId());
|
||||
void onHandleDialog(PlayerCustomClickEvent event) {
|
||||
Player player = null;
|
||||
if (event.getCommonConnection() instanceof PlayerGameConnection conn) {
|
||||
player = conn.getPlayer();
|
||||
}
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
UUID playerUuid = player.getUniqueId();
|
||||
|
||||
Key key = event.getIdentifier();
|
||||
|
||||
if (key.equals(Key.key("mineroo:bind_user/confirm"))) {
|
||||
DialogResponseView view = event.getDialogResponseView();
|
||||
if (view == null)
|
||||
return;
|
||||
|
||||
String userEmail = view.getText("user_email");
|
||||
String playerName = player.getName();
|
||||
|
||||
final UUID targetUuid = playerUuid;
|
||||
|
||||
plugin.getBindRequest()
|
||||
.PlayerBindRequest(userEmail, targetUuid, playerName)
|
||||
.thenAccept(response -> setConnectionJoinResult(targetUuid, response))
|
||||
.exceptionally(ex -> {
|
||||
setConnectionJoinResult(
|
||||
targetUuid,
|
||||
BindRequest.createPlayerBindErrorResponse("Network Error: " + ex.getMessage())
|
||||
);
|
||||
return null;
|
||||
});
|
||||
} else if (key.equals(Key.key("mineroo:bind_user/cancel"))) {
|
||||
setConnectionJoinResult(
|
||||
playerUuid, BindRequest.createPlayerBindErrorResponse("Canceled by user")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@EventHandler
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
awaitingResponse.remove(uuid);
|
||||
restrictedPlayers.remove(uuid);
|
||||
}
|
||||
|
||||
* Simple utility method for setting a connection's dialog response result.
|
||||
// --- Restriction Event Handlers ---
|
||||
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerMove(PlayerMoveEvent event) {
|
||||
if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) {
|
||||
if (event.getFrom().getX() != event.getTo().getX()
|
||||
|| event.getFrom().getY() != event.getTo().getY()
|
||||
|| event.getFrom().getZ() != event.getTo().getZ()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (restrictedPlayers.contains(event.getPlayer().getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onInventoryOpen(InventoryOpenEvent event) {
|
||||
if (event.getPlayer() instanceof Player player
|
||||
&& restrictedPlayers.contains(player.getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (event.getWhoClicked() instanceof Player player
|
||||
&& restrictedPlayers.contains(player.getUniqueId())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void setConnectionJoinResult(UUID uniqueId, PlayerBindResponse value) {
|
||||
CompletableFuture<PlayerBindResponse> future = awaitingResponse.get(uniqueId);
|
||||
|
||||
if (future != null) {
|
||||
future.complete(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user