Skip to content

Commit 2131e82

Browse files
[1.5] feat: 支持离线线路更新和Geyser绕过
1 parent b94719e commit 2131e82

6 files changed

Lines changed: 380 additions & 68 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>ict.minesunshineone</groupId>
77
<artifactId>simpletransfer</artifactId>
8-
<version>1.4</version>
8+
<version>1.5</version>
99
<packaging>jar</packaging>
1010

1111
<name>SimpleTransfer</name>

src/main/java/ict/minesunshineone/simpleTransfer/command/CommandRegistry.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class CommandRegistry {
1919
private final PingMonitor pingMonitor;
2020
private final Logger logger;
2121
private final PlayerConnectionManager connectionManager;
22+
private final SimpleTransfer plugin;
2223

2324
// 记录已注册的命令,用于重载时注销
2425
private final Map<String, CommandMeta> registeredCommands = new HashMap<>();
@@ -32,6 +33,7 @@ public CommandRegistry(ProxyServer server, ConfigManager configManager,
3233
this.pingMonitor = pingMonitor;
3334
this.logger = logger;
3435
this.connectionManager = connectionManager;
36+
this.plugin = plugin;
3537
}
3638

3739
/**
@@ -83,7 +85,7 @@ private void registerTransferCommands() {
8385
private void registerMainCommand() {
8486
CommandManager commandManager = server.getCommandManager();
8587

86-
MainCommand mainCommand = new MainCommand(configManager, pingMonitor, this, connectionManager, server);
88+
MainCommand mainCommand = new MainCommand(configManager, pingMonitor, this, connectionManager, server, plugin);
8789
mainCommandMeta = commandManager.metaBuilder("simpletransfer")
8890
.aliases("st", "transfer")
8991
.build();

src/main/java/ict/minesunshineone/simpleTransfer/command/MainCommand.java

Lines changed: 140 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ict.minesunshineone.simpleTransfer.command;
22

3+
import com.velocitypowered.api.command.CommandSource;
34
import com.velocitypowered.api.command.SimpleCommand;
45
import com.velocitypowered.api.proxy.Player;
6+
import ict.minesunshineone.simpleTransfer.SimpleTransfer;
57
import ict.minesunshineone.simpleTransfer.config.ConfigManager;
68
import ict.minesunshineone.simpleTransfer.connection.PlayerConnectionManager;
79
import ict.minesunshineone.simpleTransfer.model.ServerInfo;
@@ -12,6 +14,7 @@
1214
import java.util.ArrayList;
1315
import java.util.List;
1416
import java.util.Map;
17+
import java.util.UUID;
1518

1619
public class MainCommand implements SimpleCommand {
1720

@@ -20,14 +23,17 @@ public class MainCommand implements SimpleCommand {
2023
private final CommandRegistry commandRegistry;
2124
private final PlayerConnectionManager connectionManager;
2225
private final com.velocitypowered.api.proxy.ProxyServer server;
26+
private final SimpleTransfer plugin;
2327

2428
public MainCommand(ConfigManager configManager, PingMonitor pingMonitor, CommandRegistry commandRegistry,
25-
PlayerConnectionManager connectionManager, com.velocitypowered.api.proxy.ProxyServer server) {
29+
PlayerConnectionManager connectionManager, com.velocitypowered.api.proxy.ProxyServer server,
30+
SimpleTransfer plugin) {
2631
this.configManager = configManager;
2732
this.pingMonitor = pingMonitor;
2833
this.commandRegistry = commandRegistry;
2934
this.connectionManager = connectionManager;
3035
this.server = server;
36+
this.plugin = plugin;
3137
}
3238

3339
@Override
@@ -786,78 +792,165 @@ private void showPlayerStatus(Invocation invocation, String[] args) {
786792
* 管理员命令: 手动转移玩家线路
787793
*/
788794
private void adminTransferPlayer(Invocation invocation, String[] args) {
789-
// 权限检查
790-
if (!invocation.source().hasPermission("simpletransfer.admin")) {
791-
invocation.source().sendMessage(Component.text()
795+
CommandSource source = invocation.source();
796+
797+
if (!source.hasPermission("simpletransfer.admin")) {
798+
source.sendMessage(Component.text()
792799
.append(Component.text(" ✘ ", NamedTextColor.RED))
793800
.append(Component.text("您没有权限使用此命令!", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
794801
.build());
795802
return;
796803
}
797-
804+
798805
if (args.length < 3) {
799-
invocation.source().sendMessage(Component.text()
806+
source.sendMessage(Component.text()
800807
.append(Component.text(" ✘ ", NamedTextColor.RED))
801-
.append(Component.text("用法: /st transfer <玩家名> <线路>", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
808+
.append(Component.text("用法: /st transfer <玩家名或UUID> <线路>", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
802809
.build());
803-
invocation.source().sendMessage(Component.text()
810+
source.sendMessage(Component.text()
804811
.append(Component.text(" ➤ ", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
805812
.append(Component.text("可用线路: ", NamedTextColor.WHITE))
806-
.append(Component.text(String.join(", ", configManager.getTransferServers().keySet()),
813+
.append(Component.text(String.join(", ", configManager.getTransferServers().keySet()),
807814
net.kyori.adventure.text.format.TextColor.color(0xAAFFFF)))
808815
.build());
809816
return;
810817
}
811-
812-
String playerName = args[1];
818+
819+
String targetInput = args[1];
813820
String routeName = args[2].toLowerCase();
814-
815-
Player targetPlayer = server.getPlayer(playerName).orElse(null);
816-
817-
if (targetPlayer == null) {
818-
invocation.source().sendMessage(Component.text()
819-
.append(Component.text(" ✘ ", NamedTextColor.RED))
820-
.append(Component.text("玩家 " + playerName + " 不在线或不存在!", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
821-
.build());
822-
return;
823-
}
824-
821+
825822
ServerInfo serverInfo = configManager.getTransferServers().get(routeName);
826-
823+
827824
if (serverInfo == null) {
828-
invocation.source().sendMessage(Component.text()
825+
source.sendMessage(Component.text()
829826
.append(Component.text(" ✘ ", NamedTextColor.RED))
830827
.append(Component.text("线路 " + routeName + " 不存在!", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
831828
.build());
832-
invocation.source().sendMessage(Component.text()
829+
source.sendMessage(Component.text()
833830
.append(Component.text(" ➤ ", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
834831
.append(Component.text("可用线路: ", NamedTextColor.WHITE))
835-
.append(Component.text(String.join(", ", configManager.getTransferServers().keySet()),
832+
.append(Component.text(String.join(", ", configManager.getTransferServers().keySet()),
836833
net.kyori.adventure.text.format.TextColor.color(0xAAFFFF)))
837834
.build());
838835
return;
839836
}
840-
841-
// 执行转移
842-
invocation.source().sendMessage(Component.text(" "));
843-
invocation.source().sendMessage(Component.text()
844-
.append(Component.text("正在将玩家 ", NamedTextColor.WHITE))
845-
.append(Component.text(targetPlayer.getUsername(), net.kyori.adventure.text.format.TextColor.color(0x00FF88)))
846-
.append(Component.text(" 转移到线路 ", NamedTextColor.WHITE))
847-
.append(Component.text("[" + routeName.toUpperCase() + "]", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
848-
.append(Component.text("...", NamedTextColor.WHITE))
849-
.build());
850-
invocation.source().sendMessage(Component.text(" "));
851-
852-
// 通知目标玩家
853-
targetPlayer.sendMessage(Component.text(" "));
854-
targetPlayer.sendMessage(Component.text()
855-
.append(Component.text("管理员正在将您转移到线路 ", NamedTextColor.WHITE))
856-
.append(Component.text("[" + serverInfo.getDisplayName() + "]", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
837+
838+
Player onlinePlayer = server.getPlayer(targetInput).orElse(null);
839+
840+
if (onlinePlayer != null) {
841+
// 若在线玩家为 Geyser/Floodgate(Bedrock)连接,则跳过处理
842+
if (connectionManager.isFloodgatePlayer(onlinePlayer.getUniqueId())) {
843+
source.sendMessage(Component.text()
844+
.append(Component.text(" ✦ ", NamedTextColor.GOLD))
845+
.append(Component.text("目标玩家通过 Geyser/Floodgate 连接,已跳过 SimpleTransfer 处理", net.kyori.adventure.text.format.TextColor.color(0xFFDD55)))
846+
.build());
847+
return;
848+
}
849+
source.sendMessage(Component.text(" "));
850+
source.sendMessage(Component.text()
851+
.append(Component.text("正在将玩家 ", NamedTextColor.WHITE))
852+
.append(Component.text(onlinePlayer.getUsername(), net.kyori.adventure.text.format.TextColor.color(0x00FF88)))
853+
.append(Component.text(" 转移到线路 ", NamedTextColor.WHITE))
854+
.append(Component.text("[" + routeName.toUpperCase() + "]", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
855+
.append(Component.text("...", NamedTextColor.WHITE))
856+
.build());
857+
source.sendMessage(Component.text(" "));
858+
859+
onlinePlayer.sendMessage(Component.text(" "));
860+
onlinePlayer.sendMessage(Component.text()
861+
.append(Component.text("管理员正在将您转移到线路 ", NamedTextColor.WHITE))
862+
.append(Component.text("[" + serverInfo.getDisplayName() + "]", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
863+
.build());
864+
onlinePlayer.sendMessage(Component.text(" "));
865+
866+
connectionManager.adminTransferPlayer(onlinePlayer, routeName, serverInfo);
867+
return;
868+
}
869+
870+
source.sendMessage(Component.text()
871+
.append(Component.text(" ➤ ", net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
872+
.append(Component.text("玩家当前不在线,正在更新其历史线路记录...", NamedTextColor.WHITE))
857873
.build());
858-
targetPlayer.sendMessage(Component.text(" "));
859-
860-
// 执行转移
861-
connectionManager.adminTransferPlayer(targetPlayer, routeName, serverInfo);
874+
875+
UUID directUuid = tryParseUuid(targetInput);
876+
if (directUuid != null) {
877+
updateOfflineRouteRecord(source, directUuid, targetInput, routeName, serverInfo);
878+
return;
879+
}
880+
881+
connectionManager.findPlayerUuidByName(targetInput)
882+
.ifPresentOrElse(uuid -> updateOfflineRouteRecord(source, uuid, targetInput, routeName, serverInfo),
883+
() -> sendAsyncMessage(source, Component.text()
884+
.append(Component.text(" ✘ ", NamedTextColor.RED))
885+
.append(Component.text("未找到玩家 " + targetInput + " 的历史线路记录,请确认名称是否正确或直接使用 UUID", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
886+
.build()));
887+
connectionManager.findPlayerUuidByName(targetInput)
888+
.ifPresentOrElse(uuid -> {
889+
if (connectionManager.isFloodgatePlayer(uuid)) {
890+
sendAsyncMessage(source, Component.text()
891+
.append(Component.text(" ✦ ", NamedTextColor.GOLD))
892+
.append(Component.text("目标玩家通过 Geyser/Floodgate 连接,已跳过 SimpleTransfer 处理", net.kyori.adventure.text.format.TextColor.color(0xFFDD55)))
893+
.build());
894+
return;
895+
}
896+
897+
updateOfflineRouteRecord(source, uuid, targetInput, routeName, serverInfo);
898+
}, () -> sendAsyncMessage(source, Component.text()
899+
.append(Component.text(" ✘ ", NamedTextColor.RED))
900+
.append(Component.text("未找到玩家 " + targetInput + " 的历史线路记录,请确认名称是否正确或直接使用 UUID", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
901+
.build()));
862902
}
903+
904+
private void updateOfflineRouteRecord(CommandSource source, UUID targetUuid, String targetName,
905+
String routeName, ServerInfo serverInfo) {
906+
String operatorName = getSourceName(source);
907+
String resolvedName = targetName;
908+
if (resolvedName == null || tryParseUuid(resolvedName) != null) {
909+
resolvedName = connectionManager.getLastKnownUsername(targetUuid)
910+
.orElse(resolvedName != null ? resolvedName : targetUuid.toString());
911+
}
912+
913+
final String displayName = resolvedName;
914+
915+
connectionManager.updateOfflineRoute(targetUuid, routeName, displayName, operatorName)
916+
.whenComplete((success, error) -> {
917+
if (error != null || !Boolean.TRUE.equals(success)) {
918+
sendAsyncMessage(source, Component.text()
919+
.append(Component.text(" ✘ ", NamedTextColor.RED))
920+
.append(Component.text("更新线路记录失败,请检查控制台日志。", net.kyori.adventure.text.format.TextColor.color(0xFFAAAA)))
921+
.build());
922+
return;
923+
}
924+
925+
sendAsyncMessage(source, Component.text()
926+
.append(Component.text(" ✓ ", NamedTextColor.GREEN))
927+
.append(Component.text("已将玩家 ", NamedTextColor.WHITE))
928+
.append(Component.text(displayName, net.kyori.adventure.text.format.TextColor.color(0x00FF88)))
929+
.append(Component.text(" 的历史线路更新为 ", NamedTextColor.WHITE))
930+
.append(Component.text("[" + routeName.toUpperCase() + "] ",
931+
net.kyori.adventure.text.format.TextColor.color(0x00DDFF)))
932+
.append(Component.text(serverInfo.getDescription(), net.kyori.adventure.text.format.TextColor.color(0xAAFFFF)))
933+
.append(Component.text(",玩家下次登录时将自动应用。", NamedTextColor.WHITE))
934+
.build());
935+
});
936+
}
937+
938+
private void sendAsyncMessage(CommandSource source, Component message) {
939+
server.getScheduler().buildTask(plugin, () -> source.sendMessage(message)).schedule();
940+
}
941+
942+
private String getSourceName(CommandSource source) {
943+
if (source instanceof Player player) {
944+
return player.getUsername();
945+
}
946+
return "控制台";
947+
}
948+
949+
private UUID tryParseUuid(String input) {
950+
try {
951+
return UUID.fromString(input);
952+
} catch (IllegalArgumentException ignored) {
953+
return null;
954+
}
955+
}
863956
}

0 commit comments

Comments
 (0)