diff --git a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java index e496729..b449932 100644 --- a/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java +++ b/ball-bukkit/src/main/java/cn/hamster3/mc/plugin/ball/bukkit/HamsterBallPlugin.java @@ -6,18 +6,26 @@ import cn.hamster3.mc.plugin.ball.bukkit.listener.BallBukkitListener; import cn.hamster3.mc.plugin.ball.bukkit.listener.UpdatePlayerInfoListener; import cn.hamster3.mc.plugin.ball.bukkit.util.BallBukkitUtils; import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.BallCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; +import cn.hamster3.mc.plugin.core.bukkit.api.CoreBukkitAPI; import cn.hamster3.mc.plugin.core.common.config.YamlConfig; import lombok.Getter; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.util.List; import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -129,4 +137,35 @@ public class HamsterBallPlugin extends JavaPlugin { long time = System.currentTimeMillis() - start; logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return BallCommand.INSTANCE.onCommand(new AdaptCommandSender() { + @Override + public boolean hasPermission(@NotNull String permission) { + return sender.hasPermission(permission); + } + + @Override + public void sendMessage(@NotNull Component message) { + CoreBukkitAPI.getInstance().getAudienceProvider().sender(sender).sendMessage(message); + } + }, args); + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + return BallCommand.INSTANCE.onTabComplete(new AdaptCommandSender() { + @Override + public boolean hasPermission(@NotNull String permission) { + return sender.hasPermission(permission); + } + + @Override + public void sendMessage(@NotNull Component message) { + CoreBukkitAPI.getInstance().getAudienceProvider().sender(sender).sendMessage(message); + } + }, args); + } } diff --git a/ball-bukkit/src/main/resources/plugin.yml b/ball-bukkit/src/main/resources/plugin.yml index 13005df..eb2d969 100644 --- a/ball-bukkit/src/main/resources/plugin.yml +++ b/ball-bukkit/src/main/resources/plugin.yml @@ -21,3 +21,11 @@ depend: softdepend: - PlaceholderAPI + +commands: + hamster-ball: + aliases: [ ball ] + +permissions: + hamster.ball.admin: + default: op diff --git a/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/HamsterBallPlugin.java b/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/HamsterBallPlugin.java index 2be20c5..56c240f 100644 --- a/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/HamsterBallPlugin.java +++ b/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/HamsterBallPlugin.java @@ -1,6 +1,7 @@ package cn.hamster3.mc.plugin.ball.bungee; import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI; +import cn.hamster3.mc.plugin.ball.bungee.command.BungeeBallCommand; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BungeeServerListener; @@ -71,6 +72,8 @@ public class HamsterBallPlugin extends Plugin { ProxyServer.getInstance().stop("仓鼠球启动失败"); return; } + ProxyServer.getInstance().getPluginManager().registerCommand(this, BungeeBallCommand.INSTANCE); + logger.info("已注册命令 BungeeBallCommand"); BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE); logger.info("已注册监听器 BallBungeeListener"); ProxyServer.getInstance().getPluginManager().registerListener(this, BallBungeeMainListener.INSTANCE); diff --git a/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/command/BungeeBallCommand.java b/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/command/BungeeBallCommand.java new file mode 100644 index 0000000..455934c --- /dev/null +++ b/ball-bungee/src/main/java/cn/hamster3/mc/plugin/ball/bungee/command/BungeeBallCommand.java @@ -0,0 +1,32 @@ +package cn.hamster3.mc.plugin.ball.bungee.command; + +import cn.hamster3.mc.plugin.ball.common.command.BallCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import cn.hamster3.mc.plugin.core.bungee.api.CoreBungeeAPI; +import net.kyori.adventure.text.Component; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import org.jetbrains.annotations.NotNull; + +public class BungeeBallCommand extends Command { + public static final BungeeBallCommand INSTANCE = new BungeeBallCommand(); + + public BungeeBallCommand() { + super("hamster-ball", "hamster.ball.admin", "ball"); + } + + @Override + public void execute(CommandSender sender, String[] args) { + BallCommand.INSTANCE.onCommand(new AdaptCommandSender() { + @Override + public boolean hasPermission(@NotNull String permission) { + return sender.hasPermission(permission); + } + + @Override + public void sendMessage(@NotNull Component message) { + CoreBungeeAPI.getInstance().getAudienceProvider().sender(sender); + } + }, args); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java index 582b444..05bb698 100644 --- a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/api/BallAPI.java @@ -132,16 +132,16 @@ public abstract class BallAPI { try (Connection connection = getDatasource().getConnection()) { try (Statement statement = connection.createStatement()) { statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_player_info`(" + - "`uuid` CHAR(36) PRIMARY KEY," + - "`name` VARCHAR(16) NOT NULL," + - "`game_server` VARCHAR(32) NOT NULL," + - "`proxy_server` VARCHAR(32) NOT NULL," + - "`online` BOOLEAN NOT NULL" + - ") CHARSET utf8mb4;"); + "`uuid` CHAR(36) PRIMARY KEY," + + "`name` VARCHAR(16) NOT NULL," + + "`game_server` VARCHAR(32) NOT NULL," + + "`proxy_server` VARCHAR(32) NOT NULL," + + "`online` BOOLEAN NOT NULL" + + ") CHARSET utf8mb4;"); statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" + - "`uuid` CHAR(36) NOT NULL," + - "`message` TEXT NOT NULL" + - ") CHARSET utf8mb4;"); + "`uuid` CHAR(36) NOT NULL," + + "`message` TEXT NOT NULL" + + ") CHARSET utf8mb4;"); } if (getBallConfig().isGameServerUpdatePlayerInfo()) { try (Statement statement = connection.createStatement()) { @@ -267,8 +267,8 @@ public abstract class BallAPI { /** * 强制玩家执行命令 * - * @param type 执行对象的服务端类型 - * @param uuid 执行对象的 UUID + * @param type 执行对象的服务端类型,null代表所有类型 + * @param uuid 执行对象的 UUID,null代表所有玩家 * @param command 命令内容 */ public void dispatchPlayerCommand(@Nullable BallServerType type, @Nullable UUID uuid, @NotNull String command) { diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/BallCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/BallCommand.java new file mode 100644 index 0000000..1d5bbce --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/BallCommand.java @@ -0,0 +1,21 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.command.adapt.ParentCommand; +import org.jetbrains.annotations.NotNull; + +public class BallCommand extends ParentCommand { + public static final BallCommand INSTANCE = new BallCommand(); + + private BallCommand() { + addChildCommand(PlayerInfoCommand.INSTANCE); + addChildCommand(SudoPlayerCommand.INSTANCE); + addChildCommand(SudoAllConsoleCommand.INSTANCE); + addChildCommand(SudoConsoleCommand.INSTANCE); + addChildCommand(SudoAllConsoleCommand.INSTANCE); + } + + @Override + public @NotNull String getName() { + return "hamster-ball"; + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/PlayerInfoCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/PlayerInfoCommand.java new file mode 100644 index 0000000..2a6fb62 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/PlayerInfoCommand.java @@ -0,0 +1,77 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class PlayerInfoCommand extends ChildCommand { + public static final PlayerInfoCommand INSTANCE = new PlayerInfoCommand(); + + private PlayerInfoCommand() { + } + + @Override + public @NotNull String getName() { + return "player-info"; + } + + @Override + public @NotNull String getUsage() { + return "player-info <玩家名|UUID>"; + } + + @Override + public @NotNull String getDescription() { + return "查看玩家信息"; + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return sender.hasPermission("hamster.ball.admin"); + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length < 1) { + sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage()); + return true; + } + BallPlayerInfo info; + try { + UUID uuid = UUID.fromString(args[0]); + info = BallAPI.getInstance().getPlayerInfo(uuid); + } catch (Exception e) { + info = BallAPI.getInstance().getPlayerInfo(args[0]); + } + if (info == null) { + sender.sendMessage("§c未找到玩家 " + args[0] + " 的信息"); + return true; + } + sender.sendMessage("§a玩家名称: " + info.getName()); + sender.sendMessage("§a玩家UUID: " + info.getUuid()); + sender.sendMessage("§a玩家在线: " + info.isOnline()); + sender.sendMessage("§a接入点: " + info.getProxyServer()); + sender.sendMessage("§a所在子服: " + info.getGameServer()); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length == 1) { + return BallAPI.getInstance().getAllPlayerInfo().values().stream() + .map(BallPlayerInfo::getName) + .filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase())) + .limit(10) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllConsoleCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllConsoleCommand.java new file mode 100644 index 0000000..a2f3cf4 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllConsoleCommand.java @@ -0,0 +1,58 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class SudoAllConsoleCommand extends ChildCommand { + public static final SudoAllConsoleCommand INSTANCE = new SudoAllConsoleCommand(); + + private SudoAllConsoleCommand() { + } + + @Override + public @NotNull String getName() { + return "sudo-all-console"; + } + + @Override + public @NotNull String getUsage() { + return "sudo-all-console <服务器ID> <命令内容>"; + } + + @Override + public @NotNull String getDescription() { + return "强制所有控制台执行指令"; + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return sender.hasPermission("hamster.ball.admin"); + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length < 1) { + sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage()); + return true; + } + StringBuilder builder = new StringBuilder(args[0]); + for (int i = 1; i < args.length; i++) { + builder.append(" ").append(args[i]); + } + String command = builder.toString(); + BallAPI.getInstance().dispatchConsoleCommand(null, null, command); + sender.sendMessage("§a已强制所有服务器控制台执行命令: §e/" + command); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + return Collections.emptyList(); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllPlayerCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllPlayerCommand.java new file mode 100644 index 0000000..0019d43 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoAllPlayerCommand.java @@ -0,0 +1,58 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class SudoAllPlayerCommand extends ChildCommand { + public static final SudoAllPlayerCommand INSTANCE = new SudoAllPlayerCommand(); + + private SudoAllPlayerCommand() { + } + + @Override + public @NotNull String getName() { + return "sudo-all-player"; + } + + @Override + public @NotNull String getUsage() { + return "sudo-all-player <命令内容>"; + } + + @Override + public @NotNull String getDescription() { + return "强制所有玩家执行指令"; + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return sender.hasPermission("hamster.ball.admin"); + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length < 1) { + sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage()); + return true; + } + StringBuilder builder = new StringBuilder(args[0]); + for (int i = 1; i < args.length; i++) { + builder.append(" ").append(args[i]); + } + String command = builder.toString(); + BallAPI.getInstance().dispatchPlayerCommand(null, null, command); + sender.sendMessage("§a已强制所有玩家执行命令: §e/" + command); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + return Collections.emptyList(); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoConsoleCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoConsoleCommand.java new file mode 100644 index 0000000..a70af57 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoConsoleCommand.java @@ -0,0 +1,72 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import cn.hamster3.mc.plugin.ball.common.entity.BallServerInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class SudoConsoleCommand extends ChildCommand { + public static final SudoConsoleCommand INSTANCE = new SudoConsoleCommand(); + + private SudoConsoleCommand() { + } + + @Override + public @NotNull String getName() { + return "sudo-console"; + } + + @Override + public @NotNull String getUsage() { + return "sudo-console <服务器ID> <命令内容>"; + } + + @Override + public @NotNull String getDescription() { + return "强制控制台执行指令"; + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return sender.hasPermission("hamster.ball.admin"); + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length < 2) { + sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage()); + return true; + } + BallServerInfo info = BallAPI.getInstance().getServerInfo(args[0]); + if (info == null) { + sender.sendMessage("§c服务器 " + args[0] + " 不在线"); + return true; + } + StringBuilder builder = new StringBuilder(args[1]); + for (int i = 2; i < args.length; i++) { + builder.append(" ").append(args[i]); + } + String command = builder.toString(); + BallAPI.getInstance().dispatchConsoleCommand(null, info.getId(), command); + sender.sendMessage("§a已强制服务器 " + info.getName() + " 控制台执行命令: §e/" + command); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length == 1) { + return BallAPI.getInstance().getAllServerInfo().values().stream() + .map(BallServerInfo::getId) + .filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase())) + .limit(10) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoPlayerCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoPlayerCommand.java new file mode 100644 index 0000000..2314a49 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/SudoPlayerCommand.java @@ -0,0 +1,83 @@ +package cn.hamster3.mc.plugin.ball.common.command; + +import cn.hamster3.mc.plugin.ball.common.api.BallAPI; +import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class SudoPlayerCommand extends ChildCommand { + public static final SudoPlayerCommand INSTANCE = new SudoPlayerCommand(); + + private SudoPlayerCommand() { + } + + @Override + public @NotNull String getName() { + return "sudo-player"; + } + + @Override + public @NotNull String getUsage() { + return "sudo-player <玩家名|UUID> <命令内容>"; + } + + @Override + public @NotNull String getDescription() { + return "强制玩家执行指令"; + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return sender.hasPermission("hamster.ball.admin"); + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length < 2) { + sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage()); + return true; + } + BallPlayerInfo info; + try { + UUID uuid = UUID.fromString(args[0]); + info = BallAPI.getInstance().getPlayerInfo(uuid); + } catch (Exception e) { + info = BallAPI.getInstance().getPlayerInfo(args[0]); + } + if (info == null) { + sender.sendMessage("§c未找到玩家 " + args[0]); + return true; + } + if (!info.isOnline()) { + sender.sendMessage("§c玩家 " + args[0] + " 不在线"); + return true; + } + StringBuilder builder = new StringBuilder(args[1]); + for (int i = 2; i < args.length; i++) { + builder.append(" ").append(args[i]); + } + String command = builder.toString(); + BallAPI.getInstance().dispatchPlayerCommand(null, info.getUuid(), command); + sender.sendMessage("§a已强制玩家 " + info.getName() + " 执行命令: §e/" + command); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length == 1) { + return BallAPI.getInstance().getAllPlayerInfo().values().stream() + .map(BallPlayerInfo::getName) + .filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase())) + .limit(10) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/AdaptCommandSender.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/AdaptCommandSender.java new file mode 100644 index 0000000..9ebfbda --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/AdaptCommandSender.java @@ -0,0 +1,14 @@ +package cn.hamster3.mc.plugin.ball.common.command.adapt; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +public interface AdaptCommandSender { + boolean hasPermission(@NotNull String permission); + + void sendMessage(@NotNull Component message); + + default void sendMessage(@NotNull String message) { + sendMessage(Component.text(message)); + } +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ChildCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ChildCommand.java new file mode 100644 index 0000000..c837735 --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ChildCommand.java @@ -0,0 +1,24 @@ +package cn.hamster3.mc.plugin.ball.common.command.adapt; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class ChildCommand { + @NotNull + public abstract String getName(); + + @NotNull + public abstract String getUsage(); + + @NotNull + public abstract String getDescription(); + + public abstract boolean hasPermission(@NotNull AdaptCommandSender sender); + + public abstract boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args); + + @Nullable + public abstract List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args); +} diff --git a/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ParentCommand.java b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ParentCommand.java new file mode 100644 index 0000000..151c02d --- /dev/null +++ b/ball-common/src/main/java/cn/hamster3/mc/plugin/ball/common/command/adapt/ParentCommand.java @@ -0,0 +1,151 @@ +package cn.hamster3.mc.plugin.ball.common.command.adapt; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +public abstract class ParentCommand extends ChildCommand { + @NotNull + private final Map childCommands; + + public ParentCommand() { + childCommands = new LinkedHashMap<>(); + } + + @NotNull + @Override + public abstract String getName(); + + @Nullable + public ParentCommand getParent() { + return null; + } + + @NotNull + public Collection getChildCommands() { + return childCommands.values(); + } + + @NotNull + @Override + public String getUsage() { + ParentCommand parent = getParent(); + if (parent == null) { + return "/" + getName(); + } + return parent.getUsage() + " " + getName(); + } + + @Override + public boolean hasPermission(@NotNull AdaptCommandSender sender) { + return true; + } + + @Override + public @NotNull String getDescription() { + return ""; + } + + /** + * 获取所有子命令 + *

+ * 如果子命令也是 ParentCommand 类型,则继续递归获取该 ParentCommand 的子命令 + * + * @return 所有子命令 + */ + @NotNull + public List getEndChildCommands() { + ArrayList list = new ArrayList<>(); + for (ChildCommand command : getChildCommands()) { + if (command instanceof ParentCommand) { + list.addAll(((ParentCommand) command).getEndChildCommands()); + } else { + list.add(command); + } + } + return list; + } + + public void addChildCommand(@NotNull ChildCommand command) { + childCommands.put(command.getName(), command); + } + + @NotNull + public Map getCommandHelp(AdaptCommandSender sender) { + Map map = new LinkedHashMap<>(); + for (ChildCommand child : getChildCommands()) { + if (!child.hasPermission(sender)) { + continue; + } + if (child instanceof ParentCommand) { + Map childMap = ((ParentCommand) child).getCommandHelp(sender); + map.putAll(childMap); + continue; + } + map.put(getUsage() + " " + child.getUsage(), child.getDescription()); + } + return map; + } + + public void sendHelp(@NotNull AdaptCommandSender sender) { + sender.sendMessage("§e==================== [ " + getName() + " 使用帮助] ===================="); + Map map = getCommandHelp(sender); + int maxLength = map.keySet().stream() + .map(String::length) + .max(Integer::compareTo) + .orElse(-1); + for (Map.Entry entry : map.entrySet()) { + sender.sendMessage(String.format("§a%-" + maxLength + "s - %s", entry.getKey(), entry.getValue())); + } + } + + @Override + public boolean onCommand(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (!hasPermission(sender)) { + sender.sendMessage(Component.translatable("commands.help.failed").color(NamedTextColor.RED)); + return true; + } + if (args.length == 0) { + sendHelp(sender); + return true; + } + for (ChildCommand childCommand : getChildCommands()) { + if (!childCommand.getName().equalsIgnoreCase(args[0])) { + continue; + } + if (!childCommand.hasPermission(sender)) { + sender.sendMessage(Component.translatable("commands.help.failed").color(NamedTextColor.RED)); + return true; + } + return childCommand.onCommand(sender, Arrays.copyOfRange(args, 1, args.length)); + } + sender.sendMessage(Component.translatable("commands.help.failed").color(NamedTextColor.RED)); + return true; + } + + @Override + public List onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) { + if (args.length == 0) { + return getChildCommands().stream() + .filter(o -> o.hasPermission(sender)) + .map(ChildCommand::getName) + .collect(Collectors.toList()); + } + for (ChildCommand child : getChildCommands()) { + if (args[0].equalsIgnoreCase(child.getName())) { + return child.onTabComplete(sender, Arrays.copyOfRange(args, 1, args.length)); + } + } + args[0] = args[0].toLowerCase(); + return getChildCommands().stream() + .filter(o -> o.hasPermission(sender)) + .map(ChildCommand::getName) + .filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } +} diff --git a/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/HamsterBallPlugin.java b/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/HamsterBallPlugin.java index e6f88a1..03e0e81 100644 --- a/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/HamsterBallPlugin.java +++ b/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/HamsterBallPlugin.java @@ -5,6 +5,7 @@ import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo; import cn.hamster3.mc.plugin.ball.common.event.BallActions; import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; import cn.hamster3.mc.plugin.ball.velocity.api.CoreVelocityAPI; +import cn.hamster3.mc.plugin.ball.velocity.command.VelocityBallCommand; import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityListener; import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityMainListener; import cn.hamster3.mc.plugin.ball.velocity.listener.UpdatePlayerInfoListener; @@ -12,6 +13,7 @@ import cn.hamster3.mc.plugin.ball.velocity.listener.VelocityServerListener; import cn.hamster3.mc.plugin.ball.velocity.util.BallVelocityUtils; import cn.hamster3.mc.plugin.core.common.config.YamlConfig; import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; @@ -29,7 +31,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Objects; -import java.util.logging.Level; @Plugin( id = "hamster-ball", @@ -60,12 +61,12 @@ public class HamsterBallPlugin { this.slf4jLogger = slf4jLogger; this.proxyServer = proxyServer; dataFolder = dataPath.toFile(); - logger.info("仓鼠球正在初始化"); + slf4jLogger.info("仓鼠球正在初始化"); instance = this; try { File dataFolder = getDataFolder(); if (dataFolder.mkdir()) { - logger.info("已生成插件存档文件夹"); + slf4jLogger.info("已生成插件存档文件夹"); } File configFile = new File(dataFolder, "config.yml"); if (!configFile.exists()) { @@ -77,37 +78,42 @@ public class HamsterBallPlugin { } config = YamlConfig.load(configFile); CoreVelocityAPI.init(config); - logger.info("已初始化 BallAPI"); + slf4jLogger.info("已初始化 BallAPI"); } catch (Exception e) { slf4jLogger.error("BallAPI 初始化失败", e); proxyServer.shutdown(Component.text("由于 HamsterBall 初始化失败, 服务器将立即关闭")); } long time = System.currentTimeMillis() - start; - logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms"); + slf4jLogger.info("仓鼠球初始化完成,总计耗时 " + time + " ms"); } @Subscribe(order = PostOrder.EARLY) public void onProxyInitialization(ProxyInitializeEvent event) { long start = System.currentTimeMillis(); - java.util.logging.Logger logger = getLogger(); - logger.info("仓鼠球正在启动"); + slf4jLogger.info("仓鼠球正在启动"); try { CoreVelocityAPI.getInstance().enable(); } catch (Exception e) { - logger.log(Level.SEVERE, "仓鼠球启动失败", e); - logger.info("由于仓鼠球启动失败,服务器将立即关闭"); + slf4jLogger.error("仓鼠球启动失败", e); + slf4jLogger.info("由于仓鼠球启动失败,服务器将立即关闭"); proxyServer.shutdown(Component.text("仓鼠球启动失败")); return; } + CommandMeta commandMeta = proxyServer.getCommandManager() + .metaBuilder("hamster-ball") + .aliases("ball") + .plugin(this) + .build(); + proxyServer.getCommandManager().register(commandMeta, VelocityBallCommand.INSTANCE); BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE); - logger.info("已注册监听器 BallVelocityListener"); + slf4jLogger.info("已注册监听器 BallVelocityListener"); proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE); - logger.info("已注册监听器 BallVelocityMainListener"); + slf4jLogger.info("已注册监听器 BallVelocityMainListener"); proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE); - logger.info("已注册监听器 UpdatePlayerInfoListener"); + slf4jLogger.info("已注册监听器 UpdatePlayerInfoListener"); if (config.getBoolean("auto-register-game-server", false)) { BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE); - logger.info("已注册监听器 VelocityServerListener"); + slf4jLogger.info("已注册监听器 VelocityServerListener"); VelocityServerListener.INSTANCE.onEnable(); } @@ -130,20 +136,19 @@ public class HamsterBallPlugin { BallVelocityUtils.uploadPlayerInfo(playerInfo); }); long time = System.currentTimeMillis() - start; - logger.info("仓鼠球启动完成,总计耗时 " + time + " ms"); + slf4jLogger.info("仓鼠球启动完成,总计耗时 {} ms", time); } @Subscribe(order = PostOrder.LATE) public void onProxyShutdown(ProxyShutdownEvent event) { long start = System.currentTimeMillis(); - java.util.logging.Logger logger = getLogger(); - logger.info("仓鼠球正在关闭"); + slf4jLogger.info("仓鼠球正在关闭"); try { CoreVelocityAPI.getInstance().disable(); } catch (Exception e) { - logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e); + slf4jLogger.error("关闭仓鼠球时遇到了一个异常", e); } long time = System.currentTimeMillis() - start; - logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); + slf4jLogger.info("仓鼠球已关闭,总计耗时 {} ms", time); } } \ No newline at end of file diff --git a/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/command/VelocityBallCommand.java b/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/command/VelocityBallCommand.java new file mode 100644 index 0000000..5f4f8b3 --- /dev/null +++ b/ball-velocity/src/main/java/cn/hamster3/mc/plugin/ball/velocity/command/VelocityBallCommand.java @@ -0,0 +1,46 @@ +package cn.hamster3.mc.plugin.ball.velocity.command; + +import cn.hamster3.mc.plugin.ball.common.command.BallCommand; +import cn.hamster3.mc.plugin.ball.common.command.adapt.AdaptCommandSender; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class VelocityBallCommand implements SimpleCommand { + public static final VelocityBallCommand INSTANCE = new VelocityBallCommand(); + + private VelocityBallCommand() { + } + + @Override + public void execute(Invocation invocation) { + BallCommand.INSTANCE.onCommand(adaptCommandSender(invocation.source()), invocation.arguments()); + } + + @Override + public boolean hasPermission(Invocation invocation) { + return invocation.source().hasPermission("hamster.ball.admin"); + } + + @Override + public List suggest(Invocation invocation) { + return BallCommand.INSTANCE.onTabComplete(adaptCommandSender(invocation.source()), invocation.arguments()); + } + + private AdaptCommandSender adaptCommandSender(@NotNull CommandSource source) { + return new AdaptCommandSender() { + @Override + public boolean hasPermission(@NotNull String permission) { + return source.hasPermission(permission); + } + + @Override + public void sendMessage(@NotNull Component message) { + source.sendMessage(message); + } + }; + } +}