21 Commits
1.6.2 ... 1.7.2

Author SHA1 Message Date
00dbcba628 fix(ball-common): 提前对服务器 id 和 name 进行空判断 2025-01-26 15:01:15 +08:00
2a4a3e9065 perf: 优化消息缓存表格 2024-11-09 22:30:51 +08:00
69432dbbc8 style: 修改错误日志 2024-08-09 04:29:36 +08:00
20f395fa45 build: 更新版本号 2024-08-08 04:47:05 +08:00
4d3d93887d feat: 添加管理员指令 2024-08-08 04:46:07 +08:00
518517a4e0 feat: 添加管理员指令 2024-08-08 03:48:17 +08:00
3570930b1f Merge branch 'refs/heads/master' into dev 2024-08-08 03:42:06 +08:00
5724e5e662 feat: 添加管理员指令 2024-08-08 03:41:50 +08:00
e4425c0b1b Merge branch 'refs/heads/master' into dev 2024-08-08 03:38:47 +08:00
5c913fa2db feat: 添加 singleton-server-id 配置 2024-04-24 14:12:06 +08:00
08a41c1209 refactor: 在初始化中订阅redis连接 2024-04-24 11:44:36 +08:00
d38bd5e41f build: 更新版本号到 1.6.4 2024-03-27 10:37:33 +08:00
52e25a0433 Merge branch 'dev' 2024-03-27 10:36:18 +08:00
cc33304ad4 fix(ball-common): 修复 jedis 线程占用问题 2024-03-27 09:41:56 +08:00
6d2abfa44b build: 更改版本号 2024-03-26 22:25:26 +08:00
bc49510c46 Merge branch 'dev' 2024-03-26 22:23:34 +08:00
74f1a4557d perf: 将 jedis 订阅频道处理代码移动至异步线程处理 2024-03-26 19:00:55 +08:00
84721e31ee fix(ball-common): 修复当事件处理失败时直接会阻塞后续redis消息的bug 2024-03-26 17:25:49 +08:00
4e29246441 build: 优化构建脚本 2024-03-21 23:30:04 +08:00
aa0e94c7b7 docs: 修改描述 2024-03-19 16:22:36 +08:00
aac7248994 docs: 修改版本更新信息 2024-03-19 16:19:00 +08:00
34 changed files with 882 additions and 128 deletions

View File

@@ -1,6 +1,6 @@
# [HamsterBall](https://git.airgame.net/MiniDay/hamster-ball) # [HamsterBall](https://git.airgame.net/MiniDay/hamster-ball)
仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService 基于 Redis 的 Minecraft 服务端通用消息中间件
该插件依赖于 [仓鼠核心](https://git.airgame.net/MiniDay/hamster-core) 该插件依赖于 [仓鼠核心](https://git.airgame.net/MiniDay/hamster-core)
@@ -49,9 +49,9 @@ repositories {
dependencies { dependencies {
// 对于 Bukkit 插件 // 对于 Bukkit 插件
compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.6.2") compileOnly("cn.hamster3.mc.plugin:ball-bukkit:1.7.0")
// 对于 BungeeCord 插件 // 对于 BungeeCord 插件
compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.6.2") compileOnly("cn.hamster3.mc.plugin:ball-bungee:1.7.0")
} }
``` ```
@@ -77,13 +77,13 @@ dependencies {
<dependency> <dependency>
<groupId>cn.hamster3.mc.plugin</groupId> <groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bukkit</artifactId> <artifactId>ball-bukkit</artifactId>
<version>1.6.2</version> <version>1.7.0</version>
</dependency> </dependency>
<!--对于 BungeeCord 插件--> <!--对于 BungeeCord 插件-->
<dependency> <dependency>
<groupId>cn.hamster3.mc.plugin</groupId> <groupId>cn.hamster3.mc.plugin</groupId>
<artifactId>ball-bungee</artifactId> <artifactId>ball-bungee</artifactId>
<version>1.6.2</version> <version>1.7.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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.listener.UpdatePlayerInfoListener;
import cn.hamster3.mc.plugin.ball.bukkit.util.BallBukkitUtils; 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.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.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.event.BallActions; 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.common.event.server.ServerOnlineEvent;
import cn.hamster3.mc.plugin.core.bukkit.api.CoreBukkitAPI;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig; import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import lombok.Getter; import lombok.Getter;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -129,4 +137,35 @@ public class HamsterBallPlugin extends JavaPlugin {
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); 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<String> 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);
}
} }

View File

@@ -3,7 +3,6 @@ package cn.hamster3.mc.plugin.ball.bukkit.listener;
import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin; import cn.hamster3.mc.plugin.ball.bukkit.HamsterBallPlugin;
import cn.hamster3.mc.plugin.ball.bukkit.data.BukkitLocation; import cn.hamster3.mc.plugin.ball.bukkit.data.BukkitLocation;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI; import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.operate.*; import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage; import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
@@ -95,9 +94,6 @@ public class BallBukkitListener implements Listener {
@Subscribe @Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) { public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) { if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return; return;
} }
@@ -106,9 +102,6 @@ public class BallBukkitListener implements Listener {
@Subscribe @Subscribe
public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) { public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
if (event.getUuid() != null) { if (event.getUuid() != null) {
Player player = Bukkit.getPlayer(event.getUuid()); Player player = Bukkit.getPlayer(event.getUuid());
if (player == null) { if (player == null) {

View File

@@ -1,6 +1,13 @@
# 是否允许在控制台输出调试信息 # 是否允许在控制台输出调试信息
debug: false debug: false
# 是否启用服务器 ID 单例模式
# 启用后,当一个服务器启动后将会占用 服务器唯一识别码
# 其他使用相同 服务器唯一识别码 的服务器将无法启动
# 测试端中可关闭该功能
# 推荐在正式服中开启该功能以防止服务器 ID 重复
singleton-server-id: false
# 频道名前缀 # 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组 # 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信 # 只有在同一个频道名的子服才能互相通信

View File

@@ -4,8 +4,15 @@ version: ${version}
api-version: 1.13 api-version: 1.13
author: MiniDay author: MiniDay
description: ${description}
website: https://git.airgame.net/MiniDay/hamster-ball website: https://git.airgame.net/MiniDay/hamster-ball
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService
UPDATE_CHECKER:
VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/
load: STARTUP load: STARTUP
@@ -14,3 +21,11 @@ depend:
softdepend: softdepend:
- PlaceholderAPI - PlaceholderAPI
commands:
hamster-ball:
aliases: [ ball ]
permissions:
hamster.ball.admin:
default: op

View File

@@ -1,6 +0,0 @@
version: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -1,6 +1,7 @@
package cn.hamster3.mc.plugin.ball.bungee; package cn.hamster3.mc.plugin.ball.bungee;
import cn.hamster3.mc.plugin.ball.bungee.api.BallBungeeCordAPI; 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.BallBungeeListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BallBungeeMainListener;
import cn.hamster3.mc.plugin.ball.bungee.listener.BungeeServerListener; import cn.hamster3.mc.plugin.ball.bungee.listener.BungeeServerListener;
@@ -71,6 +72,8 @@ public class HamsterBallPlugin extends Plugin {
ProxyServer.getInstance().stop("仓鼠球启动失败"); ProxyServer.getInstance().stop("仓鼠球启动失败");
return; return;
} }
ProxyServer.getInstance().getPluginManager().registerCommand(this, BungeeBallCommand.INSTANCE);
logger.info("已注册命令 BungeeBallCommand");
BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE); BallAPI.getInstance().getEventBus().register(BallBungeeListener.INSTANCE);
logger.info("已注册监听器 BallBungeeListener"); logger.info("已注册监听器 BallBungeeListener");
ProxyServer.getInstance().getPluginManager().registerListener(this, BallBungeeMainListener.INSTANCE); ProxyServer.getInstance().getPluginManager().registerListener(this, BallBungeeMainListener.INSTANCE);

View File

@@ -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-bungee-ball", "hamster.ball.admin", "bungee-ball", "bball");
}
@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).sendMessage(message);
}
}, args);
}
}

View File

@@ -2,7 +2,6 @@ package cn.hamster3.mc.plugin.ball.bungee.listener;
import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin; import cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI; import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.operate.*; import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
@@ -23,9 +22,6 @@ public class BallBungeeListener {
@Subscribe @Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) { public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.PROXY) {
return;
}
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) { if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return; return;
} }
@@ -35,9 +31,6 @@ public class BallBungeeListener {
@Subscribe @Subscribe
public void onDispatchPlayerCommandEvent(DispatchPlayerCommandEvent event) { public void onDispatchPlayerCommandEvent(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = ProxyServer.getInstance(); ProxyServer server = ProxyServer.getInstance();
if (event.getUuid() != null) { if (event.getUuid() != null) {
ProxiedPlayer player = server.getPlayer(event.getUuid()); ProxiedPlayer player = server.getPlayer(event.getUuid());

View File

@@ -3,7 +3,15 @@ main: cn.hamster3.mc.plugin.ball.bungee.HamsterBallPlugin
version: ${version} version: ${version}
author: MiniDay author: MiniDay
description: 仓鼠球:一个基于 Redis 的 Minecraft 服务端通用消息中间件原HamsterService description: ${description}
website: https://git.airgame.net/MiniDay/hamster-ball
UPDATE_CHECKER:
VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/
depend: depend:
- HamsterCore - HamsterCore

View File

@@ -1,6 +1,10 @@
# 是否允许在控制台输出调试信息 # 是否允许在控制台输出调试信息
debug: false debug: false
# 是否启用服务器 ID 单例模式
# 启用后,服务器唯一识别码 相同的服务器将无法启动
singleton-server-id: false
# 频道名前缀 # 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组 # 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信 # 只有在同一个频道名的子服才能互相通信

View File

@@ -1,6 +0,0 @@
version: ${version}
CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -102,12 +102,13 @@ public abstract class BallAPI {
getLogger().warning("已启用调试模式"); getLogger().warning("已启用调试模式");
eventBus.register(BallDebugListener.INSTANCE); eventBus.register(BallDebugListener.INSTANCE);
} }
CoreAPI.getInstance().getExecutorService().submit(() -> redisSub.subscribe(BallRedisListener.INSTANCE, BALL_CHANNEL));
} }
protected void enable() throws SQLException, InterruptedException { protected void enable() throws SQLException, InterruptedException {
try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) { try (Jedis jedis = CoreAPI.getInstance().getJedisPool().getResource()) {
String key = "HamsterBall:ServerInfo:" + localServerInfo.getId(); String key = "HamsterBall:ServerInfo:" + localServerInfo.getId();
if (jedis.exists(key)) { if (jedis.exists(key) && ballConfig.isSingletonServerID()) {
throw new IllegalStateException("已经有一个服务器占用了该 ID"); throw new IllegalStateException("已经有一个服务器占用了该 ID");
} }
jedis.hset(key, "id", localServerInfo.getId()); jedis.hset(key, "id", localServerInfo.getId());
@@ -139,7 +140,10 @@ public abstract class BallAPI {
") CHARSET utf8mb4;"); ") CHARSET utf8mb4;");
statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" + statement.execute("CREATE TABLE IF NOT EXISTS `hamster_ball_cached_message`(" +
"`uuid` CHAR(36) NOT NULL," + "`uuid` CHAR(36) NOT NULL," +
"`message` TEXT NOT NULL" + "`message` TEXT NOT NULL," +
"`time` DATETIME NOT NULL DEFAULT NOW()," +
"INDEX `idx_uuid` USING BTREE (`uuid`)," +
"INDEX `idx_time` USING BTREE (`time`)" +
") CHARSET utf8mb4;"); ") CHARSET utf8mb4;");
} }
if (getBallConfig().isGameServerUpdatePlayerInfo()) { if (getBallConfig().isGameServerUpdatePlayerInfo()) {
@@ -179,7 +183,6 @@ public abstract class BallAPI {
} }
getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息"); getLogger().info("从数据库中加载了 " + allServerInfo.size() + " 条服务器信息");
getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息"); getLogger().info("从数据库中加载了 " + allPlayerInfo.size() + " 条玩家信息");
subscribeRaw(BALL_CHANNEL);
} }
protected void disable() throws SQLException, InterruptedException { protected void disable() throws SQLException, InterruptedException {
@@ -257,25 +260,24 @@ public abstract class BallAPI {
*/ */
public void dispatchConsoleCommand(@Nullable BallServerType type, @Nullable String serverID, @NotNull String command) { public void dispatchConsoleCommand(@Nullable BallServerType type, @Nullable String serverID, @NotNull String command) {
sendBallMessage(BALL_CHANNEL, new BallMessage( sendBallMessage(BALL_CHANNEL, new BallMessage(
getLocalServerId(), null, BallServerType.GAME, getLocalServerId(), null, type,
BallActions.DispatchConsoleCommand.name(), BallActions.DispatchConsoleCommand.name(),
CoreAPI.getInstance().getGson().toJsonTree(new DispatchConsoleCommandEvent(type, serverID, command)) CoreAPI.getInstance().getGson().toJsonTree(new DispatchConsoleCommandEvent(serverID, command))
), false); ), false);
} }
/** /**
* 强制玩家执行命令 * 强制玩家执行命令
* *
* @param type 执行对象的服务端类型 * @param type 执行对象的服务端类型null代表所有类型
* @param uuid 执行对象的 UUID * @param uuid 执行对象的 UUIDnull代表所有玩家
* @param command 命令内容 * @param command 命令内容
*/ */
public void dispatchPlayerCommand(@Nullable BallServerType type, @Nullable UUID uuid, @NotNull String command) { public void dispatchPlayerCommand(@Nullable BallServerType type, @Nullable UUID uuid, @NotNull String command) {
sendBallMessage(BALL_CHANNEL, new BallMessage( sendBallMessage(BALL_CHANNEL, new BallMessage(
getLocalServerId(), null, BallServerType.GAME, getLocalServerId(), null, type,
BallActions.DispatchPlayerCommand.name(), BallActions.DispatchPlayerCommand.name(),
CoreAPI.getInstance().getGson().toJsonTree(new DispatchPlayerCommandEvent(type, uuid, command)) CoreAPI.getInstance().getGson().toJsonTree(new DispatchPlayerCommandEvent(uuid, command))
), false); ), false);
} }
@@ -330,7 +332,7 @@ public abstract class BallAPI {
} }
try (Connection connection = getDatasource().getConnection()) { try (Connection connection = getDatasource().getConnection()) {
try (PreparedStatement statement = connection.prepareStatement( try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO `hamster_ball_cached_message` VALUES(?, ?);" "INSERT INTO `hamster_ball_cached_message` VALUES(?, ?, DEFAULT);"
)) { )) {
statement.setString(1, receiver.toString()); statement.setString(1, receiver.toString());
statement.setString(2, message.toJson().toString()); statement.setString(2, message.toJson().toString());
@@ -506,26 +508,29 @@ public abstract class BallAPI {
* <p> * <p>
* 会自动加上 config 中设置的频道前缀 * 会自动加上 config 中设置的频道前缀
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void subscribe(@NotNull String... channel) { public void subscribe(@NotNull String... channels) {
for (int i = 0; i < channel.length; i++) { for (int i = 0; i < channels.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i]; channels[i] = ballConfig.getChannelPrefix() + channels[i];
} }
CoreAPI.getInstance().getExecutorService().submit( subscribeRaw(channels);
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel)
);
} }
/** /**
* 忽略频道前缀配置,订阅 redis 消息频道 * 忽略频道前缀配置,订阅 redis 消息频道
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void subscribeRaw(@NotNull String... channel) { public void subscribeRaw(@NotNull String... channels) {
CoreAPI.getInstance().getExecutorService().submit( BallRedisListener.INSTANCE.subscribe(channels);
() -> redisSub.subscribe(BallRedisListener.INSTANCE, channel) // CoreAPI.getInstance().getExecutorService().submit(() -> {
); // try {
// redisSub.subscribe(BallRedisListener.INSTANCE, channels);
// } catch (Exception | Error e) {
// e.printStackTrace();
// }
// });
} }
/** /**
@@ -534,9 +539,10 @@ public abstract class BallAPI {
* @param patterns 频道名称正则表达式 * @param patterns 频道名称正则表达式
*/ */
public void subscribePatterns(@NotNull String patterns) { public void subscribePatterns(@NotNull String patterns) {
CoreAPI.getInstance().getExecutorService().submit( BallRedisListener.INSTANCE.psubscribe(patterns);
() -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns) // CoreAPI.getInstance().getExecutorService().submit(
); // () -> redisSub.psubscribe(BallRedisListener.INSTANCE, patterns)
// );
} }
/** /**
@@ -544,22 +550,22 @@ public abstract class BallAPI {
* <p> * <p>
* 会自动加上 config 中设置的频道前缀 * 会自动加上 config 中设置的频道前缀
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void unsubscribe(@NotNull String... channel) { public void unsubscribe(@NotNull String... channels) {
for (int i = 0; i < channel.length; i++) { for (int i = 0; i < channels.length; i++) {
channel[i] = ballConfig.getChannelPrefix() + channel[i]; channels[i] = ballConfig.getChannelPrefix() + channels[i];
} }
BallRedisListener.INSTANCE.unsubscribe(channel); unsubscribeRaw(channels);
} }
/** /**
* 忽略频道前缀配置,取消订阅 redis 消息频道 * 忽略频道前缀配置,取消订阅 redis 消息频道
* *
* @param channel 频道名称 * @param channels 频道名称
*/ */
public void unsubscribeIgnorePrefix(@NotNull String... channel) { public void unsubscribeRaw(@NotNull String... channels) {
BallRedisListener.INSTANCE.unsubscribe(channel); BallRedisListener.INSTANCE.unsubscribe(channels);
} }
/** /**

View File

@@ -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(SudoAllPlayerCommand.INSTANCE);
addChildCommand(SudoConsoleCommand.INSTANCE);
addChildCommand(SudoAllConsoleCommand.INSTANCE);
}
@Override
public @NotNull String getName() {
return "hamster-ball";
}
}

View File

@@ -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<String> 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();
}
}

View File

@@ -0,0 +1,75 @@
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.AdaptCommandSender;
import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
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 <服务器类型> <命令内容>";
}
@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;
}
BallServerType serverType;
try {
serverType = BallServerType.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
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(serverType, null, command);
sender.sendMessage("§a已强制所有服务器控制台执行命令: §e/" + command);
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) {
if (args.length == 1) {
return Arrays.stream(BallServerType.values())
.map(Enum::name)
.filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase()))
.limit(10)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,75 @@
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.BallServerType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
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 < 2) {
sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage());
return true;
}
BallServerType serverType;
try {
serverType = BallServerType.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
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(serverType, null, command);
sender.sendMessage("§a已强制所有玩家执行命令: §e/" + command);
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) {
if (args.length == 1) {
return Arrays.stream(BallServerType.values())
.map(Enum::name)
.filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase()))
.limit(10)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
}

View File

@@ -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.AdaptCommandSender;
import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand;
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<String> 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();
}
}

View File

@@ -0,0 +1,101 @@
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.AdaptCommandSender;
import cn.hamster3.mc.plugin.ball.common.command.adapt.ChildCommand;
import cn.hamster3.mc.plugin.ball.common.entity.BallPlayerInfo;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
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 < 3) {
sender.sendMessage(BallCommand.INSTANCE.getUsage() + " " + getUsage());
return true;
}
BallServerType serverType;
try {
serverType = BallServerType.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException e) {
sender.sendMessage("§c未知的服务器类型: " + args[0]);
return true;
}
BallPlayerInfo info;
try {
UUID uuid = UUID.fromString(args[1]);
info = BallAPI.getInstance().getPlayerInfo(uuid);
} catch (Exception e) {
info = BallAPI.getInstance().getPlayerInfo(args[1]);
}
if (info == null) {
sender.sendMessage("§c未找到玩家 " + args[1]);
return true;
}
if (!info.isOnline()) {
sender.sendMessage("§c玩家 " + args[1] + " 不在线");
return true;
}
StringBuilder builder = new StringBuilder(args[2]);
for (int i = 3; i < args.length; i++) {
builder.append(" ").append(args[i]);
}
String command = builder.toString();
BallAPI.getInstance().dispatchPlayerCommand(serverType, info.getUuid(), command);
sender.sendMessage("§a已强制玩家 " + info.getName() + " 执行命令: §e/" + command);
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args) {
switch (args.length) {
case 1: {
return Arrays.stream(BallServerType.values())
.map(Enum::name)
.filter(o -> o.toLowerCase().startsWith(args[0].toLowerCase()))
.limit(10)
.collect(Collectors.toList());
}
case 2: {
return BallAPI.getInstance().getAllPlayerInfo().values().stream()
.map(BallPlayerInfo::getName)
.filter(o -> o.toLowerCase().startsWith(args[1].toLowerCase()))
.limit(10)
.collect(Collectors.toList());
}
}
return Collections.emptyList();
}
}

View File

@@ -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));
}
}

View File

@@ -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<String> onTabComplete(@NotNull AdaptCommandSender sender, @NotNull String[] args);
}

View File

@@ -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<String, ChildCommand> childCommands;
public ParentCommand() {
childCommands = new LinkedHashMap<>();
}
@NotNull
@Override
public abstract String getName();
@Nullable
public ParentCommand getParent() {
return null;
}
@NotNull
public Collection<ChildCommand> 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 "";
}
/**
* 获取所有子命令
* <p>
* 如果子命令也是 ParentCommand 类型,则继续递归获取该 ParentCommand 的子命令
*
* @return 所有子命令
*/
@NotNull
public List<ChildCommand> getEndChildCommands() {
ArrayList<ChildCommand> 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<String, String> getCommandHelp(AdaptCommandSender sender) {
Map<String, String> map = new LinkedHashMap<>();
for (ChildCommand child : getChildCommands()) {
if (!child.hasPermission(sender)) {
continue;
}
if (child instanceof ParentCommand) {
Map<String, String> 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<String, String> map = getCommandHelp(sender);
int maxLength = map.keySet().stream()
.map(String::length)
.max(Integer::compareTo)
.orElse(-1);
for (Map.Entry<String, String> 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<String> 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());
}
}

View File

@@ -11,13 +11,16 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
public class BallConfig { public class BallConfig {
private boolean debug; private boolean debug;
private boolean singletonServerID;
@NotNull @NotNull
private String channelPrefix; private String channelPrefix;
private boolean gameServerUpdatePlayerInfo; private boolean gameServerUpdatePlayerInfo;
@NotNull
private List<String> loadPlayerInfoFilter; private List<String> loadPlayerInfoFilter;
public BallConfig(@NotNull ConfigSection config) { public BallConfig(@NotNull ConfigSection config) {
debug = config.getBoolean("debug", false); debug = config.getBoolean("debug", false);
singletonServerID = config.getBoolean("singleton-server-id", false);
channelPrefix = config.getString("channel-prefix", ""); channelPrefix = config.getString("channel-prefix", "");
channelPrefix = channelPrefix.isEmpty() ? channelPrefix : channelPrefix + ":"; channelPrefix = channelPrefix.isEmpty() ? channelPrefix : channelPrefix + ":";
gameServerUpdatePlayerInfo = config.getBoolean("game-server-update-player-info", false); gameServerUpdatePlayerInfo = config.getBoolean("game-server-update-player-info", false);

View File

@@ -46,7 +46,13 @@ public class BallServerInfo {
public BallServerInfo(@NotNull ConfigSection config, @NotNull BallServerType type) { public BallServerInfo(@NotNull ConfigSection config, @NotNull BallServerType type) {
Map<String, String> env = System.getenv(); Map<String, String> env = System.getenv();
id = env.getOrDefault("BALL_SERVER_ID", config.getString("id")); id = env.getOrDefault("BALL_SERVER_ID", config.getString("id"));
if (id == null || id.isEmpty()) {
throw new IllegalArgumentException("id 不能为空");
}
name = env.getOrDefault("BALL_SERVER_NAME", config.getString("name")); name = env.getOrDefault("BALL_SERVER_NAME", config.getString("name"));
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("name 不能为空");
}
this.type = type; this.type = type;
host = "0.0.0.0"; host = "0.0.0.0";
port = 0; port = 0;

View File

@@ -1,6 +1,5 @@
package cn.hamster3.mc.plugin.ball.common.event.operate; package cn.hamster3.mc.plugin.ball.common.event.operate;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -9,8 +8,6 @@ import org.jetbrains.annotations.Nullable;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class DispatchConsoleCommandEvent { public class DispatchConsoleCommandEvent {
@Nullable
private final BallServerType type;
@Nullable @Nullable
private final String serverID; private final String serverID;
@NotNull @NotNull

View File

@@ -1,6 +1,5 @@
package cn.hamster3.mc.plugin.ball.common.event.operate; package cn.hamster3.mc.plugin.ball.common.event.operate;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -11,8 +10,6 @@ import java.util.UUID;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class DispatchPlayerCommandEvent { public class DispatchPlayerCommandEvent {
@Nullable
private final BallServerType type;
@Nullable @Nullable
private final UUID uuid; private final UUID uuid;
@NotNull @NotNull

View File

@@ -7,6 +7,8 @@ import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import redis.clients.jedis.JedisPubSub; import redis.clients.jedis.JedisPubSub;
import java.util.logging.Level;
public class BallRedisListener extends JedisPubSub { public class BallRedisListener extends JedisPubSub {
public static final BallRedisListener INSTANCE = new BallRedisListener(); public static final BallRedisListener INSTANCE = new BallRedisListener();
@@ -15,8 +17,11 @@ public class BallRedisListener extends JedisPubSub {
@Override @Override
public void onMessage(String channel, String message) { public void onMessage(String channel, String message) {
if (channel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) { CoreAPI.getInstance().getExecutorService().submit(() -> {
channel = channel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length()); try {
String finalChannel = channel;
if (finalChannel.startsWith(BallAPI.getInstance().getBallConfig().getChannelPrefix())) {
finalChannel = finalChannel.substring(BallAPI.getInstance().getBallConfig().getChannelPrefix().length());
} }
BallMessage ballMessage = CoreAPI.getInstance().getGson().fromJson(message, BallMessage.class); BallMessage ballMessage = CoreAPI.getInstance().getGson().fromJson(message, BallMessage.class);
BallAPI ballAPI = BallAPI.getInstance(); BallAPI ballAPI = BallAPI.getInstance();
@@ -27,7 +32,11 @@ public class BallRedisListener extends JedisPubSub {
if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) { if (ballMessage.getReceiverID() != null && !ballAPI.isLocalServer(ballMessage.getReceiverID())) {
return; return;
} }
eventBus.post(new MessageReceivedEvent(channel, ballMessage)); eventBus.post(new MessageReceivedEvent(finalChannel, ballMessage));
} catch (Exception | Error e) {
BallAPI.getInstance().getLogger().log(Level.SEVERE, "解析来自频道 " + channel + " 的数据出错: " + message, e);
}
});
} }
@Override @Override

View File

@@ -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.BallActions;
import cn.hamster3.mc.plugin.ball.common.event.server.ServerOnlineEvent; 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.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.BallVelocityListener;
import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityMainListener; import cn.hamster3.mc.plugin.ball.velocity.listener.BallVelocityMainListener;
import cn.hamster3.mc.plugin.ball.velocity.listener.UpdatePlayerInfoListener; 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.ball.velocity.util.BallVelocityUtils;
import cn.hamster3.mc.plugin.core.common.config.YamlConfig; import cn.hamster3.mc.plugin.core.common.config.YamlConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 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.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.Objects; import java.util.Objects;
import java.util.logging.Level;
@Plugin( @Plugin(
id = "hamster-ball", id = "hamster-ball",
@@ -60,12 +61,12 @@ public class HamsterBallPlugin {
this.slf4jLogger = slf4jLogger; this.slf4jLogger = slf4jLogger;
this.proxyServer = proxyServer; this.proxyServer = proxyServer;
dataFolder = dataPath.toFile(); dataFolder = dataPath.toFile();
logger.info("仓鼠球正在初始化"); slf4jLogger.info("仓鼠球正在初始化");
instance = this; instance = this;
try { try {
File dataFolder = getDataFolder(); File dataFolder = getDataFolder();
if (dataFolder.mkdir()) { if (dataFolder.mkdir()) {
logger.info("已生成插件存档文件夹"); slf4jLogger.info("已生成插件存档文件夹");
} }
File configFile = new File(dataFolder, "config.yml"); File configFile = new File(dataFolder, "config.yml");
if (!configFile.exists()) { if (!configFile.exists()) {
@@ -77,37 +78,42 @@ public class HamsterBallPlugin {
} }
config = YamlConfig.load(configFile); config = YamlConfig.load(configFile);
CoreVelocityAPI.init(config); CoreVelocityAPI.init(config);
logger.info("已初始化 BallAPI"); slf4jLogger.info("已初始化 BallAPI");
} catch (Exception e) { } catch (Exception e) {
slf4jLogger.error("BallAPI 初始化失败", e); slf4jLogger.error("BallAPI 初始化失败", e);
proxyServer.shutdown(Component.text("由于 HamsterBall 初始化失败, 服务器将立即关闭")); proxyServer.shutdown(Component.text("由于 HamsterBall 初始化失败, 服务器将立即关闭"));
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球初始化完成,总计耗时 " + time + " ms"); slf4jLogger.info("仓鼠球初始化完成,总计耗时 " + time + " ms");
} }
@Subscribe(order = PostOrder.EARLY) @Subscribe(order = PostOrder.EARLY)
public void onProxyInitialization(ProxyInitializeEvent event) { public void onProxyInitialization(ProxyInitializeEvent event) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
java.util.logging.Logger logger = getLogger(); slf4jLogger.info("仓鼠球正在启动");
logger.info("仓鼠球正在启动");
try { try {
CoreVelocityAPI.getInstance().enable(); CoreVelocityAPI.getInstance().enable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "仓鼠球启动失败", e); slf4jLogger.error("仓鼠球启动失败", e);
logger.info("由于仓鼠球启动失败,服务器将立即关闭"); slf4jLogger.info("由于仓鼠球启动失败,服务器将立即关闭");
proxyServer.shutdown(Component.text("仓鼠球启动失败")); proxyServer.shutdown(Component.text("仓鼠球启动失败"));
return; return;
} }
CommandMeta commandMeta = proxyServer.getCommandManager()
.metaBuilder("hamster-velocity-ball")
.aliases("velocity-ball", "vball")
.plugin(this)
.build();
proxyServer.getCommandManager().register(commandMeta, VelocityBallCommand.INSTANCE);
BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE); BallAPI.getInstance().getEventBus().register(BallVelocityListener.INSTANCE);
logger.info("已注册监听器 BallVelocityListener"); slf4jLogger.info("已注册监听器 BallVelocityListener");
proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE); proxyServer.getEventManager().register(this, BallVelocityMainListener.INSTANCE);
logger.info("已注册监听器 BallVelocityMainListener"); slf4jLogger.info("已注册监听器 BallVelocityMainListener");
proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE); proxyServer.getEventManager().register(this, UpdatePlayerInfoListener.INSTANCE);
logger.info("已注册监听器 UpdatePlayerInfoListener"); slf4jLogger.info("已注册监听器 UpdatePlayerInfoListener");
if (config.getBoolean("auto-register-game-server", false)) { if (config.getBoolean("auto-register-game-server", false)) {
BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE); BallAPI.getInstance().getEventBus().register(VelocityServerListener.INSTANCE);
logger.info("已注册监听器 VelocityServerListener"); slf4jLogger.info("已注册监听器 VelocityServerListener");
VelocityServerListener.INSTANCE.onEnable(); VelocityServerListener.INSTANCE.onEnable();
} }
@@ -130,20 +136,19 @@ public class HamsterBallPlugin {
BallVelocityUtils.uploadPlayerInfo(playerInfo); BallVelocityUtils.uploadPlayerInfo(playerInfo);
}); });
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球启动完成,总计耗时 " + time + " ms"); slf4jLogger.info("仓鼠球启动完成,总计耗时 {} ms", time);
} }
@Subscribe(order = PostOrder.LATE) @Subscribe(order = PostOrder.LATE)
public void onProxyShutdown(ProxyShutdownEvent event) { public void onProxyShutdown(ProxyShutdownEvent event) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
java.util.logging.Logger logger = getLogger(); slf4jLogger.info("仓鼠球正在关闭");
logger.info("仓鼠球正在关闭");
try { try {
CoreVelocityAPI.getInstance().disable(); CoreVelocityAPI.getInstance().disable();
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "关闭仓鼠球时遇到了一个异常", e); slf4jLogger.error("关闭仓鼠球时遇到了一个异常", e);
} }
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
logger.info("仓鼠球已关闭,总计耗时 " + time + " ms"); slf4jLogger.info("仓鼠球已关闭,总计耗时 {} ms", time);
} }
} }

View File

@@ -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<String> 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);
}
};
}
}

View File

@@ -1,7 +1,6 @@
package cn.hamster3.mc.plugin.ball.velocity.listener; package cn.hamster3.mc.plugin.ball.velocity.listener;
import cn.hamster3.mc.plugin.ball.common.api.BallAPI; import cn.hamster3.mc.plugin.ball.common.api.BallAPI;
import cn.hamster3.mc.plugin.ball.common.entity.BallServerType;
import cn.hamster3.mc.plugin.ball.common.event.operate.*; import cn.hamster3.mc.plugin.ball.common.event.operate.*;
import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin; import cn.hamster3.mc.plugin.ball.velocity.HamsterBallPlugin;
import cn.hamster3.mc.plugin.core.common.api.CoreAPI; import cn.hamster3.mc.plugin.core.common.api.CoreAPI;
@@ -23,9 +22,6 @@ public class BallVelocityListener {
@Subscribe @Subscribe
public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) { public void onDispatchConsoleCommand(DispatchConsoleCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.PROXY) {
return;
}
if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) { if (event.getServerID() != null && !BallAPI.getInstance().isLocalServer(event.getServerID())) {
return; return;
} }
@@ -35,9 +31,6 @@ public class BallVelocityListener {
@Subscribe @Subscribe
public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) { public void onDispatchPlayerCommand(DispatchPlayerCommandEvent event) {
if (event.getType() != null && event.getType() != BallServerType.GAME) {
return;
}
ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer(); ProxyServer server = HamsterBallPlugin.getInstance().getProxyServer();
if (event.getUuid() != null) { if (event.getUuid() != null) {
Player player = server.getPlayer(event.getUuid()).orElse(null); Player player = server.getPlayer(event.getUuid()).orElse(null);

View File

@@ -1,6 +1,10 @@
# 是否允许在控制台输出调试信息 # 是否允许在控制台输出调试信息
debug: false debug: false
# 是否启用服务器 ID 单例模式
# 启用后,服务器唯一识别码 相同的服务器将无法启动
singleton-server-id: false
# 频道名前缀 # 频道名前缀
# 使用这个配置选项可以划分子服消息通信分组 # 使用这个配置选项可以划分子服消息通信分组
# 只有在同一个频道名的子服才能互相通信 # 只有在同一个频道名的子服才能互相通信

View File

@@ -1,6 +1,5 @@
version: ${version} VERSION: ${version}
CHECK_TYPE: GITEA_RELEASES CHECK_TYPE: GITEA_RELEASES
GIT_BASE_URL: https://git.airgame.net GIT_BASE_URL: https://git.airgame.net
GIT_REPO: MiniDay/hamster-ball GIT_REPO: MiniDay/hamster-ball
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/ DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-ball/

View File

@@ -5,7 +5,8 @@ plugins {
} }
group = "cn.hamster3.mc.plugin" group = "cn.hamster3.mc.plugin"
version = "1.6.2" version = "1.7.2"
description = "基于 Redis 的 Minecraft 服务端通用消息中间件"
subprojects { subprojects {
apply { apply {
@@ -16,6 +17,7 @@ subprojects {
group = rootProject.group group = rootProject.group
version = rootProject.version version = rootProject.version
description = rootProject.description
repositories { repositories {
maven("https://maven.airgame.net/maven-public/") maven("https://maven.airgame.net/maven-public/")
@@ -43,11 +45,6 @@ subprojects {
from(rootProject.file("LICENSE")) from(rootProject.file("LICENSE"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
processResources {
filesMatching("update.yml") {
expand(rootProject.properties)
}
}
build { build {
dependsOn(shadowJar) dependsOn(shadowJar)
} }

View File

@@ -1,6 +1,6 @@
#Sun Aug 20 16:53:32 CST 2023 #Sun Aug 20 16:53:32 CST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists